wave-agent-sdk 0.13.6 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +4 -2
  3. package/dist/managers/aiManager.d.ts +3 -0
  4. package/dist/managers/aiManager.d.ts.map +1 -1
  5. package/dist/managers/aiManager.js +93 -8
  6. package/dist/managers/messageManager.d.ts +15 -0
  7. package/dist/managers/messageManager.d.ts.map +1 -1
  8. package/dist/managers/messageManager.js +52 -2
  9. package/dist/managers/permissionManager.d.ts +4 -0
  10. package/dist/managers/permissionManager.d.ts.map +1 -1
  11. package/dist/managers/permissionManager.js +6 -0
  12. package/dist/managers/subagentManager.d.ts.map +1 -1
  13. package/dist/managers/subagentManager.js +23 -17
  14. package/dist/prompts/index.d.ts +2 -1
  15. package/dist/prompts/index.d.ts.map +1 -1
  16. package/dist/prompts/index.js +50 -25
  17. package/dist/services/aiService.d.ts.map +1 -1
  18. package/dist/services/aiService.js +11 -1
  19. package/dist/tools/agentTool.d.ts.map +1 -1
  20. package/dist/tools/agentTool.js +14 -2
  21. package/dist/tools/bashTool.d.ts.map +1 -1
  22. package/dist/tools/bashTool.js +27 -5
  23. package/dist/tools/types.d.ts +1 -0
  24. package/dist/tools/types.d.ts.map +1 -1
  25. package/dist/tools/webFetchTool.d.ts.map +1 -1
  26. package/dist/tools/webFetchTool.js +202 -78
  27. package/dist/types/messaging.d.ts +1 -0
  28. package/dist/types/messaging.d.ts.map +1 -1
  29. package/dist/utils/convertMessagesForAPI.js +1 -1
  30. package/dist/utils/groupMessagesByApiRound.d.ts +24 -0
  31. package/dist/utils/groupMessagesByApiRound.d.ts.map +1 -0
  32. package/dist/utils/groupMessagesByApiRound.js +97 -0
  33. package/dist/utils/messageOperations.d.ts +1 -0
  34. package/dist/utils/messageOperations.d.ts.map +1 -1
  35. package/dist/utils/microcompact.d.ts +7 -0
  36. package/dist/utils/microcompact.d.ts.map +1 -0
  37. package/dist/utils/microcompact.js +78 -0
  38. package/package.json +2 -1
  39. package/src/agent.ts +4 -2
  40. package/src/managers/aiManager.ts +117 -15
  41. package/src/managers/messageManager.ts +64 -2
  42. package/src/managers/permissionManager.ts +7 -0
  43. package/src/managers/subagentManager.ts +28 -24
  44. package/src/prompts/index.ts +51 -25
  45. package/src/services/aiService.ts +14 -1
  46. package/src/tools/agentTool.ts +14 -2
  47. package/src/tools/bashTool.ts +27 -5
  48. package/src/tools/types.ts +1 -0
  49. package/src/tools/webFetchTool.ts +276 -86
  50. package/src/types/messaging.ts +1 -0
  51. package/src/utils/convertMessagesForAPI.ts +1 -1
  52. package/src/utils/groupMessagesByApiRound.ts +120 -0
  53. package/src/utils/messageOperations.ts +1 -0
  54. package/src/utils/microcompact.ts +101 -0
@@ -148,28 +148,52 @@ This is critical - your turn should only end with either using the ${ASK_USER_QU
148
148
  NOTE: At any point in time through this workflow you should feel free to ask the user questions or clarifications using the ${ASK_USER_QUESTION_TOOL_NAME} tool. Don't make large assumptions about user intent. The goal is to present a well researched plan to the user, and tie any loose ends before implementation begins.`;
149
149
  }
150
150
  export const DEFAULT_SYSTEM_PROMPT = BASE_SYSTEM_PROMPT;
151
- export const COMPRESS_MESSAGES_SYSTEM_PROMPT = `You have been working on the task described above but have not yet completed it. Write a continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary. Your summary should be structured, concise, and actionable. Include:
152
- 1. Task Overview
153
- The user's core request and success criteria
154
- Any clarifications or constraints they specified
155
- 2. Current State
156
- What has been completed so far
157
- Files created, modified, or analyzed (with paths if relevant)
158
- Key outputs or artifacts produced
159
- 3. Important Discoveries
160
- Technical constraints or requirements uncovered
161
- Decisions made and their rationale
162
- Errors encountered and how they were resolved
163
- What approaches were tried that didn't work (and why)
164
- 4. Next Steps
165
- Specific actions needed to complete the task
166
- Any blockers or open questions to resolve
167
- Priority order if multiple steps remain
168
- 5. Context to Preserve
169
- User preferences or style requirements
170
- Domain-specific details that aren't obvious
171
- Any promises made to the user
172
- Be concise but complete—err on the side of including information that would prevent duplicate work or repeated mistakes. Write in a way that enables immediate resumption of the task.
151
+ export const COMPRESS_MESSAGES_SYSTEM_PROMPT = `You are continuing work on a software engineering task. Write a detailed continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary.
152
+
153
+ First, write your analysis in <analysis> tags as a thinking scratchpad:
154
+ - Chronologically review the conversation
155
+ - Identify user intents and goals
156
+ - Note files read/modified, approaches tried, decisions made
157
+ - Check for accuracy and completeness ensure nothing critical is missing
158
+
159
+ Then produce a structured summary in <summary> tags with these sections:
160
+
161
+ ## Primary Request and Intent
162
+ - The user's core request and success criteria
163
+ - Clarifications, constraints, or scope changes
164
+
165
+ ## Key Technical Concepts
166
+ - Frameworks, libraries, patterns, architectural decisions
167
+
168
+ ## Files and Code Sections
169
+ - Files read, modified, created (with full paths)
170
+ - Critical code snippets (function signatures, bug fixes, key logic)
171
+ - Focus on recent messages — include full code for important sections
172
+
173
+ ## Errors and Fixes
174
+ - Errors encountered, root causes, how they were resolved
175
+ - Approaches tried that didn't work and why
176
+
177
+ ## Problem Solving
178
+ - Approach evolution, trade-offs considered, decisions made
179
+
180
+ ## All User Messages
181
+ - Complete list of all user messages (non-tool content)
182
+ - Preserve exact wording where load-bearing
183
+
184
+ ## Pending Tasks
185
+ - Outstanding work, TODOs, unresolved questions
186
+
187
+ ## Current Work
188
+ - What was being worked on at the time of summarization
189
+ - Exact state of in-progress changes
190
+
191
+ ## Optional Next Step
192
+ - Immediate next action needed
193
+ - Include verbatim quotes from recent conversation if relevant
194
+
195
+ Be concise but complete — include information that prevents duplicate work or repeated mistakes.
196
+ Respond with text only. Do NOT call any tools.
173
197
  Wrap your summary in <summary></summary> tags.`;
174
198
  export const WEB_CONTENT_SYSTEM_PROMPT = `You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.`;
175
199
  export const BTW_SYSTEM_PROMPT = `You are a helpful assistant. Answer the user's side question based on the conversation history.
@@ -194,8 +218,9 @@ export function buildSystemPrompt(basePrompt, tools, options = {}) {
194
218
  if (options.planMode) {
195
219
  prompt += `\n\n${buildPlanModePrompt(options.planMode.planFilePath, options.planMode.planExists, options.isSubagent)}`;
196
220
  }
197
- if (options.workdir) {
198
- const isGitRepo = isGitRepository(options.workdir);
221
+ const workdirForPrompt = options.originalWorkdir || options.workdir;
222
+ if (workdirForPrompt) {
223
+ const isGitRepo = isGitRepository(workdirForPrompt);
199
224
  const platform = os.platform();
200
225
  const osVersion = `${os.type()} ${os.release()}`;
201
226
  const today = new Date().toISOString().split("T")[0];
@@ -209,7 +234,7 @@ export function buildSystemPrompt(basePrompt, tools, options = {}) {
209
234
 
210
235
  Here is useful information about the environment you are running in:
211
236
  <env>
212
- Working directory: ${options.workdir}
237
+ Working directory: ${workdirForPrompt}
213
238
  Is directory a git repo: ${isGitRepo}
214
239
  Platform: ${platform}
215
240
  Shell: ${shellName}
@@ -1 +1 @@
1
- {"version":3,"file":"aiService.d.ts","sourceRoot":"","sources":["../../src/services/aiService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aiService.d.ts","sourceRoot":"","sources":["../../src/services/aiService.ts"],"names":[],"mappings":"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"}
@@ -457,6 +457,16 @@ export async function compressMessages(options) {
457
457
  modelConfig.model === "rate-limit-test") {
458
458
  await acquireSlot(abortSignal);
459
459
  }
460
+ // Strip images from messages before compact API call to reduce token usage
461
+ const cleanedMessages = messages.map((msg) => {
462
+ // Handle user/assistant messages with array content
463
+ if (Array.isArray(msg.content)) {
464
+ const textParts = msg.content.filter((part) => part.type === "text");
465
+ const text = textParts.map((p) => p.text).join("\n");
466
+ return { ...msg, content: text || "(empty message)" };
467
+ }
468
+ return msg;
469
+ });
460
470
  // Create OpenAI client with injected configuration
461
471
  const openai = new OpenAIClient({
462
472
  apiKey: gatewayConfig.apiKey,
@@ -484,7 +494,7 @@ export async function compressMessages(options) {
484
494
  role: "system",
485
495
  content: COMPRESS_MESSAGES_SYSTEM_PROMPT,
486
496
  },
487
- ...messages,
497
+ ...cleanedMessages,
488
498
  {
489
499
  role: "user",
490
500
  content: `Please create a detailed summary of the conversation so far.`,
@@ -1 +1 @@
1
- {"version":3,"file":"agentTool.d.ts","sourceRoot":"","sources":["../../src/tools/agentTool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAStE;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,UAgQvB,CAAC"}
1
+ {"version":3,"file":"agentTool.d.ts","sourceRoot":"","sources":["../../src/tools/agentTool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAStE;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,UA4QvB,CAAC"}
@@ -61,7 +61,11 @@ When using the Agent tool, you must specify a subagent_type parameter to select
61
61
 
62
62
  - When doing file search, prefer to use the ${AGENT_TOOL_NAME} tool in order to reduce context usage.
63
63
  - You should proactively use the ${AGENT_TOOL_NAME} tool with specialized agents when the task at hand matches the agent's description.
64
- - VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the ${AGENT_TOOL_NAME} tool with subagent_type=${EXPLORE_SUBAGENT_TYPE} instead of running search commands directly.`;
64
+ - VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the ${AGENT_TOOL_NAME} tool with subagent_type=${EXPLORE_SUBAGENT_TYPE} instead of running search commands directly.
65
+ - You can optionally run agents in the background using the run_in_background parameter. When an agent runs in the background, you will be automatically notified when it completes — do NOT sleep, poll, or proactively check on its progress. Continue with other work or respond to the user instead.
66
+ - **Foreground vs background**: Use foreground (default) when you need the agent's results before you can proceed — e.g., research agents whose findings inform your next steps. Use background when you have genuinely independent work to do in parallel.
67
+ - **Don't peek.** The tool result includes an output file path — do not Read or tail it unless the user explicitly asks for a progress check. You get a completion notification; trust it. Reading the transcript mid-flight pulls the agent's tool noise into your context, which defeats the point of backgrounding.
68
+ - **Don't race.** After launching, you know nothing about what the agent found. Never fabricate or predict agent results in any format — not as prose, summary, or structured output. The notification arrives as a user-role message in a later turn; it is never something you write yourself. If the user asks a follow-up before the notification lands, tell them the agent is still running — give status, not a guess.`;
65
69
  },
66
70
  execute: async (args, context) => {
67
71
  const subagentManager = context.subagentManager;
@@ -169,9 +173,17 @@ When using the Agent tool, you must specify a subagent_type parameter to select
169
173
  if (run_in_background) {
170
174
  const task = context.backgroundTaskManager?.getTask(result);
171
175
  const outputPath = task?.outputPath;
176
+ const backgroundMsg = [
177
+ `Agent started in background with ID: ${result}.`,
178
+ `The agent is working in the background. You will be notified automatically when it completes.`,
179
+ `Do not duplicate this agent's work — avoid working with the same files or topics it is using.`,
180
+ outputPath
181
+ ? `output_file: ${outputPath}`
182
+ : `Briefly tell the user what you launched and end your response.`,
183
+ ].join("\n");
172
184
  resolve({
173
185
  success: true,
174
- content: `Agent started in background with ID: ${result}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
186
+ content: backgroundMsg,
175
187
  shortResult: `Agent started in background: ${result}`,
176
188
  });
177
189
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"bashTool.d.ts","sourceRoot":"","sources":["../../src/tools/bashTool.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAwCtE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,UA+ctB,CAAC"}
1
+ {"version":3,"file":"bashTool.d.ts","sourceRoot":"","sources":["../../src/tools/bashTool.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAwCtE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,UAqetB,CAAC"}
@@ -122,7 +122,10 @@ Use the gh command via the Bash tool for GitHub-related tasks including working
122
122
  - Do not retry failing commands in a sleep loop — diagnose the root cause.
123
123
  - If waiting for a background task you started with \`run_in_background\`, you will be notified when it completes — do not poll.
124
124
  - If you must poll an external process, use a check command (e.g. \`gh run view\`) rather than sleeping first.
125
- - If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.`,
125
+ - If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.
126
+
127
+ # CWD management
128
+ Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of \`cd\`. You may use \`cd\` if the User explicitly requests it. When you use \`cd\`, the shell working directory will be reset to the original working directory after the command completes.`,
126
129
  execute: async (args, context) => {
127
130
  const command = args.command;
128
131
  const runInBackground = args.run_in_background;
@@ -186,9 +189,16 @@ Use the gh command via the Bash tool for GitHub-related tasks including working
186
189
  const { id: taskId } = backgroundTaskManager.startShell(command, timeout);
187
190
  const task = backgroundTaskManager.getTask(taskId);
188
191
  const outputPath = task?.outputPath;
192
+ const backgroundMsg = [
193
+ `Command started in background with ID: ${taskId}.`,
194
+ `You will be notified automatically when it completes.`,
195
+ outputPath
196
+ ? `output_file: ${outputPath}`
197
+ : `Use ${READ_TOOL_NAME} tool with task_id="${taskId}" to read the output.`,
198
+ ].join("\n");
189
199
  return {
190
200
  success: true,
191
- content: `Command started in background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ` Use ${READ_TOOL_NAME} tool with task_id="${taskId}" to monitor output.`}`,
201
+ content: backgroundMsg,
192
202
  shortResult: `Background process ${taskId} started`,
193
203
  };
194
204
  }
@@ -376,14 +386,26 @@ Use the gh command via the Bash tool for GitHub-related tasks including working
376
386
  logger.error("Failed to clean up temp CWD file:", fileError);
377
387
  }
378
388
  }
379
- // If CWD changed, call the onCwdChange callback
389
+ // If CWD changed, call the onCwdChange callback and add notification
390
+ let cwdChangedNotification = "";
380
391
  if (newCwd && newCwd !== context.workdir && context.onCwdChange) {
381
- context.onCwdChange(newCwd);
392
+ const isInSafeZone = context.permissionManager?.isPathInSafeZone?.(newCwd) ?? true;
393
+ if (isInSafeZone) {
394
+ context.onCwdChange(newCwd);
395
+ }
396
+ else if (context.originalWorkdir) {
397
+ context.onCwdChange(context.originalWorkdir);
398
+ cwdChangedNotification = `Shell cwd was reset to ${context.originalWorkdir}\n`;
399
+ }
400
+ else {
401
+ context.onCwdChange(newCwd);
402
+ }
382
403
  }
383
404
  const exitCode = code ?? 0;
384
405
  const combinedOutput = outputBuffer + (errorBuffer ? "\n" + errorBuffer : "");
385
406
  // Handle large output by truncation and persistence if needed
386
- const finalOutput = combinedOutput || `Command executed with exit code: ${exitCode}`;
407
+ const finalOutput = cwdChangedNotification +
408
+ (combinedOutput || `Command executed with exit code: ${exitCode}`);
387
409
  const content = processOutput(finalOutput);
388
410
  const lines = combinedOutput.trim().split("\n");
389
411
  const shortResult = lines.length <= 3
@@ -38,6 +38,7 @@ export interface ToolContext {
38
38
  abortSignal?: AbortSignal;
39
39
  backgroundTaskManager?: import("../managers/backgroundTaskManager.js").BackgroundTaskManager;
40
40
  workdir: string;
41
+ originalWorkdir?: string;
41
42
  /** Permission mode for this tool execution */
42
43
  permissionMode?: PermissionMode;
43
44
  /** Custom permission callback */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"webFetchTool.d.ts","sourceRoot":"","sources":["../../src/tools/webFetchTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AA+BtE,eAAO,MAAM,YAAY,EAAE,UAiK1B,CAAC"}
1
+ {"version":3,"file":"webFetchTool.d.ts","sourceRoot":"","sources":["../../src/tools/webFetchTool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAoGtE,eAAO,MAAM,YAAY,EAAE,UAoL1B,CAAC"}
@@ -1,30 +1,80 @@
1
1
  import TurndownService from "turndown";
2
+ import { LRUCache } from "lru-cache";
2
3
  import { WEB_FETCH_TOOL_NAME } from "../constants/tools.js";
3
4
  import { logger } from "../utils/globalLogger.js";
5
+ // --- Security Limits ---
6
+ const MAX_HTTP_CONTENT_LENGTH = 10 * 1024 * 1024; // 10MB
7
+ const FETCH_TIMEOUT_MS = 60000; // 60s
8
+ const MAX_REDIRECTS = 10;
9
+ const MAX_MARKDOWN_LENGTH = 100000;
10
+ const USER_AGENT = "Wave-User (+https://github.com/netease-lcap/wave-agent)";
11
+ // --- Cache (LRU with 15min TTL, 50MB max) ---
4
12
  const CACHE_TTL = 15 * 60 * 1000; // 15 minutes
5
- const cache = new Map();
6
- function getFromCache(url) {
7
- const cached = cache.get(url);
8
- if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
9
- return cached.content;
13
+ const CACHE_MAX_BYTES = 50 * 1024 * 1024; // 50MB
14
+ const cache = new LRUCache({
15
+ ttl: CACHE_TTL,
16
+ maxSize: CACHE_MAX_BYTES,
17
+ sizeCalculation: (entry) => entry.bytes,
18
+ });
19
+ // --- Helpers ---
20
+ function formatSize(bytes) {
21
+ if (bytes < 1024)
22
+ return `${bytes}B`;
23
+ if (bytes < 1024 * 1024)
24
+ return `${(bytes / 1024).toFixed(1)}KB`;
25
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
26
+ }
27
+ function validateURL(url) {
28
+ if (url.length > 2000) {
29
+ return {
30
+ valid: false,
31
+ error: "URL exceeds maximum length of 2000 characters",
32
+ };
10
33
  }
11
- if (cached) {
12
- cache.delete(url);
34
+ let parsed;
35
+ try {
36
+ parsed = new URL(url);
13
37
  }
14
- return null;
15
- }
16
- function setToCache(url, content) {
17
- cache.set(url, { content, timestamp: Date.now() });
38
+ catch (error) {
39
+ return {
40
+ valid: false,
41
+ error: `Invalid URL: ${error instanceof Error ? error.message : String(error)}`,
42
+ };
43
+ }
44
+ if (parsed.username || parsed.password) {
45
+ return { valid: false, error: "URL must not contain username or password" };
46
+ }
47
+ const hostParts = parsed.hostname.split(".");
48
+ if (hostParts.length < 2) {
49
+ return {
50
+ valid: false,
51
+ error: "URL hostname must have at least two parts (e.g., example.com)",
52
+ };
53
+ }
54
+ return { valid: true };
18
55
  }
19
- // Clean up cache every 15 minutes
20
- setInterval(() => {
21
- const now = Date.now();
22
- for (const [url, cached] of cache.entries()) {
23
- if (now - cached.timestamp >= CACHE_TTL) {
24
- cache.delete(url);
25
- }
56
+ function isPermittedRedirect(originalUrl, redirectUrl) {
57
+ try {
58
+ const original = new URL(originalUrl);
59
+ const redirect = new URL(redirectUrl);
60
+ const origHost = original.host;
61
+ const redirHost = redirect.host;
62
+ // Same host
63
+ if (origHost === redirHost)
64
+ return true;
65
+ // www. variation (e.g., example.com <-> www.example.com)
66
+ const bareOrig = origHost.replace(/^www\./, "");
67
+ const bareRedir = redirHost.replace(/^www\./, "");
68
+ if (bareOrig === bareRedir)
69
+ return true;
70
+ return false;
71
+ }
72
+ catch {
73
+ return false;
26
74
  }
27
- }, CACHE_TTL).unref();
75
+ }
76
+ const GITHUB_URL_ERROR = "For GitHub URLs, please use the 'gh' CLI via the Bash tool instead (e.g., 'gh pr view', 'gh issue view', 'gh api').";
77
+ // --- Tool ---
28
78
  export const webFetchTool = {
29
79
  name: WEB_FETCH_TOOL_NAME,
30
80
  config: {
@@ -44,10 +94,11 @@ Usage notes:
44
94
  - IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions.
45
95
  - The URL must be a fully-formed valid URL
46
96
  - HTTP URLs will be automatically upgraded to HTTPS
97
+ - Content exceeding ${formatSize(MAX_MARKDOWN_LENGTH)} will be truncated
47
98
  - The prompt should describe what information you want to extract from the page
48
99
  - This tool is read-only and does not modify any files
49
100
  - Results may be summarized if the content is very large
50
- - Includes a self-cleaning 15-minute cache for faster responses when repeatedly accessing the same URL
101
+ - Includes an LRU cache with a 15-minute TTL for faster responses when repeatedly accessing the same URL
51
102
  - When a URL redirects to a different host, the tool will inform you and provide the redirect URL in a special format. You should then make a new WebFetch request with the redirect URL to fetch the content.
52
103
  - For GitHub URLs, prefer using the gh CLI via Bash instead (e.g., gh pr view, gh issue view, gh api).`,
53
104
  parameters: {
@@ -82,79 +133,82 @@ Usage notes:
82
133
  if (url.startsWith("http://")) {
83
134
  url = "https://" + url.substring(7);
84
135
  }
136
+ // Validate URL
137
+ const validation = validateURL(url);
138
+ if (!validation.valid) {
139
+ return {
140
+ success: false,
141
+ content: "",
142
+ error: validation.error,
143
+ };
144
+ }
85
145
  // Check for GitHub URLs
86
146
  if (url.includes("github.com")) {
87
147
  return {
88
148
  success: false,
89
149
  content: "",
90
- error: "For GitHub URLs, please use the 'gh' CLI via the Bash tool instead (e.g., 'gh pr view', 'gh issue view', 'gh api').",
150
+ error: GITHUB_URL_ERROR,
91
151
  };
92
152
  }
93
153
  try {
94
- let markdown = getFromCache(url);
95
- if (!markdown) {
96
- const response = await fetch(url, {
97
- redirect: "manual",
98
- headers: {
99
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
100
- },
101
- });
102
- if (response.status >= 300 && response.status < 400) {
103
- const location = response.headers.get("location");
104
- if (location) {
105
- const redirectUrl = new URL(location, url).toString();
106
- const originalHost = new URL(url).host;
107
- const redirectHost = new URL(redirectUrl).host;
108
- if (originalHost !== redirectHost) {
109
- return {
110
- success: true,
111
- content: `REDIRECT_TO: ${redirectUrl}\nThe URL redirected to a different host. Please make a new WebFetch request with this redirect URL if you wish to continue.`,
112
- };
113
- }
114
- // If same host, we could follow it, but the requirement says "When a URL redirects to a different host, the tool will inform you".
115
- // For simplicity and following the requirement strictly, let's just return the redirect for different hosts.
116
- // If it's the same host, we can try to fetch again or just return it too.
117
- return {
118
- success: true,
119
- content: `REDIRECT_TO: ${redirectUrl}\nThe URL redirected. Please make a new WebFetch request with this redirect URL.`,
120
- };
121
- }
122
- }
123
- if (!response.ok) {
124
- return {
125
- success: false,
126
- content: "",
127
- error: `Failed to fetch URL: ${response.status} ${response.statusText}`,
128
- };
129
- }
130
- const html = await response.text();
131
- const turndownService = new TurndownService();
132
- markdown = turndownService.turndown(html);
133
- setToCache(url, markdown);
154
+ const cached = cache.get(url);
155
+ if (cached) {
156
+ const markdown = cached.content;
157
+ return processWithAI(url, prompt, markdown, cached.code, cached.codeText, context);
158
+ }
159
+ // Fetch with redirect following
160
+ const result = await fetchWithRedirects(url, context.abortSignal);
161
+ if (result.kind === "redirect") {
162
+ return {
163
+ success: true,
164
+ content: `REDIRECT_TO: ${result.redirectUrl}\nThe URL redirected to a different host. Please make a new WebFetch request with this redirect URL if you wish to continue.`,
165
+ };
134
166
  }
135
- // Process with AI
136
- if (!context.aiManager || !context.aiService) {
167
+ if (result.kind === "error") {
137
168
  return {
138
169
  success: false,
139
- content: markdown,
140
- error: "AI Manager or AI Service not available for processing content",
170
+ content: "",
171
+ error: result.error,
141
172
  };
142
173
  }
143
- const modelConfig = context.aiManager.getModelConfig();
144
- const fastModel = modelConfig.fastModel;
145
- const aiResponse = await context.aiService.processWebContent({
146
- gatewayConfig: context.aiManager.getGatewayConfig(),
147
- modelConfig: modelConfig,
174
+ const { response, finalUrl } = result;
175
+ if (!response.ok) {
176
+ return {
177
+ success: false,
178
+ content: "",
179
+ error: `Failed to fetch URL: ${response.status} ${response.statusText}`,
180
+ };
181
+ }
182
+ const contentLengthHeader = response.headers.get("content-length");
183
+ const contentLength = contentLengthHeader
184
+ ? parseInt(contentLengthHeader, 10)
185
+ : null;
186
+ if (contentLength !== null && contentLength > MAX_HTTP_CONTENT_LENGTH) {
187
+ return {
188
+ success: false,
189
+ content: "",
190
+ error: `Content too large: ${formatSize(contentLength)} exceeds limit of ${formatSize(MAX_HTTP_CONTENT_LENGTH)}`,
191
+ };
192
+ }
193
+ const html = await response.text();
194
+ const turndownService = new TurndownService();
195
+ let markdown = turndownService.turndown(html);
196
+ const markdownBytes = new TextEncoder().encode(markdown).length;
197
+ // Truncate if too large
198
+ if (markdown.length > MAX_MARKDOWN_LENGTH) {
199
+ markdown =
200
+ markdown.substring(0, MAX_MARKDOWN_LENGTH) +
201
+ `[Content truncated at ${MAX_MARKDOWN_LENGTH} characters due to length limit.]`;
202
+ }
203
+ // Store in LRU cache
204
+ cache.set(finalUrl, {
205
+ bytes: markdownBytes,
206
+ code: response.status,
207
+ codeText: response.statusText,
148
208
  content: markdown,
149
- prompt: prompt,
150
- model: fastModel,
151
- abortSignal: context.abortSignal,
209
+ contentType: response.headers.get("content-type") || "",
152
210
  });
153
- return {
154
- success: true,
155
- content: aiResponse.content || "",
156
- shortResult: `Processed content from ${url}`,
157
- };
211
+ return processWithAI(finalUrl, prompt, markdown, response.status, response.statusText, context, markdownBytes);
158
212
  }
159
213
  catch (error) {
160
214
  logger.error(`WebFetch failed for ${url}:`, error);
@@ -169,3 +223,73 @@ Usage notes:
169
223
  return `Fetch ${params.url}`;
170
224
  },
171
225
  };
226
+ // --- Fetch with redirect following ---
227
+ async function fetchWithRedirects(initialUrl, abortSignal, redirectCount = 0) {
228
+ if (redirectCount >= MAX_REDIRECTS) {
229
+ return {
230
+ kind: "error",
231
+ error: `Too many redirects (max ${MAX_REDIRECTS})`,
232
+ };
233
+ }
234
+ const controller = new AbortController();
235
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
236
+ // Forward the context's abort signal if provided
237
+ if (abortSignal) {
238
+ abortSignal.addEventListener("abort", () => controller.abort(), {
239
+ once: true,
240
+ });
241
+ }
242
+ let response;
243
+ try {
244
+ response = await fetch(initialUrl, {
245
+ redirect: "manual",
246
+ signal: controller.signal,
247
+ headers: {
248
+ "User-Agent": USER_AGENT,
249
+ Accept: "text/markdown, text/html, */*",
250
+ },
251
+ });
252
+ }
253
+ finally {
254
+ clearTimeout(timeoutId);
255
+ }
256
+ if (response.status >= 300 && response.status < 400) {
257
+ const location = response.headers.get("location");
258
+ if (location) {
259
+ const redirectUrl = new URL(location, initialUrl).toString();
260
+ if (!isPermittedRedirect(initialUrl, redirectUrl)) {
261
+ return { kind: "redirect", redirectUrl };
262
+ }
263
+ // Follow permitted redirect recursively
264
+ return fetchWithRedirects(redirectUrl, abortSignal, redirectCount + 1);
265
+ }
266
+ }
267
+ return { kind: "response", response, finalUrl: initialUrl };
268
+ }
269
+ // --- AI Processing ---
270
+ async function processWithAI(url, prompt, markdown, statusCode, statusText, context, contentSize) {
271
+ if (!context.aiManager || !context.aiService) {
272
+ return {
273
+ success: false,
274
+ content: markdown,
275
+ error: "AI Manager or AI Service not available for processing content",
276
+ };
277
+ }
278
+ const modelConfig = context.aiManager.getModelConfig();
279
+ const fastModel = modelConfig.fastModel;
280
+ const aiResponse = await context.aiService.processWebContent({
281
+ gatewayConfig: context.aiManager.getGatewayConfig(),
282
+ modelConfig: modelConfig,
283
+ content: markdown,
284
+ prompt: prompt,
285
+ model: fastModel,
286
+ abortSignal: context.abortSignal,
287
+ });
288
+ const sizeStr = contentSize !== undefined ? formatSize(contentSize) : "unknown size";
289
+ const statusStr = `${statusCode} ${statusText}`.trim();
290
+ return {
291
+ success: true,
292
+ content: aiResponse.content || "",
293
+ shortResult: `Received ${sizeStr} (${statusStr}) from ${url}`,
294
+ };
295
+ }
@@ -52,6 +52,7 @@ export interface ToolBlock {
52
52
  compactParams?: string;
53
53
  parametersChunk?: string;
54
54
  isManuallyBackgrounded?: boolean;
55
+ timestamp?: number;
55
56
  }
56
57
  export interface ImageBlock {
57
58
  type: "image";
@@ -1 +1 @@
1
- {"version":3,"file":"messaging.d.ts","sourceRoot":"","sources":["../../src/types/messaging.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"messaging.d.ts","sourceRoot":"","sources":["../../src/types/messaging.ts"],"names":[],"mappings":"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"}
@@ -42,7 +42,7 @@ export function convertMessagesForAPI(messages) {
42
42
  const compressBlock = message.blocks.find((block) => block.type === "compress");
43
43
  if (compressBlock && compressBlock.type === "compress") {
44
44
  recentMessages.unshift({
45
- role: "assistant",
45
+ role: "user",
46
46
  content: compressBlock.content,
47
47
  });
48
48
  }