mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-01 21:20:44 +00:00
feat(core): Migrate read_many_files, shell, and web_fetch. (#6167)
This commit is contained in:
parent
904f4623b6
commit
c0c0e9b7a0
6 changed files with 503 additions and 464 deletions
|
|
@ -6,14 +6,16 @@
|
|||
|
||||
import { SchemaValidator } from '../utils/schemaValidator.js';
|
||||
import {
|
||||
BaseTool,
|
||||
ToolResult,
|
||||
BaseDeclarativeTool,
|
||||
BaseToolInvocation,
|
||||
Kind,
|
||||
ToolCallConfirmationDetails,
|
||||
ToolConfirmationOutcome,
|
||||
Kind,
|
||||
ToolInvocation,
|
||||
ToolResult,
|
||||
} from './tools.js';
|
||||
import { getErrorMessage } from '../utils/errors.js';
|
||||
import { Config, ApprovalMode } from '../config/config.js';
|
||||
import { ApprovalMode, Config } from '../config/config.js';
|
||||
import { getResponseText } from '../utils/generateContentResponseUtilities.js';
|
||||
import { fetchWithTimeout, isPrivateIp } from '../utils/fetch.js';
|
||||
import { convert } from 'html-to-text';
|
||||
|
|
@ -59,41 +61,19 @@ export interface WebFetchToolParams {
|
|||
prompt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the WebFetch tool logic
|
||||
*/
|
||||
export class WebFetchTool extends BaseTool<WebFetchToolParams, ToolResult> {
|
||||
static readonly Name: string = 'web_fetch';
|
||||
|
||||
constructor(private readonly config: Config) {
|
||||
super(
|
||||
WebFetchTool.Name,
|
||||
'WebFetch',
|
||||
"Processes content from URL(s), including local and private network addresses (e.g., localhost), embedded in a prompt. Include up to 20 URLs and instructions (e.g., summarize, extract specific data) directly in the 'prompt' parameter.",
|
||||
Kind.Fetch,
|
||||
{
|
||||
properties: {
|
||||
prompt: {
|
||||
description:
|
||||
'A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). Must contain as least one URL starting with http:// or https://.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['prompt'],
|
||||
type: 'object',
|
||||
},
|
||||
);
|
||||
const proxy = config.getProxy();
|
||||
if (proxy) {
|
||||
setGlobalDispatcher(new ProxyAgent(proxy as string));
|
||||
}
|
||||
class WebFetchToolInvocation extends BaseToolInvocation<
|
||||
WebFetchToolParams,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
params: WebFetchToolParams,
|
||||
) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
private async executeFallback(
|
||||
params: WebFetchToolParams,
|
||||
signal: AbortSignal,
|
||||
): Promise<ToolResult> {
|
||||
const urls = extractUrls(params.prompt);
|
||||
private async executeFallback(signal: AbortSignal): Promise<ToolResult> {
|
||||
const urls = extractUrls(this.params.prompt);
|
||||
if (urls.length === 0) {
|
||||
return {
|
||||
llmContent: 'Error: No URL found in the prompt for fallback.',
|
||||
|
|
@ -127,13 +107,14 @@ export class WebFetchTool extends BaseTool<WebFetchToolParams, ToolResult> {
|
|||
}).substring(0, MAX_CONTENT_LENGTH);
|
||||
|
||||
const geminiClient = this.config.getGeminiClient();
|
||||
const fallbackPrompt = `The user requested the following: "${params.prompt}".
|
||||
const fallbackPrompt = `The user requested the following: "${this.params.prompt}".
|
||||
|
||||
I was unable to access the URL directly. Instead, I have fetched the raw content of the page. Please use the following content to answer the user's request. Do not attempt to access the URL again.
|
||||
I was unable to access the URL directly. Instead, I have fetched the raw content of the page. Please use the following content to answer the request. Do not attempt to access the URL again.
|
||||
|
||||
---
|
||||
${textContent}
|
||||
---`;
|
||||
---
|
||||
`;
|
||||
const result = await geminiClient.generateContent(
|
||||
[{ role: 'user', parts: [{ text: fallbackPrompt }] }],
|
||||
{},
|
||||
|
|
@ -154,49 +135,22 @@ ${textContent}
|
|||
}
|
||||
}
|
||||
|
||||
validateParams(params: WebFetchToolParams): string | null {
|
||||
const errors = SchemaValidator.validate(
|
||||
this.schema.parametersJsonSchema,
|
||||
params,
|
||||
);
|
||||
if (errors) {
|
||||
return errors;
|
||||
}
|
||||
if (!params.prompt || params.prompt.trim() === '') {
|
||||
return "The 'prompt' parameter cannot be empty and must contain URL(s) and instructions.";
|
||||
}
|
||||
if (
|
||||
!params.prompt.includes('http://') &&
|
||||
!params.prompt.includes('https://')
|
||||
) {
|
||||
return "The 'prompt' must contain at least one valid URL (starting with http:// or https://).";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getDescription(params: WebFetchToolParams): string {
|
||||
getDescription(): string {
|
||||
const displayPrompt =
|
||||
params.prompt.length > 100
|
||||
? params.prompt.substring(0, 97) + '...'
|
||||
: params.prompt;
|
||||
this.params.prompt.length > 100
|
||||
? this.params.prompt.substring(0, 97) + '...'
|
||||
: this.params.prompt;
|
||||
return `Processing URLs and instructions from prompt: "${displayPrompt}"`;
|
||||
}
|
||||
|
||||
async shouldConfirmExecute(
|
||||
params: WebFetchToolParams,
|
||||
): Promise<ToolCallConfirmationDetails | false> {
|
||||
async shouldConfirmExecute(): Promise<ToolCallConfirmationDetails | false> {
|
||||
if (this.config.getApprovalMode() === ApprovalMode.AUTO_EDIT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const validationError = this.validateParams(params);
|
||||
if (validationError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform GitHub URL conversion here to differentiate between user-provided
|
||||
// URL and the actual URL to be fetched.
|
||||
const urls = extractUrls(params.prompt).map((url) => {
|
||||
const urls = extractUrls(this.params.prompt).map((url) => {
|
||||
if (url.includes('github.com') && url.includes('/blob/')) {
|
||||
return url
|
||||
.replace('github.com', 'raw.githubusercontent.com')
|
||||
|
|
@ -208,7 +162,7 @@ ${textContent}
|
|||
const confirmationDetails: ToolCallConfirmationDetails = {
|
||||
type: 'info',
|
||||
title: `Confirm Web Fetch`,
|
||||
prompt: params.prompt,
|
||||
prompt: this.params.prompt,
|
||||
urls,
|
||||
onConfirm: async (outcome: ToolConfirmationOutcome) => {
|
||||
if (outcome === ToolConfirmationOutcome.ProceedAlways) {
|
||||
|
|
@ -219,25 +173,14 @@ ${textContent}
|
|||
return confirmationDetails;
|
||||
}
|
||||
|
||||
async execute(
|
||||
params: WebFetchToolParams,
|
||||
signal: AbortSignal,
|
||||
): Promise<ToolResult> {
|
||||
const validationError = this.validateParams(params);
|
||||
if (validationError) {
|
||||
return {
|
||||
llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`,
|
||||
returnDisplay: validationError,
|
||||
};
|
||||
}
|
||||
|
||||
const userPrompt = params.prompt;
|
||||
async execute(signal: AbortSignal): Promise<ToolResult> {
|
||||
const userPrompt = this.params.prompt;
|
||||
const urls = extractUrls(userPrompt);
|
||||
const url = urls[0];
|
||||
const isPrivate = isPrivateIp(url);
|
||||
|
||||
if (isPrivate) {
|
||||
return this.executeFallback(params, signal);
|
||||
return this.executeFallback(signal);
|
||||
}
|
||||
|
||||
const geminiClient = this.config.getGeminiClient();
|
||||
|
|
@ -295,7 +238,7 @@ ${textContent}
|
|||
}
|
||||
|
||||
if (processingError) {
|
||||
return this.executeFallback(params, signal);
|
||||
return this.executeFallback(signal);
|
||||
}
|
||||
|
||||
const sourceListFormatted: string[] = [];
|
||||
|
|
@ -360,3 +303,63 @@ ${sourceListFormatted.join('\n')}`;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the WebFetch tool logic
|
||||
*/
|
||||
export class WebFetchTool extends BaseDeclarativeTool<
|
||||
WebFetchToolParams,
|
||||
ToolResult
|
||||
> {
|
||||
static readonly Name: string = 'web_fetch';
|
||||
|
||||
constructor(private readonly config: Config) {
|
||||
super(
|
||||
WebFetchTool.Name,
|
||||
'WebFetch',
|
||||
"Processes content from URL(s), including local and private network addresses (e.g., localhost), embedded in a prompt. Include up to 20 URLs and instructions (e.g., summarize, extract specific data) directly in the 'prompt' parameter.",
|
||||
Kind.Fetch,
|
||||
{
|
||||
properties: {
|
||||
prompt: {
|
||||
description:
|
||||
'A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). Must contain as least one URL starting with http:// or https://.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['prompt'],
|
||||
type: 'object',
|
||||
},
|
||||
);
|
||||
const proxy = config.getProxy();
|
||||
if (proxy) {
|
||||
setGlobalDispatcher(new ProxyAgent(proxy as string));
|
||||
}
|
||||
}
|
||||
|
||||
protected validateToolParams(params: WebFetchToolParams): string | null {
|
||||
const errors = SchemaValidator.validate(
|
||||
this.schema.parametersJsonSchema,
|
||||
params,
|
||||
);
|
||||
if (errors) {
|
||||
return errors;
|
||||
}
|
||||
if (!params.prompt || params.prompt.trim() === '') {
|
||||
return "The 'prompt' parameter cannot be empty and must contain URL(s) and instructions.";
|
||||
}
|
||||
if (
|
||||
!params.prompt.includes('http://') &&
|
||||
!params.prompt.includes('https://')
|
||||
) {
|
||||
return "The 'prompt' must contain at least one valid URL (starting with http:// or https://).";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected createInvocation(
|
||||
params: WebFetchToolParams,
|
||||
): ToolInvocation<WebFetchToolParams, ToolResult> {
|
||||
return new WebFetchToolInvocation(this.config, params);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue