-
Notifications
You must be signed in to change notification settings - Fork 788
feat(agent): prefer ReadMediaFile for video analysis in system prompt #2208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
57f1eec
8a55f6f
2ff4c2f
3f4c3e9
dff0a80
cfdbb02
a30582b
ceed3fb
8c34320
4ca9b1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| "@moonshot-ai/kimi-code": patch | ||
| "@moonshot-ai/vis-server": patch | ||
| --- | ||
|
|
||
| feat(vis): show LAN URLs when binding to 0.0.0.0 for remote control | ||
|
|
||
| When vis-server binds to 0.0.0.0 or :: (all interfaces), the startup | ||
| banner and CLI output now display the actual LAN IP addresses that | ||
| other devices on the same network can use to connect. This enables | ||
| lan-range remote control from phones, tablets, or other machines. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| "@moonshot-ai/kimi-code": patch | ||
| --- | ||
|
|
||
| feat(agent): prefer ReadMediaFile for video analysis in system prompt | ||
|
|
||
| Updated the system prompt (`system.md`) to explicitly guide the AI to | ||
| use `ReadMediaFile` directly for video analysis, rather than writing | ||
| Python scripts to extract frames. This leverages the built-in video | ||
| input capability of supported models and avoids unnecessary overhead. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import { serve } from '@hono/node-server'; | ||
|
|
||
| import { createApp } from './app'; | ||
| import { hostForUrl, resolveHost, resolveKimiCodeHome, resolvePort, resolveVisAuthToken } from './config'; | ||
| import { getLocalNetworkAddresses, hostForUrl, isAllInterfaces, resolveHost, resolveKimiCodeHome, resolvePort, resolveVisAuthToken } from './config'; | ||
| import type { WebAsset } from './lib/web-asset'; | ||
|
|
||
| export interface StartVisServerOptions { | ||
|
|
@@ -18,6 +18,7 @@ export interface StartedVisServer { | |
| readonly port: number; | ||
| readonly host: string; | ||
| readonly url: string; | ||
| readonly lanUrls?: string[]; | ||
| readonly close: () => Promise<void>; | ||
| } | ||
|
|
||
|
|
@@ -36,6 +37,7 @@ export async function startVisServer( | |
| port: info.port, | ||
| host, | ||
| url: `http://${hostForUrl(host)}:${info.port}/`, | ||
| lanUrls: isAllInterfaces(host) ? getLocalNetworkAddresses(info.port) : undefined, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When binding to Useful? React with 👍 / 👎. |
||
| close: () => | ||
| new Promise<void>((done, fail) => { | ||
| server.close((err?: Error) => (err ? fail(err) : done())); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { describe, it, expect } from 'vitest'; | ||
| import { hostForUrl } from '../../src/config'; | ||
| import { hostForUrl, isAllInterfaces, getLocalNetworkAddresses } from '../../src/config'; | ||
|
|
||
| describe('hostForUrl', () => { | ||
| it('brackets a bare IPv6 literal for use in a URL', () => { | ||
|
|
@@ -18,3 +18,39 @@ describe('hostForUrl', () => { | |
| expect(hostForUrl('[::1]')).toBe('[::1]'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('isAllInterfaces', () => { | ||
| it('returns true for 0.0.0.0', () => { | ||
| expect(isAllInterfaces('0.0.0.0')).toBe(true); | ||
| }); | ||
|
|
||
| it('returns true for ::', () => { | ||
| expect(isAllInterfaces('::')).toBe(true); | ||
| }); | ||
|
|
||
| it('returns false for loopback hosts', () => { | ||
| expect(isAllInterfaces('127.0.0.1')).toBe(false); | ||
| expect(isAllInterfaces('localhost')).toBe(false); | ||
| expect(isAllInterfaces('::1')).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getLocalNetworkAddresses', () => { | ||
| it('returns non-empty array of IPv4 URLs for a valid port', () => { | ||
| const addresses = getLocalNetworkAddresses(3001); | ||
| expect(addresses.length).toBeGreaterThan(0); | ||
|
Comment on lines
+40
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This test fails on valid IPv6-only, loopback-only, or network-isolated CI environments because Useful? React with 👍 / 👎. |
||
| for (const addr of addresses) { | ||
| expect(addr).toMatch(/^http:\/\/\d+\.\d+\.\d+\.\d+:3001\/$/); | ||
| } | ||
| }); | ||
|
|
||
| it('returns different URLs for different ports', () => { | ||
| const addrs3001 = getLocalNetworkAddresses(3001); | ||
| const addrs8080 = getLocalNetworkAddresses(8080); | ||
| expect(addrs3001.length).toBe(addrs8080.length); | ||
| for (let i = 0; i < addrs3001.length; i++) { | ||
| expect(addrs3001[i]).toContain(':3001/'); | ||
| expect(addrs8080[i]).toContain(':8080/'); | ||
| } | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,7 +58,7 @@ The user may ask you to research on certain topics, process or generate certain | |
| - Understand the user's requirements thoroughly, ask for clarification before you start if needed. | ||
| - Make plans before doing deep or wide research, to ensure you are always on track. | ||
| - Search on the Internet if possible, with carefully-designed search queries to improve efficiency and accuracy. | ||
| - Use proper tools or shell commands or Python packages to process or generate images, videos, PDFs, docs, spreadsheets, presentations, or other multimedia files. Detect if there are already such tools in the environment. If you have to install third-party tools/packages, you MUST ensure that they are installed in a virtual/isolated environment. | ||
| - Use proper tools or shell commands or Python packages to process or generate images, videos, PDFs, docs, spreadsheets, presentations, or other multimedia files. For video analysis, prefer `ReadMediaFile` directly over writing Python scripts to extract frames. Detect if there are already such tools in the environment. If you have to install third-party tools/packages, you MUST ensure that they are installed in a virtual/isolated environment. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This changes only the agent-core-v2 template, but the normal AGENTS.md reference: AGENTS.md:L17-L17 Useful? React with 👍 / 👎. |
||
| - Once you generate or edit any images, videos or other media files, try to read it again before proceed, to ensure that the content is as expected. | ||
| - Avoid installing or deleting anything to/from outside of the current working directory. If you have to do so, ask the user for confirmation. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,10 @@ import { parseHTML as rawParseHTML } from 'linkedom'; | |
| import { Agent, type Dispatcher } from 'undici'; | ||
|
|
||
| import { isProxyConfigured, makeNoProxyMatcher, resolveNoProxy } from '#/_base/utils/proxy'; | ||
| import { | ||
| MODEL_ACCEPTED_IMAGE_MIMES, | ||
| normalizeImageMime, | ||
| } from '#/agent/media/image-format-policy'; | ||
|
|
||
| import { HttpFetchError, type UrlFetcher, type UrlFetchResult } from '../tools/fetch-url-types'; | ||
|
|
||
|
|
@@ -109,6 +113,27 @@ export class LocalFetchURLProvider implements UrlFetcher { | |
| } | ||
| } | ||
|
|
||
| const contentType = (response.headers.get('content-type') ?? '').toLowerCase(); | ||
|
|
||
| // Handle accepted image formats directly — convert to base64 data URL | ||
| // so the model can view them inline. | ||
| const normalizedMime = normalizeImageMime(contentType); | ||
| if (MODEL_ACCEPTED_IMAGE_MIMES.has(normalizedMime)) { | ||
|
Comment on lines
+120
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an origin labels AVIF, BMP, or arbitrary bytes as Useful? React with 👍 / 👎. |
||
| const buffer = await response.arrayBuffer(); | ||
| if (buffer.byteLength > this.maxBytes) { | ||
| throw new Error( | ||
| `Response body too large: ${String(buffer.byteLength)} bytes exceeds maxBytes (${String(this.maxBytes)}).`, | ||
|
Comment on lines
+122
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For a valid supported image between the media delivery ceiling and Useful? React with 👍 / 👎. |
||
| ); | ||
| } | ||
| const base64 = Buffer.from(buffer).toString('base64'); | ||
| const dataUrl = `data:${normalizedMime};base64,${base64}`; | ||
| return { | ||
| content: `Fetched image (${normalizedMime}).`, | ||
| kind: 'image', | ||
| imageUrl: dataUrl, | ||
| }; | ||
| } | ||
|
|
||
| const body = await response.text(); | ||
|
|
||
| const actualBytes = Buffer.byteLength(body, 'utf8'); | ||
|
|
@@ -118,7 +143,6 @@ export class LocalFetchURLProvider implements UrlFetcher { | |
| ); | ||
| } | ||
|
|
||
| const contentType = (response.headers.get('content-type') ?? '').toLowerCase(); | ||
| if (contentType.startsWith('text/plain') || contentType.startsWith('text/markdown')) { | ||
| return { content: body, kind: 'passthrough' }; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For
--host ::on an IPv6-only host or LAN, this filter discards every reachable address, solanUrlsis empty even though the server is listening and the changeset explicitly promises support for::. Include non-internal IPv6 interfaces and format them with brackets in URL authorities; IPv4 URLs can still be included when dual-stack connectivity is available.Useful? React with 👍 / 👎.