@dudko.dev/agent-web 0.0.19 → 0.0.21
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/README.md +59 -1
- package/dist/agent-web.js +138 -100
- package/dist/agent-web.js.map +1 -1
- package/dist/{chunk-H7W6MBK2.js → chunk-V7COB2EA.js} +6 -2
- package/dist/chunk-V7COB2EA.js.map +1 -0
- package/dist/index.cjs +2210 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1069 -69
- package/dist/index.d.ts +1069 -69
- package/dist/index.js +2134 -190
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +88 -37
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts +10 -0
- package/dist/mcp.d.ts +10 -0
- package/dist/mcp.js +85 -37
- package/dist/mcp.js.map +1 -1
- package/package.json +13 -13
- package/dist/chunk-H7W6MBK2.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
FILES_STORE,
|
|
2
3
|
IndexedDBVault,
|
|
3
4
|
KEYS_STORE,
|
|
4
5
|
SECRETS_STORE,
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
encryptJSON,
|
|
8
9
|
getOrCreateVaultKey,
|
|
9
10
|
openAgentWebDB
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-V7COB2EA.js";
|
|
11
12
|
import {
|
|
12
13
|
createWebLLMModel,
|
|
13
14
|
isWebGPUAvailable,
|
|
@@ -15,29 +16,48 @@ import {
|
|
|
15
16
|
unloadWebLLMModel
|
|
16
17
|
} from "./chunk-IT65EW77.js";
|
|
17
18
|
|
|
18
|
-
// src/
|
|
19
|
-
var
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
replanAfter: c.replanAfter ?? "failure",
|
|
37
|
-
synthesize: c.synthesize ?? true,
|
|
38
|
-
compressAfterChars: c.compressAfterChars ?? 12e3,
|
|
39
|
-
logLevel: c.logLevel ?? "warn"
|
|
19
|
+
// src/caching.ts
|
|
20
|
+
var resolveCaching = (s) => {
|
|
21
|
+
if (s === false) return { enabled: false };
|
|
22
|
+
if (s === void 0 || s === true) return { enabled: true };
|
|
23
|
+
return { enabled: true, ttl: s.ttl, key: s.key };
|
|
24
|
+
};
|
|
25
|
+
var cachedInstructions = (system, caching) => caching.enabled ? {
|
|
26
|
+
role: "system",
|
|
27
|
+
content: system,
|
|
28
|
+
providerOptions: {
|
|
29
|
+
anthropic: {
|
|
30
|
+
cacheControl: { type: "ephemeral", ...caching.ttl ? { ttl: caching.ttl } : {} }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} : system;
|
|
34
|
+
var cachingProviderOptions = (caching, clientName, stage) => caching.enabled ? { openai: { promptCacheKey: caching.key ?? `${clientName}:${stage}` } } : void 0;
|
|
35
|
+
var breakpoint = (ttl) => ({
|
|
36
|
+
anthropic: { cacheControl: { type: "ephemeral", ...ttl ? { ttl } : {} } }
|
|
40
37
|
});
|
|
38
|
+
var withoutBreakpoint = (m) => {
|
|
39
|
+
const own = m.providerOptions;
|
|
40
|
+
if (!own?.anthropic || !("cacheControl" in own.anthropic)) return m;
|
|
41
|
+
const { cacheControl: _drop, ...anthropic } = own.anthropic;
|
|
42
|
+
const { anthropic: _old, ...rest } = own;
|
|
43
|
+
const providerOptions = Object.keys(anthropic).length ? { ...rest, anthropic } : rest;
|
|
44
|
+
const { providerOptions: _po, ...base } = m;
|
|
45
|
+
return Object.keys(providerOptions).length ? { ...base, providerOptions } : base;
|
|
46
|
+
};
|
|
47
|
+
var withRollingBreakpoint = (messages, caching) => {
|
|
48
|
+
if (!caching.enabled || messages.length === 0) return messages;
|
|
49
|
+
const out = messages.map((m, i) => i < messages.length - 1 ? withoutBreakpoint(m) : m);
|
|
50
|
+
const last = out[out.length - 1];
|
|
51
|
+
const own = last.providerOptions ?? {};
|
|
52
|
+
out[out.length - 1] = {
|
|
53
|
+
...last,
|
|
54
|
+
providerOptions: {
|
|
55
|
+
...own,
|
|
56
|
+
anthropic: { ...own.anthropic, ...breakpoint(caching.ttl).anthropic }
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return out;
|
|
60
|
+
};
|
|
41
61
|
|
|
42
62
|
// src/llm/generate.ts
|
|
43
63
|
import {
|
|
@@ -55,7 +75,10 @@ var normalizeUsage = (u) => {
|
|
|
55
75
|
return {
|
|
56
76
|
inputTokens,
|
|
57
77
|
outputTokens,
|
|
58
|
-
totalTokens: u?.totalTokens ?? inputTokens + outputTokens
|
|
78
|
+
totalTokens: u?.totalTokens ?? inputTokens + outputTokens,
|
|
79
|
+
reasoningTokens: u?.outputTokenDetails?.reasoningTokens ?? u?.reasoningTokens ?? 0,
|
|
80
|
+
cachedInputTokens: u?.inputTokenDetails?.cacheReadTokens ?? u?.cachedInputTokens ?? 0,
|
|
81
|
+
cacheWriteTokens: u?.inputTokenDetails?.cacheWriteTokens ?? 0
|
|
59
82
|
};
|
|
60
83
|
};
|
|
61
84
|
var timeoutSignal = (signal, ms) => {
|
|
@@ -66,53 +89,78 @@ var timeoutSignal = (signal, ms) => {
|
|
|
66
89
|
};
|
|
67
90
|
|
|
68
91
|
// src/llm/generate.ts
|
|
69
|
-
var
|
|
70
|
-
|
|
71
|
-
system: opts.system,
|
|
72
|
-
...promptOf(opts),
|
|
73
|
-
tools: opts.tools,
|
|
92
|
+
var callSettings = (opts) => ({
|
|
93
|
+
...opts.system !== void 0 ? { instructions: opts.system } : {},
|
|
74
94
|
maxOutputTokens: opts.maxOutputTokens,
|
|
75
95
|
temperature: opts.temperature,
|
|
96
|
+
...opts.reasoning ? { reasoning: opts.reasoning } : {},
|
|
97
|
+
...opts.providerOptions ? { providerOptions: opts.providerOptions } : {},
|
|
76
98
|
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
77
99
|
});
|
|
100
|
+
var generate = (model, opts = {}) => generateText({
|
|
101
|
+
model,
|
|
102
|
+
...callSettings(opts),
|
|
103
|
+
...promptOf(opts),
|
|
104
|
+
tools: opts.tools
|
|
105
|
+
});
|
|
78
106
|
var stream = (model, opts = {}) => streamText({
|
|
79
107
|
model,
|
|
80
|
-
|
|
108
|
+
...callSettings(opts),
|
|
81
109
|
...promptOf(opts),
|
|
82
|
-
tools: opts.tools
|
|
83
|
-
maxOutputTokens: opts.maxOutputTokens,
|
|
84
|
-
temperature: opts.temperature,
|
|
85
|
-
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
110
|
+
tools: opts.tools
|
|
86
111
|
});
|
|
87
112
|
var generateStructured = (model, schema, opts = {}) => generateObject({
|
|
88
113
|
model,
|
|
89
114
|
schema,
|
|
90
|
-
|
|
91
|
-
...promptOf(opts)
|
|
92
|
-
maxOutputTokens: opts.maxOutputTokens,
|
|
93
|
-
temperature: opts.temperature,
|
|
94
|
-
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
115
|
+
...callSettings(opts),
|
|
116
|
+
...promptOf(opts)
|
|
95
117
|
});
|
|
96
118
|
|
|
97
119
|
// src/memory/compress.ts
|
|
98
|
-
var
|
|
120
|
+
var resolveCompaction = (c) => {
|
|
121
|
+
const contextWindowTokens = c?.contextWindowTokens ?? 128e3;
|
|
122
|
+
return {
|
|
123
|
+
auto: c?.auto ?? true,
|
|
124
|
+
contextWindowTokens,
|
|
125
|
+
thresholdTokens: c?.thresholdTokens ?? Math.floor(contextWindowTokens / 2),
|
|
126
|
+
keepRecentTurns: c?.keepRecentTurns ?? 4,
|
|
127
|
+
keepRecentSteps: c?.keepRecentSteps ?? 3,
|
|
128
|
+
summaryMaxTokens: c?.summaryMaxTokens ?? 1024,
|
|
129
|
+
maxToolOutputChars: c?.maxToolOutputChars ?? 2e4,
|
|
130
|
+
clearToolResultsAfterTokens: c?.clearToolResultsAfterTokens ?? Math.floor(contextWindowTokens / 4),
|
|
131
|
+
keepToolResults: c?.keepToolResults ?? 3
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
var estimateTokens = (text) => Math.ceil((text ?? "").length / 4);
|
|
135
|
+
var messageTokens = (msgs) => msgs.reduce((n, m) => n + estimateTokens(`${m.role}: ${m.content}
|
|
136
|
+
`), 0);
|
|
137
|
+
var SUMMARY_SYSTEM = "Summarize the following conversation into a compact set of durable facts, decisions, identifiers, open tasks and user preferences. Keep names, numbers and IDs verbatim. Output plain text only.";
|
|
99
138
|
var compressHistory = async (messages, model, opts = {}) => {
|
|
100
|
-
const maxChars = opts.maxChars ?? 12e3;
|
|
101
139
|
const keepRecent = opts.keepRecent ?? 6;
|
|
102
|
-
if (
|
|
103
|
-
|
|
140
|
+
if (messages.length <= keepRecent + 1) return messages;
|
|
141
|
+
if (!opts.force) {
|
|
142
|
+
if (opts.thresholdTokens !== void 0) {
|
|
143
|
+
if (opts.thresholdTokens <= 0 || messageTokens(messages) <= opts.thresholdTokens) {
|
|
144
|
+
return messages;
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
const maxChars = opts.maxChars ?? 12e3;
|
|
148
|
+
const chars = messages.reduce((n, m) => n + m.content.length, 0);
|
|
149
|
+
if (maxChars <= 0 || chars <= maxChars) return messages;
|
|
150
|
+
}
|
|
104
151
|
}
|
|
105
152
|
const head = messages.slice(0, messages.length - keepRecent);
|
|
106
153
|
const tail = messages.slice(messages.length - keepRecent);
|
|
107
|
-
const transcript = head.map((m) => `${m.role}: ${m.content}`).join("\n").slice(
|
|
154
|
+
const transcript = head.map((m) => `${m.role}: ${m.content}`).join("\n").slice(-48e3);
|
|
108
155
|
try {
|
|
109
156
|
const result = await generate(model, {
|
|
110
|
-
system:
|
|
157
|
+
system: SUMMARY_SYSTEM,
|
|
111
158
|
prompt: transcript,
|
|
112
|
-
maxOutputTokens: 400,
|
|
159
|
+
maxOutputTokens: opts.summaryMaxTokens ?? 400,
|
|
113
160
|
timeoutMs: opts.timeoutMs,
|
|
114
161
|
abortSignal: opts.abortSignal
|
|
115
162
|
});
|
|
163
|
+
opts.onUsage?.(normalizeUsage(result.usage));
|
|
116
164
|
const clean = (result.text || "").trim();
|
|
117
165
|
if (!clean) return messages;
|
|
118
166
|
const summaryMsg = {
|
|
@@ -126,6 +174,93 @@ ${clean}`,
|
|
|
126
174
|
return messages;
|
|
127
175
|
}
|
|
128
176
|
};
|
|
177
|
+
var compactHistory = compressHistory;
|
|
178
|
+
var TRACE_SYSTEM = "Summarize these completed agent steps into a compact progress note: what was done, what was found (keep names, numbers and IDs verbatim) and what failed. Plain text only.";
|
|
179
|
+
var compactSteps = async (done, model, opts) => {
|
|
180
|
+
const keepRecent = opts.keepRecent ?? 3;
|
|
181
|
+
if (done.length <= keepRecent + 1) return done;
|
|
182
|
+
if (!opts.force && estimateTokens(done.join("\n")) <= opts.thresholdTokens) return done;
|
|
183
|
+
const head = done.slice(0, done.length - keepRecent);
|
|
184
|
+
try {
|
|
185
|
+
const result = await generate(model, {
|
|
186
|
+
system: TRACE_SYSTEM,
|
|
187
|
+
prompt: head.map((d, i) => `${i + 1}. ${d}`).join("\n"),
|
|
188
|
+
maxOutputTokens: opts.summaryMaxTokens ?? 600,
|
|
189
|
+
timeoutMs: opts.timeoutMs,
|
|
190
|
+
abortSignal: opts.abortSignal
|
|
191
|
+
});
|
|
192
|
+
opts.onUsage?.(normalizeUsage(result.usage));
|
|
193
|
+
const clean = (result.text || "").trim();
|
|
194
|
+
if (!clean) return done;
|
|
195
|
+
return [`Summary of ${head.length} earlier step(s): ${clean}`, ...done.slice(head.length)];
|
|
196
|
+
} catch {
|
|
197
|
+
return done;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/config.ts
|
|
202
|
+
var resolveConfig = (c) => ({
|
|
203
|
+
clientName: c.clientName ?? "agent-web",
|
|
204
|
+
sessionId: c.sessionId ?? "default",
|
|
205
|
+
toolMode: c.toolMode ?? "auto",
|
|
206
|
+
toolSelectionStrategy: c.toolSelectionStrategy ?? "auto",
|
|
207
|
+
toolSearchThreshold: c.toolSearchThreshold ?? 40,
|
|
208
|
+
maxIterations: c.maxIterations ?? 8,
|
|
209
|
+
maxStepsPerTask: c.maxStepsPerTask ?? 4,
|
|
210
|
+
maxRevisions: c.maxRevisions ?? 2,
|
|
211
|
+
maxToolCalls: c.maxToolCalls ?? 0,
|
|
212
|
+
maxPlanSteps: c.maxPlanSteps ?? 8,
|
|
213
|
+
chatTimeoutMs: c.chatTimeoutMs ?? 12e4,
|
|
214
|
+
budgets: {
|
|
215
|
+
planner: c.limits?.perCall?.planner ?? c.budgets?.planner ?? 800,
|
|
216
|
+
executor: c.limits?.perCall?.executor ?? c.budgets?.executor ?? 1200,
|
|
217
|
+
replanner: c.limits?.perCall?.replanner ?? c.budgets?.replanner ?? 400,
|
|
218
|
+
synthesizer: c.limits?.perCall?.synthesizer ?? c.budgets?.synthesizer ?? 400,
|
|
219
|
+
compaction: c.limits?.perCall?.compaction ?? c.compaction?.summaryMaxTokens ?? 1024
|
|
220
|
+
},
|
|
221
|
+
limits: c.limits ?? {},
|
|
222
|
+
temperature: c.temperature,
|
|
223
|
+
replan: c.replan ?? true,
|
|
224
|
+
replanAfter: c.replanAfter ?? "failure",
|
|
225
|
+
synthesize: c.synthesize ?? true,
|
|
226
|
+
compressAfterChars: c.compressAfterChars ?? 12e3,
|
|
227
|
+
compaction: resolveCompaction(c.compaction),
|
|
228
|
+
logLevel: c.logLevel ?? "warn"
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// src/limits.ts
|
|
232
|
+
var over = (value, cap) => typeof cap === "number" && cap > 0 && (value ?? 0) >= cap;
|
|
233
|
+
var checkLimits = (usage, limits) => {
|
|
234
|
+
if (!limits) return void 0;
|
|
235
|
+
if (over(usage.totalTokens, limits.maxTotalTokens)) {
|
|
236
|
+
return { kind: "total", tokens: usage.totalTokens, cap: limits.maxTotalTokens };
|
|
237
|
+
}
|
|
238
|
+
if (over(usage.inputTokens, limits.maxInputTokens)) {
|
|
239
|
+
return { kind: "input", tokens: usage.inputTokens, cap: limits.maxInputTokens };
|
|
240
|
+
}
|
|
241
|
+
if (over(usage.outputTokens, limits.maxOutputTokens)) {
|
|
242
|
+
return { kind: "output", tokens: usage.outputTokens, cap: limits.maxOutputTokens };
|
|
243
|
+
}
|
|
244
|
+
if (over(usage.reasoningTokens, limits.maxReasoningTokens)) {
|
|
245
|
+
return {
|
|
246
|
+
kind: "reasoning",
|
|
247
|
+
tokens: usage.reasoningTokens ?? 0,
|
|
248
|
+
cap: limits.maxReasoningTokens
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return void 0;
|
|
252
|
+
};
|
|
253
|
+
var ToolBudgetError = class extends Error {
|
|
254
|
+
constructor(cap) {
|
|
255
|
+
super(
|
|
256
|
+
`Tool-call budget exhausted (${cap} per run). Do not call more tools; report what you have.`
|
|
257
|
+
);
|
|
258
|
+
this.name = "ToolBudgetError";
|
|
259
|
+
}
|
|
260
|
+
toJSON() {
|
|
261
|
+
return this.message;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
129
264
|
|
|
130
265
|
// src/providers/types.ts
|
|
131
266
|
var isDirectModel = (m) => typeof m === "string" || typeof m === "object" && m !== null && "specificationVersion" in m;
|
|
@@ -328,54 +463,77 @@ var historyBlock = (history) => {
|
|
|
328
463
|
CONVERSATION SO FAR:
|
|
329
464
|
${lines.join("\n")}`;
|
|
330
465
|
};
|
|
331
|
-
var
|
|
332
|
-
Produce a brief "thought" and an ordered "steps" list (1\u20136 DISTINCT, self-contained steps) grounded in the current STATE and the available TOOLS.
|
|
333
|
-
If the user only greets, thanks, makes small talk, asks a question, or is unclear: return an EMPTY steps list and put a short, friendly answer in "thought".
|
|
334
|
-
If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns.
|
|
335
|
-
Never repeat or pad steps.`;
|
|
336
|
-
var PLANNER_PROMPTED = `You are the PLANNER of a tool-using agent that changes a workspace step by step.
|
|
466
|
+
var section = (title, body) => body && body.trim() ? `
|
|
337
467
|
|
|
338
|
-
|
|
339
|
-
{
|
|
340
|
-
|
|
341
|
-
-
|
|
468
|
+
${title}:
|
|
469
|
+
${body.trim()}` : "";
|
|
470
|
+
var PLANNER_RULES = `- Act autonomously: whenever the TOOLS can do the work or look up the answer, plan the steps \u2014 a question that needs data, a lookup or a check is a real goal too.
|
|
471
|
+
- Never plan a step that asks the user something. If the request is ambiguous, pick the most reasonable interpretation and say which one you chose.
|
|
342
472
|
- Plan realistic steps the available TOOLS can perform, grounded in the current STATE.
|
|
343
|
-
- If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
473
|
+
- If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns.
|
|
474
|
+
- Never repeat or pad steps.`;
|
|
475
|
+
var PLANNER_NATIVE = `You are the PLANNER of an autonomous tool-using agent that works step by step.
|
|
476
|
+
Produce a brief "thought" and an ordered "steps" list (1\u20136 DISTINCT, self-contained steps).
|
|
477
|
+
Return an EMPTY steps list ONLY for a pure greeting, thanks or small talk, or a question you can answer completely from the STATE and general knowledge without any tool \u2014 then put the answer itself in "thought".
|
|
478
|
+
${PLANNER_RULES}`;
|
|
479
|
+
var PLANNER_PROMPTED = `You are the PLANNER of an autonomous tool-using agent that works step by step.
|
|
480
|
+
|
|
481
|
+
Reply with a single JSON object, nothing else:
|
|
482
|
+
{ "reply": string, "plan": string[], "skills": string[] }
|
|
483
|
+
- For a real goal: "reply" is one short sentence; "plan" is 1\u20136 DISTINCT, self-contained steps.
|
|
484
|
+
- Set "plan": [] ONLY for a pure greeting, thanks or small talk, or a question you can answer completely from the STATE and general knowledge without any tool \u2014 then put the answer itself in "reply".
|
|
485
|
+
${PLANNER_RULES}`;
|
|
486
|
+
var PLANNER_SKILLS = `
|
|
487
|
+
If a SKILLS list is present, list the names of the skills that apply to this goal in "skills" (or leave it empty).`;
|
|
488
|
+
var PLANNER_SEARCH = `
|
|
489
|
+
The TOOLS list may be abbreviated: the executor can search the full catalogue, so describe WHAT to do; name tools only when you see them listed.`;
|
|
490
|
+
var EXECUTOR_RULES = `- Work autonomously: never ask the user questions or for confirmation. Look things up with the tools, choose sensible defaults, and state any assumption in your reply. Permission to run tools is handled by the system, not by you.
|
|
491
|
+
- Build on the current STATE and on the results of earlier steps \u2014 do not repeat work that is already done.`;
|
|
492
|
+
var EXECUTOR_NATIVE = `You are the EXECUTOR of an autonomous tool-using agent. Carry out ONLY the current step by calling the provided tools.
|
|
493
|
+
${EXECUTOR_RULES}
|
|
494
|
+
- When the step is done, reply with a short factual summary of what you did and found, including the concrete data (names, ids, numbers, values) later steps or the final answer need. No JSON.
|
|
495
|
+
- If you truly CANNOT complete the step (a needed tool is missing or was denied, credentials or data are unavailable), explain why in one sentence and include the token [BLOCKER].`;
|
|
496
|
+
var EXECUTOR_PROMPTED = `You are the EXECUTOR of an autonomous tool-using agent. Carry out ONLY the current step by emitting tool calls.
|
|
349
497
|
|
|
350
498
|
Reply with a single JSON object, nothing else:
|
|
351
499
|
{ "reply": string, "actions": [ { "tool": string, "args": object } ] }
|
|
352
500
|
- "actions" are the tool calls for THIS step ([] if none are needed). Use ONLY tools from the TOOLS list; "args" must match the tool's parameters.
|
|
353
|
-
|
|
354
|
-
- "reply" is
|
|
501
|
+
${EXECUTOR_RULES}
|
|
502
|
+
- "reply" is a short factual summary (no JSON) of what you did and found, with the concrete data later steps need.
|
|
355
503
|
- After your actions run you will see their TOOL RESULTS and may continue the same step; finish with "actions": [] once it is done.
|
|
356
|
-
- If you CANNOT complete the step, set "actions": [] and put the token [BLOCKER] in "reply" with a short reason.`;
|
|
357
|
-
var
|
|
504
|
+
- If you truly CANNOT complete the step, set "actions": [] and put the token [BLOCKER] in "reply" with a short reason.`;
|
|
505
|
+
var EXECUTOR_SEARCH = `
|
|
506
|
+
- If a tool you need is not in your current tool list, call find_tools with keywords; the tools it returns become callable right after.`;
|
|
507
|
+
var EXECUTOR_SKILLS = `
|
|
508
|
+
- If a skill from the SKILLS list covers this step, call load_skill to read it first (read_skill_file for its bundled files).`;
|
|
509
|
+
var REPLANNER_RULES = `Judge from the current STATE and the progress vs the goal. Prefer "continue".
|
|
510
|
+
After a failure, prefer revising around it (another tool, another approach) over giving up; never add a step that asks the user something.`;
|
|
511
|
+
var REPLANNER_NATIVE = `You are the REPLANNER of an autonomous agent. After an executed step, decide whether to keep going, revise the remaining steps, or finish.
|
|
358
512
|
"continue": the remaining steps still fit. "revise": provide a better "plan" for the REMAINING work (never repeat done work). "finish": the goal is already met.
|
|
359
|
-
|
|
360
|
-
var REPLANNER_PROMPTED = `You are the REPLANNER. After each executed step you decide whether to keep going, revise the remaining steps, or finish.
|
|
513
|
+
${REPLANNER_RULES}`;
|
|
514
|
+
var REPLANNER_PROMPTED = `You are the REPLANNER of an autonomous agent. After each executed step you decide whether to keep going, revise the remaining steps, or finish.
|
|
361
515
|
|
|
362
516
|
Reply with a single JSON object, nothing else:
|
|
363
517
|
{ "decision": "continue" | "revise" | "finish", "reason": string, "plan": string[] }
|
|
364
518
|
- "continue": remaining steps still fit \u2014 proceed (omit "plan").
|
|
365
519
|
- "revise": replace the REMAINING steps with a better list in "plan"; never repeat done work.
|
|
366
520
|
- "finish": the goal is already met \u2014 stop (omit "plan").
|
|
367
|
-
|
|
368
|
-
var SYNTHESIZER_SYSTEM = `You are the SYNTHESIZER.
|
|
521
|
+
${REPLANNER_RULES}`;
|
|
522
|
+
var SYNTHESIZER_SYSTEM = `You are the SYNTHESIZER. Answer the user's goal from what the agent did and found.
|
|
523
|
+
- Lead with the result: the concrete data, from the FINDINGS verbatim when accuracy matters (names, numbers, ids), then \u2014 briefly \u2014 what was changed.
|
|
524
|
+
- Mention any assumption the agent made. If something could not be done, say so plainly and why.
|
|
525
|
+
- Do not ask follow-up questions unless the goal genuinely cannot be completed without the user.
|
|
526
|
+
- Be concise and friendly. Plain text (light markdown is fine) \u2014 no JSON, no code.`;
|
|
369
527
|
var defaultPrompts = {
|
|
370
528
|
planner: (ctx) => ({
|
|
371
|
-
system: ctx.mode === "prompted" ? PLANNER_PROMPTED : PLANNER_NATIVE,
|
|
372
|
-
prompt: `GOAL: ${ctx.goal}${historyBlock(ctx.history)}${stateBlock(ctx.state)}
|
|
529
|
+
system: (ctx.mode === "prompted" ? PLANNER_PROMPTED : PLANNER_NATIVE) + (ctx.skills ? PLANNER_SKILLS : "") + (ctx.searchMode ? PLANNER_SEARCH : "") + section("SKILLS", ctx.skills) + `
|
|
373
530
|
|
|
374
531
|
TOOLS:
|
|
375
|
-
${ctx.toolCatalog}
|
|
532
|
+
${ctx.toolCatalog}`,
|
|
533
|
+
prompt: `GOAL: ${ctx.goal}${historyBlock(ctx.history)}${stateBlock(ctx.state)}`
|
|
376
534
|
}),
|
|
377
535
|
executor: (ctx) => ({
|
|
378
|
-
system: ctx.mode === "prompted" ? EXECUTOR_PROMPTED : EXECUTOR_NATIVE,
|
|
536
|
+
system: (ctx.mode === "prompted" ? EXECUTOR_PROMPTED : EXECUTOR_NATIVE) + (ctx.searchMode ? EXECUTOR_SEARCH : "") + (ctx.skills ? EXECUTOR_SKILLS : "") + section("SKILLS", ctx.skills) + section("ACTIVE SKILL INSTRUCTIONS", ctx.activeSkills),
|
|
379
537
|
prompt: [
|
|
380
538
|
`GOAL: ${ctx.goal}`,
|
|
381
539
|
stateBlock(ctx.state).trimStart(),
|
|
@@ -387,7 +545,7 @@ ${numbered(ctx.done, "")}` : "",
|
|
|
387
545
|
].filter(Boolean).join("\n\n")
|
|
388
546
|
}),
|
|
389
547
|
replanner: (ctx) => ({
|
|
390
|
-
system: ctx.mode === "prompted" ? REPLANNER_PROMPTED : REPLANNER_NATIVE,
|
|
548
|
+
system: (ctx.mode === "prompted" ? REPLANNER_PROMPTED : REPLANNER_NATIVE) + section("ACTIVE SKILL INSTRUCTIONS", ctx.activeSkills),
|
|
391
549
|
prompt: [
|
|
392
550
|
`GOAL: ${ctx.goal}`,
|
|
393
551
|
stateBlock(ctx.state).trimStart(),
|
|
@@ -399,13 +557,15 @@ ${numbered(ctx.remaining, "(none)")}`,
|
|
|
399
557
|
].filter(Boolean).join("\n\n")
|
|
400
558
|
}),
|
|
401
559
|
synthesizer: (ctx) => ({
|
|
402
|
-
system: SYNTHESIZER_SYSTEM,
|
|
560
|
+
system: SYNTHESIZER_SYSTEM + section("ACTIVE SKILL INSTRUCTIONS", ctx.activeSkills),
|
|
403
561
|
prompt: [
|
|
404
562
|
`GOAL: ${ctx.goal}`,
|
|
405
563
|
stateBlock(ctx.state).trimStart(),
|
|
406
564
|
`What was done:
|
|
407
565
|
${numbered(ctx.done, "(no changes)")}`,
|
|
408
|
-
|
|
566
|
+
ctx.findings?.length ? `FINDINGS (tool results):
|
|
567
|
+
${ctx.findings.join("\n")}` : "",
|
|
568
|
+
"Write the final answer for the user."
|
|
409
569
|
].filter(Boolean).join("\n\n")
|
|
410
570
|
})
|
|
411
571
|
};
|
|
@@ -413,6 +573,294 @@ var withSystem = (base, extra) => extra && extra.trim() ? `${extra.trim()}
|
|
|
413
573
|
|
|
414
574
|
${base}` : base;
|
|
415
575
|
|
|
576
|
+
// src/skills.ts
|
|
577
|
+
import { jsonSchema, tool } from "ai";
|
|
578
|
+
var SKILL_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
579
|
+
var SKILL_TOOL_NAMES = ["load_skill", "read_skill_file"];
|
|
580
|
+
var defineSkill = (skill) => {
|
|
581
|
+
if (!skill || typeof skill !== "object") throw new Error("skill must be an object");
|
|
582
|
+
if (typeof skill.name !== "string" || !SKILL_NAME_RE.test(skill.name)) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
`skill name ${JSON.stringify(skill.name)} is invalid: use 1-64 lowercase letters, digits and dashes`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
if (typeof skill.description !== "string" || !skill.description.trim()) {
|
|
588
|
+
throw new Error(`skill "${skill.name}" needs a description`);
|
|
589
|
+
}
|
|
590
|
+
if (typeof skill.content !== "string") {
|
|
591
|
+
throw new Error(`skill "${skill.name}" needs a content string`);
|
|
592
|
+
}
|
|
593
|
+
for (const f of skill.files ?? []) {
|
|
594
|
+
if (typeof f?.path !== "string" || !f.path || f.path.startsWith("/") || f.path.includes("..")) {
|
|
595
|
+
throw new Error(`skill "${skill.name}": invalid file path ${JSON.stringify(f?.path)}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return skill;
|
|
599
|
+
};
|
|
600
|
+
var unquote = (v) => {
|
|
601
|
+
const t = v.trim();
|
|
602
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
603
|
+
try {
|
|
604
|
+
return JSON.parse(t);
|
|
605
|
+
} catch {
|
|
606
|
+
return t.slice(1, -1);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (t.length >= 2 && t.startsWith("'") && t.endsWith("'")) {
|
|
610
|
+
return t.slice(1, -1).replace(/''/g, "'");
|
|
611
|
+
}
|
|
612
|
+
return t;
|
|
613
|
+
};
|
|
614
|
+
var parseFrontmatter = (block) => {
|
|
615
|
+
const out = {};
|
|
616
|
+
const lines = block.split(/\r?\n/);
|
|
617
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
618
|
+
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(lines[i]);
|
|
619
|
+
if (!m) continue;
|
|
620
|
+
const [, key, rest] = m;
|
|
621
|
+
if (rest === ">" || rest === "|" || rest === ">-" || rest === "|-") {
|
|
622
|
+
const body = [];
|
|
623
|
+
while (i + 1 < lines.length && (/^\s+\S/.test(lines[i + 1]) || lines[i + 1] === "")) {
|
|
624
|
+
body.push(lines[i + 1].trim());
|
|
625
|
+
i += 1;
|
|
626
|
+
}
|
|
627
|
+
out[key] = rest.startsWith(">") ? body.filter(Boolean).join(" ") : body.join("\n").trim();
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
out[key] = unquote(rest);
|
|
631
|
+
}
|
|
632
|
+
return out;
|
|
633
|
+
};
|
|
634
|
+
var parseSkillMarkdown = (markdown, files) => {
|
|
635
|
+
const text = (markdown ?? "").replace(/^/, "");
|
|
636
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text);
|
|
637
|
+
if (!m) throw new Error("SKILL.md must start with a --- frontmatter block (name, description)");
|
|
638
|
+
const meta = parseFrontmatter(m[1]);
|
|
639
|
+
return defineSkill({
|
|
640
|
+
name: meta.name ?? "",
|
|
641
|
+
description: meta.description ?? "",
|
|
642
|
+
content: m[2].trim(),
|
|
643
|
+
...files?.length ? { files } : {}
|
|
644
|
+
});
|
|
645
|
+
};
|
|
646
|
+
var loadSkillFromUrl = async (url, opts = {}) => {
|
|
647
|
+
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
648
|
+
const res = await fetchFn(url);
|
|
649
|
+
if (!res.ok) throw new Error(`could not fetch ${url}: HTTP ${res.status}`);
|
|
650
|
+
const markdown = await res.text();
|
|
651
|
+
const files = [];
|
|
652
|
+
for (const path of opts.files ?? []) {
|
|
653
|
+
const r = await fetchFn(new URL(path, url).toString());
|
|
654
|
+
if (!r.ok) throw new Error(`could not fetch skill file ${path}: HTTP ${r.status}`);
|
|
655
|
+
files.push({ path, content: await r.text() });
|
|
656
|
+
}
|
|
657
|
+
return parseSkillMarkdown(markdown, files);
|
|
658
|
+
};
|
|
659
|
+
var renderSkillIndex = (skills) => skills.map((s) => `- ${s.name}: ${s.description.replace(/\s+/g, " ").trim()}`).join("\n");
|
|
660
|
+
var ACTIVE_SKILLS_BUDGET = 24e3;
|
|
661
|
+
var renderActiveSkills = (skills) => {
|
|
662
|
+
let budget = ACTIVE_SKILLS_BUDGET;
|
|
663
|
+
const parts = [];
|
|
664
|
+
for (const s of skills) {
|
|
665
|
+
if (budget <= 0) break;
|
|
666
|
+
const files = s.files?.length ? `
|
|
667
|
+
(bundled files, read with read_skill_file: ${s.files.map((f) => f.path).join(", ")})` : "";
|
|
668
|
+
const block = clip(`### Skill: ${s.name}
|
|
669
|
+
${s.content}${files}`, budget);
|
|
670
|
+
budget -= block.length;
|
|
671
|
+
parts.push(block);
|
|
672
|
+
}
|
|
673
|
+
return parts.join("\n\n");
|
|
674
|
+
};
|
|
675
|
+
var createSkillTools = (skills, onLoad) => {
|
|
676
|
+
const byName = new Map(skills.map((s) => [s.name, s]));
|
|
677
|
+
const known = skills.map((s) => s.name).join(", ");
|
|
678
|
+
const loadSkill = tool({
|
|
679
|
+
description: "Load the full instructions of a skill from the SKILLS list before doing work it covers.",
|
|
680
|
+
inputSchema: jsonSchema({
|
|
681
|
+
type: "object",
|
|
682
|
+
properties: { name: { type: "string", description: "The skill name" } },
|
|
683
|
+
required: ["name"],
|
|
684
|
+
additionalProperties: false
|
|
685
|
+
}),
|
|
686
|
+
execute: async ({ name }) => {
|
|
687
|
+
const skill = byName.get(name);
|
|
688
|
+
if (!skill) throw new Error(`unknown skill "${name}"; available: ${known}`);
|
|
689
|
+
onLoad?.(skill);
|
|
690
|
+
return {
|
|
691
|
+
name: skill.name,
|
|
692
|
+
content: skill.content,
|
|
693
|
+
files: (skill.files ?? []).map((f) => f.path)
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
const readFile = tool({
|
|
698
|
+
description: "Read one of a skill's bundled files (paths are listed by load_skill).",
|
|
699
|
+
inputSchema: jsonSchema({
|
|
700
|
+
type: "object",
|
|
701
|
+
properties: {
|
|
702
|
+
name: { type: "string", description: "The skill name" },
|
|
703
|
+
path: { type: "string", description: "The file path, relative to the skill" }
|
|
704
|
+
},
|
|
705
|
+
required: ["name", "path"],
|
|
706
|
+
additionalProperties: false
|
|
707
|
+
}),
|
|
708
|
+
execute: async ({ name, path }) => {
|
|
709
|
+
const skill = byName.get(name);
|
|
710
|
+
if (!skill) throw new Error(`unknown skill "${name}"; available: ${known}`);
|
|
711
|
+
const file = skill.files?.find((f) => f.path === path);
|
|
712
|
+
if (!file) {
|
|
713
|
+
const listed = (skill.files ?? []).map((f) => f.path).join(", ") || "none";
|
|
714
|
+
throw new Error(`skill "${name}" has no file "${path}"; files: ${listed}`);
|
|
715
|
+
}
|
|
716
|
+
return file.content;
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
for (const t of [loadSkill, readFile]) {
|
|
720
|
+
;
|
|
721
|
+
t.readOnly = true;
|
|
722
|
+
t.promptHint = t === loadSkill ? "{ name: string }" : "{ name: string, path: string }";
|
|
723
|
+
}
|
|
724
|
+
return { load_skill: loadSkill, read_skill_file: readFile };
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// src/tools/approval.ts
|
|
728
|
+
var TOOL_APPROVAL_MODES = [
|
|
729
|
+
"autopilot",
|
|
730
|
+
"ask-writes",
|
|
731
|
+
"ask-all",
|
|
732
|
+
"read-only"
|
|
733
|
+
];
|
|
734
|
+
var ToolDeniedError = class extends Error {
|
|
735
|
+
constructor(reason) {
|
|
736
|
+
super(
|
|
737
|
+
`Tool call denied by the user${reason ? `: ${reason}` : ""}. Do not retry it; continue without it, or report what is blocked.`
|
|
738
|
+
);
|
|
739
|
+
this.name = "ToolDeniedError";
|
|
740
|
+
}
|
|
741
|
+
toJSON() {
|
|
742
|
+
return this.message;
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
var markReadOnly = (tool6, readOnly = true) => {
|
|
746
|
+
;
|
|
747
|
+
tool6.readOnly = readOnly;
|
|
748
|
+
return tool6;
|
|
749
|
+
};
|
|
750
|
+
var isReadOnlyTool = (tool6) => tool6?.readOnly === true;
|
|
751
|
+
var globToRegExp = (glob) => new RegExp(
|
|
752
|
+
`^${glob.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`
|
|
753
|
+
);
|
|
754
|
+
var matchRule = (rules, toolName) => {
|
|
755
|
+
if (!rules) return void 0;
|
|
756
|
+
if (Object.hasOwn(rules, toolName)) return rules[toolName];
|
|
757
|
+
let best;
|
|
758
|
+
for (const [pattern, perm] of Object.entries(rules)) {
|
|
759
|
+
if (!pattern.includes("*")) continue;
|
|
760
|
+
if (!globToRegExp(pattern).test(toolName)) continue;
|
|
761
|
+
const len = pattern.replace(/\*/g, "").length;
|
|
762
|
+
if (!best || len > best.len) best = { len, perm };
|
|
763
|
+
}
|
|
764
|
+
return best?.perm;
|
|
765
|
+
};
|
|
766
|
+
var decideToolPermission = (opts) => {
|
|
767
|
+
if (opts.remembered?.has(opts.toolName)) return "allow";
|
|
768
|
+
const rule = matchRule(opts.rules, opts.toolName);
|
|
769
|
+
if (rule) return rule;
|
|
770
|
+
switch (opts.mode) {
|
|
771
|
+
case "autopilot":
|
|
772
|
+
return "allow";
|
|
773
|
+
case "read-only":
|
|
774
|
+
return opts.readOnly ? "allow" : "deny";
|
|
775
|
+
case "ask-writes":
|
|
776
|
+
return opts.readOnly ? "allow" : "ask";
|
|
777
|
+
case "ask-all":
|
|
778
|
+
return "ask";
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
var normalizeDecision = (d) => typeof d === "boolean" ? { approved: d } : { approved: Boolean(d?.approved), reason: d?.reason, remember: d?.remember };
|
|
782
|
+
var createApprovalState = (config) => ({
|
|
783
|
+
mode: config?.mode ?? "autopilot",
|
|
784
|
+
config: config ?? {},
|
|
785
|
+
remembered: /* @__PURE__ */ new Set()
|
|
786
|
+
});
|
|
787
|
+
var seq = 0;
|
|
788
|
+
var gateToolCall = async (state, call, events, signal) => {
|
|
789
|
+
const permission = decideToolPermission({
|
|
790
|
+
toolName: call.toolName,
|
|
791
|
+
readOnly: call.readOnly,
|
|
792
|
+
mode: state.mode,
|
|
793
|
+
rules: state.config.rules,
|
|
794
|
+
remembered: state.remembered
|
|
795
|
+
});
|
|
796
|
+
if (permission === "allow") return;
|
|
797
|
+
const id = `approval-${Date.now().toString(36)}-${seq += 1}`;
|
|
798
|
+
if (permission === "deny") {
|
|
799
|
+
const reason = state.mode === "read-only" && !matchRule(state.config.rules, call.toolName) ? "the agent is in read-only mode" : "blocked by policy";
|
|
800
|
+
events.resolved({ id, name: call.toolName, approved: false, reason, automatic: true });
|
|
801
|
+
throw new ToolDeniedError(reason);
|
|
802
|
+
}
|
|
803
|
+
const onRequest = state.config.onRequest;
|
|
804
|
+
if (!onRequest) {
|
|
805
|
+
const reason = "no approval handler configured";
|
|
806
|
+
events.resolved({ id, name: call.toolName, approved: false, reason, automatic: true });
|
|
807
|
+
throw new ToolDeniedError(reason);
|
|
808
|
+
}
|
|
809
|
+
const req = { id, ...call };
|
|
810
|
+
events.requested(req);
|
|
811
|
+
let decision;
|
|
812
|
+
try {
|
|
813
|
+
decision = normalizeDecision(
|
|
814
|
+
await withDeadline(Promise.resolve(onRequest(req)), state.config.timeoutMs, signal)
|
|
815
|
+
);
|
|
816
|
+
} catch (err) {
|
|
817
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
818
|
+
events.resolved({ id, name: call.toolName, approved: false, reason, automatic: true });
|
|
819
|
+
throw new ToolDeniedError(reason);
|
|
820
|
+
}
|
|
821
|
+
if (decision.approved && decision.remember) state.remembered.add(call.toolName);
|
|
822
|
+
events.resolved({
|
|
823
|
+
id,
|
|
824
|
+
name: call.toolName,
|
|
825
|
+
approved: decision.approved,
|
|
826
|
+
reason: decision.reason,
|
|
827
|
+
automatic: false
|
|
828
|
+
});
|
|
829
|
+
if (!decision.approved) throw new ToolDeniedError(decision.reason);
|
|
830
|
+
};
|
|
831
|
+
var withDeadline = (p, timeoutMs, signal) => {
|
|
832
|
+
if ((!timeoutMs || timeoutMs <= 0) && !signal) return p;
|
|
833
|
+
return new Promise((resolve, reject) => {
|
|
834
|
+
let timer;
|
|
835
|
+
const done = () => {
|
|
836
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
837
|
+
signal?.removeEventListener("abort", onAbort);
|
|
838
|
+
};
|
|
839
|
+
const onAbort = () => {
|
|
840
|
+
done();
|
|
841
|
+
reject(new Error("the run was stopped while waiting for approval"));
|
|
842
|
+
};
|
|
843
|
+
if (signal?.aborted) return onAbort();
|
|
844
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
845
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
846
|
+
timer = setTimeout(() => {
|
|
847
|
+
done();
|
|
848
|
+
reject(new Error(`no approval decision within ${timeoutMs} ms`));
|
|
849
|
+
}, timeoutMs);
|
|
850
|
+
}
|
|
851
|
+
p.then(
|
|
852
|
+
(v) => {
|
|
853
|
+
done();
|
|
854
|
+
resolve(v);
|
|
855
|
+
},
|
|
856
|
+
(e) => {
|
|
857
|
+
done();
|
|
858
|
+
reject(e);
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
});
|
|
862
|
+
};
|
|
863
|
+
|
|
416
864
|
// src/tools/mode.ts
|
|
417
865
|
var LOCAL_RE = /web-?llm|mlc|browser-ai|transformers|nano|built-?in/i;
|
|
418
866
|
var selectToolMode = (model, mode = "auto") => {
|
|
@@ -423,15 +871,15 @@ var selectToolMode = (model, mode = "auto") => {
|
|
|
423
871
|
};
|
|
424
872
|
|
|
425
873
|
// src/tools/types.ts
|
|
426
|
-
var promptHintOf = (
|
|
874
|
+
var promptHintOf = (tool6) => typeof tool6.promptHint === "string" ? tool6.promptHint : void 0;
|
|
427
875
|
|
|
428
876
|
// src/tools/prompted.ts
|
|
429
|
-
var renderCatalog = (tools) => {
|
|
877
|
+
var renderCatalog = (tools, hintOf) => {
|
|
430
878
|
const names = Object.keys(tools);
|
|
431
879
|
if (names.length === 0) return "(no tools)";
|
|
432
880
|
return names.map((name) => {
|
|
433
881
|
const t = tools[name];
|
|
434
|
-
const hint = promptHintOf(t) ?? "";
|
|
882
|
+
const hint = promptHintOf(t) ?? hintOf?.(name) ?? "";
|
|
435
883
|
const desc = typeof t.description === "string" ? t.description : "";
|
|
436
884
|
return `- ${name}(${hint}): ${desc}`;
|
|
437
885
|
}).join("\n");
|
|
@@ -481,6 +929,244 @@ var dispatch = async (action, tools, ctx = {}) => {
|
|
|
481
929
|
}
|
|
482
930
|
};
|
|
483
931
|
|
|
932
|
+
// src/tools/search.ts
|
|
933
|
+
import { jsonSchema as jsonSchema2, tool as tool2 } from "ai";
|
|
934
|
+
var FIND_TOOLS_NAME = "find_tools";
|
|
935
|
+
var toolCatalogOf = (tools) => Object.entries(tools).map(([name, t]) => {
|
|
936
|
+
const sep = name.indexOf("__");
|
|
937
|
+
return {
|
|
938
|
+
name,
|
|
939
|
+
description: typeof t.description === "string" ? t.description : "",
|
|
940
|
+
...sep > 0 ? { server: name.slice(0, sep) } : {},
|
|
941
|
+
readOnly: isReadOnlyTool(t)
|
|
942
|
+
};
|
|
943
|
+
});
|
|
944
|
+
var tokenize = (s) => (s ?? "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
945
|
+
var hits = (term, words) => words.some(
|
|
946
|
+
(w) => w === term || term.length >= 3 && (w.startsWith(term) || term.startsWith(w) && w.length >= 3)
|
|
947
|
+
);
|
|
948
|
+
var searchTools = (catalog, query, opts = {}) => {
|
|
949
|
+
const terms = [...new Set(tokenize(query))];
|
|
950
|
+
const limit = Math.min(Math.max(1, opts.limit ?? 8), 20);
|
|
951
|
+
const server = opts.server?.toLowerCase();
|
|
952
|
+
const scored = [];
|
|
953
|
+
catalog.forEach((entry, index) => {
|
|
954
|
+
if (opts.exclude?.has(entry.name)) return;
|
|
955
|
+
if (server && entry.server?.toLowerCase() !== server) return;
|
|
956
|
+
const nameWords = tokenize(
|
|
957
|
+
entry.server ? entry.name.slice(entry.server.length + 2) : entry.name
|
|
958
|
+
);
|
|
959
|
+
const serverWords = tokenize(entry.server ?? "");
|
|
960
|
+
const descWords = tokenize(entry.description);
|
|
961
|
+
let score = 0;
|
|
962
|
+
for (const term of terms) {
|
|
963
|
+
if (hits(term, nameWords)) score += 3;
|
|
964
|
+
if (hits(term, serverWords)) score += 2;
|
|
965
|
+
if (hits(term, descWords)) score += 1;
|
|
966
|
+
}
|
|
967
|
+
if (terms.length === 0 && server) score = 1;
|
|
968
|
+
if (score > 0) scored.push({ entry, score, index });
|
|
969
|
+
});
|
|
970
|
+
scored.sort((a, b) => b.score - a.score || a.index - b.index);
|
|
971
|
+
return scored.slice(0, limit).map((s) => s.entry);
|
|
972
|
+
};
|
|
973
|
+
var renderSearchCatalog = (catalog, budget = 12e3) => {
|
|
974
|
+
if (catalog.length === 0) return "(no tools)";
|
|
975
|
+
const groups = /* @__PURE__ */ new Map();
|
|
976
|
+
for (const e of catalog) {
|
|
977
|
+
const key = e.server ?? "(host)";
|
|
978
|
+
groups.set(key, [...groups.get(key) ?? [], e]);
|
|
979
|
+
}
|
|
980
|
+
const lines = [];
|
|
981
|
+
let used = 0;
|
|
982
|
+
let shown = 0;
|
|
983
|
+
for (const [server, entries] of groups) {
|
|
984
|
+
const head = `[${server}] ${entries.length} tool(s)`;
|
|
985
|
+
if (used + head.length > budget) break;
|
|
986
|
+
lines.push(head);
|
|
987
|
+
used += head.length + 1;
|
|
988
|
+
for (const e of entries) {
|
|
989
|
+
const desc = e.description.replace(/\s+/g, " ").trim().slice(0, 60);
|
|
990
|
+
const line = `- ${e.name}${desc ? `: ${desc}` : ""}`;
|
|
991
|
+
if (used + line.length > budget) break;
|
|
992
|
+
lines.push(line);
|
|
993
|
+
used += line.length + 1;
|
|
994
|
+
shown += 1;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
const rest = catalog.length - shown;
|
|
998
|
+
if (rest > 0)
|
|
999
|
+
lines.push(`\u2026 ${rest} more tool(s) \u2014 the executor can find them with ${FIND_TOOLS_NAME}`);
|
|
1000
|
+
return lines.join("\n");
|
|
1001
|
+
};
|
|
1002
|
+
var createFindToolsTool = (catalog, onFound, hintOf) => {
|
|
1003
|
+
const t = tool2({
|
|
1004
|
+
description: "Search the full tool catalogue by keywords and make the matching tools callable from the next step on. Use it whenever the tool you need is not in your current tool list.",
|
|
1005
|
+
inputSchema: jsonSchema2({
|
|
1006
|
+
type: "object",
|
|
1007
|
+
properties: {
|
|
1008
|
+
query: { type: "string", description: "Keywords: what the tool should do" },
|
|
1009
|
+
server: { type: "string", description: "Optional: only tools of this MCP server" },
|
|
1010
|
+
limit: { type: "number", description: "Max results (default 8, max 20)" }
|
|
1011
|
+
},
|
|
1012
|
+
required: ["query"],
|
|
1013
|
+
additionalProperties: false
|
|
1014
|
+
}),
|
|
1015
|
+
execute: async ({ query, server, limit }) => {
|
|
1016
|
+
const found = searchTools(catalog(), query, { server, limit });
|
|
1017
|
+
onFound(
|
|
1018
|
+
query,
|
|
1019
|
+
found.map((f) => f.name)
|
|
1020
|
+
);
|
|
1021
|
+
return {
|
|
1022
|
+
tools: found.map((f) => ({
|
|
1023
|
+
name: f.name,
|
|
1024
|
+
description: f.description,
|
|
1025
|
+
...f.server ? { server: f.server } : {},
|
|
1026
|
+
readOnly: Boolean(f.readOnly),
|
|
1027
|
+
...hintOf?.(f.name) ? { args: hintOf(f.name) } : {}
|
|
1028
|
+
})),
|
|
1029
|
+
note: found.length ? "These tools are now callable." : "No tool matched; try other keywords or a server name."
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
t.readOnly = true;
|
|
1034
|
+
t.promptHint = "{ query: string, server?: string, limit?: number }";
|
|
1035
|
+
return t;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// src/tools/wrap.ts
|
|
1039
|
+
import { asSchema } from "ai";
|
|
1040
|
+
var toolRunContextOf = (options) => options?.agentRun;
|
|
1041
|
+
var truncated = (s, max) => s.length > max ? `${s.slice(0, max)}\u2026 [truncated ${s.length - max} chars]` : s;
|
|
1042
|
+
var toText = (v) => {
|
|
1043
|
+
if (typeof v === "string") return v;
|
|
1044
|
+
try {
|
|
1045
|
+
return JSON.stringify(v) ?? String(v);
|
|
1046
|
+
} catch {
|
|
1047
|
+
return String(v);
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
var limitModelOutput = (original, maxChars) => (async (opts) => {
|
|
1051
|
+
const base = original ? await original(opts) : typeof opts.output === "string" ? { type: "text", value: opts.output } : { type: "json", value: opts.output ?? null };
|
|
1052
|
+
if (base.type === "text" || base.type === "error-text") {
|
|
1053
|
+
return { ...base, value: truncated(base.value, maxChars) };
|
|
1054
|
+
}
|
|
1055
|
+
if (base.type === "json" || base.type === "error-json") {
|
|
1056
|
+
const text = toText(base.value);
|
|
1057
|
+
if (text.length <= maxChars) return base;
|
|
1058
|
+
return {
|
|
1059
|
+
type: base.type === "json" ? "text" : "error-text",
|
|
1060
|
+
value: truncated(text, maxChars)
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
return base;
|
|
1064
|
+
});
|
|
1065
|
+
var wrapToolsForRun = (tools, o) => {
|
|
1066
|
+
const out = {};
|
|
1067
|
+
const gate = (toolName, input, readOnly, signal) => gateToolCall(
|
|
1068
|
+
o.approval,
|
|
1069
|
+
{ toolName, input, readOnly, step: o.step() },
|
|
1070
|
+
{
|
|
1071
|
+
requested: (req) => o.emit({
|
|
1072
|
+
type: "tool.approval-requested",
|
|
1073
|
+
id: req.id,
|
|
1074
|
+
name: req.toolName,
|
|
1075
|
+
input: req.input,
|
|
1076
|
+
readOnly: req.readOnly,
|
|
1077
|
+
step: req.step
|
|
1078
|
+
}),
|
|
1079
|
+
resolved: (r) => o.emit({ type: "tool.approval-resolved", ...r })
|
|
1080
|
+
},
|
|
1081
|
+
signal ?? o.signal
|
|
1082
|
+
);
|
|
1083
|
+
for (const [name, t] of Object.entries(tools)) {
|
|
1084
|
+
const execute = t.execute;
|
|
1085
|
+
const builtin = o.builtins.has(name);
|
|
1086
|
+
const readOnly = builtin || isReadOnlyTool(t);
|
|
1087
|
+
const wrapped = { ...t };
|
|
1088
|
+
if (o.maxToolOutputChars > 0) {
|
|
1089
|
+
wrapped.toModelOutput = limitModelOutput(t.toModelOutput, o.maxToolOutputChars);
|
|
1090
|
+
}
|
|
1091
|
+
if (typeof execute === "function") {
|
|
1092
|
+
wrapped.execute = (input, options) => {
|
|
1093
|
+
const run = () => {
|
|
1094
|
+
if (!builtin) o.countCall();
|
|
1095
|
+
const agentRun = {
|
|
1096
|
+
emit: o.emit,
|
|
1097
|
+
step: o.step(),
|
|
1098
|
+
signal: o.signal,
|
|
1099
|
+
addUsage: o.addUsage,
|
|
1100
|
+
approve: (toolName, toolInput, ro) => gate(toolName, toolInput, ro)
|
|
1101
|
+
};
|
|
1102
|
+
return execute.call(t, input, { ...options, agentRun });
|
|
1103
|
+
};
|
|
1104
|
+
if (builtin) return run();
|
|
1105
|
+
const permission = decideToolPermission({
|
|
1106
|
+
toolName: name,
|
|
1107
|
+
readOnly,
|
|
1108
|
+
mode: o.approval.mode,
|
|
1109
|
+
rules: o.approval.config.rules,
|
|
1110
|
+
remembered: o.approval.remembered
|
|
1111
|
+
});
|
|
1112
|
+
if (permission === "allow") return run();
|
|
1113
|
+
return gate(
|
|
1114
|
+
name,
|
|
1115
|
+
input,
|
|
1116
|
+
readOnly,
|
|
1117
|
+
options?.abortSignal
|
|
1118
|
+
).then(run);
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
out[name] = wrapped;
|
|
1122
|
+
}
|
|
1123
|
+
return out;
|
|
1124
|
+
};
|
|
1125
|
+
var createCallCounter = (cap) => {
|
|
1126
|
+
let calls = 0;
|
|
1127
|
+
return {
|
|
1128
|
+
count: () => {
|
|
1129
|
+
if (cap && cap > 0 && calls >= cap) throw new ToolBudgetError(cap);
|
|
1130
|
+
calls += 1;
|
|
1131
|
+
},
|
|
1132
|
+
get calls() {
|
|
1133
|
+
return calls;
|
|
1134
|
+
},
|
|
1135
|
+
get exhausted() {
|
|
1136
|
+
return Boolean(cap && cap > 0 && calls >= cap);
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
};
|
|
1140
|
+
var typeHint = (s, depth) => {
|
|
1141
|
+
if (!s) return "any";
|
|
1142
|
+
if (Array.isArray(s.enum) && s.enum.length > 0 && s.enum.length <= 8) {
|
|
1143
|
+
return s.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
1144
|
+
}
|
|
1145
|
+
const type = Array.isArray(s.type) ? s.type.find((x) => x !== "null") : s.type;
|
|
1146
|
+
if (type === "array") return `${typeHint(s.items, depth)}[]`;
|
|
1147
|
+
if (type === "object" || s.properties) {
|
|
1148
|
+
if (depth > 1 || !s.properties) return "object";
|
|
1149
|
+
return objectHint(s, depth + 1);
|
|
1150
|
+
}
|
|
1151
|
+
return type ?? "any";
|
|
1152
|
+
};
|
|
1153
|
+
var objectHint = (s, depth = 0) => {
|
|
1154
|
+
const required = new Set(s.required ?? []);
|
|
1155
|
+
const props = Object.entries(s.properties ?? {});
|
|
1156
|
+
if (props.length === 0) return "{}";
|
|
1157
|
+
return `{ ${props.slice(0, 12).map(([k, v]) => `${k}${required.has(k) ? "" : "?"}: ${typeHint(v, depth)}`).join(", ")}${props.length > 12 ? ", \u2026" : ""} }`;
|
|
1158
|
+
};
|
|
1159
|
+
var schemaHintOf = async (t) => {
|
|
1160
|
+
const own = promptHintOf(t);
|
|
1161
|
+
if (own) return own;
|
|
1162
|
+
try {
|
|
1163
|
+
const schema = await asSchema(t.inputSchema).jsonSchema;
|
|
1164
|
+
return schema && typeof schema === "object" ? objectHint(schema) : void 0;
|
|
1165
|
+
} catch {
|
|
1166
|
+
return void 0;
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
484
1170
|
// src/llm/tool-loop.ts
|
|
485
1171
|
import {
|
|
486
1172
|
generateText as generateText2,
|
|
@@ -490,15 +1176,26 @@ import {
|
|
|
490
1176
|
|
|
491
1177
|
// src/agent/loop-types.ts
|
|
492
1178
|
var BLOCKER = "[BLOCKER]";
|
|
493
|
-
var emptyUsage = () => ({
|
|
1179
|
+
var emptyUsage = () => ({
|
|
1180
|
+
inputTokens: 0,
|
|
1181
|
+
outputTokens: 0,
|
|
1182
|
+
totalTokens: 0,
|
|
1183
|
+
reasoningTokens: 0,
|
|
1184
|
+
cachedInputTokens: 0,
|
|
1185
|
+
cacheWriteTokens: 0
|
|
1186
|
+
});
|
|
494
1187
|
var addUsage = (a, b) => ({
|
|
495
1188
|
inputTokens: a.inputTokens + b.inputTokens,
|
|
496
1189
|
outputTokens: a.outputTokens + b.outputTokens,
|
|
497
|
-
totalTokens: a.totalTokens + b.totalTokens
|
|
1190
|
+
totalTokens: a.totalTokens + b.totalTokens,
|
|
1191
|
+
reasoningTokens: (a.reasoningTokens ?? 0) + (b.reasoningTokens ?? 0),
|
|
1192
|
+
cachedInputTokens: (a.cachedInputTokens ?? 0) + (b.cachedInputTokens ?? 0),
|
|
1193
|
+
cacheWriteTokens: (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0)
|
|
498
1194
|
});
|
|
499
1195
|
|
|
500
1196
|
// src/parse.ts
|
|
501
|
-
var
|
|
1197
|
+
var stripThinking = (text) => text.replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/^[\s\S]*?<\/think>/i, "");
|
|
1198
|
+
var stripFences = (text) => stripThinking(text || "").replace(/```(?:json)?/gi, "").trim();
|
|
502
1199
|
var extractBalanced = (text, open) => {
|
|
503
1200
|
const close = open === "{" ? "}" : "]";
|
|
504
1201
|
const start = text.indexOf(open);
|
|
@@ -588,15 +1285,16 @@ var parsePlannerResponse = (raw) => {
|
|
|
588
1285
|
const obj = asObject(text);
|
|
589
1286
|
if (obj) {
|
|
590
1287
|
const reply = typeof obj.reply === "string" ? obj.reply.trim() : "";
|
|
591
|
-
return { reply, plan: normalizeSteps(obj.plan) };
|
|
1288
|
+
return { reply, plan: normalizeSteps(obj.plan), skills: normalizeSteps(obj.skills) };
|
|
592
1289
|
}
|
|
593
1290
|
if (/"plan"\s*:/.test(text)) {
|
|
594
1291
|
return {
|
|
595
1292
|
reply: salvageString(text, "reply"),
|
|
596
|
-
plan: normalizeSteps(salvageStringArray(text, "plan"))
|
|
1293
|
+
plan: normalizeSteps(salvageStringArray(text, "plan")),
|
|
1294
|
+
skills: normalizeSteps(salvageStringArray(text, "skills"))
|
|
597
1295
|
};
|
|
598
1296
|
}
|
|
599
|
-
return { reply: looksLikeJson(text) ? "" : text.trim(), plan: [] };
|
|
1297
|
+
return { reply: looksLikeJson(text) ? "" : text.trim(), plan: [], skills: [] };
|
|
600
1298
|
};
|
|
601
1299
|
var toActions = (value) => {
|
|
602
1300
|
if (!Array.isArray(value)) return [];
|
|
@@ -639,20 +1337,99 @@ var parsePlainText = (raw) => {
|
|
|
639
1337
|
return looksLikeJson(text) ? "" : text.trim();
|
|
640
1338
|
};
|
|
641
1339
|
|
|
1340
|
+
// src/llm/context-editing.ts
|
|
1341
|
+
var STUB = (name) => `[${name} result cleared to save context \u2014 call the tool again if you still need it]`;
|
|
1342
|
+
var estimate = (messages) => {
|
|
1343
|
+
try {
|
|
1344
|
+
return estimateTokens(JSON.stringify(messages));
|
|
1345
|
+
} catch {
|
|
1346
|
+
return 0;
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
var positionsOf = (messages) => messages.flatMap(
|
|
1350
|
+
(m, i) => m.role === "tool" && Array.isArray(m.content) ? m.content.flatMap(
|
|
1351
|
+
(p, j) => p.type === "tool-result" ? [`${i}:${j}`] : []
|
|
1352
|
+
) : []
|
|
1353
|
+
);
|
|
1354
|
+
var createToolResultClearer = (opts, onCleared) => {
|
|
1355
|
+
const keep = Math.max(0, opts.keep ?? 3);
|
|
1356
|
+
const cleared = /* @__PURE__ */ new Set();
|
|
1357
|
+
const apply = (messages) => cleared.size === 0 ? messages : messages.map(
|
|
1358
|
+
(m, i) => m.role === "tool" && Array.isArray(m.content) ? {
|
|
1359
|
+
...m,
|
|
1360
|
+
content: m.content.map(
|
|
1361
|
+
(p, j) => p.type === "tool-result" && cleared.has(`${i}:${j}`) ? { ...p, output: { type: "text", value: STUB(p.toolName) } } : p
|
|
1362
|
+
)
|
|
1363
|
+
} : m
|
|
1364
|
+
);
|
|
1365
|
+
return (messages) => {
|
|
1366
|
+
let edited = apply(messages);
|
|
1367
|
+
const before = estimate(edited);
|
|
1368
|
+
if (before <= opts.triggerTokens) return edited;
|
|
1369
|
+
const positions = positionsOf(messages);
|
|
1370
|
+
let added = 0;
|
|
1371
|
+
for (const pos of positions.slice(0, Math.max(0, positions.length - keep))) {
|
|
1372
|
+
if (!cleared.has(pos)) {
|
|
1373
|
+
cleared.add(pos);
|
|
1374
|
+
added += 1;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (added === 0) return edited;
|
|
1378
|
+
edited = apply(messages);
|
|
1379
|
+
onCleared?.({ cleared: added, beforeTokens: before, afterTokens: estimate(edited) });
|
|
1380
|
+
return edited;
|
|
1381
|
+
};
|
|
1382
|
+
};
|
|
1383
|
+
|
|
642
1384
|
// src/llm/tool-loop.ts
|
|
643
1385
|
var runToolLoop = (model, opts) => opts.mode === "prompted" ? runPrompted(model, opts) : runNative(model, opts);
|
|
1386
|
+
var activeOf = (opts) => typeof opts.activeTools === "function" ? opts.activeTools() : opts.activeTools;
|
|
1387
|
+
var callExtras = (opts) => ({
|
|
1388
|
+
...opts.system !== void 0 ? { instructions: opts.system } : {},
|
|
1389
|
+
maxOutputTokens: opts.maxOutputTokens,
|
|
1390
|
+
temperature: opts.temperature,
|
|
1391
|
+
...opts.reasoning ? { reasoning: opts.reasoning } : {},
|
|
1392
|
+
...opts.providerOptions ? { providerOptions: opts.providerOptions } : {}
|
|
1393
|
+
});
|
|
644
1394
|
var runNative = async (model, opts) => {
|
|
645
1395
|
const toolCalls = [];
|
|
646
1396
|
const pending = /* @__PURE__ */ new Map();
|
|
1397
|
+
let stoppedByBudget = false;
|
|
1398
|
+
const stopWhen = [stepCountIs(opts.maxSteps ?? 4)];
|
|
1399
|
+
if (opts.shouldStop) {
|
|
1400
|
+
const shouldStop = opts.shouldStop;
|
|
1401
|
+
stopWhen.push(({ steps }) => {
|
|
1402
|
+
const used = steps.reduce((u, s) => addUsage(u, normalizeUsage(s.usage)), emptyUsage());
|
|
1403
|
+
if (!shouldStop(used)) return false;
|
|
1404
|
+
stoppedByBudget = true;
|
|
1405
|
+
return true;
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
const initialActive = activeOf(opts);
|
|
1409
|
+
const dynamic = typeof opts.activeTools === "function";
|
|
1410
|
+
const caching = opts.caching?.enabled ? opts.caching : void 0;
|
|
1411
|
+
const clear = opts.clearToolResults ? createToolResultClearer(opts.clearToolResults, opts.callbacks?.onToolResultsCleared) : void 0;
|
|
1412
|
+
const editMessages = caching || clear;
|
|
647
1413
|
const result = streamText2({
|
|
648
1414
|
model,
|
|
649
1415
|
tools: opts.tools,
|
|
650
|
-
...
|
|
651
|
-
|
|
652
|
-
|
|
1416
|
+
...initialActive ? { activeTools: initialActive } : {},
|
|
1417
|
+
// Before every round: re-read the active set so tools activated mid-call
|
|
1418
|
+
// (find_tools) become callable; clear stale tool results; move the cache
|
|
1419
|
+
// breakpoint to the newest message.
|
|
1420
|
+
...dynamic || editMessages ? {
|
|
1421
|
+
prepareStep: ({ messages }) => {
|
|
1422
|
+
let next = clear ? clear(messages) : messages;
|
|
1423
|
+
if (caching) next = withRollingBreakpoint(next, caching);
|
|
1424
|
+
return {
|
|
1425
|
+
...dynamic ? { activeTools: activeOf(opts) } : {},
|
|
1426
|
+
...editMessages ? { messages: next } : {}
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
} : {},
|
|
1430
|
+
stopWhen,
|
|
1431
|
+
...callExtras(opts),
|
|
653
1432
|
...promptOf(opts),
|
|
654
|
-
maxOutputTokens: opts.maxOutputTokens,
|
|
655
|
-
temperature: opts.temperature,
|
|
656
1433
|
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
657
1434
|
});
|
|
658
1435
|
for await (const part of result.fullStream) {
|
|
@@ -660,6 +1437,9 @@ var runNative = async (model, opts) => {
|
|
|
660
1437
|
case "text-delta":
|
|
661
1438
|
if (part.text) opts.callbacks?.onTextDelta?.(part.text);
|
|
662
1439
|
break;
|
|
1440
|
+
case "reasoning-delta":
|
|
1441
|
+
if (part.text) opts.callbacks?.onReasoningDelta?.(part.text);
|
|
1442
|
+
break;
|
|
663
1443
|
case "tool-call":
|
|
664
1444
|
pending.set(part.toolCallId, { name: part.toolName, input: part.input });
|
|
665
1445
|
opts.callbacks?.onToolCall?.(part.toolName, part.input);
|
|
@@ -693,7 +1473,7 @@ var runNative = async (model, opts) => {
|
|
|
693
1473
|
}
|
|
694
1474
|
}
|
|
695
1475
|
const [text, usage] = await Promise.all([result.text, result.usage]);
|
|
696
|
-
return { text: text.trim(), toolCalls, usage: normalizeUsage(usage) };
|
|
1476
|
+
return { text: text.trim(), toolCalls, usage: normalizeUsage(usage), stoppedByBudget };
|
|
697
1477
|
};
|
|
698
1478
|
var asJson = (value) => {
|
|
699
1479
|
try {
|
|
@@ -702,13 +1482,16 @@ var asJson = (value) => {
|
|
|
702
1482
|
return String(value);
|
|
703
1483
|
}
|
|
704
1484
|
};
|
|
705
|
-
var toolResultsPrompt = (results) => {
|
|
1485
|
+
var toolResultsPrompt = (results, newTools) => {
|
|
706
1486
|
const lines = results.map(
|
|
707
1487
|
(r) => `- ${r.name} ${clip(asJson(r.input), 160)} \u2192 ${r.ok ? "ok" : "FAILED"}: ${clip(asJson(r.output), 400)}`
|
|
708
1488
|
);
|
|
709
1489
|
return `TOOL RESULTS:
|
|
710
1490
|
${lines.join("\n")}
|
|
711
|
-
|
|
1491
|
+
${newTools ? `
|
|
1492
|
+
TOOLS NOW AVAILABLE (call them by these exact names):
|
|
1493
|
+
${newTools}
|
|
1494
|
+
` : ""}
|
|
712
1495
|
Continue THIS step using the results above. Reply with a single JSON object, nothing else:
|
|
713
1496
|
{ "reply": string, "actions": [ { "tool": string, "args": object } ] }
|
|
714
1497
|
- If the step is now complete: "actions": [] and a short outcome in "reply".
|
|
@@ -716,22 +1499,28 @@ Continue THIS step using the results above. Reply with a single JSON object, not
|
|
|
716
1499
|
};
|
|
717
1500
|
var runPrompted = async (model, opts) => {
|
|
718
1501
|
const maxRounds = Math.max(1, opts.maxSteps ?? 4);
|
|
719
|
-
const
|
|
720
|
-
|
|
1502
|
+
const callable = () => {
|
|
1503
|
+
const active = activeOf(opts);
|
|
1504
|
+
return active ? Object.fromEntries(Object.entries(opts.tools).filter(([name]) => active.includes(name))) : opts.tools;
|
|
1505
|
+
};
|
|
721
1506
|
const signal = timeoutSignal(opts.abortSignal, opts.timeoutMs);
|
|
722
1507
|
const messages = opts.messages ? [...opts.messages] : [{ role: "user", content: opts.prompt ?? "" }];
|
|
723
1508
|
const toolCalls = [];
|
|
724
1509
|
const seen = /* @__PURE__ */ new Set();
|
|
725
1510
|
let usage = emptyUsage();
|
|
726
1511
|
let text = "";
|
|
1512
|
+
let stoppedByBudget = false;
|
|
727
1513
|
for (let round = 1; round <= maxRounds; round += 1) {
|
|
728
1514
|
if (signal?.aborted) break;
|
|
1515
|
+
if (opts.shouldStop?.(usage)) {
|
|
1516
|
+
stoppedByBudget = true;
|
|
1517
|
+
break;
|
|
1518
|
+
}
|
|
1519
|
+
const before = new Set(Object.keys(callable()));
|
|
729
1520
|
const result = await generateText2({
|
|
730
1521
|
model,
|
|
731
|
-
|
|
732
|
-
messages,
|
|
733
|
-
maxOutputTokens: opts.maxOutputTokens,
|
|
734
|
-
temperature: opts.temperature,
|
|
1522
|
+
...callExtras(opts),
|
|
1523
|
+
messages: opts.caching ? withRollingBreakpoint(messages, opts.caching) : messages,
|
|
735
1524
|
abortSignal: signal
|
|
736
1525
|
});
|
|
737
1526
|
usage = addUsage(usage, normalizeUsage(result.usage));
|
|
@@ -746,21 +1535,195 @@ var runPrompted = async (model, opts) => {
|
|
|
746
1535
|
for (const action of parsed.actions) {
|
|
747
1536
|
if (signal?.aborted) break;
|
|
748
1537
|
opts.callbacks?.onToolCall?.(action.tool, action.args);
|
|
749
|
-
const rec = await dispatch(action,
|
|
1538
|
+
const rec = await dispatch(action, callable(), { abortSignal: signal });
|
|
750
1539
|
toolCalls.push(rec);
|
|
751
1540
|
results.push(rec);
|
|
752
1541
|
opts.callbacks?.onToolResult?.(rec.name, rec.output, rec.ok);
|
|
753
1542
|
}
|
|
754
1543
|
if (round === maxRounds || signal?.aborted) break;
|
|
1544
|
+
const added = Object.keys(callable()).filter((n) => !before.has(n));
|
|
755
1545
|
messages.push({ role: "assistant", content: result.text });
|
|
756
|
-
messages.push({
|
|
1546
|
+
messages.push({
|
|
1547
|
+
role: "user",
|
|
1548
|
+
content: toolResultsPrompt(
|
|
1549
|
+
results,
|
|
1550
|
+
added.length && opts.describeTools ? opts.describeTools(added) : ""
|
|
1551
|
+
)
|
|
1552
|
+
});
|
|
757
1553
|
}
|
|
758
1554
|
if (text) opts.callbacks?.onTextDelta?.(text);
|
|
759
|
-
return { text, toolCalls, usage };
|
|
1555
|
+
return { text, toolCalls, usage, stoppedByBudget };
|
|
760
1556
|
};
|
|
761
1557
|
|
|
1558
|
+
// src/thinking.ts
|
|
1559
|
+
var normalize = (setting) => {
|
|
1560
|
+
if (setting === void 0 || setting === false) return void 0;
|
|
1561
|
+
if (setting === true) return { level: "medium" };
|
|
1562
|
+
if (typeof setting === "string") return { level: setting };
|
|
1563
|
+
return setting;
|
|
1564
|
+
};
|
|
1565
|
+
var resolveThinking = (setting) => {
|
|
1566
|
+
const cfg = normalize(setting);
|
|
1567
|
+
if (!cfg) return {};
|
|
1568
|
+
const level = cfg.level ?? "medium";
|
|
1569
|
+
const webllm = (on) => ({ "web-llm": { extra_body: { enable_thinking: on } } });
|
|
1570
|
+
if (level === "none") return { reasoning: "none", providerOptions: webllm(false) };
|
|
1571
|
+
const includeThoughts = cfg.includeThoughts !== false;
|
|
1572
|
+
const budget = cfg.budgetTokens;
|
|
1573
|
+
if (typeof budget === "number" && budget > 0) {
|
|
1574
|
+
return {
|
|
1575
|
+
reasoning: level,
|
|
1576
|
+
providerOptions: {
|
|
1577
|
+
anthropic: { thinking: { type: "enabled", budgetTokens: budget } },
|
|
1578
|
+
google: { thinkingConfig: { thinkingBudget: budget, includeThoughts } },
|
|
1579
|
+
...webllm(true)
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
if (!includeThoughts) return { reasoning: level, providerOptions: webllm(true) };
|
|
1584
|
+
return {
|
|
1585
|
+
reasoning: level,
|
|
1586
|
+
providerOptions: {
|
|
1587
|
+
google: { thinkingConfig: { includeThoughts: true } },
|
|
1588
|
+
openai: { reasoningSummary: "auto" },
|
|
1589
|
+
...webllm(true)
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
};
|
|
1593
|
+
var thinkingFor = (stage, top, perStage) => perStage && stage in perStage ? perStage[stage] : top;
|
|
1594
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1595
|
+
var deepMerge = (a, b) => {
|
|
1596
|
+
const out = { ...a };
|
|
1597
|
+
for (const [k, v] of Object.entries(b)) {
|
|
1598
|
+
out[k] = isPlainObject(out[k]) && isPlainObject(v) ? deepMerge(out[k], v) : v;
|
|
1599
|
+
}
|
|
1600
|
+
return out;
|
|
1601
|
+
};
|
|
1602
|
+
var mergeProviderOptions = (...maps) => {
|
|
1603
|
+
let out;
|
|
1604
|
+
for (const m of maps) {
|
|
1605
|
+
if (!m || Object.keys(m).length === 0) continue;
|
|
1606
|
+
out = out ? deepMerge(out, m) : { ...m };
|
|
1607
|
+
}
|
|
1608
|
+
return out;
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
// src/images.ts
|
|
1612
|
+
var DATA_URL_RE = /^data:([^;,]+)?;base64,(.*)$/s;
|
|
1613
|
+
var HTTP_RE = /^https?:\/\//i;
|
|
1614
|
+
var mediaTypeOf = (f) => {
|
|
1615
|
+
if (f.mediaType) return f.mediaType;
|
|
1616
|
+
if (typeof f.data === "string") {
|
|
1617
|
+
const m = DATA_URL_RE.exec(f.data);
|
|
1618
|
+
if (m?.[1]) return m[1];
|
|
1619
|
+
}
|
|
1620
|
+
const url = f.data instanceof URL ? f.data.href : typeof f.data === "string" ? f.data : "";
|
|
1621
|
+
if (HTTP_RE.test(url) || f.name) {
|
|
1622
|
+
const ext = (f.name ?? new URL(url, "http://x").pathname).split(".").pop()?.toLowerCase();
|
|
1623
|
+
if (ext === "pdf") return "application/pdf";
|
|
1624
|
+
if (ext && ["png", "jpg", "jpeg", "gif", "webp"].includes(ext)) {
|
|
1625
|
+
return `image/${ext === "jpg" ? "jpeg" : ext}`;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return void 0;
|
|
1629
|
+
};
|
|
1630
|
+
var attachmentKind = (f) => {
|
|
1631
|
+
const mt = mediaTypeOf(f) ?? "";
|
|
1632
|
+
if (mt.startsWith("image/")) return "image";
|
|
1633
|
+
if (mt === "application/pdf") return "pdf";
|
|
1634
|
+
return "file";
|
|
1635
|
+
};
|
|
1636
|
+
var KIND_LABEL = {
|
|
1637
|
+
image: "images",
|
|
1638
|
+
pdf: "PDF files",
|
|
1639
|
+
file: "file attachments"
|
|
1640
|
+
};
|
|
1641
|
+
var KIND_HINT = {
|
|
1642
|
+
image: "Remove the image, or switch to a vision-capable model such as Gemini, Claude or GPT-4o/GPT-5.",
|
|
1643
|
+
pdf: "Convert the PDF to text first (e.g. PDF \u2192 Markdown), or switch to a model that reads PDFs such as Gemini, Claude or GPT-4o/GPT-5.",
|
|
1644
|
+
file: "Paste its text instead, or switch to a model that accepts this file type."
|
|
1645
|
+
};
|
|
1646
|
+
var AttachmentsNotSupportedError = class extends Error {
|
|
1647
|
+
kind;
|
|
1648
|
+
constructor(model, kind, detail) {
|
|
1649
|
+
super(
|
|
1650
|
+
`The model "${model}" can't take ${KIND_LABEL[kind]}${detail ? ` (${detail})` : ""}. ${KIND_HINT[kind]}`
|
|
1651
|
+
);
|
|
1652
|
+
this.name = "AttachmentsNotSupportedError";
|
|
1653
|
+
this.kind = kind;
|
|
1654
|
+
}
|
|
1655
|
+
toJSON() {
|
|
1656
|
+
return this.message;
|
|
1657
|
+
}
|
|
1658
|
+
};
|
|
1659
|
+
var ImagesNotSupportedError = class extends AttachmentsNotSupportedError {
|
|
1660
|
+
constructor(model, detail) {
|
|
1661
|
+
super(model, "image", detail);
|
|
1662
|
+
this.name = "ImagesNotSupportedError";
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1665
|
+
var unsupportedAttachment = (model, kind, detail) => kind === "image" ? new ImagesNotSupportedError(model, detail) : new AttachmentsNotSupportedError(model, kind, detail);
|
|
1666
|
+
var dataOf = (f) => {
|
|
1667
|
+
if (typeof f.data === "string") {
|
|
1668
|
+
const m = DATA_URL_RE.exec(f.data);
|
|
1669
|
+
if (m) return m[2];
|
|
1670
|
+
if (HTTP_RE.test(f.data)) return new URL(f.data);
|
|
1671
|
+
}
|
|
1672
|
+
return f.data;
|
|
1673
|
+
};
|
|
1674
|
+
var toFilePart = (f) => {
|
|
1675
|
+
const kind = attachmentKind(f);
|
|
1676
|
+
const mediaType = mediaTypeOf(f) ?? (kind === "image" ? "image" : "application/octet-stream");
|
|
1677
|
+
return {
|
|
1678
|
+
type: "file",
|
|
1679
|
+
data: dataOf(f),
|
|
1680
|
+
mediaType,
|
|
1681
|
+
...f.name ? { filename: f.name } : {}
|
|
1682
|
+
};
|
|
1683
|
+
};
|
|
1684
|
+
var toImagePart = toFilePart;
|
|
1685
|
+
var modelLabel = (model) => {
|
|
1686
|
+
if (typeof model === "string") return model;
|
|
1687
|
+
const m = model;
|
|
1688
|
+
return m?.modelId ? `${m.modelId}${m.provider ? ` (${m.provider})` : ""}` : "this model";
|
|
1689
|
+
};
|
|
1690
|
+
var REFUSAL_RE = /image|vision|multimodal|multi-modal|media type|mediatype|pdf|document|unsupported (file|content|part|mime)|does not support (file|content)|invalid content type|image_url|inline_?data|file_?data/i;
|
|
1691
|
+
var isAttachmentRefusal = (err) => REFUSAL_RE.test(err instanceof Error ? `${err.name} ${err.message}` : String(err));
|
|
1692
|
+
var isImageRefusal = isAttachmentRefusal;
|
|
1693
|
+
|
|
762
1694
|
// src/agent/internal.ts
|
|
1695
|
+
var promptFor = (ctx, prompt) => ctx.images?.length ? {
|
|
1696
|
+
messages: [
|
|
1697
|
+
{
|
|
1698
|
+
role: "user",
|
|
1699
|
+
content: [{ type: "text", text: prompt }, ...ctx.images.map(toFilePart)]
|
|
1700
|
+
}
|
|
1701
|
+
]
|
|
1702
|
+
} : { prompt };
|
|
1703
|
+
var imageRefusal = (ctx, err, model) => {
|
|
1704
|
+
if (!ctx.images?.length || err instanceof AttachmentsNotSupportedError || !isAttachmentRefusal(err)) {
|
|
1705
|
+
return void 0;
|
|
1706
|
+
}
|
|
1707
|
+
const kinds = new Set(ctx.images.map(attachmentKind));
|
|
1708
|
+
const kind = kinds.size === 1 ? [...kinds][0] : "file";
|
|
1709
|
+
const detail = err instanceof Error ? err.message.slice(0, 160) : void 0;
|
|
1710
|
+
return unsupportedAttachment(modelLabel(model), kind, detail && `the provider said: ${detail}`);
|
|
1711
|
+
};
|
|
763
1712
|
var systemFor = (ctx, base) => withSystem(base, ctx.raw.systemPrompt);
|
|
1713
|
+
var stageCall = (ctx, stage, baseSystem) => {
|
|
1714
|
+
const system = systemFor(ctx, baseSystem);
|
|
1715
|
+
const thinking = resolveThinking(thinkingFor(stage, ctx.raw.thinking, ctx.raw.stageThinking));
|
|
1716
|
+
const caching = ctx.caching ?? { enabled: false };
|
|
1717
|
+
const providerOptions = mergeProviderOptions(
|
|
1718
|
+
cachingProviderOptions(caching, ctx.config.clientName, stage),
|
|
1719
|
+
thinking.providerOptions
|
|
1720
|
+
);
|
|
1721
|
+
return {
|
|
1722
|
+
system: cachedInstructions(system, caching),
|
|
1723
|
+
...thinking.reasoning ? { reasoning: thinking.reasoning } : {},
|
|
1724
|
+
...providerOptions ? { providerOptions } : {}
|
|
1725
|
+
};
|
|
1726
|
+
};
|
|
764
1727
|
|
|
765
1728
|
// src/agent/executor.ts
|
|
766
1729
|
var splitBlocker = (raw) => {
|
|
@@ -768,20 +1731,33 @@ var splitBlocker = (raw) => {
|
|
|
768
1731
|
return { summary: raw.split(BLOCKER).join("").trim(), blocked: true };
|
|
769
1732
|
};
|
|
770
1733
|
var activeToolNames = (ctx, step) => {
|
|
771
|
-
|
|
1734
|
+
const strategy = ctx.strategy ?? ctx.config.toolSelectionStrategy;
|
|
1735
|
+
const builtins = [...ctx.builtinTools ?? []].filter((n) => ctx.tools[n]);
|
|
772
1736
|
const suggested = step.suggestedTools ?? [];
|
|
773
1737
|
const known = suggested.filter((n) => ctx.tools[n]);
|
|
1738
|
+
if (strategy === "search") {
|
|
1739
|
+
const discovered = (ctx.discovered?.() ?? []).slice(-16);
|
|
1740
|
+
return [.../* @__PURE__ */ new Set([...builtins, ...known, ...discovered])];
|
|
1741
|
+
}
|
|
1742
|
+
if (strategy !== "plan-narrowed") return void 0;
|
|
774
1743
|
if (known.length === 0 && suggested.length > 0) return Object.keys(ctx.tools);
|
|
775
|
-
return known;
|
|
1744
|
+
return [.../* @__PURE__ */ new Set([...known, ...builtins])];
|
|
776
1745
|
};
|
|
777
1746
|
var summaryOf = (text, toolCallCount) => {
|
|
778
1747
|
if (text.length > 0) return text;
|
|
779
1748
|
return toolCallCount > 0 ? `Executed ${toolCallCount} tool call(s).` : "Step produced no output.";
|
|
780
1749
|
};
|
|
1750
|
+
var catalogFor = (ctx, names) => renderCatalog(
|
|
1751
|
+
Object.fromEntries(names.filter((n) => ctx.tools[n]).map((n) => [n, ctx.tools[n]])),
|
|
1752
|
+
ctx.toolHint
|
|
1753
|
+
);
|
|
781
1754
|
var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
782
1755
|
const state = await ctx.state();
|
|
783
|
-
|
|
784
|
-
const
|
|
1756
|
+
ctx.setCurrentStep?.(step);
|
|
1757
|
+
const initial = activeToolNames(ctx, step);
|
|
1758
|
+
const searchMode = (ctx.strategy ?? ctx.config.toolSelectionStrategy) === "search";
|
|
1759
|
+
const toolCatalog = catalogFor(ctx, initial ?? Object.keys(ctx.tools));
|
|
1760
|
+
const active = ctx.activeSkills?.() ?? [];
|
|
785
1761
|
const parts = ctx.prompts.executor({
|
|
786
1762
|
goal,
|
|
787
1763
|
state,
|
|
@@ -790,21 +1766,44 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
|
790
1766
|
total,
|
|
791
1767
|
toolCatalog,
|
|
792
1768
|
done,
|
|
793
|
-
mode: ctx.executorMode
|
|
1769
|
+
mode: ctx.executorMode,
|
|
1770
|
+
skills: ctx.skills?.length ? renderSkillIndex(ctx.skills) : void 0,
|
|
1771
|
+
activeSkills: active.length ? renderActiveSkills(active) : void 0,
|
|
1772
|
+
searchMode: searchMode && Boolean(ctx.tools[FIND_TOOLS_NAME])
|
|
794
1773
|
});
|
|
795
1774
|
const loop = await runToolLoop(ctx.executorModel, {
|
|
796
1775
|
mode: ctx.executorMode,
|
|
797
|
-
|
|
798
|
-
|
|
1776
|
+
...stageCall(ctx, "executor", parts.system),
|
|
1777
|
+
...promptFor(ctx, parts.prompt),
|
|
799
1778
|
tools: ctx.tools,
|
|
800
|
-
|
|
1779
|
+
// In search mode the active set grows while the step runs.
|
|
1780
|
+
activeTools: searchMode ? () => [.../* @__PURE__ */ new Set([...initial ?? [], ...ctx.discovered?.() ?? []])] : initial,
|
|
1781
|
+
describeTools: (names) => catalogFor(ctx, names),
|
|
801
1782
|
maxSteps: ctx.config.maxStepsPerTask,
|
|
1783
|
+
shouldStop: ctx.overBudget,
|
|
802
1784
|
maxOutputTokens: ctx.config.budgets.executor,
|
|
803
1785
|
temperature: ctx.config.temperature,
|
|
804
1786
|
abortSignal: ctx.signal,
|
|
805
1787
|
timeoutMs: ctx.config.chatTimeoutMs,
|
|
1788
|
+
caching: ctx.caching,
|
|
1789
|
+
...ctx.config.compaction.clearToolResultsAfterTokens > 0 ? {
|
|
1790
|
+
clearToolResults: {
|
|
1791
|
+
triggerTokens: ctx.config.compaction.clearToolResultsAfterTokens,
|
|
1792
|
+
keep: ctx.config.compaction.keepToolResults
|
|
1793
|
+
}
|
|
1794
|
+
} : {},
|
|
806
1795
|
callbacks: {
|
|
1796
|
+
onToolResultsCleared: (info) => {
|
|
1797
|
+
ctx.log.info(`cleared ${info.cleared} stale tool result(s) from the step's context`);
|
|
1798
|
+
ctx.emit({
|
|
1799
|
+
type: "context.compacted",
|
|
1800
|
+
scope: "tool-results",
|
|
1801
|
+
beforeTokens: info.beforeTokens,
|
|
1802
|
+
afterTokens: info.afterTokens
|
|
1803
|
+
});
|
|
1804
|
+
},
|
|
807
1805
|
onTextDelta: (delta) => ctx.emit({ type: "step.text-delta", step, delta }),
|
|
1806
|
+
onReasoningDelta: (delta) => ctx.emit({ type: "step.reasoning-delta", step, delta }),
|
|
808
1807
|
onToolCall: (name, input) => {
|
|
809
1808
|
ctx.log.info(`tool call: ${name}`, input);
|
|
810
1809
|
ctx.emit({ type: "step.tool-call", step, name, input });
|
|
@@ -814,6 +1813,8 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
|
814
1813
|
ctx.emit({ type: "step.tool-result", step, name, output, ok });
|
|
815
1814
|
}
|
|
816
1815
|
}
|
|
1816
|
+
}).catch((err) => {
|
|
1817
|
+
throw imageRefusal(ctx, err, ctx.executorModel) ?? err;
|
|
817
1818
|
});
|
|
818
1819
|
ctx.log.debug("executor text:", loop.text);
|
|
819
1820
|
const { summary, blocked } = splitBlocker(loop.text);
|
|
@@ -824,7 +1825,8 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
|
824
1825
|
toolCalls: loop.toolCalls,
|
|
825
1826
|
blocked
|
|
826
1827
|
},
|
|
827
|
-
usage: loop.usage
|
|
1828
|
+
usage: loop.usage,
|
|
1829
|
+
stoppedByBudget: loop.stoppedByBudget
|
|
828
1830
|
};
|
|
829
1831
|
};
|
|
830
1832
|
var shouldReplan = (result) => {
|
|
@@ -865,9 +1867,11 @@ var PlanStepSchema = z.object({
|
|
|
865
1867
|
suggestedTools: z.array(z.string()).optional()
|
|
866
1868
|
});
|
|
867
1869
|
var PlanSchema = z.object({
|
|
868
|
-
/** One short sentence. For a greeting/
|
|
1870
|
+
/** One short sentence. For a greeting/small talk, holds the answer and steps is empty. */
|
|
869
1871
|
thought: z.string(),
|
|
870
|
-
steps: z.array(PlanStepSchema)
|
|
1872
|
+
steps: z.array(PlanStepSchema),
|
|
1873
|
+
/** Names of the configured skills that apply to this goal. */
|
|
1874
|
+
skills: z.array(z.string()).optional()
|
|
871
1875
|
});
|
|
872
1876
|
var ReplanSchema = z.object({
|
|
873
1877
|
decision: z.enum(["continue", "revise", "finish"]),
|
|
@@ -877,7 +1881,7 @@ var ReplanSchema = z.object({
|
|
|
877
1881
|
});
|
|
878
1882
|
|
|
879
1883
|
// src/agent/planner.ts
|
|
880
|
-
var toSteps = (raw) => raw.filter((s) => s.description && s.description.trim()).map((s, i) => ({
|
|
1884
|
+
var toSteps = (raw, cap) => raw.filter((s) => s.description && s.description.trim()).slice(0, Math.max(1, cap)).map((s, i) => ({
|
|
881
1885
|
id: `s${i + 1}`,
|
|
882
1886
|
description: s.description.trim(),
|
|
883
1887
|
expectedOutcome: s.expectedOutcome,
|
|
@@ -888,46 +1892,72 @@ var createPlan = async (ctx, goal, nudge) => {
|
|
|
888
1892
|
const effectiveGoal = nudge ? `${goal}
|
|
889
1893
|
|
|
890
1894
|
${nudge}` : goal;
|
|
1895
|
+
const skillIndex = ctx.skills?.length ? renderSkillIndex(ctx.skills) : void 0;
|
|
1896
|
+
const cap = ctx.config.maxPlanSteps;
|
|
891
1897
|
const commonFor = (mode) => {
|
|
892
1898
|
const parts = ctx.prompts.planner({
|
|
893
1899
|
goal: effectiveGoal,
|
|
894
1900
|
state,
|
|
895
1901
|
toolCatalog: ctx.toolCatalog,
|
|
896
1902
|
mode,
|
|
897
|
-
history: ctx.history
|
|
1903
|
+
history: ctx.history,
|
|
1904
|
+
skills: skillIndex,
|
|
1905
|
+
searchMode: ctx.strategy === "search"
|
|
898
1906
|
});
|
|
899
1907
|
return {
|
|
900
|
-
|
|
901
|
-
|
|
1908
|
+
...stageCall(ctx, "planner", parts.system),
|
|
1909
|
+
...promptFor(ctx, parts.prompt),
|
|
902
1910
|
maxOutputTokens: ctx.config.budgets.planner,
|
|
903
1911
|
temperature: ctx.config.temperature,
|
|
904
1912
|
abortSignal: ctx.signal,
|
|
905
1913
|
timeoutMs: ctx.config.chatTimeoutMs
|
|
906
1914
|
};
|
|
907
1915
|
};
|
|
1916
|
+
const validSkills = (names) => {
|
|
1917
|
+
const known = new Set((ctx.skills ?? []).map((s) => s.name));
|
|
1918
|
+
const picked = [...new Set(names ?? [])];
|
|
1919
|
+
const kept = picked.filter((n) => known.has(n));
|
|
1920
|
+
if (kept.length < picked.length) {
|
|
1921
|
+
ctx.log.warn(
|
|
1922
|
+
"planner picked unknown skills, dropped:",
|
|
1923
|
+
picked.filter((n) => !known.has(n))
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
return kept;
|
|
1927
|
+
};
|
|
908
1928
|
if (ctx.plannerMode === "native") {
|
|
909
1929
|
try {
|
|
910
1930
|
const result2 = await generateStructured(ctx.plannerModel, PlanSchema, commonFor("native"));
|
|
911
1931
|
ctx.log.debug("planner (native):", result2.object);
|
|
912
1932
|
return {
|
|
913
|
-
plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps) },
|
|
914
|
-
usage: normalizeUsage(result2.usage)
|
|
1933
|
+
plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps, cap) },
|
|
1934
|
+
usage: normalizeUsage(result2.usage),
|
|
1935
|
+
skills: validSkills(result2.object.skills)
|
|
915
1936
|
};
|
|
916
1937
|
} catch (err) {
|
|
1938
|
+
if (ctx.signal?.aborted) throw err;
|
|
1939
|
+
const refused = imageRefusal(ctx, err, ctx.plannerModel);
|
|
1940
|
+
if (refused) throw refused;
|
|
917
1941
|
ctx.log.warn("planner: native structured output failed, salvaging:", asMessage(err));
|
|
918
1942
|
ctx.emit({ type: "retry", phase: "plan", attempt: 1, error: asMessage(err) });
|
|
919
1943
|
}
|
|
920
1944
|
}
|
|
921
|
-
const result = await generate(ctx.plannerModel, commonFor("prompted"))
|
|
1945
|
+
const result = await generate(ctx.plannerModel, commonFor("prompted")).catch((err) => {
|
|
1946
|
+
throw imageRefusal(ctx, err, ctx.plannerModel) ?? err;
|
|
1947
|
+
});
|
|
922
1948
|
const parsed = parsePlannerResponse(result.text);
|
|
923
1949
|
ctx.log.debug("planner (prompted) raw:", result.text);
|
|
924
1950
|
ctx.log.debug("planner (prompted) parsed:", parsed);
|
|
925
1951
|
return {
|
|
926
1952
|
plan: {
|
|
927
1953
|
thought: parsed.reply,
|
|
928
|
-
steps: toSteps(
|
|
1954
|
+
steps: toSteps(
|
|
1955
|
+
parsed.plan.map((description) => ({ description })),
|
|
1956
|
+
cap
|
|
1957
|
+
)
|
|
929
1958
|
},
|
|
930
|
-
usage: normalizeUsage(result.usage)
|
|
1959
|
+
usage: normalizeUsage(result.usage),
|
|
1960
|
+
skills: validSkills(parsed.skills)
|
|
931
1961
|
};
|
|
932
1962
|
};
|
|
933
1963
|
var asMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
@@ -935,10 +1965,18 @@ var asMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
|
935
1965
|
// src/agent/replanner.ts
|
|
936
1966
|
var decideReplan = async (ctx, goal, done, remaining) => {
|
|
937
1967
|
const state = await ctx.state();
|
|
1968
|
+
const active = ctx.activeSkills?.() ?? [];
|
|
938
1969
|
const commonFor = (mode) => {
|
|
939
|
-
const parts = ctx.prompts.replanner({
|
|
1970
|
+
const parts = ctx.prompts.replanner({
|
|
1971
|
+
goal,
|
|
1972
|
+
state,
|
|
1973
|
+
done,
|
|
1974
|
+
remaining,
|
|
1975
|
+
mode,
|
|
1976
|
+
activeSkills: active.length ? renderActiveSkills(active) : void 0
|
|
1977
|
+
});
|
|
940
1978
|
return {
|
|
941
|
-
|
|
1979
|
+
...stageCall(ctx, "replanner", parts.system),
|
|
942
1980
|
prompt: parts.prompt,
|
|
943
1981
|
maxOutputTokens: ctx.config.budgets.replanner,
|
|
944
1982
|
temperature: ctx.config.temperature,
|
|
@@ -973,12 +2011,19 @@ var decideReplan = async (ctx, goal, done, remaining) => {
|
|
|
973
2011
|
};
|
|
974
2012
|
|
|
975
2013
|
// src/agent/synthesizer.ts
|
|
976
|
-
var synthesizeAnswer = async (ctx, goal, done) => {
|
|
2014
|
+
var synthesizeAnswer = async (ctx, goal, done, findings = []) => {
|
|
977
2015
|
const state = await ctx.state();
|
|
978
|
-
const
|
|
2016
|
+
const active = ctx.activeSkills?.() ?? [];
|
|
2017
|
+
const parts = ctx.prompts.synthesizer({
|
|
2018
|
+
goal,
|
|
2019
|
+
state,
|
|
2020
|
+
done,
|
|
2021
|
+
findings,
|
|
2022
|
+
activeSkills: active.length ? renderActiveSkills(active) : void 0
|
|
2023
|
+
});
|
|
979
2024
|
const result = stream(ctx.synthesizerModel, {
|
|
980
|
-
|
|
981
|
-
|
|
2025
|
+
...stageCall(ctx, "synthesizer", parts.system),
|
|
2026
|
+
...promptFor(ctx, parts.prompt),
|
|
982
2027
|
maxOutputTokens: ctx.config.budgets.synthesizer,
|
|
983
2028
|
temperature: ctx.config.temperature,
|
|
984
2029
|
abortSignal: ctx.signal,
|
|
@@ -986,13 +2031,27 @@ var synthesizeAnswer = async (ctx, goal, done) => {
|
|
|
986
2031
|
});
|
|
987
2032
|
let text = "";
|
|
988
2033
|
let verdict = "unknown";
|
|
989
|
-
for await (const
|
|
2034
|
+
for await (const part of result.fullStream) {
|
|
2035
|
+
if (part.type === "reasoning-delta") {
|
|
2036
|
+
if (part.text) ctx.emit({ type: "final.reasoning-delta", delta: part.text });
|
|
2037
|
+
continue;
|
|
2038
|
+
}
|
|
2039
|
+
if (part.type === "error") {
|
|
2040
|
+
const err = part.error instanceof Error ? part.error : new Error(String(part.error));
|
|
2041
|
+
throw imageRefusal(ctx, err, ctx.synthesizerModel) ?? err;
|
|
2042
|
+
}
|
|
2043
|
+
if (part.type !== "text-delta") continue;
|
|
2044
|
+
const delta = part.text;
|
|
990
2045
|
text += delta;
|
|
991
2046
|
if (verdict === "unknown") {
|
|
992
2047
|
const lead = text.trimStart();
|
|
993
2048
|
if (!lead) continue;
|
|
2049
|
+
if (lead.startsWith("<think>") && !text.includes("</think>")) continue;
|
|
994
2050
|
verdict = looksLikeJson(lead) ? "suppress" : "emit";
|
|
995
|
-
if (verdict === "emit")
|
|
2051
|
+
if (verdict === "emit") {
|
|
2052
|
+
const visible = text.includes("</think>") ? text.slice(text.lastIndexOf("</think>") + "</think>".length).trimStart() : text;
|
|
2053
|
+
if (visible) ctx.emit({ type: "final.text-delta", delta: visible });
|
|
2054
|
+
}
|
|
996
2055
|
} else if (verdict === "emit") {
|
|
997
2056
|
ctx.emit({ type: "final.text-delta", delta });
|
|
998
2057
|
}
|
|
@@ -1025,6 +2084,43 @@ var createLogger = (level = "warn", sink = console) => {
|
|
|
1025
2084
|
};
|
|
1026
2085
|
};
|
|
1027
2086
|
|
|
2087
|
+
// src/providers/capabilities.ts
|
|
2088
|
+
var CLOUD = /* @__PURE__ */ new Set([
|
|
2089
|
+
"openai",
|
|
2090
|
+
"anthropic",
|
|
2091
|
+
"google",
|
|
2092
|
+
"openai-compatible",
|
|
2093
|
+
"xai",
|
|
2094
|
+
"deepseek",
|
|
2095
|
+
"gateway"
|
|
2096
|
+
]);
|
|
2097
|
+
var supportsNativeTools = (p) => CLOUD.has(p);
|
|
2098
|
+
var supportsStructuredOutput = (p) => CLOUD.has(p);
|
|
2099
|
+
var directBrowserOk = (p) => p === "google" || p === "gateway" || p === "openai-compatible" || p === "anthropic";
|
|
2100
|
+
var LOCAL_RE2 = /web-?llm|mlc|browser-ai|transformers|built-?in/i;
|
|
2101
|
+
var TEXT_ONLY_MODEL_RE = /gpt-3\.5|o1-mini|o3-mini|deepseek|text-(davinci|embedding)|embedding|babbage|davinci|codestral/i;
|
|
2102
|
+
var VISION_PROVIDER_RE = /^(google|anthropic|openai|azure|xai|vertex|bedrock|gemini)/i;
|
|
2103
|
+
var PDF_PROVIDER_RE = /^(google|anthropic|openai|azure|vertex|bedrock|gemini)/i;
|
|
2104
|
+
var supportsImages = (model) => {
|
|
2105
|
+
if (typeof model === "string") {
|
|
2106
|
+
const [provider2 = "", id2 = model] = model.includes("/") ? model.split("/", 2) : ["", model];
|
|
2107
|
+
if (TEXT_ONLY_MODEL_RE.test(id2) || /deepseek/i.test(provider2)) return false;
|
|
2108
|
+
return VISION_PROVIDER_RE.test(provider2) ? true : void 0;
|
|
2109
|
+
}
|
|
2110
|
+
if (!model || typeof model !== "object") return void 0;
|
|
2111
|
+
const provider = String(model.provider ?? "");
|
|
2112
|
+
const id = String(model.modelId ?? "");
|
|
2113
|
+
if (LOCAL_RE2.test(provider)) return false;
|
|
2114
|
+
if (/deepseek/i.test(provider) || TEXT_ONLY_MODEL_RE.test(id)) return false;
|
|
2115
|
+
return VISION_PROVIDER_RE.test(provider) ? true : void 0;
|
|
2116
|
+
};
|
|
2117
|
+
var supportsPdf = (model) => {
|
|
2118
|
+
const vision = supportsImages(model);
|
|
2119
|
+
if (vision === false) return false;
|
|
2120
|
+
const provider = typeof model === "string" ? model.split("/")[0] ?? "" : String(model?.provider ?? "");
|
|
2121
|
+
return PDF_PROVIDER_RE.test(provider) ? true : void 0;
|
|
2122
|
+
};
|
|
2123
|
+
|
|
1028
2124
|
// src/agent/runner.ts
|
|
1029
2125
|
var errMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
1030
2126
|
var filterTools = (all, available, excluded) => {
|
|
@@ -1033,6 +2129,16 @@ var filterTools = (all, available, excluded) => {
|
|
|
1033
2129
|
if (excluded && excluded.length > 0) entries = entries.filter(([n]) => !excluded.includes(n));
|
|
1034
2130
|
return Object.fromEntries(entries);
|
|
1035
2131
|
};
|
|
2132
|
+
var asJson2 = (v) => {
|
|
2133
|
+
if (typeof v === "string") return v;
|
|
2134
|
+
try {
|
|
2135
|
+
return JSON.stringify(v) ?? String(v);
|
|
2136
|
+
} catch {
|
|
2137
|
+
return String(v);
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
var FINDING_CHARS = 600;
|
|
2141
|
+
var FINDINGS_BUDGET = 6e3;
|
|
1036
2142
|
var createAgent = async (config) => {
|
|
1037
2143
|
const cfg = resolveConfig(config);
|
|
1038
2144
|
const buildStage = (override, stageName) => buildModelFromStage(
|
|
@@ -1048,16 +2154,52 @@ var createAgent = async (config) => {
|
|
|
1048
2154
|
]);
|
|
1049
2155
|
const plannerMode = selectToolMode(plannerModel, cfg.toolMode);
|
|
1050
2156
|
const executorMode = selectToolMode(executorModel, cfg.toolMode);
|
|
1051
|
-
const
|
|
2157
|
+
const baseTools = filterTools(
|
|
1052
2158
|
config.tools ?? {},
|
|
1053
2159
|
config.availableTools,
|
|
1054
2160
|
config.excludedTools
|
|
1055
2161
|
);
|
|
1056
|
-
const
|
|
2162
|
+
const skills = (config.skills ?? []).map(defineSkill);
|
|
2163
|
+
const skillNames = /* @__PURE__ */ new Set();
|
|
2164
|
+
for (const s of skills) {
|
|
2165
|
+
if (skillNames.has(s.name)) throw new Error(`duplicate skill name "${s.name}"`);
|
|
2166
|
+
skillNames.add(s.name);
|
|
2167
|
+
}
|
|
2168
|
+
const catalog = toolCatalogOf(baseTools);
|
|
2169
|
+
const strategy = cfg.toolSelectionStrategy === "auto" ? catalog.length > cfg.toolSearchThreshold ? "search" : "all" : cfg.toolSelectionStrategy;
|
|
2170
|
+
const reserved = [
|
|
2171
|
+
...skills.length ? SKILL_TOOL_NAMES : [],
|
|
2172
|
+
...strategy === "search" ? [FIND_TOOLS_NAME] : []
|
|
2173
|
+
];
|
|
2174
|
+
for (const name of reserved) {
|
|
2175
|
+
if (Object.hasOwn(baseTools, name)) {
|
|
2176
|
+
throw new Error(
|
|
2177
|
+
`tool name "${name}" is reserved by the agent's built-in tools; rename your tool`
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
const builtinTools = new Set(reserved);
|
|
2182
|
+
const hints = /* @__PURE__ */ new Map();
|
|
2183
|
+
await Promise.all(
|
|
2184
|
+
Object.entries(baseTools).map(async ([name, t]) => {
|
|
2185
|
+
const hint = await schemaHintOf(t);
|
|
2186
|
+
if (hint) hints.set(name, hint);
|
|
2187
|
+
})
|
|
2188
|
+
);
|
|
2189
|
+
const toolHint = (name) => hints.get(name);
|
|
2190
|
+
const plannerCatalog = strategy === "search" ? renderSearchCatalog(catalog) : renderCatalog(baseTools, toolHint);
|
|
1057
2191
|
const prompts = { ...defaultPrompts, ...config.prompts };
|
|
2192
|
+
const caching = resolveCaching(config.promptCaching);
|
|
2193
|
+
const textOnly = executorMode === "prompted";
|
|
2194
|
+
const accepts = {
|
|
2195
|
+
image: config.inputs?.images ?? config.vision ?? (textOnly ? false : supportsImages(executorModel)),
|
|
2196
|
+
pdf: config.inputs?.pdf ?? (textOnly ? false : supportsPdf(executorModel)),
|
|
2197
|
+
file: config.inputs?.files ?? (textOnly ? false : void 0)
|
|
2198
|
+
};
|
|
2199
|
+
const approval = createApprovalState(config.toolApproval);
|
|
1058
2200
|
const log = createLogger(cfg.logLevel, config.logger);
|
|
1059
2201
|
log.info(
|
|
1060
|
-
`agent ready \u2014
|
|
2202
|
+
`agent ready \u2014 ${catalog.length} tool(s) (${strategy}), skills: [${[...skillNames].join(", ") || "none"}], planner mode: ${plannerMode}, executor mode: ${executorMode}, approval: ${approval.mode}`
|
|
1061
2203
|
);
|
|
1062
2204
|
const run = async (goal, opts = {}) => {
|
|
1063
2205
|
const emit = (event) => {
|
|
@@ -1077,7 +2219,8 @@ var createAgent = async (config) => {
|
|
|
1077
2219
|
steps: 0,
|
|
1078
2220
|
applied: 0,
|
|
1079
2221
|
stopped: false,
|
|
1080
|
-
usage
|
|
2222
|
+
usage,
|
|
2223
|
+
skills: []
|
|
1081
2224
|
};
|
|
1082
2225
|
if (!text) return result;
|
|
1083
2226
|
const state = async () => config.describeState ? config.describeState() : void 0;
|
|
@@ -1094,7 +2237,72 @@ var createAgent = async (config) => {
|
|
|
1094
2237
|
} catch {
|
|
1095
2238
|
}
|
|
1096
2239
|
};
|
|
2240
|
+
const activeSkillNames = /* @__PURE__ */ new Set();
|
|
2241
|
+
const activateSkill = (name, by) => {
|
|
2242
|
+
if (activeSkillNames.has(name) || !skillNames.has(name)) return;
|
|
2243
|
+
activeSkillNames.add(name);
|
|
2244
|
+
result.skills = [...activeSkillNames];
|
|
2245
|
+
emit({ type: "skill.activated", name, by });
|
|
2246
|
+
};
|
|
2247
|
+
const discovered = [];
|
|
2248
|
+
let currentStep;
|
|
2249
|
+
const counter = createCallCounter(cfg.maxToolCalls);
|
|
2250
|
+
const builtins = {};
|
|
2251
|
+
if (skills.length) {
|
|
2252
|
+
Object.assign(
|
|
2253
|
+
builtins,
|
|
2254
|
+
createSkillTools(skills, (s) => activateSkill(s.name, "tool"))
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
if (strategy === "search") {
|
|
2258
|
+
builtins[FIND_TOOLS_NAME] = createFindToolsTool(
|
|
2259
|
+
() => catalog,
|
|
2260
|
+
(query, names) => {
|
|
2261
|
+
for (const n of names) {
|
|
2262
|
+
const i = discovered.indexOf(n);
|
|
2263
|
+
if (i >= 0) discovered.splice(i, 1);
|
|
2264
|
+
discovered.push(n);
|
|
2265
|
+
}
|
|
2266
|
+
emit({ type: "tools.discovered", step: currentStep, query, names });
|
|
2267
|
+
},
|
|
2268
|
+
toolHint
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
const tools = wrapToolsForRun(
|
|
2272
|
+
{ ...baseTools, ...builtins },
|
|
2273
|
+
{
|
|
2274
|
+
approval,
|
|
2275
|
+
builtins: builtinTools,
|
|
2276
|
+
emit,
|
|
2277
|
+
step: () => currentStep,
|
|
2278
|
+
signal: opts.signal,
|
|
2279
|
+
addUsage: (u) => bumpUsage(u, "subagent"),
|
|
2280
|
+
countCall: counter.count,
|
|
2281
|
+
maxToolOutputChars: cfg.compaction.maxToolOutputChars
|
|
2282
|
+
}
|
|
2283
|
+
);
|
|
2284
|
+
let budgetExceeded;
|
|
2285
|
+
const breach = (extra) => {
|
|
2286
|
+
const b = checkLimits(extra ? addUsage(usage, extra) : usage, cfg.limits);
|
|
2287
|
+
if (b) return b;
|
|
2288
|
+
if (counter.exhausted)
|
|
2289
|
+
return { kind: "tool-calls", tokens: counter.calls, cap: cfg.maxToolCalls };
|
|
2290
|
+
return void 0;
|
|
2291
|
+
};
|
|
1097
2292
|
emit({ type: "run.start", goal: text });
|
|
2293
|
+
const images = [...opts.images ?? [], ...opts.files ?? []];
|
|
2294
|
+
const refused = images.map(attachmentKind).find((kind) => accepts[kind] === false);
|
|
2295
|
+
if (refused) {
|
|
2296
|
+
const err = unsupportedAttachment(
|
|
2297
|
+
modelLabel(executorModel),
|
|
2298
|
+
refused,
|
|
2299
|
+
textOnly ? "it runs in the text-only prompted tool mode" : void 0
|
|
2300
|
+
);
|
|
2301
|
+
log.warn(err.message);
|
|
2302
|
+
emit({ type: "error", phase: "run", error: err.message });
|
|
2303
|
+
result.final = err.message;
|
|
2304
|
+
return result;
|
|
2305
|
+
}
|
|
1098
2306
|
let history = [];
|
|
1099
2307
|
if (config.memory) {
|
|
1100
2308
|
try {
|
|
@@ -1102,6 +2310,30 @@ var createAgent = async (config) => {
|
|
|
1102
2310
|
} catch {
|
|
1103
2311
|
}
|
|
1104
2312
|
}
|
|
2313
|
+
if (config.memory && config.compaction && cfg.compaction.auto && history.length > 0) {
|
|
2314
|
+
const before = messageTokens(history);
|
|
2315
|
+
if (before > cfg.compaction.thresholdTokens) {
|
|
2316
|
+
const compacted = await compressHistory(history, synthesizerModel, {
|
|
2317
|
+
thresholdTokens: cfg.compaction.thresholdTokens,
|
|
2318
|
+
keepRecent: cfg.compaction.keepRecentTurns,
|
|
2319
|
+
summaryMaxTokens: cfg.budgets.compaction,
|
|
2320
|
+
timeoutMs: cfg.chatTimeoutMs,
|
|
2321
|
+
abortSignal: opts.signal,
|
|
2322
|
+
onUsage: (u) => bumpUsage(u, "compact")
|
|
2323
|
+
});
|
|
2324
|
+
if (compacted !== history) {
|
|
2325
|
+
history = compacted;
|
|
2326
|
+
await config.memory.replace(sessionId, compacted).catch(() => {
|
|
2327
|
+
});
|
|
2328
|
+
emit({
|
|
2329
|
+
type: "context.compacted",
|
|
2330
|
+
scope: "history",
|
|
2331
|
+
beforeTokens: before,
|
|
2332
|
+
afterTokens: messageTokens(compacted)
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
1105
2337
|
const ctx = {
|
|
1106
2338
|
config: cfg,
|
|
1107
2339
|
raw: config,
|
|
@@ -1111,24 +2343,42 @@ var createAgent = async (config) => {
|
|
|
1111
2343
|
plannerMode,
|
|
1112
2344
|
executorMode,
|
|
1113
2345
|
tools,
|
|
1114
|
-
toolCatalog,
|
|
2346
|
+
toolCatalog: plannerCatalog,
|
|
1115
2347
|
prompts,
|
|
1116
2348
|
emit,
|
|
1117
2349
|
log,
|
|
1118
2350
|
signal: opts.signal,
|
|
1119
2351
|
state,
|
|
1120
|
-
history
|
|
2352
|
+
history,
|
|
2353
|
+
strategy,
|
|
2354
|
+
caching,
|
|
2355
|
+
skills,
|
|
2356
|
+
activeSkills: () => skills.filter((s) => activeSkillNames.has(s.name)),
|
|
2357
|
+
discovered: () => discovered,
|
|
2358
|
+
builtinTools,
|
|
2359
|
+
toolHint,
|
|
2360
|
+
usageSoFar: () => usage,
|
|
2361
|
+
overBudget: (extra) => breach(extra) !== void 0,
|
|
2362
|
+
setCurrentStep: (step) => {
|
|
2363
|
+
currentStep = step;
|
|
2364
|
+
},
|
|
2365
|
+
images: images.length ? images : void 0
|
|
2366
|
+
};
|
|
2367
|
+
await remember({
|
|
2368
|
+
role: "user",
|
|
2369
|
+
content: images.length ? `${text}
|
|
2370
|
+
[attached: ${images.map((f, i) => f.name ?? `${attachmentKind(f)} ${i + 1}`).join(", ")}]` : text
|
|
2371
|
+
});
|
|
2372
|
+
log.info("run:", text, images.length ? `(+${images.length} image(s))` : "");
|
|
2373
|
+
const stop = () => {
|
|
2374
|
+
emit({ type: "stopped" });
|
|
2375
|
+
result.stopped = true;
|
|
2376
|
+
return result;
|
|
1121
2377
|
};
|
|
1122
|
-
await remember({ role: "user", content: text });
|
|
1123
|
-
log.info("run:", text);
|
|
1124
2378
|
try {
|
|
1125
2379
|
let planned = await createPlan(ctx, text);
|
|
1126
2380
|
bumpUsage(planned.usage, "plan");
|
|
1127
|
-
if (isAborted())
|
|
1128
|
-
emit({ type: "stopped" });
|
|
1129
|
-
result.stopped = true;
|
|
1130
|
-
return result;
|
|
1131
|
-
}
|
|
2381
|
+
if (isAborted()) return stop();
|
|
1132
2382
|
if (planned.plan.steps.length === 0 && text.split(/\s+/).length > 2) {
|
|
1133
2383
|
log.warn(
|
|
1134
2384
|
"planner returned no steps for a multi-word goal \u2014 retrying with a nudge. thought:",
|
|
@@ -1137,17 +2387,13 @@ var createAgent = async (config) => {
|
|
|
1137
2387
|
const retried = await createPlan(
|
|
1138
2388
|
ctx,
|
|
1139
2389
|
text,
|
|
1140
|
-
"NOTE: If the message above asks to build, add, change, clear or
|
|
2390
|
+
"NOTE: If the message above asks to build, add, change, clear, delete, find, check or look up ANYTHING the tools can do, you MUST output 1-6 concrete steps. Output an empty plan ONLY for a pure greeting or small talk."
|
|
1141
2391
|
);
|
|
1142
2392
|
bumpUsage(retried.usage, "plan");
|
|
1143
2393
|
if (retried.plan.steps.length > 0) planned = retried;
|
|
1144
2394
|
}
|
|
1145
2395
|
result.plan = planned.plan;
|
|
1146
|
-
if (isAborted())
|
|
1147
|
-
emit({ type: "stopped" });
|
|
1148
|
-
result.stopped = true;
|
|
1149
|
-
return result;
|
|
1150
|
-
}
|
|
2396
|
+
if (isAborted()) return stop();
|
|
1151
2397
|
if (planned.plan.steps.length === 0) {
|
|
1152
2398
|
log.info("no plan \u2014 answering directly (no tools will run)");
|
|
1153
2399
|
const answer = planned.plan.thought.trim() || "Tell me what you'd like to do.";
|
|
@@ -1162,19 +2408,25 @@ var createAgent = async (config) => {
|
|
|
1162
2408
|
);
|
|
1163
2409
|
emit({ type: "plan.created", plan: planned.plan });
|
|
1164
2410
|
planned.plan.steps.forEach((step, index) => emit({ type: "plan.step-added", step, index }));
|
|
1165
|
-
const
|
|
2411
|
+
for (const name of planned.skills) activateSkill(name, "plan");
|
|
2412
|
+
let done = [];
|
|
2413
|
+
const findings = [];
|
|
1166
2414
|
let remaining = [...planned.plan.steps];
|
|
1167
2415
|
let iter = 0;
|
|
1168
2416
|
let revisions = 0;
|
|
1169
2417
|
while (remaining.length > 0 && iter < cfg.maxIterations) {
|
|
1170
|
-
if (isAborted())
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
2418
|
+
if (isAborted()) return stop();
|
|
2419
|
+
const hit = breach();
|
|
2420
|
+
if (hit) {
|
|
2421
|
+
budgetExceeded = hit;
|
|
2422
|
+
result.budgetExceeded = hit;
|
|
2423
|
+
log.warn(`${hit.kind} limit reached (${hit.tokens}/${hit.cap}) \u2014 writing the answer now`);
|
|
2424
|
+
emit({ type: "budget.exceeded", ...hit });
|
|
2425
|
+
break;
|
|
1174
2426
|
}
|
|
1175
2427
|
iter += 1;
|
|
1176
2428
|
const step = remaining.shift();
|
|
1177
|
-
const stepNo =
|
|
2429
|
+
const stepNo = result.trace.length + 1;
|
|
1178
2430
|
const total = stepNo + remaining.length;
|
|
1179
2431
|
emit({ type: "step.start", step, index: stepNo, total });
|
|
1180
2432
|
let stepResult;
|
|
@@ -1183,13 +2435,12 @@ var createAgent = async (config) => {
|
|
|
1183
2435
|
bumpUsage(out.usage, "execute");
|
|
1184
2436
|
stepResult = out.result;
|
|
1185
2437
|
} catch (err) {
|
|
1186
|
-
if (isAborted())
|
|
1187
|
-
|
|
1188
|
-
result.stopped = true;
|
|
1189
|
-
return result;
|
|
1190
|
-
}
|
|
2438
|
+
if (isAborted()) return stop();
|
|
2439
|
+
if (err instanceof AttachmentsNotSupportedError) throw err;
|
|
1191
2440
|
emit({ type: "error", phase: "execute", error: errMessage(err) });
|
|
1192
2441
|
stepResult = { step, summary: errMessage(err), toolCalls: [], blocked: true };
|
|
2442
|
+
} finally {
|
|
2443
|
+
currentStep = void 0;
|
|
1193
2444
|
}
|
|
1194
2445
|
result.trace.push(stepResult);
|
|
1195
2446
|
const applied = stepResult.toolCalls.filter((c) => c.ok).length;
|
|
@@ -1205,10 +2456,38 @@ var createAgent = async (config) => {
|
|
|
1205
2456
|
stepResult.toolCalls.filter((c) => !c.ok).forEach((c) => log.warn(`tool ${c.name} failed:`, c.output));
|
|
1206
2457
|
log.debug(`step ${stepNo} detail:`, stepResult);
|
|
1207
2458
|
emit({ type: "step.complete", step, result: stepResult });
|
|
2459
|
+
const counts = stepResult.toolCalls.length ? ` [${applied} tool call(s) ok${failed ? `, ${failed} failed` : ""}]` : "";
|
|
1208
2460
|
done.push(
|
|
1209
|
-
`${step.description} \u2014 ${
|
|
2461
|
+
`${step.description} \u2014 ${clip(stepResult.summary, FINDING_CHARS)}${counts}${stepResult.blocked ? " [blocked]" : ""}`
|
|
1210
2462
|
);
|
|
1211
|
-
const
|
|
2463
|
+
for (const c of stepResult.toolCalls) {
|
|
2464
|
+
if (!c.ok || builtinTools.has(c.name)) continue;
|
|
2465
|
+
findings.push(`- ${c.name}: ${clip(asJson2(c.output), FINDING_CHARS)}`);
|
|
2466
|
+
}
|
|
2467
|
+
while (findings.join("\n").length > FINDINGS_BUDGET && findings.length > 1) findings.shift();
|
|
2468
|
+
if (cfg.compaction.auto) {
|
|
2469
|
+
const before = estimateTokens(done.join("\n"));
|
|
2470
|
+
if (before > cfg.compaction.thresholdTokens) {
|
|
2471
|
+
const compacted = await compactSteps(done, synthesizerModel, {
|
|
2472
|
+
thresholdTokens: cfg.compaction.thresholdTokens,
|
|
2473
|
+
keepRecent: cfg.compaction.keepRecentSteps,
|
|
2474
|
+
summaryMaxTokens: cfg.budgets.compaction,
|
|
2475
|
+
timeoutMs: cfg.chatTimeoutMs,
|
|
2476
|
+
abortSignal: opts.signal,
|
|
2477
|
+
onUsage: (u) => bumpUsage(u, "compact")
|
|
2478
|
+
});
|
|
2479
|
+
if (compacted !== done) {
|
|
2480
|
+
done = compacted;
|
|
2481
|
+
emit({
|
|
2482
|
+
type: "context.compacted",
|
|
2483
|
+
scope: "trace",
|
|
2484
|
+
beforeTokens: before,
|
|
2485
|
+
afterTokens: estimateTokens(done.join("\n"))
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
const wantReplan = cfg.replan && iter < cfg.maxIterations && revisions < cfg.maxRevisions && !isAborted() && !breach() && await replanWanted(cfg.replanAfter, stepResult, {
|
|
1212
2491
|
signal: ctx.signal,
|
|
1213
2492
|
timeoutMs: cfg.chatTimeoutMs,
|
|
1214
2493
|
onError: (err) => log.warn("replanAfter predicate threw \u2014 using the failure rule", err)
|
|
@@ -1225,7 +2504,7 @@ var createAgent = async (config) => {
|
|
|
1225
2504
|
if (decision.decision === "finish") break;
|
|
1226
2505
|
if (decision.decision === "revise" && decision.plan.length > 0) {
|
|
1227
2506
|
revisions += 1;
|
|
1228
|
-
remaining = decision.plan.slice(0, cfg.maxIterations - iter).map((description, i) => ({ id: `r${iter}-${i + 1}`, description }));
|
|
2507
|
+
remaining = decision.plan.slice(0, Math.min(cfg.maxPlanSteps, cfg.maxIterations - iter)).map((description, i) => ({ id: `r${iter}-${i + 1}`, description }));
|
|
1229
2508
|
emit({
|
|
1230
2509
|
type: "plan.revised",
|
|
1231
2510
|
plan: { thought: planned.plan.thought, steps: remaining },
|
|
@@ -1234,62 +2513,352 @@ var createAgent = async (config) => {
|
|
|
1234
2513
|
}
|
|
1235
2514
|
}
|
|
1236
2515
|
}
|
|
1237
|
-
if (
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
2516
|
+
if (!budgetExceeded && remaining.length > 0) {
|
|
2517
|
+
const hit = breach();
|
|
2518
|
+
if (hit) {
|
|
2519
|
+
budgetExceeded = hit;
|
|
2520
|
+
result.budgetExceeded = hit;
|
|
2521
|
+
emit({ type: "budget.exceeded", ...hit });
|
|
2522
|
+
}
|
|
1241
2523
|
}
|
|
2524
|
+
if (isAborted()) return stop();
|
|
1242
2525
|
let summary = "Done \u2014 the changes have been applied.";
|
|
1243
2526
|
if (cfg.synthesize) {
|
|
1244
2527
|
try {
|
|
1245
|
-
const synth = await synthesizeAnswer(ctx, text, done);
|
|
2528
|
+
const synth = await synthesizeAnswer(ctx, text, done, findings);
|
|
1246
2529
|
bumpUsage(synth.usage, "synthesize");
|
|
1247
2530
|
if (synth.text) summary = synth.text;
|
|
1248
|
-
} catch {
|
|
2531
|
+
} catch (err) {
|
|
2532
|
+
if (isAborted()) return stop();
|
|
2533
|
+
if (err instanceof AttachmentsNotSupportedError) throw err;
|
|
1249
2534
|
}
|
|
1250
2535
|
}
|
|
1251
2536
|
log.info(`done \u2014 ${result.applied} tool call(s) applied over ${result.steps} step(s)`);
|
|
1252
2537
|
emit({ type: "final", text: summary });
|
|
1253
2538
|
await remember({ role: "assistant", content: summary });
|
|
1254
2539
|
result.final = summary;
|
|
1255
|
-
if (config.memory && cfg.compressAfterChars > 0) {
|
|
2540
|
+
if (config.memory && (config.compaction ? cfg.compaction.auto : cfg.compressAfterChars > 0)) {
|
|
1256
2541
|
try {
|
|
1257
2542
|
const transcript = await config.memory.load(sessionId);
|
|
2543
|
+
const before = messageTokens(transcript);
|
|
1258
2544
|
const compacted = await compressHistory(transcript, synthesizerModel, {
|
|
1259
|
-
|
|
2545
|
+
...config.compaction ? {
|
|
2546
|
+
thresholdTokens: cfg.compaction.thresholdTokens,
|
|
2547
|
+
keepRecent: cfg.compaction.keepRecentTurns,
|
|
2548
|
+
summaryMaxTokens: cfg.budgets.compaction
|
|
2549
|
+
} : { maxChars: cfg.compressAfterChars },
|
|
1260
2550
|
timeoutMs: cfg.chatTimeoutMs,
|
|
1261
|
-
abortSignal: opts.signal
|
|
2551
|
+
abortSignal: opts.signal,
|
|
2552
|
+
onUsage: (u) => bumpUsage(u, "compact")
|
|
1262
2553
|
});
|
|
1263
|
-
if (compacted !== transcript)
|
|
2554
|
+
if (compacted !== transcript) {
|
|
2555
|
+
await config.memory.replace(sessionId, compacted);
|
|
2556
|
+
emit({
|
|
2557
|
+
type: "context.compacted",
|
|
2558
|
+
scope: "history",
|
|
2559
|
+
beforeTokens: before,
|
|
2560
|
+
afterTokens: messageTokens(compacted)
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
1264
2563
|
} catch {
|
|
1265
2564
|
}
|
|
1266
2565
|
}
|
|
1267
2566
|
return result;
|
|
1268
2567
|
} catch (err) {
|
|
2568
|
+
if (isAborted()) return stop();
|
|
1269
2569
|
emit({ type: "error", phase: "run", error: errMessage(err) });
|
|
1270
2570
|
result.final = errMessage(err);
|
|
1271
2571
|
return result;
|
|
1272
2572
|
}
|
|
1273
2573
|
};
|
|
2574
|
+
const compact = async (o = {}) => {
|
|
2575
|
+
const empty = {
|
|
2576
|
+
compacted: false,
|
|
2577
|
+
beforeTokens: 0,
|
|
2578
|
+
afterTokens: 0,
|
|
2579
|
+
usage: emptyUsage()
|
|
2580
|
+
};
|
|
2581
|
+
if (!config.memory) return empty;
|
|
2582
|
+
const sessionId = o.sessionId ?? cfg.sessionId;
|
|
2583
|
+
const transcript = await config.memory.load(sessionId);
|
|
2584
|
+
const beforeTokens = messageTokens(transcript);
|
|
2585
|
+
let usage = emptyUsage();
|
|
2586
|
+
const compacted = await compressHistory(transcript, synthesizerModel, {
|
|
2587
|
+
thresholdTokens: cfg.compaction.thresholdTokens,
|
|
2588
|
+
keepRecent: cfg.compaction.keepRecentTurns,
|
|
2589
|
+
summaryMaxTokens: cfg.budgets.compaction,
|
|
2590
|
+
force: o.force ?? true,
|
|
2591
|
+
timeoutMs: cfg.chatTimeoutMs,
|
|
2592
|
+
abortSignal: o.signal,
|
|
2593
|
+
onUsage: (u) => {
|
|
2594
|
+
usage = addUsage(usage, u);
|
|
2595
|
+
}
|
|
2596
|
+
});
|
|
2597
|
+
if (compacted === transcript)
|
|
2598
|
+
return { ...empty, beforeTokens, afterTokens: beforeTokens, usage };
|
|
2599
|
+
await config.memory.replace(sessionId, compacted);
|
|
2600
|
+
return { compacted: true, beforeTokens, afterTokens: messageTokens(compacted), usage };
|
|
2601
|
+
};
|
|
1274
2602
|
return {
|
|
1275
2603
|
run,
|
|
2604
|
+
compact,
|
|
2605
|
+
setToolApprovalMode: (mode) => {
|
|
2606
|
+
if (!TOOL_APPROVAL_MODES.includes(mode)) {
|
|
2607
|
+
throw new Error(`unknown tool approval mode "${mode}" (${TOOL_APPROVAL_MODES.join(" | ")})`);
|
|
2608
|
+
}
|
|
2609
|
+
approval.mode = mode;
|
|
2610
|
+
log.info(`tool approval mode \u2192 ${mode}`);
|
|
2611
|
+
},
|
|
2612
|
+
get toolApprovalMode() {
|
|
2613
|
+
return approval.mode;
|
|
2614
|
+
},
|
|
2615
|
+
listTools: () => catalog.map((e) => ({ ...e })),
|
|
2616
|
+
toolStrategy: strategy,
|
|
2617
|
+
skills: skills.map((s) => ({ name: s.name, description: s.description })),
|
|
2618
|
+
capabilities: { images: accepts.image, pdf: accepts.pdf, files: accepts.file },
|
|
1276
2619
|
models: { planner: plannerModel, executor: executorModel, synthesizer: synthesizerModel }
|
|
1277
2620
|
};
|
|
1278
2621
|
};
|
|
1279
2622
|
|
|
1280
|
-
// src/
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
2623
|
+
// src/subagent/tool.ts
|
|
2624
|
+
import { asSchema as asSchema2, jsonSchema as jsonSchema3, tool as tool3 } from "ai";
|
|
2625
|
+
|
|
2626
|
+
// src/subagent/protocol.ts
|
|
2627
|
+
var toCloneable = (value) => {
|
|
2628
|
+
try {
|
|
2629
|
+
return JSON.parse(
|
|
2630
|
+
JSON.stringify(value, (_k, v) => v instanceof Error ? v.message : v) ?? "null"
|
|
2631
|
+
);
|
|
2632
|
+
} catch {
|
|
2633
|
+
return String(value);
|
|
2634
|
+
}
|
|
2635
|
+
};
|
|
2636
|
+
var safePost = (target, message) => {
|
|
2637
|
+
try {
|
|
2638
|
+
target.postMessage(message);
|
|
2639
|
+
} catch {
|
|
2640
|
+
target.postMessage(toCloneable(message));
|
|
2641
|
+
}
|
|
2642
|
+
};
|
|
2643
|
+
|
|
2644
|
+
// src/subagent/tool.ts
|
|
2645
|
+
var abortError = () => {
|
|
2646
|
+
const e = new Error("The subagent was aborted");
|
|
2647
|
+
e.name = "AbortError";
|
|
2648
|
+
return e;
|
|
2649
|
+
};
|
|
2650
|
+
var createSemaphore = (size) => {
|
|
2651
|
+
let active = 0;
|
|
2652
|
+
const queue = [];
|
|
2653
|
+
return async (signal) => {
|
|
2654
|
+
if (active >= size) {
|
|
2655
|
+
await new Promise((resolve, reject) => {
|
|
2656
|
+
const go = () => {
|
|
2657
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2658
|
+
resolve();
|
|
2659
|
+
};
|
|
2660
|
+
const onAbort = () => {
|
|
2661
|
+
const i = queue.indexOf(go);
|
|
2662
|
+
if (i >= 0) queue.splice(i, 1);
|
|
2663
|
+
reject(abortError());
|
|
2664
|
+
};
|
|
2665
|
+
queue.push(go);
|
|
2666
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
active += 1;
|
|
2670
|
+
let released = false;
|
|
2671
|
+
return () => {
|
|
2672
|
+
if (released) return;
|
|
2673
|
+
released = true;
|
|
2674
|
+
active -= 1;
|
|
2675
|
+
queue.shift()?.();
|
|
2676
|
+
};
|
|
2677
|
+
};
|
|
2678
|
+
};
|
|
2679
|
+
var seq2 = 0;
|
|
2680
|
+
var createSubagentTool = (opts) => {
|
|
2681
|
+
if (!opts.config === !opts.worker) {
|
|
2682
|
+
throw new Error(
|
|
2683
|
+
`subagent "${opts.name}": pass exactly one of \`config\` (in-process) or \`worker\``
|
|
2684
|
+
);
|
|
2685
|
+
}
|
|
2686
|
+
if (opts.worker && !opts.workerConfig) {
|
|
2687
|
+
throw new Error(`subagent "${opts.name}": a worker subagent needs \`workerConfig\``);
|
|
2688
|
+
}
|
|
2689
|
+
const acquire = createSemaphore(Math.max(1, opts.maxConcurrent ?? 4));
|
|
2690
|
+
const outputMax = opts.outputMaxChars ?? 8e3;
|
|
2691
|
+
const gatedHostTools = (approve) => Object.fromEntries(
|
|
2692
|
+
Object.entries(opts.tools ?? {}).map(([name, t2]) => {
|
|
2693
|
+
const execute = t2.execute;
|
|
2694
|
+
if (typeof execute !== "function" || !approve) return [name, t2];
|
|
2695
|
+
return [
|
|
2696
|
+
name,
|
|
2697
|
+
{
|
|
2698
|
+
...t2,
|
|
2699
|
+
execute: async (input, o) => {
|
|
2700
|
+
await approve(name, input, isReadOnlyTool(t2));
|
|
2701
|
+
return execute.call(t2, input, o);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
];
|
|
2705
|
+
})
|
|
2706
|
+
);
|
|
2707
|
+
const runInProcess = async (task, signal, forward, hostTools) => {
|
|
2708
|
+
const config = opts.config;
|
|
2709
|
+
const agent = await createAgent({ ...config, tools: { ...config.tools, ...hostTools } });
|
|
2710
|
+
let runError;
|
|
2711
|
+
const result = await agent.run(task, {
|
|
2712
|
+
signal,
|
|
2713
|
+
onEvent: (e) => {
|
|
2714
|
+
if (e.type === "error" && e.phase === "run") runError = e.error;
|
|
2715
|
+
forward(e);
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
if (result.stopped) throw abortError();
|
|
2719
|
+
if (runError) throw new Error(runError);
|
|
2720
|
+
return { text: result.final, usage: result.usage };
|
|
2721
|
+
};
|
|
2722
|
+
const proxiedSpecs = async () => Promise.all(
|
|
2723
|
+
Object.entries(opts.tools ?? {}).map(async ([name, t2]) => ({
|
|
2724
|
+
name,
|
|
2725
|
+
description: typeof t2.description === "string" ? t2.description : "",
|
|
2726
|
+
inputSchema: toCloneable(await asSchema2(t2.inputSchema).jsonSchema),
|
|
2727
|
+
readOnly: isReadOnlyTool(t2)
|
|
2728
|
+
}))
|
|
2729
|
+
);
|
|
2730
|
+
const workerConfigFor = async () => {
|
|
2731
|
+
const cfg = opts.workerConfig;
|
|
2732
|
+
let model = { ...cfg.model };
|
|
2733
|
+
if (model.credentialRef && !model.apiKey && opts.credentials) {
|
|
2734
|
+
const apiKey = await opts.credentials.getApiKey(model.credentialRef);
|
|
2735
|
+
if (!apiKey)
|
|
2736
|
+
throw new Error(`no API key stored under "${model.credentialRef}" for the subagent`);
|
|
2737
|
+
model = { ...model, apiKey };
|
|
2738
|
+
}
|
|
2739
|
+
return toCloneable({ ...cfg, model });
|
|
2740
|
+
};
|
|
2741
|
+
const runInWorker = async (task, signal, forward, hostTools) => {
|
|
2742
|
+
if (signal?.aborted) throw abortError();
|
|
2743
|
+
const [config, proxied] = await Promise.all([workerConfigFor(), proxiedSpecs()]);
|
|
2744
|
+
const worker = opts.worker();
|
|
2745
|
+
return new Promise((resolve, reject) => {
|
|
2746
|
+
let settled = false;
|
|
2747
|
+
const finish = (fn) => {
|
|
2748
|
+
if (settled) return;
|
|
2749
|
+
settled = true;
|
|
2750
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2751
|
+
worker.removeEventListener?.("message", onMessage);
|
|
2752
|
+
worker.removeEventListener?.("error", onError);
|
|
2753
|
+
worker.terminate?.();
|
|
2754
|
+
fn();
|
|
2755
|
+
};
|
|
2756
|
+
const onAbort = () => {
|
|
2757
|
+
safePost(worker, { type: "abort" });
|
|
2758
|
+
finish(() => reject(abortError()));
|
|
2759
|
+
};
|
|
2760
|
+
const onError = (ev) => finish(() => reject(new Error(`subagent worker failed: ${ev?.message ?? "unknown error"}`)));
|
|
2761
|
+
const callHostTool = async (callId, name, input) => {
|
|
2762
|
+
const t2 = hostTools[name];
|
|
2763
|
+
try {
|
|
2764
|
+
if (!t2 || typeof t2.execute !== "function") throw new Error(`unknown host tool "${name}"`);
|
|
2765
|
+
const output = await t2.execute(input, {
|
|
2766
|
+
toolCallId: callId,
|
|
2767
|
+
messages: [],
|
|
2768
|
+
abortSignal: signal
|
|
2769
|
+
});
|
|
2770
|
+
if (!settled) safePost(worker, { type: "tool-result", callId, ok: true, output });
|
|
2771
|
+
} catch (err) {
|
|
2772
|
+
const output = err instanceof Error ? err.message : String(err);
|
|
2773
|
+
if (!settled) safePost(worker, { type: "tool-result", callId, ok: false, output });
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
const onMessage = (ev) => {
|
|
2777
|
+
const msg = ev.data;
|
|
2778
|
+
switch (msg?.type) {
|
|
2779
|
+
case "event":
|
|
2780
|
+
forward(msg.event);
|
|
2781
|
+
break;
|
|
2782
|
+
case "tool-call":
|
|
2783
|
+
void callHostTool(msg.callId, msg.name, msg.input);
|
|
2784
|
+
break;
|
|
2785
|
+
case "done":
|
|
2786
|
+
finish(() => resolve({ text: msg.text, usage: msg.usage }));
|
|
2787
|
+
break;
|
|
2788
|
+
case "error":
|
|
2789
|
+
finish(() => reject(new Error(msg.error)));
|
|
2790
|
+
break;
|
|
2791
|
+
}
|
|
2792
|
+
};
|
|
2793
|
+
worker.addEventListener("message", onMessage);
|
|
2794
|
+
worker.addEventListener("error", onError);
|
|
2795
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2796
|
+
safePost(worker, { type: "run", task, config, proxied });
|
|
2797
|
+
});
|
|
2798
|
+
};
|
|
2799
|
+
const t = tool3({
|
|
2800
|
+
description: opts.description,
|
|
2801
|
+
inputSchema: jsonSchema3({
|
|
2802
|
+
type: "object",
|
|
2803
|
+
properties: {
|
|
2804
|
+
task: {
|
|
2805
|
+
type: "string",
|
|
2806
|
+
description: "A complete, self-contained task: the subagent sees nothing of your context except this text."
|
|
2807
|
+
}
|
|
2808
|
+
},
|
|
2809
|
+
required: ["task"],
|
|
2810
|
+
additionalProperties: false
|
|
2811
|
+
}),
|
|
2812
|
+
execute: async ({ task }, options) => {
|
|
2813
|
+
const runCtx = toolRunContextOf(options);
|
|
2814
|
+
const parentSignal = options?.abortSignal ?? runCtx?.signal;
|
|
2815
|
+
const id = `${opts.name}-${seq2 += 1}`;
|
|
2816
|
+
const release = await acquire(parentSignal);
|
|
2817
|
+
const emit = (e) => runCtx?.emit(e);
|
|
2818
|
+
const controller = new AbortController();
|
|
2819
|
+
const onParentAbort = () => controller.abort();
|
|
2820
|
+
parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
2821
|
+
if (parentSignal?.aborted) controller.abort();
|
|
2822
|
+
let timedOut = false;
|
|
2823
|
+
const timer = opts.timeoutMs && opts.timeoutMs > 0 ? setTimeout(() => {
|
|
2824
|
+
timedOut = true;
|
|
2825
|
+
controller.abort();
|
|
2826
|
+
}, opts.timeoutMs) : void 0;
|
|
2827
|
+
emit({ type: "subagent.start", id, name: opts.name, task });
|
|
2828
|
+
try {
|
|
2829
|
+
const forward = (event) => emit({ type: "subagent.event", id, name: opts.name, event });
|
|
2830
|
+
const hostTools = gatedHostTools(runCtx?.approve);
|
|
2831
|
+
const run = opts.worker ? runInWorker : runInProcess;
|
|
2832
|
+
const out = await run(task, controller.signal, forward, hostTools).catch((err) => {
|
|
2833
|
+
if (timedOut)
|
|
2834
|
+
throw new Error(`subagent "${opts.name}" timed out after ${opts.timeoutMs} ms`);
|
|
2835
|
+
throw err;
|
|
2836
|
+
});
|
|
2837
|
+
runCtx?.addUsage(out.usage);
|
|
2838
|
+
emit({ type: "subagent.complete", id, name: opts.name, text: out.text, usage: out.usage });
|
|
2839
|
+
return clip(out.text, outputMax);
|
|
2840
|
+
} catch (err) {
|
|
2841
|
+
emit({
|
|
2842
|
+
type: "subagent.error",
|
|
2843
|
+
id,
|
|
2844
|
+
name: opts.name,
|
|
2845
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2846
|
+
});
|
|
2847
|
+
throw err;
|
|
2848
|
+
} finally {
|
|
2849
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2850
|
+
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
2851
|
+
release();
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
});
|
|
2855
|
+
t.promptHint = "{ task: string }";
|
|
2856
|
+
if (opts.readOnly) t.readOnly = true;
|
|
2857
|
+
return t;
|
|
2858
|
+
};
|
|
2859
|
+
|
|
2860
|
+
// src/subagent/worker.ts
|
|
2861
|
+
import { dynamicTool, jsonSchema as jsonSchema4 } from "ai";
|
|
1293
2862
|
|
|
1294
2863
|
// src/secrets/store.ts
|
|
1295
2864
|
var VaultCredentialStore = class {
|
|
@@ -1323,12 +2892,333 @@ var MemoryCredentialStore = class {
|
|
|
1323
2892
|
}
|
|
1324
2893
|
};
|
|
1325
2894
|
|
|
2895
|
+
// src/subagent/worker.ts
|
|
2896
|
+
var SUBAGENT_REF = "__subagent__";
|
|
2897
|
+
var defaultResolveModel = (spec) => {
|
|
2898
|
+
const { apiKey, ...rest } = spec;
|
|
2899
|
+
const credentials = apiKey ? new MemoryCredentialStore({ [SUBAGENT_REF]: apiKey }) : void 0;
|
|
2900
|
+
return buildModelFromStage(
|
|
2901
|
+
resolveStage(apiKey ? { ...rest, credentialRef: SUBAGENT_REF } : rest, void 0, "subagent"),
|
|
2902
|
+
credentials
|
|
2903
|
+
);
|
|
2904
|
+
};
|
|
2905
|
+
var serveSubagentWorker = (opts = {}) => {
|
|
2906
|
+
const scope = opts.scope ?? globalThis;
|
|
2907
|
+
let controller;
|
|
2908
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2909
|
+
let callSeq = 0;
|
|
2910
|
+
const proxy = (spec) => {
|
|
2911
|
+
const t = dynamicTool({
|
|
2912
|
+
description: spec.description,
|
|
2913
|
+
inputSchema: jsonSchema4(spec.inputSchema),
|
|
2914
|
+
execute: (input, options) => new Promise((resolve, reject) => {
|
|
2915
|
+
const callId = `call-${callSeq += 1}`;
|
|
2916
|
+
const signal = options?.abortSignal;
|
|
2917
|
+
if (signal?.aborted) return reject(new Error("aborted"));
|
|
2918
|
+
pending.set(callId, { resolve, reject });
|
|
2919
|
+
signal?.addEventListener(
|
|
2920
|
+
"abort",
|
|
2921
|
+
() => {
|
|
2922
|
+
if (pending.delete(callId)) reject(new Error("aborted"));
|
|
2923
|
+
},
|
|
2924
|
+
{ once: true }
|
|
2925
|
+
);
|
|
2926
|
+
safePost(scope, { type: "tool-call", callId, name: spec.name, input });
|
|
2927
|
+
})
|
|
2928
|
+
});
|
|
2929
|
+
return spec.readOnly ? markReadOnly(t) : t;
|
|
2930
|
+
};
|
|
2931
|
+
const run = async (msg) => {
|
|
2932
|
+
controller = new AbortController();
|
|
2933
|
+
let runError;
|
|
2934
|
+
try {
|
|
2935
|
+
const { model: spec, ...rest } = msg.config;
|
|
2936
|
+
const model = await (opts.resolveModel ?? defaultResolveModel)(spec);
|
|
2937
|
+
const proxied = Object.fromEntries(msg.proxied.map((p) => [p.name, proxy(p)]));
|
|
2938
|
+
const agent = await createAgent({
|
|
2939
|
+
...rest,
|
|
2940
|
+
model,
|
|
2941
|
+
tools: { ...opts.tools, ...proxied }
|
|
2942
|
+
});
|
|
2943
|
+
const result = await agent.run(msg.task, {
|
|
2944
|
+
signal: controller.signal,
|
|
2945
|
+
onEvent: (event) => {
|
|
2946
|
+
if (event.type === "error" && event.phase === "run") runError = event.error;
|
|
2947
|
+
safePost(scope, { type: "event", event });
|
|
2948
|
+
}
|
|
2949
|
+
});
|
|
2950
|
+
if (result.stopped) safePost(scope, { type: "error", error: "aborted" });
|
|
2951
|
+
else if (runError) safePost(scope, { type: "error", error: runError });
|
|
2952
|
+
else
|
|
2953
|
+
safePost(scope, {
|
|
2954
|
+
type: "done",
|
|
2955
|
+
text: result.final,
|
|
2956
|
+
usage: result.usage,
|
|
2957
|
+
steps: result.steps
|
|
2958
|
+
});
|
|
2959
|
+
} catch (err) {
|
|
2960
|
+
safePost(scope, { type: "error", error: err instanceof Error ? err.message : String(err) });
|
|
2961
|
+
}
|
|
2962
|
+
};
|
|
2963
|
+
scope.addEventListener("message", (ev) => {
|
|
2964
|
+
const msg = ev.data;
|
|
2965
|
+
switch (msg?.type) {
|
|
2966
|
+
case "run":
|
|
2967
|
+
void run(msg);
|
|
2968
|
+
break;
|
|
2969
|
+
case "abort":
|
|
2970
|
+
controller?.abort();
|
|
2971
|
+
for (const [id, p] of pending) {
|
|
2972
|
+
pending.delete(id);
|
|
2973
|
+
p.reject(new Error("aborted"));
|
|
2974
|
+
}
|
|
2975
|
+
break;
|
|
2976
|
+
case "tool-result": {
|
|
2977
|
+
const p = pending.get(msg.callId);
|
|
2978
|
+
if (!p) break;
|
|
2979
|
+
pending.delete(msg.callId);
|
|
2980
|
+
if (msg.ok) p.resolve(msg.output);
|
|
2981
|
+
else
|
|
2982
|
+
p.reject(
|
|
2983
|
+
new Error(typeof msg.output === "string" ? msg.output : JSON.stringify(msg.output))
|
|
2984
|
+
);
|
|
2985
|
+
break;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
});
|
|
2989
|
+
};
|
|
2990
|
+
|
|
2991
|
+
// src/files/vfs.ts
|
|
2992
|
+
var MIME = {
|
|
2993
|
+
txt: "text/plain",
|
|
2994
|
+
md: "text/markdown",
|
|
2995
|
+
json: "application/json",
|
|
2996
|
+
csv: "text/csv",
|
|
2997
|
+
html: "text/html",
|
|
2998
|
+
css: "text/css",
|
|
2999
|
+
js: "text/javascript",
|
|
3000
|
+
ts: "text/typescript",
|
|
3001
|
+
svg: "image/svg+xml",
|
|
3002
|
+
png: "image/png",
|
|
3003
|
+
jpg: "image/jpeg",
|
|
3004
|
+
jpeg: "image/jpeg",
|
|
3005
|
+
gif: "image/gif",
|
|
3006
|
+
webp: "image/webp",
|
|
3007
|
+
pdf: "application/pdf",
|
|
3008
|
+
xml: "application/xml",
|
|
3009
|
+
yaml: "text/yaml",
|
|
3010
|
+
yml: "text/yaml"
|
|
3011
|
+
};
|
|
3012
|
+
var mimeFromPath = (path) => MIME[path.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream";
|
|
3013
|
+
var isTextMime = (mime) => /^text\/|json|xml|yaml|javascript|typescript|svg/.test(mime);
|
|
3014
|
+
var normalizePath = (input) => {
|
|
3015
|
+
const parts = [];
|
|
3016
|
+
for (const seg of String(input ?? "").replace(/\\/g, "/").split("/")) {
|
|
3017
|
+
if (!seg || seg === ".") continue;
|
|
3018
|
+
if (seg === "..") {
|
|
3019
|
+
if (parts.length === 0) throw new Error(`path "${input}" escapes the root`);
|
|
3020
|
+
parts.pop();
|
|
3021
|
+
continue;
|
|
3022
|
+
}
|
|
3023
|
+
parts.push(seg);
|
|
3024
|
+
}
|
|
3025
|
+
if (parts.length === 0) throw new Error("a file path is required");
|
|
3026
|
+
return `/${parts.join("/")}`;
|
|
3027
|
+
};
|
|
3028
|
+
var byteLength = (content, encoding) => encoding === "base64" ? Math.floor(content.replace(/=+$/, "").length * 3 / 4) : new TextEncoder().encode(content).length;
|
|
3029
|
+
var bytesToBase64 = (bytes) => {
|
|
3030
|
+
let bin = "";
|
|
3031
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
3032
|
+
bin += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
3033
|
+
}
|
|
3034
|
+
return btoa(bin);
|
|
3035
|
+
};
|
|
3036
|
+
var VirtualFileSystem = class {
|
|
3037
|
+
memory;
|
|
3038
|
+
dbName;
|
|
3039
|
+
ns;
|
|
3040
|
+
maxFileBytes;
|
|
3041
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3042
|
+
constructor(opts = {}) {
|
|
3043
|
+
if (opts.memory) this.memory = /* @__PURE__ */ new Map();
|
|
3044
|
+
this.dbName = opts.dbName;
|
|
3045
|
+
this.ns = opts.namespace ?? "default";
|
|
3046
|
+
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
3047
|
+
}
|
|
3048
|
+
key(path) {
|
|
3049
|
+
return `${this.ns}:${path}`;
|
|
3050
|
+
}
|
|
3051
|
+
/** Subscribe to writes and deletes (e.g. to refresh a file list). Returns an unsubscribe. */
|
|
3052
|
+
onChange(listener) {
|
|
3053
|
+
this.listeners.add(listener);
|
|
3054
|
+
return () => this.listeners.delete(listener);
|
|
3055
|
+
}
|
|
3056
|
+
notify(type, path) {
|
|
3057
|
+
for (const l of this.listeners) {
|
|
3058
|
+
try {
|
|
3059
|
+
l({ type, path });
|
|
3060
|
+
} catch {
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
async write(path, content, opts = {}) {
|
|
3065
|
+
const p = normalizePath(path);
|
|
3066
|
+
const binary = content instanceof Uint8Array;
|
|
3067
|
+
const encoding = binary ? "base64" : opts.encoding ?? "utf8";
|
|
3068
|
+
const text = binary ? bytesToBase64(content) : content;
|
|
3069
|
+
const size = binary ? content.length : byteLength(text, encoding);
|
|
3070
|
+
if (size > this.maxFileBytes) {
|
|
3071
|
+
throw new Error(`"${p}" is ${size} bytes; the limit is ${this.maxFileBytes}`);
|
|
3072
|
+
}
|
|
3073
|
+
const file = {
|
|
3074
|
+
path: p,
|
|
3075
|
+
content: text,
|
|
3076
|
+
encoding,
|
|
3077
|
+
mimeType: opts.mimeType ?? mimeFromPath(p),
|
|
3078
|
+
size,
|
|
3079
|
+
updatedAt: Date.now()
|
|
3080
|
+
};
|
|
3081
|
+
if (this.memory) this.memory.set(p, file);
|
|
3082
|
+
else {
|
|
3083
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
3084
|
+
await db.put(FILES_STORE, file, this.key(p));
|
|
3085
|
+
}
|
|
3086
|
+
this.notify("write", p);
|
|
3087
|
+
const { content: _drop, ...info } = file;
|
|
3088
|
+
return info;
|
|
3089
|
+
}
|
|
3090
|
+
/** Store a data URL ("data:image/png;base64,…") as a file. */
|
|
3091
|
+
async writeDataUrl(path, dataUrl) {
|
|
3092
|
+
const m = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(dataUrl);
|
|
3093
|
+
if (!m) throw new Error("not a data URL");
|
|
3094
|
+
const mimeType = m[1] ?? "application/octet-stream";
|
|
3095
|
+
return m[2] ? this.write(path, m[3], { mimeType, encoding: "base64" }) : this.write(path, decodeURIComponent(m[3]), { mimeType });
|
|
3096
|
+
}
|
|
3097
|
+
async read(path) {
|
|
3098
|
+
const p = normalizePath(path);
|
|
3099
|
+
if (this.memory) return this.memory.get(p);
|
|
3100
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
3101
|
+
return await db.get(FILES_STORE, this.key(p));
|
|
3102
|
+
}
|
|
3103
|
+
/** The file as a data URL (for <img src>, downloads). */
|
|
3104
|
+
async readDataUrl(path) {
|
|
3105
|
+
const f = await this.read(path);
|
|
3106
|
+
if (!f) return void 0;
|
|
3107
|
+
return f.encoding === "base64" ? `data:${f.mimeType};base64,${f.content}` : `data:${f.mimeType};charset=utf-8,${encodeURIComponent(f.content)}`;
|
|
3108
|
+
}
|
|
3109
|
+
async delete(path) {
|
|
3110
|
+
const p = normalizePath(path);
|
|
3111
|
+
let existed;
|
|
3112
|
+
if (this.memory) existed = this.memory.delete(p);
|
|
3113
|
+
else {
|
|
3114
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
3115
|
+
existed = await db.get(FILES_STORE, this.key(p)) !== void 0;
|
|
3116
|
+
await db.delete(FILES_STORE, this.key(p));
|
|
3117
|
+
}
|
|
3118
|
+
if (existed) this.notify("delete", p);
|
|
3119
|
+
return existed;
|
|
3120
|
+
}
|
|
3121
|
+
/** Files under a prefix ("/" = all), sorted by path. */
|
|
3122
|
+
async list(prefix = "/") {
|
|
3123
|
+
const dir = prefix === "/" ? "/" : `${normalizePath(prefix)}/`;
|
|
3124
|
+
let files;
|
|
3125
|
+
if (this.memory) files = [...this.memory.values()];
|
|
3126
|
+
else {
|
|
3127
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
3128
|
+
const range = IDBKeyRange.bound(`${this.ns}:`, `${this.ns}:\uFFFF`);
|
|
3129
|
+
files = await db.getAll(FILES_STORE, range);
|
|
3130
|
+
}
|
|
3131
|
+
return files.filter((f) => dir === "/" || f.path.startsWith(dir)).map(({ content: _drop, ...info }) => info).sort((a, b) => a.path.localeCompare(b.path));
|
|
3132
|
+
}
|
|
3133
|
+
/** Remove every file (of this namespace). */
|
|
3134
|
+
async clear() {
|
|
3135
|
+
for (const f of await this.list()) await this.delete(f.path);
|
|
3136
|
+
}
|
|
3137
|
+
};
|
|
3138
|
+
|
|
3139
|
+
// src/files/tools.ts
|
|
3140
|
+
import { jsonSchema as jsonSchema5, tool as tool4 } from "ai";
|
|
3141
|
+
var createFileTools = (vfs, opts = {}) => {
|
|
3142
|
+
const p = opts.prefix ?? "fs_";
|
|
3143
|
+
const maxRead = opts.maxReadChars ?? 5e4;
|
|
3144
|
+
const tools = {
|
|
3145
|
+
[`${p}list`]: markReadOnly(
|
|
3146
|
+
tool4({
|
|
3147
|
+
description: "List the files in the workspace (attachments the user added, files you wrote). Optionally only under a directory prefix.",
|
|
3148
|
+
inputSchema: jsonSchema5({
|
|
3149
|
+
type: "object",
|
|
3150
|
+
properties: { prefix: { type: "string", description: 'Directory prefix, e.g. "/docs"' } },
|
|
3151
|
+
additionalProperties: false
|
|
3152
|
+
}),
|
|
3153
|
+
execute: async ({ prefix }) => (await vfs.list(prefix || "/")).map((f) => ({
|
|
3154
|
+
path: f.path,
|
|
3155
|
+
mimeType: f.mimeType,
|
|
3156
|
+
size: f.size
|
|
3157
|
+
}))
|
|
3158
|
+
})
|
|
3159
|
+
),
|
|
3160
|
+
[`${p}read`]: markReadOnly(
|
|
3161
|
+
tool4({
|
|
3162
|
+
description: "Read a text file from the workspace by its path.",
|
|
3163
|
+
inputSchema: jsonSchema5({
|
|
3164
|
+
type: "object",
|
|
3165
|
+
properties: { path: { type: "string", description: 'Absolute path, e.g. "/notes.md"' } },
|
|
3166
|
+
required: ["path"],
|
|
3167
|
+
additionalProperties: false
|
|
3168
|
+
}),
|
|
3169
|
+
execute: async ({ path }) => {
|
|
3170
|
+
const f = await vfs.read(path);
|
|
3171
|
+
if (!f) {
|
|
3172
|
+
const known = (await vfs.list()).map((x) => x.path).slice(0, 30);
|
|
3173
|
+
throw new Error(`no file "${path}"; files: ${known.join(", ") || "none"}`);
|
|
3174
|
+
}
|
|
3175
|
+
if (f.encoding === "base64" && !isTextMime(f.mimeType)) {
|
|
3176
|
+
return `"${f.path}" is a binary file (${f.mimeType}, ${f.size} bytes) and cannot be read as text.`;
|
|
3177
|
+
}
|
|
3178
|
+
const text = f.encoding === "base64" ? atob(f.content) : f.content;
|
|
3179
|
+
return text.length > maxRead ? `${text.slice(0, maxRead)}\u2026 [truncated ${text.length - maxRead} chars]` : text;
|
|
3180
|
+
}
|
|
3181
|
+
})
|
|
3182
|
+
)
|
|
3183
|
+
};
|
|
3184
|
+
if (opts.readOnly) return tools;
|
|
3185
|
+
tools[`${p}write`] = tool4({
|
|
3186
|
+
description: "Write a text file to the workspace (creates or replaces it). Use it for reports, drafts, data the user should be able to open.",
|
|
3187
|
+
inputSchema: jsonSchema5({
|
|
3188
|
+
type: "object",
|
|
3189
|
+
properties: {
|
|
3190
|
+
path: { type: "string", description: 'Absolute path, e.g. "/reports/summary.md"' },
|
|
3191
|
+
content: { type: "string", description: "The full file content" },
|
|
3192
|
+
mimeType: { type: "string", description: "Optional MIME type (guessed from the name)" }
|
|
3193
|
+
},
|
|
3194
|
+
required: ["path", "content"],
|
|
3195
|
+
additionalProperties: false
|
|
3196
|
+
}),
|
|
3197
|
+
execute: async ({ path, content, mimeType }) => {
|
|
3198
|
+
const info = await vfs.write(path, content, mimeType ? { mimeType } : {});
|
|
3199
|
+
return { written: info.path, size: info.size };
|
|
3200
|
+
}
|
|
3201
|
+
});
|
|
3202
|
+
tools[`${p}delete`] = tool4({
|
|
3203
|
+
description: "Delete a file from the workspace.",
|
|
3204
|
+
inputSchema: jsonSchema5({
|
|
3205
|
+
type: "object",
|
|
3206
|
+
properties: { path: { type: "string" } },
|
|
3207
|
+
required: ["path"],
|
|
3208
|
+
additionalProperties: false
|
|
3209
|
+
}),
|
|
3210
|
+
execute: async ({ path }) => ({ deleted: await vfs.delete(path) })
|
|
3211
|
+
});
|
|
3212
|
+
return tools;
|
|
3213
|
+
};
|
|
3214
|
+
|
|
1326
3215
|
// src/tools/define.ts
|
|
1327
|
-
import { tool } from "ai";
|
|
3216
|
+
import { tool as tool5 } from "ai";
|
|
1328
3217
|
var defineTool = (config) => {
|
|
1329
|
-
const { promptHint, ...rest } = config;
|
|
1330
|
-
const t =
|
|
3218
|
+
const { promptHint, readOnly, ...rest } = config;
|
|
3219
|
+
const t = tool5(rest);
|
|
1331
3220
|
if (promptHint) t.promptHint = promptHint;
|
|
3221
|
+
if (readOnly) t.readOnly = true;
|
|
1332
3222
|
return t;
|
|
1333
3223
|
};
|
|
1334
3224
|
|
|
@@ -1382,6 +3272,10 @@ var IndexedDBStore = class {
|
|
|
1382
3272
|
}
|
|
1383
3273
|
};
|
|
1384
3274
|
export {
|
|
3275
|
+
AttachmentsNotSupportedError,
|
|
3276
|
+
FILES_STORE,
|
|
3277
|
+
FIND_TOOLS_NAME,
|
|
3278
|
+
ImagesNotSupportedError,
|
|
1385
3279
|
IndexedDBStore,
|
|
1386
3280
|
IndexedDBVault,
|
|
1387
3281
|
KEYS_STORE,
|
|
@@ -1392,28 +3286,59 @@ export {
|
|
|
1392
3286
|
ReplanSchema,
|
|
1393
3287
|
SECRETS_STORE,
|
|
1394
3288
|
SESSIONS_STORE,
|
|
3289
|
+
SKILL_TOOL_NAMES,
|
|
3290
|
+
TOOL_APPROVAL_MODES,
|
|
3291
|
+
ToolBudgetError,
|
|
3292
|
+
ToolDeniedError,
|
|
1395
3293
|
VaultCredentialStore,
|
|
3294
|
+
VirtualFileSystem,
|
|
3295
|
+
attachmentKind,
|
|
1396
3296
|
buildModelFromStage,
|
|
3297
|
+
bytesToBase64,
|
|
3298
|
+
checkLimits,
|
|
3299
|
+
compactHistory,
|
|
3300
|
+
compactSteps,
|
|
1397
3301
|
compressHistory,
|
|
1398
3302
|
createAgent,
|
|
3303
|
+
createFileTools,
|
|
3304
|
+
createFindToolsTool,
|
|
1399
3305
|
createLogger,
|
|
1400
3306
|
createPlan,
|
|
3307
|
+
createSkillTools,
|
|
3308
|
+
createSubagentTool,
|
|
3309
|
+
createToolResultClearer,
|
|
1401
3310
|
createWebLLMModel,
|
|
1402
3311
|
decideReplan,
|
|
3312
|
+
decideToolPermission,
|
|
1403
3313
|
decryptJSON,
|
|
1404
3314
|
defaultPrompts,
|
|
3315
|
+
defineSkill,
|
|
1405
3316
|
defineTool,
|
|
1406
3317
|
directBrowserOk,
|
|
1407
3318
|
dispatch,
|
|
1408
3319
|
encryptJSON,
|
|
3320
|
+
messageTokens as estimateMessagesTokens,
|
|
3321
|
+
estimateTokens,
|
|
1409
3322
|
executeStep,
|
|
1410
3323
|
generate,
|
|
1411
3324
|
generateStructured,
|
|
1412
3325
|
getOrCreateVaultKey,
|
|
3326
|
+
isAttachmentRefusal,
|
|
1413
3327
|
isDirectModel,
|
|
3328
|
+
isImageRefusal,
|
|
1414
3329
|
isProviderSpec,
|
|
3330
|
+
isReadOnlyTool,
|
|
3331
|
+
isTextMime,
|
|
1415
3332
|
isWebGPUAvailable,
|
|
3333
|
+
limitModelOutput,
|
|
3334
|
+
loadSkillFromUrl,
|
|
1416
3335
|
looksLikeJson,
|
|
3336
|
+
markReadOnly,
|
|
3337
|
+
matchRule,
|
|
3338
|
+
mediaTypeOf,
|
|
3339
|
+
mergeProviderOptions,
|
|
3340
|
+
mimeFromPath,
|
|
3341
|
+
normalizePath,
|
|
1417
3342
|
normalizeSteps,
|
|
1418
3343
|
normalizeUsage,
|
|
1419
3344
|
openAgentWebDB,
|
|
@@ -1421,22 +3346,41 @@ export {
|
|
|
1421
3346
|
parsePlainText,
|
|
1422
3347
|
parsePlannerResponse,
|
|
1423
3348
|
parseReplannerResponse,
|
|
3349
|
+
parseSkillMarkdown,
|
|
1424
3350
|
preloadWebLLMModel,
|
|
1425
3351
|
promptHintOf,
|
|
3352
|
+
renderActiveSkills,
|
|
1426
3353
|
renderCatalog,
|
|
3354
|
+
renderSearchCatalog,
|
|
3355
|
+
renderSkillIndex,
|
|
1427
3356
|
replanWanted,
|
|
3357
|
+
resolveCaching,
|
|
3358
|
+
resolveCompaction,
|
|
1428
3359
|
resolveConfig,
|
|
1429
3360
|
resolveModel,
|
|
1430
3361
|
resolveStage,
|
|
3362
|
+
resolveThinking,
|
|
1431
3363
|
runToolLoop,
|
|
3364
|
+
schemaHintOf,
|
|
3365
|
+
searchTools,
|
|
1432
3366
|
selectToolMode,
|
|
3367
|
+
serveSubagentWorker,
|
|
1433
3368
|
shouldReplan,
|
|
1434
3369
|
splitBlocker,
|
|
1435
3370
|
stream,
|
|
3371
|
+
supportsImages,
|
|
1436
3372
|
supportsNativeTools,
|
|
3373
|
+
supportsPdf,
|
|
1437
3374
|
supportsStructuredOutput,
|
|
1438
3375
|
synthesizeAnswer,
|
|
3376
|
+
thinkingFor,
|
|
3377
|
+
toFilePart,
|
|
3378
|
+
toImagePart,
|
|
3379
|
+
tokenize,
|
|
3380
|
+
toolCatalogOf,
|
|
3381
|
+
toolRunContextOf,
|
|
1439
3382
|
unloadWebLLMModel,
|
|
3383
|
+
withRollingBreakpoint,
|
|
1440
3384
|
withSystem
|
|
1441
3385
|
};
|
|
1442
3386
|
//# sourceMappingURL=index.js.map
|