(model: string)
| 40 | * to derive the fast variant that processes fetched content. |
| 41 | */ |
| 42 | export function createWebFetch(model: string): DynamicStructuredTool { |
| 43 | return new DynamicStructuredTool({ |
| 44 | name: WEB_FETCH_TOOL_NAME, |
| 45 | description: |
| 46 | 'Fetch content from a URL, convert it to markdown, and answer a prompt about it using a fast model.', |
| 47 | schema: WebFetchInputSchema, |
| 48 | func: async (input, _runManager, config?: RunnableConfig) => { |
| 49 | const start = Date.now(); |
| 50 | const { url, prompt } = input; |
| 51 | |
| 52 | // Link the tool's abort controller to any signal provided by the runtime. |
| 53 | const abortController = new AbortController(); |
| 54 | const externalSignal = config?.signal; |
| 55 | if (externalSignal) { |
| 56 | if (externalSignal.aborted) { |
| 57 | abortController.abort(); |
| 58 | } else { |
| 59 | externalSignal.addEventListener('abort', () => abortController.abort(), { once: true }); |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | try { |
| 64 | const response = await getURLMarkdownContent(url, abortController); |
| 65 | |
| 66 | // Cross-host redirect: tell the model to re-fetch the redirect target. |
| 67 | if ('type' in response && response.type === 'redirect') { |
| 68 | const statusText = |
| 69 | response.statusCode === 301 |
| 70 | ? 'Moved Permanently' |
| 71 | : response.statusCode === 308 |
| 72 | ? 'Permanent Redirect' |
| 73 | : response.statusCode === 307 |
| 74 | ? 'Temporary Redirect' |
| 75 | : 'Found'; |
| 76 | |
| 77 | const message = `REDIRECT DETECTED: The URL redirects to a different host. |
| 78 | |
| 79 | Original URL: ${response.originalUrl} |
| 80 | Redirect URL: ${response.redirectUrl} |
| 81 | Status: ${response.statusCode} ${statusText} |
| 82 | |
| 83 | To complete your request, make a new web_fetch request with these parameters: |
| 84 | - url: "${response.redirectUrl}" |
| 85 | - prompt: "${prompt}"`; |
| 86 | |
| 87 | const output: WebFetchOutput = { |
| 88 | bytes: Buffer.byteLength(message), |
| 89 | code: response.statusCode, |
| 90 | codeText: statusText, |
| 91 | result: message, |
| 92 | durationMs: Date.now() - start, |
| 93 | url, |
| 94 | }; |
| 95 | return formatToolResult(output, [url]); |
| 96 | } |
| 97 | |
| 98 | const { content, bytes, code, codeText, contentType, persistedPath, persistedSize } = |
| 99 | response as FetchedContent; |
no test coverage detected