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.
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +4 -2
- package/dist/managers/aiManager.d.ts +3 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +93 -8
- package/dist/managers/messageManager.d.ts +15 -0
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +52 -2
- package/dist/managers/permissionManager.d.ts +4 -0
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +6 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +23 -17
- package/dist/prompts/index.d.ts +2 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +50 -25
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +11 -1
- package/dist/tools/agentTool.d.ts.map +1 -1
- package/dist/tools/agentTool.js +14 -2
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +27 -5
- package/dist/tools/types.d.ts +1 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts.map +1 -1
- package/dist/tools/webFetchTool.js +202 -78
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +1 -1
- package/dist/utils/groupMessagesByApiRound.d.ts +24 -0
- package/dist/utils/groupMessagesByApiRound.d.ts.map +1 -0
- package/dist/utils/groupMessagesByApiRound.js +97 -0
- package/dist/utils/messageOperations.d.ts +1 -0
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/microcompact.d.ts +7 -0
- package/dist/utils/microcompact.d.ts.map +1 -0
- package/dist/utils/microcompact.js +78 -0
- package/package.json +2 -1
- package/src/agent.ts +4 -2
- package/src/managers/aiManager.ts +117 -15
- package/src/managers/messageManager.ts +64 -2
- package/src/managers/permissionManager.ts +7 -0
- package/src/managers/subagentManager.ts +28 -24
- package/src/prompts/index.ts +51 -25
- package/src/services/aiService.ts +14 -1
- package/src/tools/agentTool.ts +14 -2
- package/src/tools/bashTool.ts +27 -5
- package/src/tools/types.ts +1 -0
- package/src/tools/webFetchTool.ts +276 -86
- package/src/types/messaging.ts +1 -0
- package/src/utils/convertMessagesForAPI.ts +1 -1
- package/src/utils/groupMessagesByApiRound.ts +120 -0
- package/src/utils/messageOperations.ts +1 -0
- package/src/utils/microcompact.ts +101 -0
package/dist/prompts/index.js
CHANGED
|
@@ -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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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: ${
|
|
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":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAGL,0BAA0B,EAC1B,0BAA0B,EAE3B,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,+BAA+B,CAAC;AAgEvC;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AA6DD,MAAM,WAAW,gBAAgB;IAE/B,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,WAAW,CAAC;IAGzB,QAAQ,EAAE,0BAA0B,EAAE,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,0BAA0B,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE;QACxB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,KAAK,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,GAAG,KAAK,CAAC;KACnD,KAAK,IAAI,CAAC;IACX,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,aAAa,CAAC,EACV,MAAM,GACN,QAAQ,GACR,YAAY,GACZ,gBAAgB,GAChB,eAAe,GACf,IAAI,CAAC;IACT,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAoU1B;AA4OD,MAAM,WAAW,uBAAuB;IAEtC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,WAAW,CAAC;IAGzB,QAAQ,EAAE,0BAA0B,EAAE,CAAC;IACvC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE;QACN,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,sBAAsB,CAAC,
|
|
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
|
-
...
|
|
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,
|
|
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"}
|
package/dist/tools/agentTool.js
CHANGED
|
@@ -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:
|
|
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,
|
|
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"}
|
package/dist/tools/bashTool.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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 =
|
|
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
|
package/dist/tools/types.d.ts
CHANGED
|
@@ -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":"
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = new URL(url);
|
|
13
37
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
}
|
|
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
|
|
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:
|
|
150
|
+
error: GITHUB_URL_ERROR,
|
|
91
151
|
};
|
|
92
152
|
}
|
|
93
153
|
try {
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if
|
|
105
|
-
|
|
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
|
-
|
|
136
|
-
if (!context.aiManager || !context.aiService) {
|
|
167
|
+
if (result.kind === "error") {
|
|
137
168
|
return {
|
|
138
169
|
success: false,
|
|
139
|
-
content:
|
|
140
|
-
error:
|
|
170
|
+
content: "",
|
|
171
|
+
error: result.error,
|
|
141
172
|
};
|
|
142
173
|
}
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messaging.d.ts","sourceRoot":"","sources":["../../src/types/messaging.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEvC,oBAAY,aAAa;IACvB,IAAI,SAAS;IACb,IAAI,SAAS;CACd;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,UAAU,GACV,SAAS,GACT,UAAU,GACV,SAAS,GACT,aAAa,GACb,cAAc,GACd,gBAAgB,GAChB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,KAAK,CAAC;QAEb,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,GAAG,KAAK,CAAC;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,OAAO,CAAC;
|
|
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: "
|
|
45
|
+
role: "user",
|
|
46
46
|
content: compressBlock.content,
|
|
47
47
|
});
|
|
48
48
|
}
|