-
Notifications
You must be signed in to change notification settings - Fork 415
feat: FetchURL tool supports downloading images #1499
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
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 |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| Fetch content from a URL. Returns the main text content extracted from the page. Use this when you need to read a specific web page. | ||
| Fetch content from a URL. Returns the main text content extracted from the page, or images when the URL points to an image file. Use this when you need to read a specific web page or view an image from the web. | ||
|
|
||
| Only public `http`/`https` URLs are supported. Requests to private, loopback, or link-local addresses are refused, and responses larger than 10 MiB are rejected. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,8 @@ | |
| * should not be registered (not exposed to the LLM). | ||
| */ | ||
|
|
||
| import type { ContentPart } from '@moonshot-ai/kosong'; | ||
|
|
||
| import { z } from 'zod'; | ||
|
|
||
| import type { BuiltinTool } from '../../../agent/tool'; | ||
|
|
@@ -25,14 +27,19 @@ import DESCRIPTION from './fetch-url.md?raw'; | |
| * returned verbatim, in full. | ||
| * - `extracted` — the body was an HTML page; only the main article text | ||
| * was extracted and returned. | ||
| * - `image` — the body is an image; returned as a base64 data URI. | ||
| */ | ||
| export type UrlFetchKind = 'passthrough' | 'extracted'; | ||
| export type UrlFetchKind = 'passthrough' | 'extracted' | 'image'; | ||
|
|
||
| export interface UrlFetchResult { | ||
| /** The text handed to the LLM. */ | ||
| /** The text handed to the LLM, or a base64 data URI for images. */ | ||
| content: string; | ||
| /** Whether `content` is a verbatim passthrough or extracted main text. */ | ||
| /** Whether `content` is a verbatim passthrough, extracted main text, or image. */ | ||
| kind: UrlFetchKind; | ||
| /** The MIME type of the response, when known. */ | ||
| mimeType?: string | undefined; | ||
| /** Image dimensions, when the response is an image and they can be determined. */ | ||
| dimensions?: { width: number; height: number } | null | undefined; | ||
| } | ||
|
|
||
| export interface UrlFetcher { | ||
|
|
@@ -89,7 +96,7 @@ export class FetchURLTool implements BuiltinTool<FetchURLInput> { | |
| }: ExecutableToolContext, | ||
| ): Promise<ExecutableToolResult> { | ||
| try { | ||
| const { content, kind } = await this.fetcher.fetch(args.url, { toolCallId }); | ||
| const { content, kind, mimeType, dimensions } = await this.fetcher.fetch(args.url, { toolCallId }); | ||
|
|
||
| if (!content) { | ||
| return { | ||
|
|
@@ -98,6 +105,29 @@ export class FetchURLTool implements BuiltinTool<FetchURLInput> { | |
| }; | ||
| } | ||
|
|
||
| if (kind === 'image') { | ||
| const systemParts: string[] = ['Fetched image from URL.']; | ||
| if (mimeType) { | ||
| systemParts.push(`Mime type: ${mimeType}.`); | ||
| } | ||
| if (dimensions) { | ||
| systemParts.push( | ||
| `Original dimensions: ${String(dimensions.width)}x${String(dimensions.height)} pixels.`, | ||
| ); | ||
| systemParts.push( | ||
| 'If you need to output coordinates, output relative coordinates first ' + | ||
| 'and compute absolute coordinates using the original image size.', | ||
| ); | ||
| } | ||
| const output: ContentPart[] = [ | ||
| { type: 'text', text: `<system>${systemParts.join(' ')}</system>` }, | ||
| { type: 'text', text: `<image url="${args.url}">` }, | ||
| { type: 'image_url', imageUrl: { url: content } }, | ||
|
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 the current model lacks Useful? React with 👍 / 👎. |
||
| { type: 'text', text: '</image>' }, | ||
| ]; | ||
| return { output, isError: false }; | ||
| } | ||
|
|
||
| const builder = new ToolResultBuilder({ maxLineLength: null }); | ||
| builder.write(content); | ||
| // Tell the LLM whether it received the whole body or only the | ||
|
|
||
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.
In configurations with
services.moonshot_fetch.base_url,createRuntimeConfigwraps the local fetcher inMoonshotFetchURLProvider, and that provider returns{ content, kind: 'extracted' }for every 200 from the coding-fetch service (packages/agent-core/src/tools/providers/moonshot-fetch-url.ts:51-56). This image branch is therefore only reached when the service fails and falls back locally, so a successful Moonshot fetch of an image URL produces text/empty output instead of an image even though FetchURL now advertises image support. Detect image responses before/inside the Moonshot path or have that provider surfacekind: 'image'.Useful? React with 👍 / 👎.