@dudko.dev/agent-web 0.0.19 → 0.0.20
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 +2207 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1066 -69
- package/dist/index.d.ts +1066 -69
- package/dist/index.js +2131 -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.cjs
CHANGED
|
@@ -77,6 +77,10 @@ var init_webllm = __esm({
|
|
|
77
77
|
// src/index.ts
|
|
78
78
|
var src_exports = {};
|
|
79
79
|
__export(src_exports, {
|
|
80
|
+
AttachmentsNotSupportedError: () => AttachmentsNotSupportedError,
|
|
81
|
+
FILES_STORE: () => FILES_STORE,
|
|
82
|
+
FIND_TOOLS_NAME: () => FIND_TOOLS_NAME,
|
|
83
|
+
ImagesNotSupportedError: () => ImagesNotSupportedError,
|
|
80
84
|
IndexedDBStore: () => IndexedDBStore,
|
|
81
85
|
IndexedDBVault: () => IndexedDBVault,
|
|
82
86
|
KEYS_STORE: () => KEYS_STORE,
|
|
@@ -87,28 +91,59 @@ __export(src_exports, {
|
|
|
87
91
|
ReplanSchema: () => ReplanSchema,
|
|
88
92
|
SECRETS_STORE: () => SECRETS_STORE,
|
|
89
93
|
SESSIONS_STORE: () => SESSIONS_STORE,
|
|
94
|
+
SKILL_TOOL_NAMES: () => SKILL_TOOL_NAMES,
|
|
95
|
+
TOOL_APPROVAL_MODES: () => TOOL_APPROVAL_MODES,
|
|
96
|
+
ToolBudgetError: () => ToolBudgetError,
|
|
97
|
+
ToolDeniedError: () => ToolDeniedError,
|
|
90
98
|
VaultCredentialStore: () => VaultCredentialStore,
|
|
99
|
+
VirtualFileSystem: () => VirtualFileSystem,
|
|
100
|
+
attachmentKind: () => attachmentKind,
|
|
91
101
|
buildModelFromStage: () => buildModelFromStage,
|
|
102
|
+
bytesToBase64: () => bytesToBase64,
|
|
103
|
+
checkLimits: () => checkLimits,
|
|
104
|
+
compactHistory: () => compactHistory,
|
|
105
|
+
compactSteps: () => compactSteps,
|
|
92
106
|
compressHistory: () => compressHistory,
|
|
93
107
|
createAgent: () => createAgent,
|
|
108
|
+
createFileTools: () => createFileTools,
|
|
109
|
+
createFindToolsTool: () => createFindToolsTool,
|
|
94
110
|
createLogger: () => createLogger,
|
|
95
111
|
createPlan: () => createPlan,
|
|
112
|
+
createSkillTools: () => createSkillTools,
|
|
113
|
+
createSubagentTool: () => createSubagentTool,
|
|
114
|
+
createToolResultClearer: () => createToolResultClearer,
|
|
96
115
|
createWebLLMModel: () => createWebLLMModel,
|
|
97
116
|
decideReplan: () => decideReplan,
|
|
117
|
+
decideToolPermission: () => decideToolPermission,
|
|
98
118
|
decryptJSON: () => decryptJSON,
|
|
99
119
|
defaultPrompts: () => defaultPrompts,
|
|
120
|
+
defineSkill: () => defineSkill,
|
|
100
121
|
defineTool: () => defineTool,
|
|
101
122
|
directBrowserOk: () => directBrowserOk,
|
|
102
123
|
dispatch: () => dispatch,
|
|
103
124
|
encryptJSON: () => encryptJSON,
|
|
125
|
+
estimateMessagesTokens: () => messageTokens,
|
|
126
|
+
estimateTokens: () => estimateTokens,
|
|
104
127
|
executeStep: () => executeStep,
|
|
105
128
|
generate: () => generate,
|
|
106
129
|
generateStructured: () => generateStructured,
|
|
107
130
|
getOrCreateVaultKey: () => getOrCreateVaultKey,
|
|
131
|
+
isAttachmentRefusal: () => isAttachmentRefusal,
|
|
108
132
|
isDirectModel: () => isDirectModel,
|
|
133
|
+
isImageRefusal: () => isImageRefusal,
|
|
109
134
|
isProviderSpec: () => isProviderSpec,
|
|
135
|
+
isReadOnlyTool: () => isReadOnlyTool,
|
|
136
|
+
isTextMime: () => isTextMime,
|
|
110
137
|
isWebGPUAvailable: () => isWebGPUAvailable,
|
|
138
|
+
limitModelOutput: () => limitModelOutput,
|
|
139
|
+
loadSkillFromUrl: () => loadSkillFromUrl,
|
|
111
140
|
looksLikeJson: () => looksLikeJson,
|
|
141
|
+
markReadOnly: () => markReadOnly,
|
|
142
|
+
matchRule: () => matchRule,
|
|
143
|
+
mediaTypeOf: () => mediaTypeOf,
|
|
144
|
+
mergeProviderOptions: () => mergeProviderOptions,
|
|
145
|
+
mimeFromPath: () => mimeFromPath,
|
|
146
|
+
normalizePath: () => normalizePath,
|
|
112
147
|
normalizeSteps: () => normalizeSteps,
|
|
113
148
|
normalizeUsage: () => normalizeUsage,
|
|
114
149
|
openAgentWebDB: () => openAgentWebDB,
|
|
@@ -116,49 +151,87 @@ __export(src_exports, {
|
|
|
116
151
|
parsePlainText: () => parsePlainText,
|
|
117
152
|
parsePlannerResponse: () => parsePlannerResponse,
|
|
118
153
|
parseReplannerResponse: () => parseReplannerResponse,
|
|
154
|
+
parseSkillMarkdown: () => parseSkillMarkdown,
|
|
119
155
|
preloadWebLLMModel: () => preloadWebLLMModel,
|
|
120
156
|
promptHintOf: () => promptHintOf,
|
|
157
|
+
renderActiveSkills: () => renderActiveSkills,
|
|
121
158
|
renderCatalog: () => renderCatalog,
|
|
159
|
+
renderSearchCatalog: () => renderSearchCatalog,
|
|
160
|
+
renderSkillIndex: () => renderSkillIndex,
|
|
122
161
|
replanWanted: () => replanWanted,
|
|
162
|
+
resolveCaching: () => resolveCaching,
|
|
163
|
+
resolveCompaction: () => resolveCompaction,
|
|
123
164
|
resolveConfig: () => resolveConfig,
|
|
124
165
|
resolveModel: () => resolveModel,
|
|
125
166
|
resolveStage: () => resolveStage,
|
|
167
|
+
resolveThinking: () => resolveThinking,
|
|
126
168
|
runToolLoop: () => runToolLoop,
|
|
169
|
+
schemaHintOf: () => schemaHintOf,
|
|
170
|
+
searchTools: () => searchTools,
|
|
127
171
|
selectToolMode: () => selectToolMode,
|
|
172
|
+
serveSubagentWorker: () => serveSubagentWorker,
|
|
128
173
|
shouldReplan: () => shouldReplan,
|
|
129
174
|
splitBlocker: () => splitBlocker,
|
|
130
175
|
stream: () => stream,
|
|
176
|
+
supportsImages: () => supportsImages,
|
|
131
177
|
supportsNativeTools: () => supportsNativeTools,
|
|
178
|
+
supportsPdf: () => supportsPdf,
|
|
132
179
|
supportsStructuredOutput: () => supportsStructuredOutput,
|
|
133
180
|
synthesizeAnswer: () => synthesizeAnswer,
|
|
181
|
+
thinkingFor: () => thinkingFor,
|
|
182
|
+
toFilePart: () => toFilePart,
|
|
183
|
+
toImagePart: () => toImagePart,
|
|
184
|
+
tokenize: () => tokenize,
|
|
185
|
+
toolCatalogOf: () => toolCatalogOf,
|
|
186
|
+
toolRunContextOf: () => toolRunContextOf,
|
|
134
187
|
unloadWebLLMModel: () => unloadWebLLMModel,
|
|
188
|
+
withRollingBreakpoint: () => withRollingBreakpoint,
|
|
135
189
|
withSystem: () => withSystem
|
|
136
190
|
});
|
|
137
191
|
module.exports = __toCommonJS(src_exports);
|
|
138
192
|
|
|
139
|
-
// src/
|
|
140
|
-
var
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
replanAfter: c.replanAfter ?? "failure",
|
|
158
|
-
synthesize: c.synthesize ?? true,
|
|
159
|
-
compressAfterChars: c.compressAfterChars ?? 12e3,
|
|
160
|
-
logLevel: c.logLevel ?? "warn"
|
|
193
|
+
// src/caching.ts
|
|
194
|
+
var resolveCaching = (s) => {
|
|
195
|
+
if (s === false) return { enabled: false };
|
|
196
|
+
if (s === void 0 || s === true) return { enabled: true };
|
|
197
|
+
return { enabled: true, ttl: s.ttl, key: s.key };
|
|
198
|
+
};
|
|
199
|
+
var cachedInstructions = (system, caching) => caching.enabled ? {
|
|
200
|
+
role: "system",
|
|
201
|
+
content: system,
|
|
202
|
+
providerOptions: {
|
|
203
|
+
anthropic: {
|
|
204
|
+
cacheControl: { type: "ephemeral", ...caching.ttl ? { ttl: caching.ttl } : {} }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} : system;
|
|
208
|
+
var cachingProviderOptions = (caching, clientName, stage) => caching.enabled ? { openai: { promptCacheKey: caching.key ?? `${clientName}:${stage}` } } : void 0;
|
|
209
|
+
var breakpoint = (ttl) => ({
|
|
210
|
+
anthropic: { cacheControl: { type: "ephemeral", ...ttl ? { ttl } : {} } }
|
|
161
211
|
});
|
|
212
|
+
var withoutBreakpoint = (m) => {
|
|
213
|
+
const own = m.providerOptions;
|
|
214
|
+
if (!own?.anthropic || !("cacheControl" in own.anthropic)) return m;
|
|
215
|
+
const { cacheControl: _drop, ...anthropic } = own.anthropic;
|
|
216
|
+
const { anthropic: _old, ...rest } = own;
|
|
217
|
+
const providerOptions = Object.keys(anthropic).length ? { ...rest, anthropic } : rest;
|
|
218
|
+
const { providerOptions: _po, ...base } = m;
|
|
219
|
+
return Object.keys(providerOptions).length ? { ...base, providerOptions } : base;
|
|
220
|
+
};
|
|
221
|
+
var withRollingBreakpoint = (messages, caching) => {
|
|
222
|
+
if (!caching.enabled || messages.length === 0) return messages;
|
|
223
|
+
const out = messages.map((m, i) => i < messages.length - 1 ? withoutBreakpoint(m) : m);
|
|
224
|
+
const last = out[out.length - 1];
|
|
225
|
+
const own = last.providerOptions ?? {};
|
|
226
|
+
out[out.length - 1] = {
|
|
227
|
+
...last,
|
|
228
|
+
providerOptions: {
|
|
229
|
+
...own,
|
|
230
|
+
anthropic: { ...own.anthropic, ...breakpoint(caching.ttl).anthropic }
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
return out;
|
|
234
|
+
};
|
|
162
235
|
|
|
163
236
|
// src/llm/generate.ts
|
|
164
237
|
var import_ai = require("ai");
|
|
@@ -172,7 +245,10 @@ var normalizeUsage = (u) => {
|
|
|
172
245
|
return {
|
|
173
246
|
inputTokens,
|
|
174
247
|
outputTokens,
|
|
175
|
-
totalTokens: u?.totalTokens ?? inputTokens + outputTokens
|
|
248
|
+
totalTokens: u?.totalTokens ?? inputTokens + outputTokens,
|
|
249
|
+
reasoningTokens: u?.outputTokenDetails?.reasoningTokens ?? u?.reasoningTokens ?? 0,
|
|
250
|
+
cachedInputTokens: u?.inputTokenDetails?.cacheReadTokens ?? u?.cachedInputTokens ?? 0,
|
|
251
|
+
cacheWriteTokens: u?.inputTokenDetails?.cacheWriteTokens ?? 0
|
|
176
252
|
};
|
|
177
253
|
};
|
|
178
254
|
var timeoutSignal = (signal, ms) => {
|
|
@@ -183,53 +259,78 @@ var timeoutSignal = (signal, ms) => {
|
|
|
183
259
|
};
|
|
184
260
|
|
|
185
261
|
// src/llm/generate.ts
|
|
186
|
-
var
|
|
187
|
-
|
|
188
|
-
system: opts.system,
|
|
189
|
-
...promptOf(opts),
|
|
190
|
-
tools: opts.tools,
|
|
262
|
+
var callSettings = (opts) => ({
|
|
263
|
+
...opts.system !== void 0 ? { instructions: opts.system } : {},
|
|
191
264
|
maxOutputTokens: opts.maxOutputTokens,
|
|
192
265
|
temperature: opts.temperature,
|
|
266
|
+
...opts.reasoning ? { reasoning: opts.reasoning } : {},
|
|
267
|
+
...opts.providerOptions ? { providerOptions: opts.providerOptions } : {},
|
|
193
268
|
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
194
269
|
});
|
|
270
|
+
var generate = (model, opts = {}) => (0, import_ai.generateText)({
|
|
271
|
+
model,
|
|
272
|
+
...callSettings(opts),
|
|
273
|
+
...promptOf(opts),
|
|
274
|
+
tools: opts.tools
|
|
275
|
+
});
|
|
195
276
|
var stream = (model, opts = {}) => (0, import_ai.streamText)({
|
|
196
277
|
model,
|
|
197
|
-
|
|
278
|
+
...callSettings(opts),
|
|
198
279
|
...promptOf(opts),
|
|
199
|
-
tools: opts.tools
|
|
200
|
-
maxOutputTokens: opts.maxOutputTokens,
|
|
201
|
-
temperature: opts.temperature,
|
|
202
|
-
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
280
|
+
tools: opts.tools
|
|
203
281
|
});
|
|
204
282
|
var generateStructured = (model, schema, opts = {}) => (0, import_ai.generateObject)({
|
|
205
283
|
model,
|
|
206
284
|
schema,
|
|
207
|
-
|
|
208
|
-
...promptOf(opts)
|
|
209
|
-
maxOutputTokens: opts.maxOutputTokens,
|
|
210
|
-
temperature: opts.temperature,
|
|
211
|
-
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
285
|
+
...callSettings(opts),
|
|
286
|
+
...promptOf(opts)
|
|
212
287
|
});
|
|
213
288
|
|
|
214
289
|
// src/memory/compress.ts
|
|
215
|
-
var
|
|
290
|
+
var resolveCompaction = (c) => {
|
|
291
|
+
const contextWindowTokens = c?.contextWindowTokens ?? 128e3;
|
|
292
|
+
return {
|
|
293
|
+
auto: c?.auto ?? true,
|
|
294
|
+
contextWindowTokens,
|
|
295
|
+
thresholdTokens: c?.thresholdTokens ?? Math.floor(contextWindowTokens / 2),
|
|
296
|
+
keepRecentTurns: c?.keepRecentTurns ?? 4,
|
|
297
|
+
keepRecentSteps: c?.keepRecentSteps ?? 3,
|
|
298
|
+
summaryMaxTokens: c?.summaryMaxTokens ?? 1024,
|
|
299
|
+
maxToolOutputChars: c?.maxToolOutputChars ?? 2e4,
|
|
300
|
+
clearToolResultsAfterTokens: c?.clearToolResultsAfterTokens ?? Math.floor(contextWindowTokens / 4),
|
|
301
|
+
keepToolResults: c?.keepToolResults ?? 3
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
var estimateTokens = (text) => Math.ceil((text ?? "").length / 4);
|
|
305
|
+
var messageTokens = (msgs) => msgs.reduce((n, m) => n + estimateTokens(`${m.role}: ${m.content}
|
|
306
|
+
`), 0);
|
|
307
|
+
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.";
|
|
216
308
|
var compressHistory = async (messages, model, opts = {}) => {
|
|
217
|
-
const maxChars = opts.maxChars ?? 12e3;
|
|
218
309
|
const keepRecent = opts.keepRecent ?? 6;
|
|
219
|
-
if (
|
|
220
|
-
|
|
310
|
+
if (messages.length <= keepRecent + 1) return messages;
|
|
311
|
+
if (!opts.force) {
|
|
312
|
+
if (opts.thresholdTokens !== void 0) {
|
|
313
|
+
if (opts.thresholdTokens <= 0 || messageTokens(messages) <= opts.thresholdTokens) {
|
|
314
|
+
return messages;
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
const maxChars = opts.maxChars ?? 12e3;
|
|
318
|
+
const chars = messages.reduce((n, m) => n + m.content.length, 0);
|
|
319
|
+
if (maxChars <= 0 || chars <= maxChars) return messages;
|
|
320
|
+
}
|
|
221
321
|
}
|
|
222
322
|
const head = messages.slice(0, messages.length - keepRecent);
|
|
223
323
|
const tail = messages.slice(messages.length - keepRecent);
|
|
224
|
-
const transcript = head.map((m) => `${m.role}: ${m.content}`).join("\n").slice(
|
|
324
|
+
const transcript = head.map((m) => `${m.role}: ${m.content}`).join("\n").slice(-48e3);
|
|
225
325
|
try {
|
|
226
326
|
const result = await generate(model, {
|
|
227
|
-
system:
|
|
327
|
+
system: SUMMARY_SYSTEM,
|
|
228
328
|
prompt: transcript,
|
|
229
|
-
maxOutputTokens: 400,
|
|
329
|
+
maxOutputTokens: opts.summaryMaxTokens ?? 400,
|
|
230
330
|
timeoutMs: opts.timeoutMs,
|
|
231
331
|
abortSignal: opts.abortSignal
|
|
232
332
|
});
|
|
333
|
+
opts.onUsage?.(normalizeUsage(result.usage));
|
|
233
334
|
const clean = (result.text || "").trim();
|
|
234
335
|
if (!clean) return messages;
|
|
235
336
|
const summaryMsg = {
|
|
@@ -243,6 +344,93 @@ ${clean}`,
|
|
|
243
344
|
return messages;
|
|
244
345
|
}
|
|
245
346
|
};
|
|
347
|
+
var compactHistory = compressHistory;
|
|
348
|
+
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.";
|
|
349
|
+
var compactSteps = async (done, model, opts) => {
|
|
350
|
+
const keepRecent = opts.keepRecent ?? 3;
|
|
351
|
+
if (done.length <= keepRecent + 1) return done;
|
|
352
|
+
if (!opts.force && estimateTokens(done.join("\n")) <= opts.thresholdTokens) return done;
|
|
353
|
+
const head = done.slice(0, done.length - keepRecent);
|
|
354
|
+
try {
|
|
355
|
+
const result = await generate(model, {
|
|
356
|
+
system: TRACE_SYSTEM,
|
|
357
|
+
prompt: head.map((d, i) => `${i + 1}. ${d}`).join("\n"),
|
|
358
|
+
maxOutputTokens: opts.summaryMaxTokens ?? 600,
|
|
359
|
+
timeoutMs: opts.timeoutMs,
|
|
360
|
+
abortSignal: opts.abortSignal
|
|
361
|
+
});
|
|
362
|
+
opts.onUsage?.(normalizeUsage(result.usage));
|
|
363
|
+
const clean = (result.text || "").trim();
|
|
364
|
+
if (!clean) return done;
|
|
365
|
+
return [`Summary of ${head.length} earlier step(s): ${clean}`, ...done.slice(head.length)];
|
|
366
|
+
} catch {
|
|
367
|
+
return done;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/config.ts
|
|
372
|
+
var resolveConfig = (c) => ({
|
|
373
|
+
clientName: c.clientName ?? "agent-web",
|
|
374
|
+
sessionId: c.sessionId ?? "default",
|
|
375
|
+
toolMode: c.toolMode ?? "auto",
|
|
376
|
+
toolSelectionStrategy: c.toolSelectionStrategy ?? "auto",
|
|
377
|
+
toolSearchThreshold: c.toolSearchThreshold ?? 40,
|
|
378
|
+
maxIterations: c.maxIterations ?? 8,
|
|
379
|
+
maxStepsPerTask: c.maxStepsPerTask ?? 4,
|
|
380
|
+
maxRevisions: c.maxRevisions ?? 2,
|
|
381
|
+
maxToolCalls: c.maxToolCalls ?? 0,
|
|
382
|
+
maxPlanSteps: c.maxPlanSteps ?? 8,
|
|
383
|
+
chatTimeoutMs: c.chatTimeoutMs ?? 12e4,
|
|
384
|
+
budgets: {
|
|
385
|
+
planner: c.limits?.perCall?.planner ?? c.budgets?.planner ?? 800,
|
|
386
|
+
executor: c.limits?.perCall?.executor ?? c.budgets?.executor ?? 1200,
|
|
387
|
+
replanner: c.limits?.perCall?.replanner ?? c.budgets?.replanner ?? 400,
|
|
388
|
+
synthesizer: c.limits?.perCall?.synthesizer ?? c.budgets?.synthesizer ?? 400,
|
|
389
|
+
compaction: c.limits?.perCall?.compaction ?? c.compaction?.summaryMaxTokens ?? 1024
|
|
390
|
+
},
|
|
391
|
+
limits: c.limits ?? {},
|
|
392
|
+
temperature: c.temperature,
|
|
393
|
+
replan: c.replan ?? true,
|
|
394
|
+
replanAfter: c.replanAfter ?? "failure",
|
|
395
|
+
synthesize: c.synthesize ?? true,
|
|
396
|
+
compressAfterChars: c.compressAfterChars ?? 12e3,
|
|
397
|
+
compaction: resolveCompaction(c.compaction),
|
|
398
|
+
logLevel: c.logLevel ?? "warn"
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// src/limits.ts
|
|
402
|
+
var over = (value, cap) => typeof cap === "number" && cap > 0 && (value ?? 0) >= cap;
|
|
403
|
+
var checkLimits = (usage, limits) => {
|
|
404
|
+
if (!limits) return void 0;
|
|
405
|
+
if (over(usage.totalTokens, limits.maxTotalTokens)) {
|
|
406
|
+
return { kind: "total", tokens: usage.totalTokens, cap: limits.maxTotalTokens };
|
|
407
|
+
}
|
|
408
|
+
if (over(usage.inputTokens, limits.maxInputTokens)) {
|
|
409
|
+
return { kind: "input", tokens: usage.inputTokens, cap: limits.maxInputTokens };
|
|
410
|
+
}
|
|
411
|
+
if (over(usage.outputTokens, limits.maxOutputTokens)) {
|
|
412
|
+
return { kind: "output", tokens: usage.outputTokens, cap: limits.maxOutputTokens };
|
|
413
|
+
}
|
|
414
|
+
if (over(usage.reasoningTokens, limits.maxReasoningTokens)) {
|
|
415
|
+
return {
|
|
416
|
+
kind: "reasoning",
|
|
417
|
+
tokens: usage.reasoningTokens ?? 0,
|
|
418
|
+
cap: limits.maxReasoningTokens
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return void 0;
|
|
422
|
+
};
|
|
423
|
+
var ToolBudgetError = class extends Error {
|
|
424
|
+
constructor(cap) {
|
|
425
|
+
super(
|
|
426
|
+
`Tool-call budget exhausted (${cap} per run). Do not call more tools; report what you have.`
|
|
427
|
+
);
|
|
428
|
+
this.name = "ToolBudgetError";
|
|
429
|
+
}
|
|
430
|
+
toJSON() {
|
|
431
|
+
return this.message;
|
|
432
|
+
}
|
|
433
|
+
};
|
|
246
434
|
|
|
247
435
|
// src/providers/types.ts
|
|
248
436
|
var isDirectModel = (m) => typeof m === "string" || typeof m === "object" && m !== null && "specificationVersion" in m;
|
|
@@ -445,54 +633,77 @@ var historyBlock = (history) => {
|
|
|
445
633
|
CONVERSATION SO FAR:
|
|
446
634
|
${lines.join("\n")}`;
|
|
447
635
|
};
|
|
448
|
-
var
|
|
449
|
-
Produce a brief "thought" and an ordered "steps" list (1\u20136 DISTINCT, self-contained steps) grounded in the current STATE and the available TOOLS.
|
|
450
|
-
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".
|
|
451
|
-
If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns.
|
|
452
|
-
Never repeat or pad steps.`;
|
|
453
|
-
var PLANNER_PROMPTED = `You are the PLANNER of a tool-using agent that changes a workspace step by step.
|
|
636
|
+
var section = (title, body) => body && body.trim() ? `
|
|
454
637
|
|
|
455
|
-
|
|
456
|
-
{
|
|
457
|
-
|
|
458
|
-
-
|
|
638
|
+
${title}:
|
|
639
|
+
${body.trim()}` : "";
|
|
640
|
+
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.
|
|
641
|
+
- 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.
|
|
459
642
|
- Plan realistic steps the available TOOLS can perform, grounded in the current STATE.
|
|
460
|
-
- If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
643
|
+
- If a CONVERSATION SO FAR section is present, use it to resolve references to earlier turns.
|
|
644
|
+
- Never repeat or pad steps.`;
|
|
645
|
+
var PLANNER_NATIVE = `You are the PLANNER of an autonomous tool-using agent that works step by step.
|
|
646
|
+
Produce a brief "thought" and an ordered "steps" list (1\u20136 DISTINCT, self-contained steps).
|
|
647
|
+
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".
|
|
648
|
+
${PLANNER_RULES}`;
|
|
649
|
+
var PLANNER_PROMPTED = `You are the PLANNER of an autonomous tool-using agent that works step by step.
|
|
650
|
+
|
|
651
|
+
Reply with a single JSON object, nothing else:
|
|
652
|
+
{ "reply": string, "plan": string[], "skills": string[] }
|
|
653
|
+
- For a real goal: "reply" is one short sentence; "plan" is 1\u20136 DISTINCT, self-contained steps.
|
|
654
|
+
- 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".
|
|
655
|
+
${PLANNER_RULES}`;
|
|
656
|
+
var PLANNER_SKILLS = `
|
|
657
|
+
If a SKILLS list is present, list the names of the skills that apply to this goal in "skills" (or leave it empty).`;
|
|
658
|
+
var PLANNER_SEARCH = `
|
|
659
|
+
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.`;
|
|
660
|
+
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.
|
|
661
|
+
- Build on the current STATE and on the results of earlier steps \u2014 do not repeat work that is already done.`;
|
|
662
|
+
var EXECUTOR_NATIVE = `You are the EXECUTOR of an autonomous tool-using agent. Carry out ONLY the current step by calling the provided tools.
|
|
663
|
+
${EXECUTOR_RULES}
|
|
664
|
+
- 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.
|
|
665
|
+
- 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].`;
|
|
666
|
+
var EXECUTOR_PROMPTED = `You are the EXECUTOR of an autonomous tool-using agent. Carry out ONLY the current step by emitting tool calls.
|
|
466
667
|
|
|
467
668
|
Reply with a single JSON object, nothing else:
|
|
468
669
|
{ "reply": string, "actions": [ { "tool": string, "args": object } ] }
|
|
469
670
|
- "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.
|
|
470
|
-
|
|
471
|
-
- "reply" is
|
|
671
|
+
${EXECUTOR_RULES}
|
|
672
|
+
- "reply" is a short factual summary (no JSON) of what you did and found, with the concrete data later steps need.
|
|
472
673
|
- After your actions run you will see their TOOL RESULTS and may continue the same step; finish with "actions": [] once it is done.
|
|
473
|
-
- If you CANNOT complete the step, set "actions": [] and put the token [BLOCKER] in "reply" with a short reason.`;
|
|
474
|
-
var
|
|
674
|
+
- If you truly CANNOT complete the step, set "actions": [] and put the token [BLOCKER] in "reply" with a short reason.`;
|
|
675
|
+
var EXECUTOR_SEARCH = `
|
|
676
|
+
- 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.`;
|
|
677
|
+
var EXECUTOR_SKILLS = `
|
|
678
|
+
- If a skill from the SKILLS list covers this step, call load_skill to read it first (read_skill_file for its bundled files).`;
|
|
679
|
+
var REPLANNER_RULES = `Judge from the current STATE and the progress vs the goal. Prefer "continue".
|
|
680
|
+
After a failure, prefer revising around it (another tool, another approach) over giving up; never add a step that asks the user something.`;
|
|
681
|
+
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.
|
|
475
682
|
"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.
|
|
476
|
-
|
|
477
|
-
var REPLANNER_PROMPTED = `You are the REPLANNER. After each executed step you decide whether to keep going, revise the remaining steps, or finish.
|
|
683
|
+
${REPLANNER_RULES}`;
|
|
684
|
+
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.
|
|
478
685
|
|
|
479
686
|
Reply with a single JSON object, nothing else:
|
|
480
687
|
{ "decision": "continue" | "revise" | "finish", "reason": string, "plan": string[] }
|
|
481
688
|
- "continue": remaining steps still fit \u2014 proceed (omit "plan").
|
|
482
689
|
- "revise": replace the REMAINING steps with a better list in "plan"; never repeat done work.
|
|
483
690
|
- "finish": the goal is already met \u2014 stop (omit "plan").
|
|
484
|
-
|
|
485
|
-
var SYNTHESIZER_SYSTEM = `You are the SYNTHESIZER.
|
|
691
|
+
${REPLANNER_RULES}`;
|
|
692
|
+
var SYNTHESIZER_SYSTEM = `You are the SYNTHESIZER. Answer the user's goal from what the agent did and found.
|
|
693
|
+
- Lead with the result: the concrete data, from the FINDINGS verbatim when accuracy matters (names, numbers, ids), then \u2014 briefly \u2014 what was changed.
|
|
694
|
+
- Mention any assumption the agent made. If something could not be done, say so plainly and why.
|
|
695
|
+
- Do not ask follow-up questions unless the goal genuinely cannot be completed without the user.
|
|
696
|
+
- Be concise and friendly. Plain text (light markdown is fine) \u2014 no JSON, no code.`;
|
|
486
697
|
var defaultPrompts = {
|
|
487
698
|
planner: (ctx) => ({
|
|
488
|
-
system: ctx.mode === "prompted" ? PLANNER_PROMPTED : PLANNER_NATIVE,
|
|
489
|
-
prompt: `GOAL: ${ctx.goal}${historyBlock(ctx.history)}${stateBlock(ctx.state)}
|
|
699
|
+
system: (ctx.mode === "prompted" ? PLANNER_PROMPTED : PLANNER_NATIVE) + (ctx.skills ? PLANNER_SKILLS : "") + (ctx.searchMode ? PLANNER_SEARCH : "") + section("SKILLS", ctx.skills) + `
|
|
490
700
|
|
|
491
701
|
TOOLS:
|
|
492
|
-
${ctx.toolCatalog}
|
|
702
|
+
${ctx.toolCatalog}`,
|
|
703
|
+
prompt: `GOAL: ${ctx.goal}${historyBlock(ctx.history)}${stateBlock(ctx.state)}`
|
|
493
704
|
}),
|
|
494
705
|
executor: (ctx) => ({
|
|
495
|
-
system: ctx.mode === "prompted" ? EXECUTOR_PROMPTED : EXECUTOR_NATIVE,
|
|
706
|
+
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),
|
|
496
707
|
prompt: [
|
|
497
708
|
`GOAL: ${ctx.goal}`,
|
|
498
709
|
stateBlock(ctx.state).trimStart(),
|
|
@@ -504,7 +715,7 @@ ${numbered(ctx.done, "")}` : "",
|
|
|
504
715
|
].filter(Boolean).join("\n\n")
|
|
505
716
|
}),
|
|
506
717
|
replanner: (ctx) => ({
|
|
507
|
-
system: ctx.mode === "prompted" ? REPLANNER_PROMPTED : REPLANNER_NATIVE,
|
|
718
|
+
system: (ctx.mode === "prompted" ? REPLANNER_PROMPTED : REPLANNER_NATIVE) + section("ACTIVE SKILL INSTRUCTIONS", ctx.activeSkills),
|
|
508
719
|
prompt: [
|
|
509
720
|
`GOAL: ${ctx.goal}`,
|
|
510
721
|
stateBlock(ctx.state).trimStart(),
|
|
@@ -516,13 +727,15 @@ ${numbered(ctx.remaining, "(none)")}`,
|
|
|
516
727
|
].filter(Boolean).join("\n\n")
|
|
517
728
|
}),
|
|
518
729
|
synthesizer: (ctx) => ({
|
|
519
|
-
system: SYNTHESIZER_SYSTEM,
|
|
730
|
+
system: SYNTHESIZER_SYSTEM + section("ACTIVE SKILL INSTRUCTIONS", ctx.activeSkills),
|
|
520
731
|
prompt: [
|
|
521
732
|
`GOAL: ${ctx.goal}`,
|
|
522
733
|
stateBlock(ctx.state).trimStart(),
|
|
523
734
|
`What was done:
|
|
524
735
|
${numbered(ctx.done, "(no changes)")}`,
|
|
525
|
-
|
|
736
|
+
ctx.findings?.length ? `FINDINGS (tool results):
|
|
737
|
+
${ctx.findings.join("\n")}` : "",
|
|
738
|
+
"Write the final answer for the user."
|
|
526
739
|
].filter(Boolean).join("\n\n")
|
|
527
740
|
})
|
|
528
741
|
};
|
|
@@ -530,6 +743,294 @@ var withSystem = (base, extra) => extra && extra.trim() ? `${extra.trim()}
|
|
|
530
743
|
|
|
531
744
|
${base}` : base;
|
|
532
745
|
|
|
746
|
+
// src/skills.ts
|
|
747
|
+
var import_ai3 = require("ai");
|
|
748
|
+
var SKILL_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
749
|
+
var SKILL_TOOL_NAMES = ["load_skill", "read_skill_file"];
|
|
750
|
+
var defineSkill = (skill) => {
|
|
751
|
+
if (!skill || typeof skill !== "object") throw new Error("skill must be an object");
|
|
752
|
+
if (typeof skill.name !== "string" || !SKILL_NAME_RE.test(skill.name)) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`skill name ${JSON.stringify(skill.name)} is invalid: use 1-64 lowercase letters, digits and dashes`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
if (typeof skill.description !== "string" || !skill.description.trim()) {
|
|
758
|
+
throw new Error(`skill "${skill.name}" needs a description`);
|
|
759
|
+
}
|
|
760
|
+
if (typeof skill.content !== "string") {
|
|
761
|
+
throw new Error(`skill "${skill.name}" needs a content string`);
|
|
762
|
+
}
|
|
763
|
+
for (const f of skill.files ?? []) {
|
|
764
|
+
if (typeof f?.path !== "string" || !f.path || f.path.startsWith("/") || f.path.includes("..")) {
|
|
765
|
+
throw new Error(`skill "${skill.name}": invalid file path ${JSON.stringify(f?.path)}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return skill;
|
|
769
|
+
};
|
|
770
|
+
var unquote = (v) => {
|
|
771
|
+
const t = v.trim();
|
|
772
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
773
|
+
try {
|
|
774
|
+
return JSON.parse(t);
|
|
775
|
+
} catch {
|
|
776
|
+
return t.slice(1, -1);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (t.length >= 2 && t.startsWith("'") && t.endsWith("'")) {
|
|
780
|
+
return t.slice(1, -1).replace(/''/g, "'");
|
|
781
|
+
}
|
|
782
|
+
return t;
|
|
783
|
+
};
|
|
784
|
+
var parseFrontmatter = (block) => {
|
|
785
|
+
const out = {};
|
|
786
|
+
const lines = block.split(/\r?\n/);
|
|
787
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
788
|
+
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(lines[i]);
|
|
789
|
+
if (!m) continue;
|
|
790
|
+
const [, key, rest] = m;
|
|
791
|
+
if (rest === ">" || rest === "|" || rest === ">-" || rest === "|-") {
|
|
792
|
+
const body = [];
|
|
793
|
+
while (i + 1 < lines.length && (/^\s+\S/.test(lines[i + 1]) || lines[i + 1] === "")) {
|
|
794
|
+
body.push(lines[i + 1].trim());
|
|
795
|
+
i += 1;
|
|
796
|
+
}
|
|
797
|
+
out[key] = rest.startsWith(">") ? body.filter(Boolean).join(" ") : body.join("\n").trim();
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
out[key] = unquote(rest);
|
|
801
|
+
}
|
|
802
|
+
return out;
|
|
803
|
+
};
|
|
804
|
+
var parseSkillMarkdown = (markdown, files) => {
|
|
805
|
+
const text = (markdown ?? "").replace(/^/, "");
|
|
806
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text);
|
|
807
|
+
if (!m) throw new Error("SKILL.md must start with a --- frontmatter block (name, description)");
|
|
808
|
+
const meta = parseFrontmatter(m[1]);
|
|
809
|
+
return defineSkill({
|
|
810
|
+
name: meta.name ?? "",
|
|
811
|
+
description: meta.description ?? "",
|
|
812
|
+
content: m[2].trim(),
|
|
813
|
+
...files?.length ? { files } : {}
|
|
814
|
+
});
|
|
815
|
+
};
|
|
816
|
+
var loadSkillFromUrl = async (url, opts = {}) => {
|
|
817
|
+
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
818
|
+
const res = await fetchFn(url);
|
|
819
|
+
if (!res.ok) throw new Error(`could not fetch ${url}: HTTP ${res.status}`);
|
|
820
|
+
const markdown = await res.text();
|
|
821
|
+
const files = [];
|
|
822
|
+
for (const path of opts.files ?? []) {
|
|
823
|
+
const r = await fetchFn(new URL(path, url).toString());
|
|
824
|
+
if (!r.ok) throw new Error(`could not fetch skill file ${path}: HTTP ${r.status}`);
|
|
825
|
+
files.push({ path, content: await r.text() });
|
|
826
|
+
}
|
|
827
|
+
return parseSkillMarkdown(markdown, files);
|
|
828
|
+
};
|
|
829
|
+
var renderSkillIndex = (skills) => skills.map((s) => `- ${s.name}: ${s.description.replace(/\s+/g, " ").trim()}`).join("\n");
|
|
830
|
+
var ACTIVE_SKILLS_BUDGET = 24e3;
|
|
831
|
+
var renderActiveSkills = (skills) => {
|
|
832
|
+
let budget = ACTIVE_SKILLS_BUDGET;
|
|
833
|
+
const parts = [];
|
|
834
|
+
for (const s of skills) {
|
|
835
|
+
if (budget <= 0) break;
|
|
836
|
+
const files = s.files?.length ? `
|
|
837
|
+
(bundled files, read with read_skill_file: ${s.files.map((f) => f.path).join(", ")})` : "";
|
|
838
|
+
const block = clip(`### Skill: ${s.name}
|
|
839
|
+
${s.content}${files}`, budget);
|
|
840
|
+
budget -= block.length;
|
|
841
|
+
parts.push(block);
|
|
842
|
+
}
|
|
843
|
+
return parts.join("\n\n");
|
|
844
|
+
};
|
|
845
|
+
var createSkillTools = (skills, onLoad) => {
|
|
846
|
+
const byName = new Map(skills.map((s) => [s.name, s]));
|
|
847
|
+
const known = skills.map((s) => s.name).join(", ");
|
|
848
|
+
const loadSkill = (0, import_ai3.tool)({
|
|
849
|
+
description: "Load the full instructions of a skill from the SKILLS list before doing work it covers.",
|
|
850
|
+
inputSchema: (0, import_ai3.jsonSchema)({
|
|
851
|
+
type: "object",
|
|
852
|
+
properties: { name: { type: "string", description: "The skill name" } },
|
|
853
|
+
required: ["name"],
|
|
854
|
+
additionalProperties: false
|
|
855
|
+
}),
|
|
856
|
+
execute: async ({ name }) => {
|
|
857
|
+
const skill = byName.get(name);
|
|
858
|
+
if (!skill) throw new Error(`unknown skill "${name}"; available: ${known}`);
|
|
859
|
+
onLoad?.(skill);
|
|
860
|
+
return {
|
|
861
|
+
name: skill.name,
|
|
862
|
+
content: skill.content,
|
|
863
|
+
files: (skill.files ?? []).map((f) => f.path)
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
const readFile = (0, import_ai3.tool)({
|
|
868
|
+
description: "Read one of a skill's bundled files (paths are listed by load_skill).",
|
|
869
|
+
inputSchema: (0, import_ai3.jsonSchema)({
|
|
870
|
+
type: "object",
|
|
871
|
+
properties: {
|
|
872
|
+
name: { type: "string", description: "The skill name" },
|
|
873
|
+
path: { type: "string", description: "The file path, relative to the skill" }
|
|
874
|
+
},
|
|
875
|
+
required: ["name", "path"],
|
|
876
|
+
additionalProperties: false
|
|
877
|
+
}),
|
|
878
|
+
execute: async ({ name, path }) => {
|
|
879
|
+
const skill = byName.get(name);
|
|
880
|
+
if (!skill) throw new Error(`unknown skill "${name}"; available: ${known}`);
|
|
881
|
+
const file = skill.files?.find((f) => f.path === path);
|
|
882
|
+
if (!file) {
|
|
883
|
+
const listed = (skill.files ?? []).map((f) => f.path).join(", ") || "none";
|
|
884
|
+
throw new Error(`skill "${name}" has no file "${path}"; files: ${listed}`);
|
|
885
|
+
}
|
|
886
|
+
return file.content;
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
for (const t of [loadSkill, readFile]) {
|
|
890
|
+
;
|
|
891
|
+
t.readOnly = true;
|
|
892
|
+
t.promptHint = t === loadSkill ? "{ name: string }" : "{ name: string, path: string }";
|
|
893
|
+
}
|
|
894
|
+
return { load_skill: loadSkill, read_skill_file: readFile };
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// src/tools/approval.ts
|
|
898
|
+
var TOOL_APPROVAL_MODES = [
|
|
899
|
+
"autopilot",
|
|
900
|
+
"ask-writes",
|
|
901
|
+
"ask-all",
|
|
902
|
+
"read-only"
|
|
903
|
+
];
|
|
904
|
+
var ToolDeniedError = class extends Error {
|
|
905
|
+
constructor(reason) {
|
|
906
|
+
super(
|
|
907
|
+
`Tool call denied by the user${reason ? `: ${reason}` : ""}. Do not retry it; continue without it, or report what is blocked.`
|
|
908
|
+
);
|
|
909
|
+
this.name = "ToolDeniedError";
|
|
910
|
+
}
|
|
911
|
+
toJSON() {
|
|
912
|
+
return this.message;
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
var markReadOnly = (tool6, readOnly = true) => {
|
|
916
|
+
;
|
|
917
|
+
tool6.readOnly = readOnly;
|
|
918
|
+
return tool6;
|
|
919
|
+
};
|
|
920
|
+
var isReadOnlyTool = (tool6) => tool6?.readOnly === true;
|
|
921
|
+
var globToRegExp = (glob) => new RegExp(
|
|
922
|
+
`^${glob.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`
|
|
923
|
+
);
|
|
924
|
+
var matchRule = (rules, toolName) => {
|
|
925
|
+
if (!rules) return void 0;
|
|
926
|
+
if (Object.hasOwn(rules, toolName)) return rules[toolName];
|
|
927
|
+
let best;
|
|
928
|
+
for (const [pattern, perm] of Object.entries(rules)) {
|
|
929
|
+
if (!pattern.includes("*")) continue;
|
|
930
|
+
if (!globToRegExp(pattern).test(toolName)) continue;
|
|
931
|
+
const len = pattern.replace(/\*/g, "").length;
|
|
932
|
+
if (!best || len > best.len) best = { len, perm };
|
|
933
|
+
}
|
|
934
|
+
return best?.perm;
|
|
935
|
+
};
|
|
936
|
+
var decideToolPermission = (opts) => {
|
|
937
|
+
if (opts.remembered?.has(opts.toolName)) return "allow";
|
|
938
|
+
const rule = matchRule(opts.rules, opts.toolName);
|
|
939
|
+
if (rule) return rule;
|
|
940
|
+
switch (opts.mode) {
|
|
941
|
+
case "autopilot":
|
|
942
|
+
return "allow";
|
|
943
|
+
case "read-only":
|
|
944
|
+
return opts.readOnly ? "allow" : "deny";
|
|
945
|
+
case "ask-writes":
|
|
946
|
+
return opts.readOnly ? "allow" : "ask";
|
|
947
|
+
case "ask-all":
|
|
948
|
+
return "ask";
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
var normalizeDecision = (d) => typeof d === "boolean" ? { approved: d } : { approved: Boolean(d?.approved), reason: d?.reason, remember: d?.remember };
|
|
952
|
+
var createApprovalState = (config) => ({
|
|
953
|
+
mode: config?.mode ?? "autopilot",
|
|
954
|
+
config: config ?? {},
|
|
955
|
+
remembered: /* @__PURE__ */ new Set()
|
|
956
|
+
});
|
|
957
|
+
var seq = 0;
|
|
958
|
+
var gateToolCall = async (state, call, events, signal) => {
|
|
959
|
+
const permission = decideToolPermission({
|
|
960
|
+
toolName: call.toolName,
|
|
961
|
+
readOnly: call.readOnly,
|
|
962
|
+
mode: state.mode,
|
|
963
|
+
rules: state.config.rules,
|
|
964
|
+
remembered: state.remembered
|
|
965
|
+
});
|
|
966
|
+
if (permission === "allow") return;
|
|
967
|
+
const id = `approval-${Date.now().toString(36)}-${seq += 1}`;
|
|
968
|
+
if (permission === "deny") {
|
|
969
|
+
const reason = state.mode === "read-only" && !matchRule(state.config.rules, call.toolName) ? "the agent is in read-only mode" : "blocked by policy";
|
|
970
|
+
events.resolved({ id, name: call.toolName, approved: false, reason, automatic: true });
|
|
971
|
+
throw new ToolDeniedError(reason);
|
|
972
|
+
}
|
|
973
|
+
const onRequest = state.config.onRequest;
|
|
974
|
+
if (!onRequest) {
|
|
975
|
+
const reason = "no approval handler configured";
|
|
976
|
+
events.resolved({ id, name: call.toolName, approved: false, reason, automatic: true });
|
|
977
|
+
throw new ToolDeniedError(reason);
|
|
978
|
+
}
|
|
979
|
+
const req = { id, ...call };
|
|
980
|
+
events.requested(req);
|
|
981
|
+
let decision;
|
|
982
|
+
try {
|
|
983
|
+
decision = normalizeDecision(
|
|
984
|
+
await withDeadline(Promise.resolve(onRequest(req)), state.config.timeoutMs, signal)
|
|
985
|
+
);
|
|
986
|
+
} catch (err) {
|
|
987
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
988
|
+
events.resolved({ id, name: call.toolName, approved: false, reason, automatic: true });
|
|
989
|
+
throw new ToolDeniedError(reason);
|
|
990
|
+
}
|
|
991
|
+
if (decision.approved && decision.remember) state.remembered.add(call.toolName);
|
|
992
|
+
events.resolved({
|
|
993
|
+
id,
|
|
994
|
+
name: call.toolName,
|
|
995
|
+
approved: decision.approved,
|
|
996
|
+
reason: decision.reason,
|
|
997
|
+
automatic: false
|
|
998
|
+
});
|
|
999
|
+
if (!decision.approved) throw new ToolDeniedError(decision.reason);
|
|
1000
|
+
};
|
|
1001
|
+
var withDeadline = (p, timeoutMs, signal) => {
|
|
1002
|
+
if ((!timeoutMs || timeoutMs <= 0) && !signal) return p;
|
|
1003
|
+
return new Promise((resolve, reject) => {
|
|
1004
|
+
let timer;
|
|
1005
|
+
const done = () => {
|
|
1006
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1007
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1008
|
+
};
|
|
1009
|
+
const onAbort = () => {
|
|
1010
|
+
done();
|
|
1011
|
+
reject(new Error("the run was stopped while waiting for approval"));
|
|
1012
|
+
};
|
|
1013
|
+
if (signal?.aborted) return onAbort();
|
|
1014
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1015
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
1016
|
+
timer = setTimeout(() => {
|
|
1017
|
+
done();
|
|
1018
|
+
reject(new Error(`no approval decision within ${timeoutMs} ms`));
|
|
1019
|
+
}, timeoutMs);
|
|
1020
|
+
}
|
|
1021
|
+
p.then(
|
|
1022
|
+
(v) => {
|
|
1023
|
+
done();
|
|
1024
|
+
resolve(v);
|
|
1025
|
+
},
|
|
1026
|
+
(e) => {
|
|
1027
|
+
done();
|
|
1028
|
+
reject(e);
|
|
1029
|
+
}
|
|
1030
|
+
);
|
|
1031
|
+
});
|
|
1032
|
+
};
|
|
1033
|
+
|
|
533
1034
|
// src/tools/mode.ts
|
|
534
1035
|
var LOCAL_RE = /web-?llm|mlc|browser-ai|transformers|nano|built-?in/i;
|
|
535
1036
|
var selectToolMode = (model, mode = "auto") => {
|
|
@@ -540,15 +1041,15 @@ var selectToolMode = (model, mode = "auto") => {
|
|
|
540
1041
|
};
|
|
541
1042
|
|
|
542
1043
|
// src/tools/types.ts
|
|
543
|
-
var promptHintOf = (
|
|
1044
|
+
var promptHintOf = (tool6) => typeof tool6.promptHint === "string" ? tool6.promptHint : void 0;
|
|
544
1045
|
|
|
545
1046
|
// src/tools/prompted.ts
|
|
546
|
-
var renderCatalog = (tools) => {
|
|
1047
|
+
var renderCatalog = (tools, hintOf) => {
|
|
547
1048
|
const names = Object.keys(tools);
|
|
548
1049
|
if (names.length === 0) return "(no tools)";
|
|
549
1050
|
return names.map((name) => {
|
|
550
1051
|
const t = tools[name];
|
|
551
|
-
const hint = promptHintOf(t) ?? "";
|
|
1052
|
+
const hint = promptHintOf(t) ?? hintOf?.(name) ?? "";
|
|
552
1053
|
const desc = typeof t.description === "string" ? t.description : "";
|
|
553
1054
|
return `- ${name}(${hint}): ${desc}`;
|
|
554
1055
|
}).join("\n");
|
|
@@ -598,20 +1099,269 @@ var dispatch = async (action, tools, ctx = {}) => {
|
|
|
598
1099
|
}
|
|
599
1100
|
};
|
|
600
1101
|
|
|
1102
|
+
// src/tools/search.ts
|
|
1103
|
+
var import_ai4 = require("ai");
|
|
1104
|
+
var FIND_TOOLS_NAME = "find_tools";
|
|
1105
|
+
var toolCatalogOf = (tools) => Object.entries(tools).map(([name, t]) => {
|
|
1106
|
+
const sep = name.indexOf("__");
|
|
1107
|
+
return {
|
|
1108
|
+
name,
|
|
1109
|
+
description: typeof t.description === "string" ? t.description : "",
|
|
1110
|
+
...sep > 0 ? { server: name.slice(0, sep) } : {},
|
|
1111
|
+
readOnly: isReadOnlyTool(t)
|
|
1112
|
+
};
|
|
1113
|
+
});
|
|
1114
|
+
var tokenize = (s) => (s ?? "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
1115
|
+
var hits = (term, words) => words.some(
|
|
1116
|
+
(w) => w === term || term.length >= 3 && (w.startsWith(term) || term.startsWith(w) && w.length >= 3)
|
|
1117
|
+
);
|
|
1118
|
+
var searchTools = (catalog, query, opts = {}) => {
|
|
1119
|
+
const terms = [...new Set(tokenize(query))];
|
|
1120
|
+
const limit = Math.min(Math.max(1, opts.limit ?? 8), 20);
|
|
1121
|
+
const server = opts.server?.toLowerCase();
|
|
1122
|
+
const scored = [];
|
|
1123
|
+
catalog.forEach((entry, index) => {
|
|
1124
|
+
if (opts.exclude?.has(entry.name)) return;
|
|
1125
|
+
if (server && entry.server?.toLowerCase() !== server) return;
|
|
1126
|
+
const nameWords = tokenize(
|
|
1127
|
+
entry.server ? entry.name.slice(entry.server.length + 2) : entry.name
|
|
1128
|
+
);
|
|
1129
|
+
const serverWords = tokenize(entry.server ?? "");
|
|
1130
|
+
const descWords = tokenize(entry.description);
|
|
1131
|
+
let score = 0;
|
|
1132
|
+
for (const term of terms) {
|
|
1133
|
+
if (hits(term, nameWords)) score += 3;
|
|
1134
|
+
if (hits(term, serverWords)) score += 2;
|
|
1135
|
+
if (hits(term, descWords)) score += 1;
|
|
1136
|
+
}
|
|
1137
|
+
if (terms.length === 0 && server) score = 1;
|
|
1138
|
+
if (score > 0) scored.push({ entry, score, index });
|
|
1139
|
+
});
|
|
1140
|
+
scored.sort((a, b) => b.score - a.score || a.index - b.index);
|
|
1141
|
+
return scored.slice(0, limit).map((s) => s.entry);
|
|
1142
|
+
};
|
|
1143
|
+
var renderSearchCatalog = (catalog, budget = 12e3) => {
|
|
1144
|
+
if (catalog.length === 0) return "(no tools)";
|
|
1145
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1146
|
+
for (const e of catalog) {
|
|
1147
|
+
const key = e.server ?? "(host)";
|
|
1148
|
+
groups.set(key, [...groups.get(key) ?? [], e]);
|
|
1149
|
+
}
|
|
1150
|
+
const lines = [];
|
|
1151
|
+
let used = 0;
|
|
1152
|
+
let shown = 0;
|
|
1153
|
+
for (const [server, entries] of groups) {
|
|
1154
|
+
const head = `[${server}] ${entries.length} tool(s)`;
|
|
1155
|
+
if (used + head.length > budget) break;
|
|
1156
|
+
lines.push(head);
|
|
1157
|
+
used += head.length + 1;
|
|
1158
|
+
for (const e of entries) {
|
|
1159
|
+
const desc = e.description.replace(/\s+/g, " ").trim().slice(0, 60);
|
|
1160
|
+
const line = `- ${e.name}${desc ? `: ${desc}` : ""}`;
|
|
1161
|
+
if (used + line.length > budget) break;
|
|
1162
|
+
lines.push(line);
|
|
1163
|
+
used += line.length + 1;
|
|
1164
|
+
shown += 1;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
const rest = catalog.length - shown;
|
|
1168
|
+
if (rest > 0)
|
|
1169
|
+
lines.push(`\u2026 ${rest} more tool(s) \u2014 the executor can find them with ${FIND_TOOLS_NAME}`);
|
|
1170
|
+
return lines.join("\n");
|
|
1171
|
+
};
|
|
1172
|
+
var createFindToolsTool = (catalog, onFound, hintOf) => {
|
|
1173
|
+
const t = (0, import_ai4.tool)({
|
|
1174
|
+
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.",
|
|
1175
|
+
inputSchema: (0, import_ai4.jsonSchema)({
|
|
1176
|
+
type: "object",
|
|
1177
|
+
properties: {
|
|
1178
|
+
query: { type: "string", description: "Keywords: what the tool should do" },
|
|
1179
|
+
server: { type: "string", description: "Optional: only tools of this MCP server" },
|
|
1180
|
+
limit: { type: "number", description: "Max results (default 8, max 20)" }
|
|
1181
|
+
},
|
|
1182
|
+
required: ["query"],
|
|
1183
|
+
additionalProperties: false
|
|
1184
|
+
}),
|
|
1185
|
+
execute: async ({ query, server, limit }) => {
|
|
1186
|
+
const found = searchTools(catalog(), query, { server, limit });
|
|
1187
|
+
onFound(
|
|
1188
|
+
query,
|
|
1189
|
+
found.map((f) => f.name)
|
|
1190
|
+
);
|
|
1191
|
+
return {
|
|
1192
|
+
tools: found.map((f) => ({
|
|
1193
|
+
name: f.name,
|
|
1194
|
+
description: f.description,
|
|
1195
|
+
...f.server ? { server: f.server } : {},
|
|
1196
|
+
readOnly: Boolean(f.readOnly),
|
|
1197
|
+
...hintOf?.(f.name) ? { args: hintOf(f.name) } : {}
|
|
1198
|
+
})),
|
|
1199
|
+
note: found.length ? "These tools are now callable." : "No tool matched; try other keywords or a server name."
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
t.readOnly = true;
|
|
1204
|
+
t.promptHint = "{ query: string, server?: string, limit?: number }";
|
|
1205
|
+
return t;
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
// src/tools/wrap.ts
|
|
1209
|
+
var import_ai5 = require("ai");
|
|
1210
|
+
var toolRunContextOf = (options) => options?.agentRun;
|
|
1211
|
+
var truncated = (s, max) => s.length > max ? `${s.slice(0, max)}\u2026 [truncated ${s.length - max} chars]` : s;
|
|
1212
|
+
var toText = (v) => {
|
|
1213
|
+
if (typeof v === "string") return v;
|
|
1214
|
+
try {
|
|
1215
|
+
return JSON.stringify(v) ?? String(v);
|
|
1216
|
+
} catch {
|
|
1217
|
+
return String(v);
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
var limitModelOutput = (original, maxChars) => (async (opts) => {
|
|
1221
|
+
const base = original ? await original(opts) : typeof opts.output === "string" ? { type: "text", value: opts.output } : { type: "json", value: opts.output ?? null };
|
|
1222
|
+
if (base.type === "text" || base.type === "error-text") {
|
|
1223
|
+
return { ...base, value: truncated(base.value, maxChars) };
|
|
1224
|
+
}
|
|
1225
|
+
if (base.type === "json" || base.type === "error-json") {
|
|
1226
|
+
const text = toText(base.value);
|
|
1227
|
+
if (text.length <= maxChars) return base;
|
|
1228
|
+
return {
|
|
1229
|
+
type: base.type === "json" ? "text" : "error-text",
|
|
1230
|
+
value: truncated(text, maxChars)
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
return base;
|
|
1234
|
+
});
|
|
1235
|
+
var wrapToolsForRun = (tools, o) => {
|
|
1236
|
+
const out = {};
|
|
1237
|
+
const gate = (toolName, input, readOnly, signal) => gateToolCall(
|
|
1238
|
+
o.approval,
|
|
1239
|
+
{ toolName, input, readOnly, step: o.step() },
|
|
1240
|
+
{
|
|
1241
|
+
requested: (req) => o.emit({
|
|
1242
|
+
type: "tool.approval-requested",
|
|
1243
|
+
id: req.id,
|
|
1244
|
+
name: req.toolName,
|
|
1245
|
+
input: req.input,
|
|
1246
|
+
readOnly: req.readOnly,
|
|
1247
|
+
step: req.step
|
|
1248
|
+
}),
|
|
1249
|
+
resolved: (r) => o.emit({ type: "tool.approval-resolved", ...r })
|
|
1250
|
+
},
|
|
1251
|
+
signal ?? o.signal
|
|
1252
|
+
);
|
|
1253
|
+
for (const [name, t] of Object.entries(tools)) {
|
|
1254
|
+
const execute = t.execute;
|
|
1255
|
+
const builtin = o.builtins.has(name);
|
|
1256
|
+
const readOnly = builtin || isReadOnlyTool(t);
|
|
1257
|
+
const wrapped = { ...t };
|
|
1258
|
+
if (o.maxToolOutputChars > 0) {
|
|
1259
|
+
wrapped.toModelOutput = limitModelOutput(t.toModelOutput, o.maxToolOutputChars);
|
|
1260
|
+
}
|
|
1261
|
+
if (typeof execute === "function") {
|
|
1262
|
+
wrapped.execute = (input, options) => {
|
|
1263
|
+
const run = () => {
|
|
1264
|
+
if (!builtin) o.countCall();
|
|
1265
|
+
const agentRun = {
|
|
1266
|
+
emit: o.emit,
|
|
1267
|
+
step: o.step(),
|
|
1268
|
+
signal: o.signal,
|
|
1269
|
+
addUsage: o.addUsage,
|
|
1270
|
+
approve: (toolName, toolInput, ro) => gate(toolName, toolInput, ro)
|
|
1271
|
+
};
|
|
1272
|
+
return execute.call(t, input, { ...options, agentRun });
|
|
1273
|
+
};
|
|
1274
|
+
if (builtin) return run();
|
|
1275
|
+
const permission = decideToolPermission({
|
|
1276
|
+
toolName: name,
|
|
1277
|
+
readOnly,
|
|
1278
|
+
mode: o.approval.mode,
|
|
1279
|
+
rules: o.approval.config.rules,
|
|
1280
|
+
remembered: o.approval.remembered
|
|
1281
|
+
});
|
|
1282
|
+
if (permission === "allow") return run();
|
|
1283
|
+
return gate(
|
|
1284
|
+
name,
|
|
1285
|
+
input,
|
|
1286
|
+
readOnly,
|
|
1287
|
+
options?.abortSignal
|
|
1288
|
+
).then(run);
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
out[name] = wrapped;
|
|
1292
|
+
}
|
|
1293
|
+
return out;
|
|
1294
|
+
};
|
|
1295
|
+
var createCallCounter = (cap) => {
|
|
1296
|
+
let calls = 0;
|
|
1297
|
+
return {
|
|
1298
|
+
count: () => {
|
|
1299
|
+
if (cap && cap > 0 && calls >= cap) throw new ToolBudgetError(cap);
|
|
1300
|
+
calls += 1;
|
|
1301
|
+
},
|
|
1302
|
+
get calls() {
|
|
1303
|
+
return calls;
|
|
1304
|
+
},
|
|
1305
|
+
get exhausted() {
|
|
1306
|
+
return Boolean(cap && cap > 0 && calls >= cap);
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
};
|
|
1310
|
+
var typeHint = (s, depth) => {
|
|
1311
|
+
if (!s) return "any";
|
|
1312
|
+
if (Array.isArray(s.enum) && s.enum.length > 0 && s.enum.length <= 8) {
|
|
1313
|
+
return s.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
1314
|
+
}
|
|
1315
|
+
const type = Array.isArray(s.type) ? s.type.find((x) => x !== "null") : s.type;
|
|
1316
|
+
if (type === "array") return `${typeHint(s.items, depth)}[]`;
|
|
1317
|
+
if (type === "object" || s.properties) {
|
|
1318
|
+
if (depth > 1 || !s.properties) return "object";
|
|
1319
|
+
return objectHint(s, depth + 1);
|
|
1320
|
+
}
|
|
1321
|
+
return type ?? "any";
|
|
1322
|
+
};
|
|
1323
|
+
var objectHint = (s, depth = 0) => {
|
|
1324
|
+
const required = new Set(s.required ?? []);
|
|
1325
|
+
const props = Object.entries(s.properties ?? {});
|
|
1326
|
+
if (props.length === 0) return "{}";
|
|
1327
|
+
return `{ ${props.slice(0, 12).map(([k, v]) => `${k}${required.has(k) ? "" : "?"}: ${typeHint(v, depth)}`).join(", ")}${props.length > 12 ? ", \u2026" : ""} }`;
|
|
1328
|
+
};
|
|
1329
|
+
var schemaHintOf = async (t) => {
|
|
1330
|
+
const own = promptHintOf(t);
|
|
1331
|
+
if (own) return own;
|
|
1332
|
+
try {
|
|
1333
|
+
const schema = await (0, import_ai5.asSchema)(t.inputSchema).jsonSchema;
|
|
1334
|
+
return schema && typeof schema === "object" ? objectHint(schema) : void 0;
|
|
1335
|
+
} catch {
|
|
1336
|
+
return void 0;
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
|
|
601
1340
|
// src/llm/tool-loop.ts
|
|
602
|
-
var
|
|
1341
|
+
var import_ai6 = require("ai");
|
|
603
1342
|
|
|
604
1343
|
// src/agent/loop-types.ts
|
|
605
1344
|
var BLOCKER = "[BLOCKER]";
|
|
606
|
-
var emptyUsage = () => ({
|
|
1345
|
+
var emptyUsage = () => ({
|
|
1346
|
+
inputTokens: 0,
|
|
1347
|
+
outputTokens: 0,
|
|
1348
|
+
totalTokens: 0,
|
|
1349
|
+
reasoningTokens: 0,
|
|
1350
|
+
cachedInputTokens: 0,
|
|
1351
|
+
cacheWriteTokens: 0
|
|
1352
|
+
});
|
|
607
1353
|
var addUsage = (a, b) => ({
|
|
608
1354
|
inputTokens: a.inputTokens + b.inputTokens,
|
|
609
1355
|
outputTokens: a.outputTokens + b.outputTokens,
|
|
610
|
-
totalTokens: a.totalTokens + b.totalTokens
|
|
1356
|
+
totalTokens: a.totalTokens + b.totalTokens,
|
|
1357
|
+
reasoningTokens: (a.reasoningTokens ?? 0) + (b.reasoningTokens ?? 0),
|
|
1358
|
+
cachedInputTokens: (a.cachedInputTokens ?? 0) + (b.cachedInputTokens ?? 0),
|
|
1359
|
+
cacheWriteTokens: (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0)
|
|
611
1360
|
});
|
|
612
1361
|
|
|
613
1362
|
// src/parse.ts
|
|
614
|
-
var
|
|
1363
|
+
var stripThinking = (text) => text.replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/^[\s\S]*?<\/think>/i, "");
|
|
1364
|
+
var stripFences = (text) => stripThinking(text || "").replace(/```(?:json)?/gi, "").trim();
|
|
615
1365
|
var extractBalanced = (text, open) => {
|
|
616
1366
|
const close = open === "{" ? "}" : "]";
|
|
617
1367
|
const start = text.indexOf(open);
|
|
@@ -701,15 +1451,16 @@ var parsePlannerResponse = (raw) => {
|
|
|
701
1451
|
const obj = asObject(text);
|
|
702
1452
|
if (obj) {
|
|
703
1453
|
const reply = typeof obj.reply === "string" ? obj.reply.trim() : "";
|
|
704
|
-
return { reply, plan: normalizeSteps(obj.plan) };
|
|
1454
|
+
return { reply, plan: normalizeSteps(obj.plan), skills: normalizeSteps(obj.skills) };
|
|
705
1455
|
}
|
|
706
1456
|
if (/"plan"\s*:/.test(text)) {
|
|
707
1457
|
return {
|
|
708
1458
|
reply: salvageString(text, "reply"),
|
|
709
|
-
plan: normalizeSteps(salvageStringArray(text, "plan"))
|
|
1459
|
+
plan: normalizeSteps(salvageStringArray(text, "plan")),
|
|
1460
|
+
skills: normalizeSteps(salvageStringArray(text, "skills"))
|
|
710
1461
|
};
|
|
711
1462
|
}
|
|
712
|
-
return { reply: looksLikeJson(text) ? "" : text.trim(), plan: [] };
|
|
1463
|
+
return { reply: looksLikeJson(text) ? "" : text.trim(), plan: [], skills: [] };
|
|
713
1464
|
};
|
|
714
1465
|
var toActions = (value) => {
|
|
715
1466
|
if (!Array.isArray(value)) return [];
|
|
@@ -752,20 +1503,99 @@ var parsePlainText = (raw) => {
|
|
|
752
1503
|
return looksLikeJson(text) ? "" : text.trim();
|
|
753
1504
|
};
|
|
754
1505
|
|
|
1506
|
+
// src/llm/context-editing.ts
|
|
1507
|
+
var STUB = (name) => `[${name} result cleared to save context \u2014 call the tool again if you still need it]`;
|
|
1508
|
+
var estimate = (messages) => {
|
|
1509
|
+
try {
|
|
1510
|
+
return estimateTokens(JSON.stringify(messages));
|
|
1511
|
+
} catch {
|
|
1512
|
+
return 0;
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
var positionsOf = (messages) => messages.flatMap(
|
|
1516
|
+
(m, i) => m.role === "tool" && Array.isArray(m.content) ? m.content.flatMap(
|
|
1517
|
+
(p, j) => p.type === "tool-result" ? [`${i}:${j}`] : []
|
|
1518
|
+
) : []
|
|
1519
|
+
);
|
|
1520
|
+
var createToolResultClearer = (opts, onCleared) => {
|
|
1521
|
+
const keep = Math.max(0, opts.keep ?? 3);
|
|
1522
|
+
const cleared = /* @__PURE__ */ new Set();
|
|
1523
|
+
const apply = (messages) => cleared.size === 0 ? messages : messages.map(
|
|
1524
|
+
(m, i) => m.role === "tool" && Array.isArray(m.content) ? {
|
|
1525
|
+
...m,
|
|
1526
|
+
content: m.content.map(
|
|
1527
|
+
(p, j) => p.type === "tool-result" && cleared.has(`${i}:${j}`) ? { ...p, output: { type: "text", value: STUB(p.toolName) } } : p
|
|
1528
|
+
)
|
|
1529
|
+
} : m
|
|
1530
|
+
);
|
|
1531
|
+
return (messages) => {
|
|
1532
|
+
let edited = apply(messages);
|
|
1533
|
+
const before = estimate(edited);
|
|
1534
|
+
if (before <= opts.triggerTokens) return edited;
|
|
1535
|
+
const positions = positionsOf(messages);
|
|
1536
|
+
let added = 0;
|
|
1537
|
+
for (const pos of positions.slice(0, Math.max(0, positions.length - keep))) {
|
|
1538
|
+
if (!cleared.has(pos)) {
|
|
1539
|
+
cleared.add(pos);
|
|
1540
|
+
added += 1;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (added === 0) return edited;
|
|
1544
|
+
edited = apply(messages);
|
|
1545
|
+
onCleared?.({ cleared: added, beforeTokens: before, afterTokens: estimate(edited) });
|
|
1546
|
+
return edited;
|
|
1547
|
+
};
|
|
1548
|
+
};
|
|
1549
|
+
|
|
755
1550
|
// src/llm/tool-loop.ts
|
|
756
1551
|
var runToolLoop = (model, opts) => opts.mode === "prompted" ? runPrompted(model, opts) : runNative(model, opts);
|
|
1552
|
+
var activeOf = (opts) => typeof opts.activeTools === "function" ? opts.activeTools() : opts.activeTools;
|
|
1553
|
+
var callExtras = (opts) => ({
|
|
1554
|
+
...opts.system !== void 0 ? { instructions: opts.system } : {},
|
|
1555
|
+
maxOutputTokens: opts.maxOutputTokens,
|
|
1556
|
+
temperature: opts.temperature,
|
|
1557
|
+
...opts.reasoning ? { reasoning: opts.reasoning } : {},
|
|
1558
|
+
...opts.providerOptions ? { providerOptions: opts.providerOptions } : {}
|
|
1559
|
+
});
|
|
757
1560
|
var runNative = async (model, opts) => {
|
|
758
1561
|
const toolCalls = [];
|
|
759
1562
|
const pending = /* @__PURE__ */ new Map();
|
|
760
|
-
|
|
1563
|
+
let stoppedByBudget = false;
|
|
1564
|
+
const stopWhen = [(0, import_ai6.stepCountIs)(opts.maxSteps ?? 4)];
|
|
1565
|
+
if (opts.shouldStop) {
|
|
1566
|
+
const shouldStop = opts.shouldStop;
|
|
1567
|
+
stopWhen.push(({ steps }) => {
|
|
1568
|
+
const used = steps.reduce((u, s) => addUsage(u, normalizeUsage(s.usage)), emptyUsage());
|
|
1569
|
+
if (!shouldStop(used)) return false;
|
|
1570
|
+
stoppedByBudget = true;
|
|
1571
|
+
return true;
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
const initialActive = activeOf(opts);
|
|
1575
|
+
const dynamic = typeof opts.activeTools === "function";
|
|
1576
|
+
const caching = opts.caching?.enabled ? opts.caching : void 0;
|
|
1577
|
+
const clear = opts.clearToolResults ? createToolResultClearer(opts.clearToolResults, opts.callbacks?.onToolResultsCleared) : void 0;
|
|
1578
|
+
const editMessages = caching || clear;
|
|
1579
|
+
const result = (0, import_ai6.streamText)({
|
|
761
1580
|
model,
|
|
762
1581
|
tools: opts.tools,
|
|
763
|
-
...
|
|
764
|
-
|
|
765
|
-
|
|
1582
|
+
...initialActive ? { activeTools: initialActive } : {},
|
|
1583
|
+
// Before every round: re-read the active set so tools activated mid-call
|
|
1584
|
+
// (find_tools) become callable; clear stale tool results; move the cache
|
|
1585
|
+
// breakpoint to the newest message.
|
|
1586
|
+
...dynamic || editMessages ? {
|
|
1587
|
+
prepareStep: ({ messages }) => {
|
|
1588
|
+
let next = clear ? clear(messages) : messages;
|
|
1589
|
+
if (caching) next = withRollingBreakpoint(next, caching);
|
|
1590
|
+
return {
|
|
1591
|
+
...dynamic ? { activeTools: activeOf(opts) } : {},
|
|
1592
|
+
...editMessages ? { messages: next } : {}
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
} : {},
|
|
1596
|
+
stopWhen,
|
|
1597
|
+
...callExtras(opts),
|
|
766
1598
|
...promptOf(opts),
|
|
767
|
-
maxOutputTokens: opts.maxOutputTokens,
|
|
768
|
-
temperature: opts.temperature,
|
|
769
1599
|
abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
|
|
770
1600
|
});
|
|
771
1601
|
for await (const part of result.fullStream) {
|
|
@@ -773,6 +1603,9 @@ var runNative = async (model, opts) => {
|
|
|
773
1603
|
case "text-delta":
|
|
774
1604
|
if (part.text) opts.callbacks?.onTextDelta?.(part.text);
|
|
775
1605
|
break;
|
|
1606
|
+
case "reasoning-delta":
|
|
1607
|
+
if (part.text) opts.callbacks?.onReasoningDelta?.(part.text);
|
|
1608
|
+
break;
|
|
776
1609
|
case "tool-call":
|
|
777
1610
|
pending.set(part.toolCallId, { name: part.toolName, input: part.input });
|
|
778
1611
|
opts.callbacks?.onToolCall?.(part.toolName, part.input);
|
|
@@ -806,7 +1639,7 @@ var runNative = async (model, opts) => {
|
|
|
806
1639
|
}
|
|
807
1640
|
}
|
|
808
1641
|
const [text, usage] = await Promise.all([result.text, result.usage]);
|
|
809
|
-
return { text: text.trim(), toolCalls, usage: normalizeUsage(usage) };
|
|
1642
|
+
return { text: text.trim(), toolCalls, usage: normalizeUsage(usage), stoppedByBudget };
|
|
810
1643
|
};
|
|
811
1644
|
var asJson = (value) => {
|
|
812
1645
|
try {
|
|
@@ -815,13 +1648,16 @@ var asJson = (value) => {
|
|
|
815
1648
|
return String(value);
|
|
816
1649
|
}
|
|
817
1650
|
};
|
|
818
|
-
var toolResultsPrompt = (results) => {
|
|
1651
|
+
var toolResultsPrompt = (results, newTools) => {
|
|
819
1652
|
const lines = results.map(
|
|
820
1653
|
(r) => `- ${r.name} ${clip(asJson(r.input), 160)} \u2192 ${r.ok ? "ok" : "FAILED"}: ${clip(asJson(r.output), 400)}`
|
|
821
1654
|
);
|
|
822
1655
|
return `TOOL RESULTS:
|
|
823
1656
|
${lines.join("\n")}
|
|
824
|
-
|
|
1657
|
+
${newTools ? `
|
|
1658
|
+
TOOLS NOW AVAILABLE (call them by these exact names):
|
|
1659
|
+
${newTools}
|
|
1660
|
+
` : ""}
|
|
825
1661
|
Continue THIS step using the results above. Reply with a single JSON object, nothing else:
|
|
826
1662
|
{ "reply": string, "actions": [ { "tool": string, "args": object } ] }
|
|
827
1663
|
- If the step is now complete: "actions": [] and a short outcome in "reply".
|
|
@@ -829,22 +1665,28 @@ Continue THIS step using the results above. Reply with a single JSON object, not
|
|
|
829
1665
|
};
|
|
830
1666
|
var runPrompted = async (model, opts) => {
|
|
831
1667
|
const maxRounds = Math.max(1, opts.maxSteps ?? 4);
|
|
832
|
-
const
|
|
833
|
-
|
|
1668
|
+
const callable = () => {
|
|
1669
|
+
const active = activeOf(opts);
|
|
1670
|
+
return active ? Object.fromEntries(Object.entries(opts.tools).filter(([name]) => active.includes(name))) : opts.tools;
|
|
1671
|
+
};
|
|
834
1672
|
const signal = timeoutSignal(opts.abortSignal, opts.timeoutMs);
|
|
835
1673
|
const messages = opts.messages ? [...opts.messages] : [{ role: "user", content: opts.prompt ?? "" }];
|
|
836
1674
|
const toolCalls = [];
|
|
837
1675
|
const seen = /* @__PURE__ */ new Set();
|
|
838
1676
|
let usage = emptyUsage();
|
|
839
1677
|
let text = "";
|
|
1678
|
+
let stoppedByBudget = false;
|
|
840
1679
|
for (let round = 1; round <= maxRounds; round += 1) {
|
|
841
1680
|
if (signal?.aborted) break;
|
|
842
|
-
|
|
1681
|
+
if (opts.shouldStop?.(usage)) {
|
|
1682
|
+
stoppedByBudget = true;
|
|
1683
|
+
break;
|
|
1684
|
+
}
|
|
1685
|
+
const before = new Set(Object.keys(callable()));
|
|
1686
|
+
const result = await (0, import_ai6.generateText)({
|
|
843
1687
|
model,
|
|
844
|
-
|
|
845
|
-
messages,
|
|
846
|
-
maxOutputTokens: opts.maxOutputTokens,
|
|
847
|
-
temperature: opts.temperature,
|
|
1688
|
+
...callExtras(opts),
|
|
1689
|
+
messages: opts.caching ? withRollingBreakpoint(messages, opts.caching) : messages,
|
|
848
1690
|
abortSignal: signal
|
|
849
1691
|
});
|
|
850
1692
|
usage = addUsage(usage, normalizeUsage(result.usage));
|
|
@@ -859,42 +1701,226 @@ var runPrompted = async (model, opts) => {
|
|
|
859
1701
|
for (const action of parsed.actions) {
|
|
860
1702
|
if (signal?.aborted) break;
|
|
861
1703
|
opts.callbacks?.onToolCall?.(action.tool, action.args);
|
|
862
|
-
const rec = await dispatch(action,
|
|
1704
|
+
const rec = await dispatch(action, callable(), { abortSignal: signal });
|
|
863
1705
|
toolCalls.push(rec);
|
|
864
1706
|
results.push(rec);
|
|
865
1707
|
opts.callbacks?.onToolResult?.(rec.name, rec.output, rec.ok);
|
|
866
1708
|
}
|
|
867
1709
|
if (round === maxRounds || signal?.aborted) break;
|
|
1710
|
+
const added = Object.keys(callable()).filter((n) => !before.has(n));
|
|
868
1711
|
messages.push({ role: "assistant", content: result.text });
|
|
869
|
-
messages.push({
|
|
1712
|
+
messages.push({
|
|
1713
|
+
role: "user",
|
|
1714
|
+
content: toolResultsPrompt(
|
|
1715
|
+
results,
|
|
1716
|
+
added.length && opts.describeTools ? opts.describeTools(added) : ""
|
|
1717
|
+
)
|
|
1718
|
+
});
|
|
870
1719
|
}
|
|
871
1720
|
if (text) opts.callbacks?.onTextDelta?.(text);
|
|
872
|
-
return { text, toolCalls, usage };
|
|
1721
|
+
return { text, toolCalls, usage, stoppedByBudget };
|
|
873
1722
|
};
|
|
874
1723
|
|
|
875
|
-
// src/
|
|
876
|
-
var
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
return { summary: raw.split(BLOCKER).join("").trim(), blocked: true };
|
|
882
|
-
};
|
|
883
|
-
var activeToolNames = (ctx, step) => {
|
|
884
|
-
if (ctx.config.toolSelectionStrategy !== "plan-narrowed") return void 0;
|
|
885
|
-
const suggested = step.suggestedTools ?? [];
|
|
886
|
-
const known = suggested.filter((n) => ctx.tools[n]);
|
|
887
|
-
if (known.length === 0 && suggested.length > 0) return Object.keys(ctx.tools);
|
|
888
|
-
return known;
|
|
1724
|
+
// src/thinking.ts
|
|
1725
|
+
var normalize = (setting) => {
|
|
1726
|
+
if (setting === void 0 || setting === false) return void 0;
|
|
1727
|
+
if (setting === true) return { level: "medium" };
|
|
1728
|
+
if (typeof setting === "string") return { level: setting };
|
|
1729
|
+
return setting;
|
|
889
1730
|
};
|
|
890
|
-
var
|
|
891
|
-
|
|
892
|
-
|
|
1731
|
+
var resolveThinking = (setting) => {
|
|
1732
|
+
const cfg = normalize(setting);
|
|
1733
|
+
if (!cfg) return {};
|
|
1734
|
+
const level = cfg.level ?? "medium";
|
|
1735
|
+
if (level === "none") return { reasoning: "none" };
|
|
1736
|
+
const includeThoughts = cfg.includeThoughts !== false;
|
|
1737
|
+
const budget = cfg.budgetTokens;
|
|
1738
|
+
if (typeof budget === "number" && budget > 0) {
|
|
1739
|
+
return {
|
|
1740
|
+
reasoning: level,
|
|
1741
|
+
providerOptions: {
|
|
1742
|
+
anthropic: { thinking: { type: "enabled", budgetTokens: budget } },
|
|
1743
|
+
google: { thinkingConfig: { thinkingBudget: budget, includeThoughts } }
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
if (!includeThoughts) return { reasoning: level };
|
|
1748
|
+
return {
|
|
1749
|
+
reasoning: level,
|
|
1750
|
+
providerOptions: {
|
|
1751
|
+
google: { thinkingConfig: { includeThoughts: true } },
|
|
1752
|
+
openai: { reasoningSummary: "auto" }
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
893
1755
|
};
|
|
1756
|
+
var thinkingFor = (stage, top, perStage) => perStage && stage in perStage ? perStage[stage] : top;
|
|
1757
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1758
|
+
var deepMerge = (a, b) => {
|
|
1759
|
+
const out = { ...a };
|
|
1760
|
+
for (const [k, v] of Object.entries(b)) {
|
|
1761
|
+
out[k] = isPlainObject(out[k]) && isPlainObject(v) ? deepMerge(out[k], v) : v;
|
|
1762
|
+
}
|
|
1763
|
+
return out;
|
|
1764
|
+
};
|
|
1765
|
+
var mergeProviderOptions = (...maps) => {
|
|
1766
|
+
let out;
|
|
1767
|
+
for (const m of maps) {
|
|
1768
|
+
if (!m || Object.keys(m).length === 0) continue;
|
|
1769
|
+
out = out ? deepMerge(out, m) : { ...m };
|
|
1770
|
+
}
|
|
1771
|
+
return out;
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1774
|
+
// src/images.ts
|
|
1775
|
+
var DATA_URL_RE = /^data:([^;,]+)?;base64,(.*)$/s;
|
|
1776
|
+
var HTTP_RE = /^https?:\/\//i;
|
|
1777
|
+
var mediaTypeOf = (f) => {
|
|
1778
|
+
if (f.mediaType) return f.mediaType;
|
|
1779
|
+
if (typeof f.data === "string") {
|
|
1780
|
+
const m = DATA_URL_RE.exec(f.data);
|
|
1781
|
+
if (m?.[1]) return m[1];
|
|
1782
|
+
}
|
|
1783
|
+
const url = f.data instanceof URL ? f.data.href : typeof f.data === "string" ? f.data : "";
|
|
1784
|
+
if (HTTP_RE.test(url) || f.name) {
|
|
1785
|
+
const ext = (f.name ?? new URL(url, "http://x").pathname).split(".").pop()?.toLowerCase();
|
|
1786
|
+
if (ext === "pdf") return "application/pdf";
|
|
1787
|
+
if (ext && ["png", "jpg", "jpeg", "gif", "webp"].includes(ext)) {
|
|
1788
|
+
return `image/${ext === "jpg" ? "jpeg" : ext}`;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return void 0;
|
|
1792
|
+
};
|
|
1793
|
+
var attachmentKind = (f) => {
|
|
1794
|
+
const mt = mediaTypeOf(f) ?? "";
|
|
1795
|
+
if (mt.startsWith("image/")) return "image";
|
|
1796
|
+
if (mt === "application/pdf") return "pdf";
|
|
1797
|
+
return "file";
|
|
1798
|
+
};
|
|
1799
|
+
var KIND_LABEL = {
|
|
1800
|
+
image: "images",
|
|
1801
|
+
pdf: "PDF files",
|
|
1802
|
+
file: "file attachments"
|
|
1803
|
+
};
|
|
1804
|
+
var KIND_HINT = {
|
|
1805
|
+
image: "Remove the image, or switch to a vision-capable model such as Gemini, Claude or GPT-4o/GPT-5.",
|
|
1806
|
+
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.",
|
|
1807
|
+
file: "Paste its text instead, or switch to a model that accepts this file type."
|
|
1808
|
+
};
|
|
1809
|
+
var AttachmentsNotSupportedError = class extends Error {
|
|
1810
|
+
kind;
|
|
1811
|
+
constructor(model, kind, detail) {
|
|
1812
|
+
super(
|
|
1813
|
+
`The model "${model}" can't take ${KIND_LABEL[kind]}${detail ? ` (${detail})` : ""}. ${KIND_HINT[kind]}`
|
|
1814
|
+
);
|
|
1815
|
+
this.name = "AttachmentsNotSupportedError";
|
|
1816
|
+
this.kind = kind;
|
|
1817
|
+
}
|
|
1818
|
+
toJSON() {
|
|
1819
|
+
return this.message;
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
var ImagesNotSupportedError = class extends AttachmentsNotSupportedError {
|
|
1823
|
+
constructor(model, detail) {
|
|
1824
|
+
super(model, "image", detail);
|
|
1825
|
+
this.name = "ImagesNotSupportedError";
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
var unsupportedAttachment = (model, kind, detail) => kind === "image" ? new ImagesNotSupportedError(model, detail) : new AttachmentsNotSupportedError(model, kind, detail);
|
|
1829
|
+
var dataOf = (f) => {
|
|
1830
|
+
if (typeof f.data === "string") {
|
|
1831
|
+
const m = DATA_URL_RE.exec(f.data);
|
|
1832
|
+
if (m) return m[2];
|
|
1833
|
+
if (HTTP_RE.test(f.data)) return new URL(f.data);
|
|
1834
|
+
}
|
|
1835
|
+
return f.data;
|
|
1836
|
+
};
|
|
1837
|
+
var toFilePart = (f) => {
|
|
1838
|
+
const kind = attachmentKind(f);
|
|
1839
|
+
const mediaType = mediaTypeOf(f) ?? (kind === "image" ? "image" : "application/octet-stream");
|
|
1840
|
+
return {
|
|
1841
|
+
type: "file",
|
|
1842
|
+
data: dataOf(f),
|
|
1843
|
+
mediaType,
|
|
1844
|
+
...f.name ? { filename: f.name } : {}
|
|
1845
|
+
};
|
|
1846
|
+
};
|
|
1847
|
+
var toImagePart = toFilePart;
|
|
1848
|
+
var modelLabel = (model) => {
|
|
1849
|
+
if (typeof model === "string") return model;
|
|
1850
|
+
const m = model;
|
|
1851
|
+
return m?.modelId ? `${m.modelId}${m.provider ? ` (${m.provider})` : ""}` : "this model";
|
|
1852
|
+
};
|
|
1853
|
+
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;
|
|
1854
|
+
var isAttachmentRefusal = (err) => REFUSAL_RE.test(err instanceof Error ? `${err.name} ${err.message}` : String(err));
|
|
1855
|
+
var isImageRefusal = isAttachmentRefusal;
|
|
1856
|
+
|
|
1857
|
+
// src/agent/internal.ts
|
|
1858
|
+
var promptFor = (ctx, prompt) => ctx.images?.length ? {
|
|
1859
|
+
messages: [
|
|
1860
|
+
{
|
|
1861
|
+
role: "user",
|
|
1862
|
+
content: [{ type: "text", text: prompt }, ...ctx.images.map(toFilePart)]
|
|
1863
|
+
}
|
|
1864
|
+
]
|
|
1865
|
+
} : { prompt };
|
|
1866
|
+
var imageRefusal = (ctx, err, model) => {
|
|
1867
|
+
if (!ctx.images?.length || err instanceof AttachmentsNotSupportedError || !isAttachmentRefusal(err)) {
|
|
1868
|
+
return void 0;
|
|
1869
|
+
}
|
|
1870
|
+
const kinds = new Set(ctx.images.map(attachmentKind));
|
|
1871
|
+
const kind = kinds.size === 1 ? [...kinds][0] : "file";
|
|
1872
|
+
const detail = err instanceof Error ? err.message.slice(0, 160) : void 0;
|
|
1873
|
+
return unsupportedAttachment(modelLabel(model), kind, detail && `the provider said: ${detail}`);
|
|
1874
|
+
};
|
|
1875
|
+
var systemFor = (ctx, base) => withSystem(base, ctx.raw.systemPrompt);
|
|
1876
|
+
var stageCall = (ctx, stage, baseSystem) => {
|
|
1877
|
+
const system = systemFor(ctx, baseSystem);
|
|
1878
|
+
const thinking = resolveThinking(thinkingFor(stage, ctx.raw.thinking, ctx.raw.stageThinking));
|
|
1879
|
+
const caching = ctx.caching ?? { enabled: false };
|
|
1880
|
+
const providerOptions = mergeProviderOptions(
|
|
1881
|
+
cachingProviderOptions(caching, ctx.config.clientName, stage),
|
|
1882
|
+
thinking.providerOptions
|
|
1883
|
+
);
|
|
1884
|
+
return {
|
|
1885
|
+
system: cachedInstructions(system, caching),
|
|
1886
|
+
...thinking.reasoning ? { reasoning: thinking.reasoning } : {},
|
|
1887
|
+
...providerOptions ? { providerOptions } : {}
|
|
1888
|
+
};
|
|
1889
|
+
};
|
|
1890
|
+
|
|
1891
|
+
// src/agent/executor.ts
|
|
1892
|
+
var splitBlocker = (raw) => {
|
|
1893
|
+
if (!raw.includes(BLOCKER)) return { summary: raw.trim(), blocked: false };
|
|
1894
|
+
return { summary: raw.split(BLOCKER).join("").trim(), blocked: true };
|
|
1895
|
+
};
|
|
1896
|
+
var activeToolNames = (ctx, step) => {
|
|
1897
|
+
const strategy = ctx.strategy ?? ctx.config.toolSelectionStrategy;
|
|
1898
|
+
const builtins = [...ctx.builtinTools ?? []].filter((n) => ctx.tools[n]);
|
|
1899
|
+
const suggested = step.suggestedTools ?? [];
|
|
1900
|
+
const known = suggested.filter((n) => ctx.tools[n]);
|
|
1901
|
+
if (strategy === "search") {
|
|
1902
|
+
const discovered = (ctx.discovered?.() ?? []).slice(-16);
|
|
1903
|
+
return [.../* @__PURE__ */ new Set([...builtins, ...known, ...discovered])];
|
|
1904
|
+
}
|
|
1905
|
+
if (strategy !== "plan-narrowed") return void 0;
|
|
1906
|
+
if (known.length === 0 && suggested.length > 0) return Object.keys(ctx.tools);
|
|
1907
|
+
return [.../* @__PURE__ */ new Set([...known, ...builtins])];
|
|
1908
|
+
};
|
|
1909
|
+
var summaryOf = (text, toolCallCount) => {
|
|
1910
|
+
if (text.length > 0) return text;
|
|
1911
|
+
return toolCallCount > 0 ? `Executed ${toolCallCount} tool call(s).` : "Step produced no output.";
|
|
1912
|
+
};
|
|
1913
|
+
var catalogFor = (ctx, names) => renderCatalog(
|
|
1914
|
+
Object.fromEntries(names.filter((n) => ctx.tools[n]).map((n) => [n, ctx.tools[n]])),
|
|
1915
|
+
ctx.toolHint
|
|
1916
|
+
);
|
|
894
1917
|
var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
895
1918
|
const state = await ctx.state();
|
|
896
|
-
|
|
897
|
-
const
|
|
1919
|
+
ctx.setCurrentStep?.(step);
|
|
1920
|
+
const initial = activeToolNames(ctx, step);
|
|
1921
|
+
const searchMode = (ctx.strategy ?? ctx.config.toolSelectionStrategy) === "search";
|
|
1922
|
+
const toolCatalog = catalogFor(ctx, initial ?? Object.keys(ctx.tools));
|
|
1923
|
+
const active = ctx.activeSkills?.() ?? [];
|
|
898
1924
|
const parts = ctx.prompts.executor({
|
|
899
1925
|
goal,
|
|
900
1926
|
state,
|
|
@@ -903,21 +1929,44 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
|
903
1929
|
total,
|
|
904
1930
|
toolCatalog,
|
|
905
1931
|
done,
|
|
906
|
-
mode: ctx.executorMode
|
|
1932
|
+
mode: ctx.executorMode,
|
|
1933
|
+
skills: ctx.skills?.length ? renderSkillIndex(ctx.skills) : void 0,
|
|
1934
|
+
activeSkills: active.length ? renderActiveSkills(active) : void 0,
|
|
1935
|
+
searchMode: searchMode && Boolean(ctx.tools[FIND_TOOLS_NAME])
|
|
907
1936
|
});
|
|
908
1937
|
const loop = await runToolLoop(ctx.executorModel, {
|
|
909
1938
|
mode: ctx.executorMode,
|
|
910
|
-
|
|
911
|
-
|
|
1939
|
+
...stageCall(ctx, "executor", parts.system),
|
|
1940
|
+
...promptFor(ctx, parts.prompt),
|
|
912
1941
|
tools: ctx.tools,
|
|
913
|
-
|
|
1942
|
+
// In search mode the active set grows while the step runs.
|
|
1943
|
+
activeTools: searchMode ? () => [.../* @__PURE__ */ new Set([...initial ?? [], ...ctx.discovered?.() ?? []])] : initial,
|
|
1944
|
+
describeTools: (names) => catalogFor(ctx, names),
|
|
914
1945
|
maxSteps: ctx.config.maxStepsPerTask,
|
|
1946
|
+
shouldStop: ctx.overBudget,
|
|
915
1947
|
maxOutputTokens: ctx.config.budgets.executor,
|
|
916
1948
|
temperature: ctx.config.temperature,
|
|
917
1949
|
abortSignal: ctx.signal,
|
|
918
1950
|
timeoutMs: ctx.config.chatTimeoutMs,
|
|
1951
|
+
caching: ctx.caching,
|
|
1952
|
+
...ctx.config.compaction.clearToolResultsAfterTokens > 0 ? {
|
|
1953
|
+
clearToolResults: {
|
|
1954
|
+
triggerTokens: ctx.config.compaction.clearToolResultsAfterTokens,
|
|
1955
|
+
keep: ctx.config.compaction.keepToolResults
|
|
1956
|
+
}
|
|
1957
|
+
} : {},
|
|
919
1958
|
callbacks: {
|
|
1959
|
+
onToolResultsCleared: (info) => {
|
|
1960
|
+
ctx.log.info(`cleared ${info.cleared} stale tool result(s) from the step's context`);
|
|
1961
|
+
ctx.emit({
|
|
1962
|
+
type: "context.compacted",
|
|
1963
|
+
scope: "tool-results",
|
|
1964
|
+
beforeTokens: info.beforeTokens,
|
|
1965
|
+
afterTokens: info.afterTokens
|
|
1966
|
+
});
|
|
1967
|
+
},
|
|
920
1968
|
onTextDelta: (delta) => ctx.emit({ type: "step.text-delta", step, delta }),
|
|
1969
|
+
onReasoningDelta: (delta) => ctx.emit({ type: "step.reasoning-delta", step, delta }),
|
|
921
1970
|
onToolCall: (name, input) => {
|
|
922
1971
|
ctx.log.info(`tool call: ${name}`, input);
|
|
923
1972
|
ctx.emit({ type: "step.tool-call", step, name, input });
|
|
@@ -927,6 +1976,8 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
|
927
1976
|
ctx.emit({ type: "step.tool-result", step, name, output, ok });
|
|
928
1977
|
}
|
|
929
1978
|
}
|
|
1979
|
+
}).catch((err) => {
|
|
1980
|
+
throw imageRefusal(ctx, err, ctx.executorModel) ?? err;
|
|
930
1981
|
});
|
|
931
1982
|
ctx.log.debug("executor text:", loop.text);
|
|
932
1983
|
const { summary, blocked } = splitBlocker(loop.text);
|
|
@@ -937,7 +1988,8 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
|
|
|
937
1988
|
toolCalls: loop.toolCalls,
|
|
938
1989
|
blocked
|
|
939
1990
|
},
|
|
940
|
-
usage: loop.usage
|
|
1991
|
+
usage: loop.usage,
|
|
1992
|
+
stoppedByBudget: loop.stoppedByBudget
|
|
941
1993
|
};
|
|
942
1994
|
};
|
|
943
1995
|
var shouldReplan = (result) => {
|
|
@@ -978,9 +2030,11 @@ var PlanStepSchema = import_zod.z.object({
|
|
|
978
2030
|
suggestedTools: import_zod.z.array(import_zod.z.string()).optional()
|
|
979
2031
|
});
|
|
980
2032
|
var PlanSchema = import_zod.z.object({
|
|
981
|
-
/** One short sentence. For a greeting/
|
|
2033
|
+
/** One short sentence. For a greeting/small talk, holds the answer and steps is empty. */
|
|
982
2034
|
thought: import_zod.z.string(),
|
|
983
|
-
steps: import_zod.z.array(PlanStepSchema)
|
|
2035
|
+
steps: import_zod.z.array(PlanStepSchema),
|
|
2036
|
+
/** Names of the configured skills that apply to this goal. */
|
|
2037
|
+
skills: import_zod.z.array(import_zod.z.string()).optional()
|
|
984
2038
|
});
|
|
985
2039
|
var ReplanSchema = import_zod.z.object({
|
|
986
2040
|
decision: import_zod.z.enum(["continue", "revise", "finish"]),
|
|
@@ -990,7 +2044,7 @@ var ReplanSchema = import_zod.z.object({
|
|
|
990
2044
|
});
|
|
991
2045
|
|
|
992
2046
|
// src/agent/planner.ts
|
|
993
|
-
var toSteps = (raw) => raw.filter((s) => s.description && s.description.trim()).map((s, i) => ({
|
|
2047
|
+
var toSteps = (raw, cap) => raw.filter((s) => s.description && s.description.trim()).slice(0, Math.max(1, cap)).map((s, i) => ({
|
|
994
2048
|
id: `s${i + 1}`,
|
|
995
2049
|
description: s.description.trim(),
|
|
996
2050
|
expectedOutcome: s.expectedOutcome,
|
|
@@ -1001,46 +2055,72 @@ var createPlan = async (ctx, goal, nudge) => {
|
|
|
1001
2055
|
const effectiveGoal = nudge ? `${goal}
|
|
1002
2056
|
|
|
1003
2057
|
${nudge}` : goal;
|
|
2058
|
+
const skillIndex = ctx.skills?.length ? renderSkillIndex(ctx.skills) : void 0;
|
|
2059
|
+
const cap = ctx.config.maxPlanSteps;
|
|
1004
2060
|
const commonFor = (mode) => {
|
|
1005
2061
|
const parts = ctx.prompts.planner({
|
|
1006
2062
|
goal: effectiveGoal,
|
|
1007
2063
|
state,
|
|
1008
2064
|
toolCatalog: ctx.toolCatalog,
|
|
1009
2065
|
mode,
|
|
1010
|
-
history: ctx.history
|
|
2066
|
+
history: ctx.history,
|
|
2067
|
+
skills: skillIndex,
|
|
2068
|
+
searchMode: ctx.strategy === "search"
|
|
1011
2069
|
});
|
|
1012
2070
|
return {
|
|
1013
|
-
|
|
1014
|
-
|
|
2071
|
+
...stageCall(ctx, "planner", parts.system),
|
|
2072
|
+
...promptFor(ctx, parts.prompt),
|
|
1015
2073
|
maxOutputTokens: ctx.config.budgets.planner,
|
|
1016
2074
|
temperature: ctx.config.temperature,
|
|
1017
2075
|
abortSignal: ctx.signal,
|
|
1018
2076
|
timeoutMs: ctx.config.chatTimeoutMs
|
|
1019
2077
|
};
|
|
1020
2078
|
};
|
|
2079
|
+
const validSkills = (names) => {
|
|
2080
|
+
const known = new Set((ctx.skills ?? []).map((s) => s.name));
|
|
2081
|
+
const picked = [...new Set(names ?? [])];
|
|
2082
|
+
const kept = picked.filter((n) => known.has(n));
|
|
2083
|
+
if (kept.length < picked.length) {
|
|
2084
|
+
ctx.log.warn(
|
|
2085
|
+
"planner picked unknown skills, dropped:",
|
|
2086
|
+
picked.filter((n) => !known.has(n))
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
return kept;
|
|
2090
|
+
};
|
|
1021
2091
|
if (ctx.plannerMode === "native") {
|
|
1022
2092
|
try {
|
|
1023
2093
|
const result2 = await generateStructured(ctx.plannerModel, PlanSchema, commonFor("native"));
|
|
1024
2094
|
ctx.log.debug("planner (native):", result2.object);
|
|
1025
2095
|
return {
|
|
1026
|
-
plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps) },
|
|
1027
|
-
usage: normalizeUsage(result2.usage)
|
|
2096
|
+
plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps, cap) },
|
|
2097
|
+
usage: normalizeUsage(result2.usage),
|
|
2098
|
+
skills: validSkills(result2.object.skills)
|
|
1028
2099
|
};
|
|
1029
2100
|
} catch (err) {
|
|
2101
|
+
if (ctx.signal?.aborted) throw err;
|
|
2102
|
+
const refused = imageRefusal(ctx, err, ctx.plannerModel);
|
|
2103
|
+
if (refused) throw refused;
|
|
1030
2104
|
ctx.log.warn("planner: native structured output failed, salvaging:", asMessage(err));
|
|
1031
2105
|
ctx.emit({ type: "retry", phase: "plan", attempt: 1, error: asMessage(err) });
|
|
1032
2106
|
}
|
|
1033
2107
|
}
|
|
1034
|
-
const result = await generate(ctx.plannerModel, commonFor("prompted"))
|
|
2108
|
+
const result = await generate(ctx.plannerModel, commonFor("prompted")).catch((err) => {
|
|
2109
|
+
throw imageRefusal(ctx, err, ctx.plannerModel) ?? err;
|
|
2110
|
+
});
|
|
1035
2111
|
const parsed = parsePlannerResponse(result.text);
|
|
1036
2112
|
ctx.log.debug("planner (prompted) raw:", result.text);
|
|
1037
2113
|
ctx.log.debug("planner (prompted) parsed:", parsed);
|
|
1038
2114
|
return {
|
|
1039
2115
|
plan: {
|
|
1040
2116
|
thought: parsed.reply,
|
|
1041
|
-
steps: toSteps(
|
|
2117
|
+
steps: toSteps(
|
|
2118
|
+
parsed.plan.map((description) => ({ description })),
|
|
2119
|
+
cap
|
|
2120
|
+
)
|
|
1042
2121
|
},
|
|
1043
|
-
usage: normalizeUsage(result.usage)
|
|
2122
|
+
usage: normalizeUsage(result.usage),
|
|
2123
|
+
skills: validSkills(parsed.skills)
|
|
1044
2124
|
};
|
|
1045
2125
|
};
|
|
1046
2126
|
var asMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
@@ -1048,10 +2128,18 @@ var asMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
|
1048
2128
|
// src/agent/replanner.ts
|
|
1049
2129
|
var decideReplan = async (ctx, goal, done, remaining) => {
|
|
1050
2130
|
const state = await ctx.state();
|
|
2131
|
+
const active = ctx.activeSkills?.() ?? [];
|
|
1051
2132
|
const commonFor = (mode) => {
|
|
1052
|
-
const parts = ctx.prompts.replanner({
|
|
2133
|
+
const parts = ctx.prompts.replanner({
|
|
2134
|
+
goal,
|
|
2135
|
+
state,
|
|
2136
|
+
done,
|
|
2137
|
+
remaining,
|
|
2138
|
+
mode,
|
|
2139
|
+
activeSkills: active.length ? renderActiveSkills(active) : void 0
|
|
2140
|
+
});
|
|
1053
2141
|
return {
|
|
1054
|
-
|
|
2142
|
+
...stageCall(ctx, "replanner", parts.system),
|
|
1055
2143
|
prompt: parts.prompt,
|
|
1056
2144
|
maxOutputTokens: ctx.config.budgets.replanner,
|
|
1057
2145
|
temperature: ctx.config.temperature,
|
|
@@ -1086,12 +2174,19 @@ var decideReplan = async (ctx, goal, done, remaining) => {
|
|
|
1086
2174
|
};
|
|
1087
2175
|
|
|
1088
2176
|
// src/agent/synthesizer.ts
|
|
1089
|
-
var synthesizeAnswer = async (ctx, goal, done) => {
|
|
2177
|
+
var synthesizeAnswer = async (ctx, goal, done, findings = []) => {
|
|
1090
2178
|
const state = await ctx.state();
|
|
1091
|
-
const
|
|
2179
|
+
const active = ctx.activeSkills?.() ?? [];
|
|
2180
|
+
const parts = ctx.prompts.synthesizer({
|
|
2181
|
+
goal,
|
|
2182
|
+
state,
|
|
2183
|
+
done,
|
|
2184
|
+
findings,
|
|
2185
|
+
activeSkills: active.length ? renderActiveSkills(active) : void 0
|
|
2186
|
+
});
|
|
1092
2187
|
const result = stream(ctx.synthesizerModel, {
|
|
1093
|
-
|
|
1094
|
-
|
|
2188
|
+
...stageCall(ctx, "synthesizer", parts.system),
|
|
2189
|
+
...promptFor(ctx, parts.prompt),
|
|
1095
2190
|
maxOutputTokens: ctx.config.budgets.synthesizer,
|
|
1096
2191
|
temperature: ctx.config.temperature,
|
|
1097
2192
|
abortSignal: ctx.signal,
|
|
@@ -1099,13 +2194,27 @@ var synthesizeAnswer = async (ctx, goal, done) => {
|
|
|
1099
2194
|
});
|
|
1100
2195
|
let text = "";
|
|
1101
2196
|
let verdict = "unknown";
|
|
1102
|
-
for await (const
|
|
2197
|
+
for await (const part of result.fullStream) {
|
|
2198
|
+
if (part.type === "reasoning-delta") {
|
|
2199
|
+
if (part.text) ctx.emit({ type: "final.reasoning-delta", delta: part.text });
|
|
2200
|
+
continue;
|
|
2201
|
+
}
|
|
2202
|
+
if (part.type === "error") {
|
|
2203
|
+
const err = part.error instanceof Error ? part.error : new Error(String(part.error));
|
|
2204
|
+
throw imageRefusal(ctx, err, ctx.synthesizerModel) ?? err;
|
|
2205
|
+
}
|
|
2206
|
+
if (part.type !== "text-delta") continue;
|
|
2207
|
+
const delta = part.text;
|
|
1103
2208
|
text += delta;
|
|
1104
2209
|
if (verdict === "unknown") {
|
|
1105
2210
|
const lead = text.trimStart();
|
|
1106
2211
|
if (!lead) continue;
|
|
2212
|
+
if (lead.startsWith("<think>") && !text.includes("</think>")) continue;
|
|
1107
2213
|
verdict = looksLikeJson(lead) ? "suppress" : "emit";
|
|
1108
|
-
if (verdict === "emit")
|
|
2214
|
+
if (verdict === "emit") {
|
|
2215
|
+
const visible = text.includes("</think>") ? text.slice(text.lastIndexOf("</think>") + "</think>".length).trimStart() : text;
|
|
2216
|
+
if (visible) ctx.emit({ type: "final.text-delta", delta: visible });
|
|
2217
|
+
}
|
|
1109
2218
|
} else if (verdict === "emit") {
|
|
1110
2219
|
ctx.emit({ type: "final.text-delta", delta });
|
|
1111
2220
|
}
|
|
@@ -1138,6 +2247,43 @@ var createLogger = (level = "warn", sink = console) => {
|
|
|
1138
2247
|
};
|
|
1139
2248
|
};
|
|
1140
2249
|
|
|
2250
|
+
// src/providers/capabilities.ts
|
|
2251
|
+
var CLOUD = /* @__PURE__ */ new Set([
|
|
2252
|
+
"openai",
|
|
2253
|
+
"anthropic",
|
|
2254
|
+
"google",
|
|
2255
|
+
"openai-compatible",
|
|
2256
|
+
"xai",
|
|
2257
|
+
"deepseek",
|
|
2258
|
+
"gateway"
|
|
2259
|
+
]);
|
|
2260
|
+
var supportsNativeTools = (p) => CLOUD.has(p);
|
|
2261
|
+
var supportsStructuredOutput = (p) => CLOUD.has(p);
|
|
2262
|
+
var directBrowserOk = (p) => p === "google" || p === "gateway" || p === "openai-compatible" || p === "anthropic";
|
|
2263
|
+
var LOCAL_RE2 = /web-?llm|mlc|browser-ai|transformers|built-?in/i;
|
|
2264
|
+
var TEXT_ONLY_MODEL_RE = /gpt-3\.5|o1-mini|o3-mini|deepseek|text-(davinci|embedding)|embedding|babbage|davinci|codestral/i;
|
|
2265
|
+
var VISION_PROVIDER_RE = /^(google|anthropic|openai|azure|xai|vertex|bedrock|gemini)/i;
|
|
2266
|
+
var PDF_PROVIDER_RE = /^(google|anthropic|openai|azure|vertex|bedrock|gemini)/i;
|
|
2267
|
+
var supportsImages = (model) => {
|
|
2268
|
+
if (typeof model === "string") {
|
|
2269
|
+
const [provider2 = "", id2 = model] = model.includes("/") ? model.split("/", 2) : ["", model];
|
|
2270
|
+
if (TEXT_ONLY_MODEL_RE.test(id2) || /deepseek/i.test(provider2)) return false;
|
|
2271
|
+
return VISION_PROVIDER_RE.test(provider2) ? true : void 0;
|
|
2272
|
+
}
|
|
2273
|
+
if (!model || typeof model !== "object") return void 0;
|
|
2274
|
+
const provider = String(model.provider ?? "");
|
|
2275
|
+
const id = String(model.modelId ?? "");
|
|
2276
|
+
if (LOCAL_RE2.test(provider)) return false;
|
|
2277
|
+
if (/deepseek/i.test(provider) || TEXT_ONLY_MODEL_RE.test(id)) return false;
|
|
2278
|
+
return VISION_PROVIDER_RE.test(provider) ? true : void 0;
|
|
2279
|
+
};
|
|
2280
|
+
var supportsPdf = (model) => {
|
|
2281
|
+
const vision = supportsImages(model);
|
|
2282
|
+
if (vision === false) return false;
|
|
2283
|
+
const provider = typeof model === "string" ? model.split("/")[0] ?? "" : String(model?.provider ?? "");
|
|
2284
|
+
return PDF_PROVIDER_RE.test(provider) ? true : void 0;
|
|
2285
|
+
};
|
|
2286
|
+
|
|
1141
2287
|
// src/agent/runner.ts
|
|
1142
2288
|
var errMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
1143
2289
|
var filterTools = (all, available, excluded) => {
|
|
@@ -1146,6 +2292,16 @@ var filterTools = (all, available, excluded) => {
|
|
|
1146
2292
|
if (excluded && excluded.length > 0) entries = entries.filter(([n]) => !excluded.includes(n));
|
|
1147
2293
|
return Object.fromEntries(entries);
|
|
1148
2294
|
};
|
|
2295
|
+
var asJson2 = (v) => {
|
|
2296
|
+
if (typeof v === "string") return v;
|
|
2297
|
+
try {
|
|
2298
|
+
return JSON.stringify(v) ?? String(v);
|
|
2299
|
+
} catch {
|
|
2300
|
+
return String(v);
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
var FINDING_CHARS = 600;
|
|
2304
|
+
var FINDINGS_BUDGET = 6e3;
|
|
1149
2305
|
var createAgent = async (config) => {
|
|
1150
2306
|
const cfg = resolveConfig(config);
|
|
1151
2307
|
const buildStage = (override, stageName) => buildModelFromStage(
|
|
@@ -1161,16 +2317,52 @@ var createAgent = async (config) => {
|
|
|
1161
2317
|
]);
|
|
1162
2318
|
const plannerMode = selectToolMode(plannerModel, cfg.toolMode);
|
|
1163
2319
|
const executorMode = selectToolMode(executorModel, cfg.toolMode);
|
|
1164
|
-
const
|
|
2320
|
+
const baseTools = filterTools(
|
|
1165
2321
|
config.tools ?? {},
|
|
1166
2322
|
config.availableTools,
|
|
1167
2323
|
config.excludedTools
|
|
1168
2324
|
);
|
|
1169
|
-
const
|
|
2325
|
+
const skills = (config.skills ?? []).map(defineSkill);
|
|
2326
|
+
const skillNames = /* @__PURE__ */ new Set();
|
|
2327
|
+
for (const s of skills) {
|
|
2328
|
+
if (skillNames.has(s.name)) throw new Error(`duplicate skill name "${s.name}"`);
|
|
2329
|
+
skillNames.add(s.name);
|
|
2330
|
+
}
|
|
2331
|
+
const catalog = toolCatalogOf(baseTools);
|
|
2332
|
+
const strategy = cfg.toolSelectionStrategy === "auto" ? catalog.length > cfg.toolSearchThreshold ? "search" : "all" : cfg.toolSelectionStrategy;
|
|
2333
|
+
const reserved = [
|
|
2334
|
+
...skills.length ? SKILL_TOOL_NAMES : [],
|
|
2335
|
+
...strategy === "search" ? [FIND_TOOLS_NAME] : []
|
|
2336
|
+
];
|
|
2337
|
+
for (const name of reserved) {
|
|
2338
|
+
if (Object.hasOwn(baseTools, name)) {
|
|
2339
|
+
throw new Error(
|
|
2340
|
+
`tool name "${name}" is reserved by the agent's built-in tools; rename your tool`
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
const builtinTools = new Set(reserved);
|
|
2345
|
+
const hints = /* @__PURE__ */ new Map();
|
|
2346
|
+
await Promise.all(
|
|
2347
|
+
Object.entries(baseTools).map(async ([name, t]) => {
|
|
2348
|
+
const hint = await schemaHintOf(t);
|
|
2349
|
+
if (hint) hints.set(name, hint);
|
|
2350
|
+
})
|
|
2351
|
+
);
|
|
2352
|
+
const toolHint = (name) => hints.get(name);
|
|
2353
|
+
const plannerCatalog = strategy === "search" ? renderSearchCatalog(catalog) : renderCatalog(baseTools, toolHint);
|
|
1170
2354
|
const prompts = { ...defaultPrompts, ...config.prompts };
|
|
2355
|
+
const caching = resolveCaching(config.promptCaching);
|
|
2356
|
+
const textOnly = executorMode === "prompted";
|
|
2357
|
+
const accepts = {
|
|
2358
|
+
image: config.inputs?.images ?? config.vision ?? (textOnly ? false : supportsImages(executorModel)),
|
|
2359
|
+
pdf: config.inputs?.pdf ?? (textOnly ? false : supportsPdf(executorModel)),
|
|
2360
|
+
file: config.inputs?.files ?? (textOnly ? false : void 0)
|
|
2361
|
+
};
|
|
2362
|
+
const approval = createApprovalState(config.toolApproval);
|
|
1171
2363
|
const log = createLogger(cfg.logLevel, config.logger);
|
|
1172
2364
|
log.info(
|
|
1173
|
-
`agent ready \u2014
|
|
2365
|
+
`agent ready \u2014 ${catalog.length} tool(s) (${strategy}), skills: [${[...skillNames].join(", ") || "none"}], planner mode: ${plannerMode}, executor mode: ${executorMode}, approval: ${approval.mode}`
|
|
1174
2366
|
);
|
|
1175
2367
|
const run = async (goal, opts = {}) => {
|
|
1176
2368
|
const emit = (event) => {
|
|
@@ -1190,7 +2382,8 @@ var createAgent = async (config) => {
|
|
|
1190
2382
|
steps: 0,
|
|
1191
2383
|
applied: 0,
|
|
1192
2384
|
stopped: false,
|
|
1193
|
-
usage
|
|
2385
|
+
usage,
|
|
2386
|
+
skills: []
|
|
1194
2387
|
};
|
|
1195
2388
|
if (!text) return result;
|
|
1196
2389
|
const state = async () => config.describeState ? config.describeState() : void 0;
|
|
@@ -1207,7 +2400,72 @@ var createAgent = async (config) => {
|
|
|
1207
2400
|
} catch {
|
|
1208
2401
|
}
|
|
1209
2402
|
};
|
|
2403
|
+
const activeSkillNames = /* @__PURE__ */ new Set();
|
|
2404
|
+
const activateSkill = (name, by) => {
|
|
2405
|
+
if (activeSkillNames.has(name) || !skillNames.has(name)) return;
|
|
2406
|
+
activeSkillNames.add(name);
|
|
2407
|
+
result.skills = [...activeSkillNames];
|
|
2408
|
+
emit({ type: "skill.activated", name, by });
|
|
2409
|
+
};
|
|
2410
|
+
const discovered = [];
|
|
2411
|
+
let currentStep;
|
|
2412
|
+
const counter = createCallCounter(cfg.maxToolCalls);
|
|
2413
|
+
const builtins = {};
|
|
2414
|
+
if (skills.length) {
|
|
2415
|
+
Object.assign(
|
|
2416
|
+
builtins,
|
|
2417
|
+
createSkillTools(skills, (s) => activateSkill(s.name, "tool"))
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
if (strategy === "search") {
|
|
2421
|
+
builtins[FIND_TOOLS_NAME] = createFindToolsTool(
|
|
2422
|
+
() => catalog,
|
|
2423
|
+
(query, names) => {
|
|
2424
|
+
for (const n of names) {
|
|
2425
|
+
const i = discovered.indexOf(n);
|
|
2426
|
+
if (i >= 0) discovered.splice(i, 1);
|
|
2427
|
+
discovered.push(n);
|
|
2428
|
+
}
|
|
2429
|
+
emit({ type: "tools.discovered", step: currentStep, query, names });
|
|
2430
|
+
},
|
|
2431
|
+
toolHint
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
const tools = wrapToolsForRun(
|
|
2435
|
+
{ ...baseTools, ...builtins },
|
|
2436
|
+
{
|
|
2437
|
+
approval,
|
|
2438
|
+
builtins: builtinTools,
|
|
2439
|
+
emit,
|
|
2440
|
+
step: () => currentStep,
|
|
2441
|
+
signal: opts.signal,
|
|
2442
|
+
addUsage: (u) => bumpUsage(u, "subagent"),
|
|
2443
|
+
countCall: counter.count,
|
|
2444
|
+
maxToolOutputChars: cfg.compaction.maxToolOutputChars
|
|
2445
|
+
}
|
|
2446
|
+
);
|
|
2447
|
+
let budgetExceeded;
|
|
2448
|
+
const breach = (extra) => {
|
|
2449
|
+
const b = checkLimits(extra ? addUsage(usage, extra) : usage, cfg.limits);
|
|
2450
|
+
if (b) return b;
|
|
2451
|
+
if (counter.exhausted)
|
|
2452
|
+
return { kind: "tool-calls", tokens: counter.calls, cap: cfg.maxToolCalls };
|
|
2453
|
+
return void 0;
|
|
2454
|
+
};
|
|
1210
2455
|
emit({ type: "run.start", goal: text });
|
|
2456
|
+
const images = [...opts.images ?? [], ...opts.files ?? []];
|
|
2457
|
+
const refused = images.map(attachmentKind).find((kind) => accepts[kind] === false);
|
|
2458
|
+
if (refused) {
|
|
2459
|
+
const err = unsupportedAttachment(
|
|
2460
|
+
modelLabel(executorModel),
|
|
2461
|
+
refused,
|
|
2462
|
+
textOnly ? "it runs in the text-only prompted tool mode" : void 0
|
|
2463
|
+
);
|
|
2464
|
+
log.warn(err.message);
|
|
2465
|
+
emit({ type: "error", phase: "run", error: err.message });
|
|
2466
|
+
result.final = err.message;
|
|
2467
|
+
return result;
|
|
2468
|
+
}
|
|
1211
2469
|
let history = [];
|
|
1212
2470
|
if (config.memory) {
|
|
1213
2471
|
try {
|
|
@@ -1215,6 +2473,30 @@ var createAgent = async (config) => {
|
|
|
1215
2473
|
} catch {
|
|
1216
2474
|
}
|
|
1217
2475
|
}
|
|
2476
|
+
if (config.memory && config.compaction && cfg.compaction.auto && history.length > 0) {
|
|
2477
|
+
const before = messageTokens(history);
|
|
2478
|
+
if (before > cfg.compaction.thresholdTokens) {
|
|
2479
|
+
const compacted = await compressHistory(history, synthesizerModel, {
|
|
2480
|
+
thresholdTokens: cfg.compaction.thresholdTokens,
|
|
2481
|
+
keepRecent: cfg.compaction.keepRecentTurns,
|
|
2482
|
+
summaryMaxTokens: cfg.budgets.compaction,
|
|
2483
|
+
timeoutMs: cfg.chatTimeoutMs,
|
|
2484
|
+
abortSignal: opts.signal,
|
|
2485
|
+
onUsage: (u) => bumpUsage(u, "compact")
|
|
2486
|
+
});
|
|
2487
|
+
if (compacted !== history) {
|
|
2488
|
+
history = compacted;
|
|
2489
|
+
await config.memory.replace(sessionId, compacted).catch(() => {
|
|
2490
|
+
});
|
|
2491
|
+
emit({
|
|
2492
|
+
type: "context.compacted",
|
|
2493
|
+
scope: "history",
|
|
2494
|
+
beforeTokens: before,
|
|
2495
|
+
afterTokens: messageTokens(compacted)
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
1218
2500
|
const ctx = {
|
|
1219
2501
|
config: cfg,
|
|
1220
2502
|
raw: config,
|
|
@@ -1224,24 +2506,42 @@ var createAgent = async (config) => {
|
|
|
1224
2506
|
plannerMode,
|
|
1225
2507
|
executorMode,
|
|
1226
2508
|
tools,
|
|
1227
|
-
toolCatalog,
|
|
2509
|
+
toolCatalog: plannerCatalog,
|
|
1228
2510
|
prompts,
|
|
1229
2511
|
emit,
|
|
1230
2512
|
log,
|
|
1231
2513
|
signal: opts.signal,
|
|
1232
2514
|
state,
|
|
1233
|
-
history
|
|
2515
|
+
history,
|
|
2516
|
+
strategy,
|
|
2517
|
+
caching,
|
|
2518
|
+
skills,
|
|
2519
|
+
activeSkills: () => skills.filter((s) => activeSkillNames.has(s.name)),
|
|
2520
|
+
discovered: () => discovered,
|
|
2521
|
+
builtinTools,
|
|
2522
|
+
toolHint,
|
|
2523
|
+
usageSoFar: () => usage,
|
|
2524
|
+
overBudget: (extra) => breach(extra) !== void 0,
|
|
2525
|
+
setCurrentStep: (step) => {
|
|
2526
|
+
currentStep = step;
|
|
2527
|
+
},
|
|
2528
|
+
images: images.length ? images : void 0
|
|
2529
|
+
};
|
|
2530
|
+
await remember({
|
|
2531
|
+
role: "user",
|
|
2532
|
+
content: images.length ? `${text}
|
|
2533
|
+
[attached: ${images.map((f, i) => f.name ?? `${attachmentKind(f)} ${i + 1}`).join(", ")}]` : text
|
|
2534
|
+
});
|
|
2535
|
+
log.info("run:", text, images.length ? `(+${images.length} image(s))` : "");
|
|
2536
|
+
const stop = () => {
|
|
2537
|
+
emit({ type: "stopped" });
|
|
2538
|
+
result.stopped = true;
|
|
2539
|
+
return result;
|
|
1234
2540
|
};
|
|
1235
|
-
await remember({ role: "user", content: text });
|
|
1236
|
-
log.info("run:", text);
|
|
1237
2541
|
try {
|
|
1238
2542
|
let planned = await createPlan(ctx, text);
|
|
1239
2543
|
bumpUsage(planned.usage, "plan");
|
|
1240
|
-
if (isAborted())
|
|
1241
|
-
emit({ type: "stopped" });
|
|
1242
|
-
result.stopped = true;
|
|
1243
|
-
return result;
|
|
1244
|
-
}
|
|
2544
|
+
if (isAborted()) return stop();
|
|
1245
2545
|
if (planned.plan.steps.length === 0 && text.split(/\s+/).length > 2) {
|
|
1246
2546
|
log.warn(
|
|
1247
2547
|
"planner returned no steps for a multi-word goal \u2014 retrying with a nudge. thought:",
|
|
@@ -1250,17 +2550,13 @@ var createAgent = async (config) => {
|
|
|
1250
2550
|
const retried = await createPlan(
|
|
1251
2551
|
ctx,
|
|
1252
2552
|
text,
|
|
1253
|
-
"NOTE: If the message above asks to build, add, change, clear or
|
|
2553
|
+
"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."
|
|
1254
2554
|
);
|
|
1255
2555
|
bumpUsage(retried.usage, "plan");
|
|
1256
2556
|
if (retried.plan.steps.length > 0) planned = retried;
|
|
1257
2557
|
}
|
|
1258
2558
|
result.plan = planned.plan;
|
|
1259
|
-
if (isAborted())
|
|
1260
|
-
emit({ type: "stopped" });
|
|
1261
|
-
result.stopped = true;
|
|
1262
|
-
return result;
|
|
1263
|
-
}
|
|
2559
|
+
if (isAborted()) return stop();
|
|
1264
2560
|
if (planned.plan.steps.length === 0) {
|
|
1265
2561
|
log.info("no plan \u2014 answering directly (no tools will run)");
|
|
1266
2562
|
const answer = planned.plan.thought.trim() || "Tell me what you'd like to do.";
|
|
@@ -1275,19 +2571,25 @@ var createAgent = async (config) => {
|
|
|
1275
2571
|
);
|
|
1276
2572
|
emit({ type: "plan.created", plan: planned.plan });
|
|
1277
2573
|
planned.plan.steps.forEach((step, index) => emit({ type: "plan.step-added", step, index }));
|
|
1278
|
-
const
|
|
2574
|
+
for (const name of planned.skills) activateSkill(name, "plan");
|
|
2575
|
+
let done = [];
|
|
2576
|
+
const findings = [];
|
|
1279
2577
|
let remaining = [...planned.plan.steps];
|
|
1280
2578
|
let iter = 0;
|
|
1281
2579
|
let revisions = 0;
|
|
1282
2580
|
while (remaining.length > 0 && iter < cfg.maxIterations) {
|
|
1283
|
-
if (isAborted())
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
2581
|
+
if (isAborted()) return stop();
|
|
2582
|
+
const hit = breach();
|
|
2583
|
+
if (hit) {
|
|
2584
|
+
budgetExceeded = hit;
|
|
2585
|
+
result.budgetExceeded = hit;
|
|
2586
|
+
log.warn(`${hit.kind} limit reached (${hit.tokens}/${hit.cap}) \u2014 writing the answer now`);
|
|
2587
|
+
emit({ type: "budget.exceeded", ...hit });
|
|
2588
|
+
break;
|
|
1287
2589
|
}
|
|
1288
2590
|
iter += 1;
|
|
1289
2591
|
const step = remaining.shift();
|
|
1290
|
-
const stepNo =
|
|
2592
|
+
const stepNo = result.trace.length + 1;
|
|
1291
2593
|
const total = stepNo + remaining.length;
|
|
1292
2594
|
emit({ type: "step.start", step, index: stepNo, total });
|
|
1293
2595
|
let stepResult;
|
|
@@ -1296,13 +2598,12 @@ var createAgent = async (config) => {
|
|
|
1296
2598
|
bumpUsage(out.usage, "execute");
|
|
1297
2599
|
stepResult = out.result;
|
|
1298
2600
|
} catch (err) {
|
|
1299
|
-
if (isAborted())
|
|
1300
|
-
|
|
1301
|
-
result.stopped = true;
|
|
1302
|
-
return result;
|
|
1303
|
-
}
|
|
2601
|
+
if (isAborted()) return stop();
|
|
2602
|
+
if (err instanceof AttachmentsNotSupportedError) throw err;
|
|
1304
2603
|
emit({ type: "error", phase: "execute", error: errMessage(err) });
|
|
1305
2604
|
stepResult = { step, summary: errMessage(err), toolCalls: [], blocked: true };
|
|
2605
|
+
} finally {
|
|
2606
|
+
currentStep = void 0;
|
|
1306
2607
|
}
|
|
1307
2608
|
result.trace.push(stepResult);
|
|
1308
2609
|
const applied = stepResult.toolCalls.filter((c) => c.ok).length;
|
|
@@ -1318,10 +2619,38 @@ var createAgent = async (config) => {
|
|
|
1318
2619
|
stepResult.toolCalls.filter((c) => !c.ok).forEach((c) => log.warn(`tool ${c.name} failed:`, c.output));
|
|
1319
2620
|
log.debug(`step ${stepNo} detail:`, stepResult);
|
|
1320
2621
|
emit({ type: "step.complete", step, result: stepResult });
|
|
2622
|
+
const counts = stepResult.toolCalls.length ? ` [${applied} tool call(s) ok${failed ? `, ${failed} failed` : ""}]` : "";
|
|
1321
2623
|
done.push(
|
|
1322
|
-
`${step.description} \u2014 ${
|
|
2624
|
+
`${step.description} \u2014 ${clip(stepResult.summary, FINDING_CHARS)}${counts}${stepResult.blocked ? " [blocked]" : ""}`
|
|
1323
2625
|
);
|
|
1324
|
-
const
|
|
2626
|
+
for (const c of stepResult.toolCalls) {
|
|
2627
|
+
if (!c.ok || builtinTools.has(c.name)) continue;
|
|
2628
|
+
findings.push(`- ${c.name}: ${clip(asJson2(c.output), FINDING_CHARS)}`);
|
|
2629
|
+
}
|
|
2630
|
+
while (findings.join("\n").length > FINDINGS_BUDGET && findings.length > 1) findings.shift();
|
|
2631
|
+
if (cfg.compaction.auto) {
|
|
2632
|
+
const before = estimateTokens(done.join("\n"));
|
|
2633
|
+
if (before > cfg.compaction.thresholdTokens) {
|
|
2634
|
+
const compacted = await compactSteps(done, synthesizerModel, {
|
|
2635
|
+
thresholdTokens: cfg.compaction.thresholdTokens,
|
|
2636
|
+
keepRecent: cfg.compaction.keepRecentSteps,
|
|
2637
|
+
summaryMaxTokens: cfg.budgets.compaction,
|
|
2638
|
+
timeoutMs: cfg.chatTimeoutMs,
|
|
2639
|
+
abortSignal: opts.signal,
|
|
2640
|
+
onUsage: (u) => bumpUsage(u, "compact")
|
|
2641
|
+
});
|
|
2642
|
+
if (compacted !== done) {
|
|
2643
|
+
done = compacted;
|
|
2644
|
+
emit({
|
|
2645
|
+
type: "context.compacted",
|
|
2646
|
+
scope: "trace",
|
|
2647
|
+
beforeTokens: before,
|
|
2648
|
+
afterTokens: estimateTokens(done.join("\n"))
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
const wantReplan = cfg.replan && iter < cfg.maxIterations && revisions < cfg.maxRevisions && !isAborted() && !breach() && await replanWanted(cfg.replanAfter, stepResult, {
|
|
1325
2654
|
signal: ctx.signal,
|
|
1326
2655
|
timeoutMs: cfg.chatTimeoutMs,
|
|
1327
2656
|
onError: (err) => log.warn("replanAfter predicate threw \u2014 using the failure rule", err)
|
|
@@ -1338,7 +2667,7 @@ var createAgent = async (config) => {
|
|
|
1338
2667
|
if (decision.decision === "finish") break;
|
|
1339
2668
|
if (decision.decision === "revise" && decision.plan.length > 0) {
|
|
1340
2669
|
revisions += 1;
|
|
1341
|
-
remaining = decision.plan.slice(0, cfg.maxIterations - iter).map((description, i) => ({ id: `r${iter}-${i + 1}`, description }));
|
|
2670
|
+
remaining = decision.plan.slice(0, Math.min(cfg.maxPlanSteps, cfg.maxIterations - iter)).map((description, i) => ({ id: `r${iter}-${i + 1}`, description }));
|
|
1342
2671
|
emit({
|
|
1343
2672
|
type: "plan.revised",
|
|
1344
2673
|
plan: { thought: planned.plan.thought, steps: remaining },
|
|
@@ -1347,81 +2676,371 @@ var createAgent = async (config) => {
|
|
|
1347
2676
|
}
|
|
1348
2677
|
}
|
|
1349
2678
|
}
|
|
1350
|
-
if (
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
2679
|
+
if (!budgetExceeded && remaining.length > 0) {
|
|
2680
|
+
const hit = breach();
|
|
2681
|
+
if (hit) {
|
|
2682
|
+
budgetExceeded = hit;
|
|
2683
|
+
result.budgetExceeded = hit;
|
|
2684
|
+
emit({ type: "budget.exceeded", ...hit });
|
|
2685
|
+
}
|
|
1354
2686
|
}
|
|
2687
|
+
if (isAborted()) return stop();
|
|
1355
2688
|
let summary = "Done \u2014 the changes have been applied.";
|
|
1356
2689
|
if (cfg.synthesize) {
|
|
1357
2690
|
try {
|
|
1358
|
-
const synth = await synthesizeAnswer(ctx, text, done);
|
|
2691
|
+
const synth = await synthesizeAnswer(ctx, text, done, findings);
|
|
1359
2692
|
bumpUsage(synth.usage, "synthesize");
|
|
1360
2693
|
if (synth.text) summary = synth.text;
|
|
1361
|
-
} catch {
|
|
2694
|
+
} catch (err) {
|
|
2695
|
+
if (isAborted()) return stop();
|
|
2696
|
+
if (err instanceof AttachmentsNotSupportedError) throw err;
|
|
1362
2697
|
}
|
|
1363
2698
|
}
|
|
1364
2699
|
log.info(`done \u2014 ${result.applied} tool call(s) applied over ${result.steps} step(s)`);
|
|
1365
2700
|
emit({ type: "final", text: summary });
|
|
1366
2701
|
await remember({ role: "assistant", content: summary });
|
|
1367
2702
|
result.final = summary;
|
|
1368
|
-
if (config.memory && cfg.compressAfterChars > 0) {
|
|
2703
|
+
if (config.memory && (config.compaction ? cfg.compaction.auto : cfg.compressAfterChars > 0)) {
|
|
1369
2704
|
try {
|
|
1370
2705
|
const transcript = await config.memory.load(sessionId);
|
|
2706
|
+
const before = messageTokens(transcript);
|
|
1371
2707
|
const compacted = await compressHistory(transcript, synthesizerModel, {
|
|
1372
|
-
|
|
2708
|
+
...config.compaction ? {
|
|
2709
|
+
thresholdTokens: cfg.compaction.thresholdTokens,
|
|
2710
|
+
keepRecent: cfg.compaction.keepRecentTurns,
|
|
2711
|
+
summaryMaxTokens: cfg.budgets.compaction
|
|
2712
|
+
} : { maxChars: cfg.compressAfterChars },
|
|
1373
2713
|
timeoutMs: cfg.chatTimeoutMs,
|
|
1374
|
-
abortSignal: opts.signal
|
|
2714
|
+
abortSignal: opts.signal,
|
|
2715
|
+
onUsage: (u) => bumpUsage(u, "compact")
|
|
1375
2716
|
});
|
|
1376
|
-
if (compacted !== transcript)
|
|
2717
|
+
if (compacted !== transcript) {
|
|
2718
|
+
await config.memory.replace(sessionId, compacted);
|
|
2719
|
+
emit({
|
|
2720
|
+
type: "context.compacted",
|
|
2721
|
+
scope: "history",
|
|
2722
|
+
beforeTokens: before,
|
|
2723
|
+
afterTokens: messageTokens(compacted)
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
1377
2726
|
} catch {
|
|
1378
2727
|
}
|
|
1379
2728
|
}
|
|
1380
2729
|
return result;
|
|
1381
2730
|
} catch (err) {
|
|
2731
|
+
if (isAborted()) return stop();
|
|
1382
2732
|
emit({ type: "error", phase: "run", error: errMessage(err) });
|
|
1383
2733
|
result.final = errMessage(err);
|
|
1384
2734
|
return result;
|
|
1385
2735
|
}
|
|
1386
2736
|
};
|
|
2737
|
+
const compact = async (o = {}) => {
|
|
2738
|
+
const empty = {
|
|
2739
|
+
compacted: false,
|
|
2740
|
+
beforeTokens: 0,
|
|
2741
|
+
afterTokens: 0,
|
|
2742
|
+
usage: emptyUsage()
|
|
2743
|
+
};
|
|
2744
|
+
if (!config.memory) return empty;
|
|
2745
|
+
const sessionId = o.sessionId ?? cfg.sessionId;
|
|
2746
|
+
const transcript = await config.memory.load(sessionId);
|
|
2747
|
+
const beforeTokens = messageTokens(transcript);
|
|
2748
|
+
let usage = emptyUsage();
|
|
2749
|
+
const compacted = await compressHistory(transcript, synthesizerModel, {
|
|
2750
|
+
thresholdTokens: cfg.compaction.thresholdTokens,
|
|
2751
|
+
keepRecent: cfg.compaction.keepRecentTurns,
|
|
2752
|
+
summaryMaxTokens: cfg.budgets.compaction,
|
|
2753
|
+
force: o.force ?? true,
|
|
2754
|
+
timeoutMs: cfg.chatTimeoutMs,
|
|
2755
|
+
abortSignal: o.signal,
|
|
2756
|
+
onUsage: (u) => {
|
|
2757
|
+
usage = addUsage(usage, u);
|
|
2758
|
+
}
|
|
2759
|
+
});
|
|
2760
|
+
if (compacted === transcript)
|
|
2761
|
+
return { ...empty, beforeTokens, afterTokens: beforeTokens, usage };
|
|
2762
|
+
await config.memory.replace(sessionId, compacted);
|
|
2763
|
+
return { compacted: true, beforeTokens, afterTokens: messageTokens(compacted), usage };
|
|
2764
|
+
};
|
|
1387
2765
|
return {
|
|
1388
2766
|
run,
|
|
2767
|
+
compact,
|
|
2768
|
+
setToolApprovalMode: (mode) => {
|
|
2769
|
+
if (!TOOL_APPROVAL_MODES.includes(mode)) {
|
|
2770
|
+
throw new Error(`unknown tool approval mode "${mode}" (${TOOL_APPROVAL_MODES.join(" | ")})`);
|
|
2771
|
+
}
|
|
2772
|
+
approval.mode = mode;
|
|
2773
|
+
log.info(`tool approval mode \u2192 ${mode}`);
|
|
2774
|
+
},
|
|
2775
|
+
get toolApprovalMode() {
|
|
2776
|
+
return approval.mode;
|
|
2777
|
+
},
|
|
2778
|
+
listTools: () => catalog.map((e) => ({ ...e })),
|
|
2779
|
+
toolStrategy: strategy,
|
|
2780
|
+
skills: skills.map((s) => ({ name: s.name, description: s.description })),
|
|
2781
|
+
capabilities: { images: accepts.image, pdf: accepts.pdf, files: accepts.file },
|
|
1389
2782
|
models: { planner: plannerModel, executor: executorModel, synthesizer: synthesizerModel }
|
|
1390
2783
|
};
|
|
1391
2784
|
};
|
|
1392
2785
|
|
|
1393
|
-
// src/
|
|
1394
|
-
|
|
2786
|
+
// src/subagent/tool.ts
|
|
2787
|
+
var import_ai7 = require("ai");
|
|
1395
2788
|
|
|
1396
|
-
// src/
|
|
1397
|
-
var
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
var
|
|
1407
|
-
|
|
1408
|
-
|
|
2789
|
+
// src/subagent/protocol.ts
|
|
2790
|
+
var toCloneable = (value) => {
|
|
2791
|
+
try {
|
|
2792
|
+
return JSON.parse(
|
|
2793
|
+
JSON.stringify(value, (_k, v) => v instanceof Error ? v.message : v) ?? "null"
|
|
2794
|
+
);
|
|
2795
|
+
} catch {
|
|
2796
|
+
return String(value);
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
var safePost = (target, message) => {
|
|
2800
|
+
try {
|
|
2801
|
+
target.postMessage(message);
|
|
2802
|
+
} catch {
|
|
2803
|
+
target.postMessage(toCloneable(message));
|
|
2804
|
+
}
|
|
2805
|
+
};
|
|
2806
|
+
|
|
2807
|
+
// src/subagent/tool.ts
|
|
2808
|
+
var abortError = () => {
|
|
2809
|
+
const e = new Error("The subagent was aborted");
|
|
2810
|
+
e.name = "AbortError";
|
|
2811
|
+
return e;
|
|
2812
|
+
};
|
|
2813
|
+
var createSemaphore = (size) => {
|
|
2814
|
+
let active = 0;
|
|
2815
|
+
const queue = [];
|
|
2816
|
+
return async (signal) => {
|
|
2817
|
+
if (active >= size) {
|
|
2818
|
+
await new Promise((resolve, reject) => {
|
|
2819
|
+
const go = () => {
|
|
2820
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2821
|
+
resolve();
|
|
2822
|
+
};
|
|
2823
|
+
const onAbort = () => {
|
|
2824
|
+
const i = queue.indexOf(go);
|
|
2825
|
+
if (i >= 0) queue.splice(i, 1);
|
|
2826
|
+
reject(abortError());
|
|
2827
|
+
};
|
|
2828
|
+
queue.push(go);
|
|
2829
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2832
|
+
active += 1;
|
|
2833
|
+
let released = false;
|
|
2834
|
+
return () => {
|
|
2835
|
+
if (released) return;
|
|
2836
|
+
released = true;
|
|
2837
|
+
active -= 1;
|
|
2838
|
+
queue.shift()?.();
|
|
2839
|
+
};
|
|
2840
|
+
};
|
|
2841
|
+
};
|
|
2842
|
+
var seq2 = 0;
|
|
2843
|
+
var createSubagentTool = (opts) => {
|
|
2844
|
+
if (!opts.config === !opts.worker) {
|
|
2845
|
+
throw new Error(
|
|
2846
|
+
`subagent "${opts.name}": pass exactly one of \`config\` (in-process) or \`worker\``
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2849
|
+
if (opts.worker && !opts.workerConfig) {
|
|
2850
|
+
throw new Error(`subagent "${opts.name}": a worker subagent needs \`workerConfig\``);
|
|
2851
|
+
}
|
|
2852
|
+
const acquire = createSemaphore(Math.max(1, opts.maxConcurrent ?? 4));
|
|
2853
|
+
const outputMax = opts.outputMaxChars ?? 8e3;
|
|
2854
|
+
const gatedHostTools = (approve) => Object.fromEntries(
|
|
2855
|
+
Object.entries(opts.tools ?? {}).map(([name, t2]) => {
|
|
2856
|
+
const execute = t2.execute;
|
|
2857
|
+
if (typeof execute !== "function" || !approve) return [name, t2];
|
|
2858
|
+
return [
|
|
2859
|
+
name,
|
|
2860
|
+
{
|
|
2861
|
+
...t2,
|
|
2862
|
+
execute: async (input, o) => {
|
|
2863
|
+
await approve(name, input, isReadOnlyTool(t2));
|
|
2864
|
+
return execute.call(t2, input, o);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
];
|
|
2868
|
+
})
|
|
2869
|
+
);
|
|
2870
|
+
const runInProcess = async (task, signal, forward, hostTools) => {
|
|
2871
|
+
const config = opts.config;
|
|
2872
|
+
const agent = await createAgent({ ...config, tools: { ...config.tools, ...hostTools } });
|
|
2873
|
+
let runError;
|
|
2874
|
+
const result = await agent.run(task, {
|
|
2875
|
+
signal,
|
|
2876
|
+
onEvent: (e) => {
|
|
2877
|
+
if (e.type === "error" && e.phase === "run") runError = e.error;
|
|
2878
|
+
forward(e);
|
|
2879
|
+
}
|
|
2880
|
+
});
|
|
2881
|
+
if (result.stopped) throw abortError();
|
|
2882
|
+
if (runError) throw new Error(runError);
|
|
2883
|
+
return { text: result.final, usage: result.usage };
|
|
2884
|
+
};
|
|
2885
|
+
const proxiedSpecs = async () => Promise.all(
|
|
2886
|
+
Object.entries(opts.tools ?? {}).map(async ([name, t2]) => ({
|
|
2887
|
+
name,
|
|
2888
|
+
description: typeof t2.description === "string" ? t2.description : "",
|
|
2889
|
+
inputSchema: toCloneable(await (0, import_ai7.asSchema)(t2.inputSchema).jsonSchema),
|
|
2890
|
+
readOnly: isReadOnlyTool(t2)
|
|
2891
|
+
}))
|
|
2892
|
+
);
|
|
2893
|
+
const workerConfigFor = async () => {
|
|
2894
|
+
const cfg = opts.workerConfig;
|
|
2895
|
+
let model = { ...cfg.model };
|
|
2896
|
+
if (model.credentialRef && !model.apiKey && opts.credentials) {
|
|
2897
|
+
const apiKey = await opts.credentials.getApiKey(model.credentialRef);
|
|
2898
|
+
if (!apiKey)
|
|
2899
|
+
throw new Error(`no API key stored under "${model.credentialRef}" for the subagent`);
|
|
2900
|
+
model = { ...model, apiKey };
|
|
2901
|
+
}
|
|
2902
|
+
return toCloneable({ ...cfg, model });
|
|
2903
|
+
};
|
|
2904
|
+
const runInWorker = async (task, signal, forward, hostTools) => {
|
|
2905
|
+
if (signal?.aborted) throw abortError();
|
|
2906
|
+
const [config, proxied] = await Promise.all([workerConfigFor(), proxiedSpecs()]);
|
|
2907
|
+
const worker = opts.worker();
|
|
2908
|
+
return new Promise((resolve, reject) => {
|
|
2909
|
+
let settled = false;
|
|
2910
|
+
const finish = (fn) => {
|
|
2911
|
+
if (settled) return;
|
|
2912
|
+
settled = true;
|
|
2913
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2914
|
+
worker.removeEventListener?.("message", onMessage);
|
|
2915
|
+
worker.removeEventListener?.("error", onError);
|
|
2916
|
+
worker.terminate?.();
|
|
2917
|
+
fn();
|
|
2918
|
+
};
|
|
2919
|
+
const onAbort = () => {
|
|
2920
|
+
safePost(worker, { type: "abort" });
|
|
2921
|
+
finish(() => reject(abortError()));
|
|
2922
|
+
};
|
|
2923
|
+
const onError = (ev) => finish(() => reject(new Error(`subagent worker failed: ${ev?.message ?? "unknown error"}`)));
|
|
2924
|
+
const callHostTool = async (callId, name, input) => {
|
|
2925
|
+
const t2 = hostTools[name];
|
|
2926
|
+
try {
|
|
2927
|
+
if (!t2 || typeof t2.execute !== "function") throw new Error(`unknown host tool "${name}"`);
|
|
2928
|
+
const output = await t2.execute(input, {
|
|
2929
|
+
toolCallId: callId,
|
|
2930
|
+
messages: [],
|
|
2931
|
+
abortSignal: signal
|
|
2932
|
+
});
|
|
2933
|
+
if (!settled) safePost(worker, { type: "tool-result", callId, ok: true, output });
|
|
2934
|
+
} catch (err) {
|
|
2935
|
+
const output = err instanceof Error ? err.message : String(err);
|
|
2936
|
+
if (!settled) safePost(worker, { type: "tool-result", callId, ok: false, output });
|
|
2937
|
+
}
|
|
2938
|
+
};
|
|
2939
|
+
const onMessage = (ev) => {
|
|
2940
|
+
const msg = ev.data;
|
|
2941
|
+
switch (msg?.type) {
|
|
2942
|
+
case "event":
|
|
2943
|
+
forward(msg.event);
|
|
2944
|
+
break;
|
|
2945
|
+
case "tool-call":
|
|
2946
|
+
void callHostTool(msg.callId, msg.name, msg.input);
|
|
2947
|
+
break;
|
|
2948
|
+
case "done":
|
|
2949
|
+
finish(() => resolve({ text: msg.text, usage: msg.usage }));
|
|
2950
|
+
break;
|
|
2951
|
+
case "error":
|
|
2952
|
+
finish(() => reject(new Error(msg.error)));
|
|
2953
|
+
break;
|
|
2954
|
+
}
|
|
2955
|
+
};
|
|
2956
|
+
worker.addEventListener("message", onMessage);
|
|
2957
|
+
worker.addEventListener("error", onError);
|
|
2958
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2959
|
+
safePost(worker, { type: "run", task, config, proxied });
|
|
2960
|
+
});
|
|
2961
|
+
};
|
|
2962
|
+
const t = (0, import_ai7.tool)({
|
|
2963
|
+
description: opts.description,
|
|
2964
|
+
inputSchema: (0, import_ai7.jsonSchema)({
|
|
2965
|
+
type: "object",
|
|
2966
|
+
properties: {
|
|
2967
|
+
task: {
|
|
2968
|
+
type: "string",
|
|
2969
|
+
description: "A complete, self-contained task: the subagent sees nothing of your context except this text."
|
|
2970
|
+
}
|
|
2971
|
+
},
|
|
2972
|
+
required: ["task"],
|
|
2973
|
+
additionalProperties: false
|
|
2974
|
+
}),
|
|
2975
|
+
execute: async ({ task }, options) => {
|
|
2976
|
+
const runCtx = toolRunContextOf(options);
|
|
2977
|
+
const parentSignal = options?.abortSignal ?? runCtx?.signal;
|
|
2978
|
+
const id = `${opts.name}-${seq2 += 1}`;
|
|
2979
|
+
const release = await acquire(parentSignal);
|
|
2980
|
+
const emit = (e) => runCtx?.emit(e);
|
|
2981
|
+
const controller = new AbortController();
|
|
2982
|
+
const onParentAbort = () => controller.abort();
|
|
2983
|
+
parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
2984
|
+
if (parentSignal?.aborted) controller.abort();
|
|
2985
|
+
let timedOut = false;
|
|
2986
|
+
const timer = opts.timeoutMs && opts.timeoutMs > 0 ? setTimeout(() => {
|
|
2987
|
+
timedOut = true;
|
|
2988
|
+
controller.abort();
|
|
2989
|
+
}, opts.timeoutMs) : void 0;
|
|
2990
|
+
emit({ type: "subagent.start", id, name: opts.name, task });
|
|
2991
|
+
try {
|
|
2992
|
+
const forward = (event) => emit({ type: "subagent.event", id, name: opts.name, event });
|
|
2993
|
+
const hostTools = gatedHostTools(runCtx?.approve);
|
|
2994
|
+
const run = opts.worker ? runInWorker : runInProcess;
|
|
2995
|
+
const out = await run(task, controller.signal, forward, hostTools).catch((err) => {
|
|
2996
|
+
if (timedOut)
|
|
2997
|
+
throw new Error(`subagent "${opts.name}" timed out after ${opts.timeoutMs} ms`);
|
|
2998
|
+
throw err;
|
|
2999
|
+
});
|
|
3000
|
+
runCtx?.addUsage(out.usage);
|
|
3001
|
+
emit({ type: "subagent.complete", id, name: opts.name, text: out.text, usage: out.usage });
|
|
3002
|
+
return clip(out.text, outputMax);
|
|
3003
|
+
} catch (err) {
|
|
3004
|
+
emit({
|
|
3005
|
+
type: "subagent.error",
|
|
3006
|
+
id,
|
|
3007
|
+
name: opts.name,
|
|
3008
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3009
|
+
});
|
|
3010
|
+
throw err;
|
|
3011
|
+
} finally {
|
|
3012
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
3013
|
+
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
3014
|
+
release();
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
});
|
|
3018
|
+
t.promptHint = "{ task: string }";
|
|
3019
|
+
if (opts.readOnly) t.readOnly = true;
|
|
3020
|
+
return t;
|
|
3021
|
+
};
|
|
3022
|
+
|
|
3023
|
+
// src/subagent/worker.ts
|
|
3024
|
+
var import_ai8 = require("ai");
|
|
1409
3025
|
|
|
1410
3026
|
// src/storage/db.ts
|
|
1411
3027
|
var import_idb = require("idb");
|
|
1412
3028
|
var KEYS_STORE = "keys";
|
|
1413
3029
|
var SECRETS_STORE = "secrets";
|
|
1414
3030
|
var SESSIONS_STORE = "sessions";
|
|
3031
|
+
var FILES_STORE = "files";
|
|
3032
|
+
var DB_VERSION = 2;
|
|
1415
3033
|
var cache = /* @__PURE__ */ new Map();
|
|
1416
3034
|
var openAgentWebDB = (opts = {}) => {
|
|
1417
3035
|
const name = opts.dbName ?? "agent-web";
|
|
1418
3036
|
let p = cache.get(name);
|
|
1419
3037
|
if (!p) {
|
|
1420
|
-
p = (0, import_idb.openDB)(name,
|
|
3038
|
+
p = (0, import_idb.openDB)(name, DB_VERSION, {
|
|
1421
3039
|
upgrade(db) {
|
|
1422
3040
|
if (!db.objectStoreNames.contains(KEYS_STORE)) db.createObjectStore(KEYS_STORE);
|
|
1423
3041
|
if (!db.objectStoreNames.contains(SECRETS_STORE)) db.createObjectStore(SECRETS_STORE);
|
|
1424
3042
|
if (!db.objectStoreNames.contains(SESSIONS_STORE)) db.createObjectStore(SESSIONS_STORE);
|
|
3043
|
+
if (!db.objectStoreNames.contains(FILES_STORE)) db.createObjectStore(FILES_STORE);
|
|
1425
3044
|
}
|
|
1426
3045
|
});
|
|
1427
3046
|
cache.set(name, p);
|
|
@@ -1553,12 +3172,336 @@ var MemoryCredentialStore = class {
|
|
|
1553
3172
|
}
|
|
1554
3173
|
};
|
|
1555
3174
|
|
|
3175
|
+
// src/subagent/worker.ts
|
|
3176
|
+
var SUBAGENT_REF = "__subagent__";
|
|
3177
|
+
var defaultResolveModel = (spec) => {
|
|
3178
|
+
const { apiKey, ...rest } = spec;
|
|
3179
|
+
const credentials = apiKey ? new MemoryCredentialStore({ [SUBAGENT_REF]: apiKey }) : void 0;
|
|
3180
|
+
return buildModelFromStage(
|
|
3181
|
+
resolveStage(apiKey ? { ...rest, credentialRef: SUBAGENT_REF } : rest, void 0, "subagent"),
|
|
3182
|
+
credentials
|
|
3183
|
+
);
|
|
3184
|
+
};
|
|
3185
|
+
var serveSubagentWorker = (opts = {}) => {
|
|
3186
|
+
const scope = opts.scope ?? globalThis;
|
|
3187
|
+
let controller;
|
|
3188
|
+
const pending = /* @__PURE__ */ new Map();
|
|
3189
|
+
let callSeq = 0;
|
|
3190
|
+
const proxy = (spec) => {
|
|
3191
|
+
const t = (0, import_ai8.dynamicTool)({
|
|
3192
|
+
description: spec.description,
|
|
3193
|
+
inputSchema: (0, import_ai8.jsonSchema)(spec.inputSchema),
|
|
3194
|
+
execute: (input, options) => new Promise((resolve, reject) => {
|
|
3195
|
+
const callId = `call-${callSeq += 1}`;
|
|
3196
|
+
const signal = options?.abortSignal;
|
|
3197
|
+
if (signal?.aborted) return reject(new Error("aborted"));
|
|
3198
|
+
pending.set(callId, { resolve, reject });
|
|
3199
|
+
signal?.addEventListener(
|
|
3200
|
+
"abort",
|
|
3201
|
+
() => {
|
|
3202
|
+
if (pending.delete(callId)) reject(new Error("aborted"));
|
|
3203
|
+
},
|
|
3204
|
+
{ once: true }
|
|
3205
|
+
);
|
|
3206
|
+
safePost(scope, { type: "tool-call", callId, name: spec.name, input });
|
|
3207
|
+
})
|
|
3208
|
+
});
|
|
3209
|
+
return spec.readOnly ? markReadOnly(t) : t;
|
|
3210
|
+
};
|
|
3211
|
+
const run = async (msg) => {
|
|
3212
|
+
controller = new AbortController();
|
|
3213
|
+
let runError;
|
|
3214
|
+
try {
|
|
3215
|
+
const { model: spec, ...rest } = msg.config;
|
|
3216
|
+
const model = await (opts.resolveModel ?? defaultResolveModel)(spec);
|
|
3217
|
+
const proxied = Object.fromEntries(msg.proxied.map((p) => [p.name, proxy(p)]));
|
|
3218
|
+
const agent = await createAgent({
|
|
3219
|
+
...rest,
|
|
3220
|
+
model,
|
|
3221
|
+
tools: { ...opts.tools, ...proxied }
|
|
3222
|
+
});
|
|
3223
|
+
const result = await agent.run(msg.task, {
|
|
3224
|
+
signal: controller.signal,
|
|
3225
|
+
onEvent: (event) => {
|
|
3226
|
+
if (event.type === "error" && event.phase === "run") runError = event.error;
|
|
3227
|
+
safePost(scope, { type: "event", event });
|
|
3228
|
+
}
|
|
3229
|
+
});
|
|
3230
|
+
if (result.stopped) safePost(scope, { type: "error", error: "aborted" });
|
|
3231
|
+
else if (runError) safePost(scope, { type: "error", error: runError });
|
|
3232
|
+
else
|
|
3233
|
+
safePost(scope, {
|
|
3234
|
+
type: "done",
|
|
3235
|
+
text: result.final,
|
|
3236
|
+
usage: result.usage,
|
|
3237
|
+
steps: result.steps
|
|
3238
|
+
});
|
|
3239
|
+
} catch (err) {
|
|
3240
|
+
safePost(scope, { type: "error", error: err instanceof Error ? err.message : String(err) });
|
|
3241
|
+
}
|
|
3242
|
+
};
|
|
3243
|
+
scope.addEventListener("message", (ev) => {
|
|
3244
|
+
const msg = ev.data;
|
|
3245
|
+
switch (msg?.type) {
|
|
3246
|
+
case "run":
|
|
3247
|
+
void run(msg);
|
|
3248
|
+
break;
|
|
3249
|
+
case "abort":
|
|
3250
|
+
controller?.abort();
|
|
3251
|
+
for (const [id, p] of pending) {
|
|
3252
|
+
pending.delete(id);
|
|
3253
|
+
p.reject(new Error("aborted"));
|
|
3254
|
+
}
|
|
3255
|
+
break;
|
|
3256
|
+
case "tool-result": {
|
|
3257
|
+
const p = pending.get(msg.callId);
|
|
3258
|
+
if (!p) break;
|
|
3259
|
+
pending.delete(msg.callId);
|
|
3260
|
+
if (msg.ok) p.resolve(msg.output);
|
|
3261
|
+
else
|
|
3262
|
+
p.reject(
|
|
3263
|
+
new Error(typeof msg.output === "string" ? msg.output : JSON.stringify(msg.output))
|
|
3264
|
+
);
|
|
3265
|
+
break;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
3269
|
+
};
|
|
3270
|
+
|
|
3271
|
+
// src/files/vfs.ts
|
|
3272
|
+
var MIME = {
|
|
3273
|
+
txt: "text/plain",
|
|
3274
|
+
md: "text/markdown",
|
|
3275
|
+
json: "application/json",
|
|
3276
|
+
csv: "text/csv",
|
|
3277
|
+
html: "text/html",
|
|
3278
|
+
css: "text/css",
|
|
3279
|
+
js: "text/javascript",
|
|
3280
|
+
ts: "text/typescript",
|
|
3281
|
+
svg: "image/svg+xml",
|
|
3282
|
+
png: "image/png",
|
|
3283
|
+
jpg: "image/jpeg",
|
|
3284
|
+
jpeg: "image/jpeg",
|
|
3285
|
+
gif: "image/gif",
|
|
3286
|
+
webp: "image/webp",
|
|
3287
|
+
pdf: "application/pdf",
|
|
3288
|
+
xml: "application/xml",
|
|
3289
|
+
yaml: "text/yaml",
|
|
3290
|
+
yml: "text/yaml"
|
|
3291
|
+
};
|
|
3292
|
+
var mimeFromPath = (path) => MIME[path.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream";
|
|
3293
|
+
var isTextMime = (mime) => /^text\/|json|xml|yaml|javascript|typescript|svg/.test(mime);
|
|
3294
|
+
var normalizePath = (input) => {
|
|
3295
|
+
const parts = [];
|
|
3296
|
+
for (const seg of String(input ?? "").replace(/\\/g, "/").split("/")) {
|
|
3297
|
+
if (!seg || seg === ".") continue;
|
|
3298
|
+
if (seg === "..") {
|
|
3299
|
+
if (parts.length === 0) throw new Error(`path "${input}" escapes the root`);
|
|
3300
|
+
parts.pop();
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
parts.push(seg);
|
|
3304
|
+
}
|
|
3305
|
+
if (parts.length === 0) throw new Error("a file path is required");
|
|
3306
|
+
return `/${parts.join("/")}`;
|
|
3307
|
+
};
|
|
3308
|
+
var byteLength = (content, encoding) => encoding === "base64" ? Math.floor(content.replace(/=+$/, "").length * 3 / 4) : new TextEncoder().encode(content).length;
|
|
3309
|
+
var bytesToBase64 = (bytes) => {
|
|
3310
|
+
let bin = "";
|
|
3311
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
3312
|
+
bin += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
3313
|
+
}
|
|
3314
|
+
return btoa(bin);
|
|
3315
|
+
};
|
|
3316
|
+
var VirtualFileSystem = class {
|
|
3317
|
+
memory;
|
|
3318
|
+
dbName;
|
|
3319
|
+
ns;
|
|
3320
|
+
maxFileBytes;
|
|
3321
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3322
|
+
constructor(opts = {}) {
|
|
3323
|
+
if (opts.memory) this.memory = /* @__PURE__ */ new Map();
|
|
3324
|
+
this.dbName = opts.dbName;
|
|
3325
|
+
this.ns = opts.namespace ?? "default";
|
|
3326
|
+
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
3327
|
+
}
|
|
3328
|
+
key(path) {
|
|
3329
|
+
return `${this.ns}:${path}`;
|
|
3330
|
+
}
|
|
3331
|
+
/** Subscribe to writes and deletes (e.g. to refresh a file list). Returns an unsubscribe. */
|
|
3332
|
+
onChange(listener) {
|
|
3333
|
+
this.listeners.add(listener);
|
|
3334
|
+
return () => this.listeners.delete(listener);
|
|
3335
|
+
}
|
|
3336
|
+
notify(type, path) {
|
|
3337
|
+
for (const l of this.listeners) {
|
|
3338
|
+
try {
|
|
3339
|
+
l({ type, path });
|
|
3340
|
+
} catch {
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
async write(path, content, opts = {}) {
|
|
3345
|
+
const p = normalizePath(path);
|
|
3346
|
+
const binary = content instanceof Uint8Array;
|
|
3347
|
+
const encoding = binary ? "base64" : opts.encoding ?? "utf8";
|
|
3348
|
+
const text = binary ? bytesToBase64(content) : content;
|
|
3349
|
+
const size = binary ? content.length : byteLength(text, encoding);
|
|
3350
|
+
if (size > this.maxFileBytes) {
|
|
3351
|
+
throw new Error(`"${p}" is ${size} bytes; the limit is ${this.maxFileBytes}`);
|
|
3352
|
+
}
|
|
3353
|
+
const file = {
|
|
3354
|
+
path: p,
|
|
3355
|
+
content: text,
|
|
3356
|
+
encoding,
|
|
3357
|
+
mimeType: opts.mimeType ?? mimeFromPath(p),
|
|
3358
|
+
size,
|
|
3359
|
+
updatedAt: Date.now()
|
|
3360
|
+
};
|
|
3361
|
+
if (this.memory) this.memory.set(p, file);
|
|
3362
|
+
else {
|
|
3363
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
3364
|
+
await db.put(FILES_STORE, file, this.key(p));
|
|
3365
|
+
}
|
|
3366
|
+
this.notify("write", p);
|
|
3367
|
+
const { content: _drop, ...info } = file;
|
|
3368
|
+
return info;
|
|
3369
|
+
}
|
|
3370
|
+
/** Store a data URL ("data:image/png;base64,…") as a file. */
|
|
3371
|
+
async writeDataUrl(path, dataUrl) {
|
|
3372
|
+
const m = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(dataUrl);
|
|
3373
|
+
if (!m) throw new Error("not a data URL");
|
|
3374
|
+
const mimeType = m[1] ?? "application/octet-stream";
|
|
3375
|
+
return m[2] ? this.write(path, m[3], { mimeType, encoding: "base64" }) : this.write(path, decodeURIComponent(m[3]), { mimeType });
|
|
3376
|
+
}
|
|
3377
|
+
async read(path) {
|
|
3378
|
+
const p = normalizePath(path);
|
|
3379
|
+
if (this.memory) return this.memory.get(p);
|
|
3380
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
3381
|
+
return await db.get(FILES_STORE, this.key(p));
|
|
3382
|
+
}
|
|
3383
|
+
/** The file as a data URL (for <img src>, downloads). */
|
|
3384
|
+
async readDataUrl(path) {
|
|
3385
|
+
const f = await this.read(path);
|
|
3386
|
+
if (!f) return void 0;
|
|
3387
|
+
return f.encoding === "base64" ? `data:${f.mimeType};base64,${f.content}` : `data:${f.mimeType};charset=utf-8,${encodeURIComponent(f.content)}`;
|
|
3388
|
+
}
|
|
3389
|
+
async delete(path) {
|
|
3390
|
+
const p = normalizePath(path);
|
|
3391
|
+
let existed;
|
|
3392
|
+
if (this.memory) existed = this.memory.delete(p);
|
|
3393
|
+
else {
|
|
3394
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
3395
|
+
existed = await db.get(FILES_STORE, this.key(p)) !== void 0;
|
|
3396
|
+
await db.delete(FILES_STORE, this.key(p));
|
|
3397
|
+
}
|
|
3398
|
+
if (existed) this.notify("delete", p);
|
|
3399
|
+
return existed;
|
|
3400
|
+
}
|
|
3401
|
+
/** Files under a prefix ("/" = all), sorted by path. */
|
|
3402
|
+
async list(prefix = "/") {
|
|
3403
|
+
const dir = prefix === "/" ? "/" : `${normalizePath(prefix)}/`;
|
|
3404
|
+
let files;
|
|
3405
|
+
if (this.memory) files = [...this.memory.values()];
|
|
3406
|
+
else {
|
|
3407
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
3408
|
+
const range = IDBKeyRange.bound(`${this.ns}:`, `${this.ns}:\uFFFF`);
|
|
3409
|
+
files = await db.getAll(FILES_STORE, range);
|
|
3410
|
+
}
|
|
3411
|
+
return files.filter((f) => dir === "/" || f.path.startsWith(dir)).map(({ content: _drop, ...info }) => info).sort((a, b) => a.path.localeCompare(b.path));
|
|
3412
|
+
}
|
|
3413
|
+
/** Remove every file (of this namespace). */
|
|
3414
|
+
async clear() {
|
|
3415
|
+
for (const f of await this.list()) await this.delete(f.path);
|
|
3416
|
+
}
|
|
3417
|
+
};
|
|
3418
|
+
|
|
3419
|
+
// src/files/tools.ts
|
|
3420
|
+
var import_ai9 = require("ai");
|
|
3421
|
+
var createFileTools = (vfs, opts = {}) => {
|
|
3422
|
+
const p = opts.prefix ?? "fs_";
|
|
3423
|
+
const maxRead = opts.maxReadChars ?? 5e4;
|
|
3424
|
+
const tools = {
|
|
3425
|
+
[`${p}list`]: markReadOnly(
|
|
3426
|
+
(0, import_ai9.tool)({
|
|
3427
|
+
description: "List the files in the workspace (attachments the user added, files you wrote). Optionally only under a directory prefix.",
|
|
3428
|
+
inputSchema: (0, import_ai9.jsonSchema)({
|
|
3429
|
+
type: "object",
|
|
3430
|
+
properties: { prefix: { type: "string", description: 'Directory prefix, e.g. "/docs"' } },
|
|
3431
|
+
additionalProperties: false
|
|
3432
|
+
}),
|
|
3433
|
+
execute: async ({ prefix }) => (await vfs.list(prefix || "/")).map((f) => ({
|
|
3434
|
+
path: f.path,
|
|
3435
|
+
mimeType: f.mimeType,
|
|
3436
|
+
size: f.size
|
|
3437
|
+
}))
|
|
3438
|
+
})
|
|
3439
|
+
),
|
|
3440
|
+
[`${p}read`]: markReadOnly(
|
|
3441
|
+
(0, import_ai9.tool)({
|
|
3442
|
+
description: "Read a text file from the workspace by its path.",
|
|
3443
|
+
inputSchema: (0, import_ai9.jsonSchema)({
|
|
3444
|
+
type: "object",
|
|
3445
|
+
properties: { path: { type: "string", description: 'Absolute path, e.g. "/notes.md"' } },
|
|
3446
|
+
required: ["path"],
|
|
3447
|
+
additionalProperties: false
|
|
3448
|
+
}),
|
|
3449
|
+
execute: async ({ path }) => {
|
|
3450
|
+
const f = await vfs.read(path);
|
|
3451
|
+
if (!f) {
|
|
3452
|
+
const known = (await vfs.list()).map((x) => x.path).slice(0, 30);
|
|
3453
|
+
throw new Error(`no file "${path}"; files: ${known.join(", ") || "none"}`);
|
|
3454
|
+
}
|
|
3455
|
+
if (f.encoding === "base64" && !isTextMime(f.mimeType)) {
|
|
3456
|
+
return `"${f.path}" is a binary file (${f.mimeType}, ${f.size} bytes) and cannot be read as text.`;
|
|
3457
|
+
}
|
|
3458
|
+
const text = f.encoding === "base64" ? atob(f.content) : f.content;
|
|
3459
|
+
return text.length > maxRead ? `${text.slice(0, maxRead)}\u2026 [truncated ${text.length - maxRead} chars]` : text;
|
|
3460
|
+
}
|
|
3461
|
+
})
|
|
3462
|
+
)
|
|
3463
|
+
};
|
|
3464
|
+
if (opts.readOnly) return tools;
|
|
3465
|
+
tools[`${p}write`] = (0, import_ai9.tool)({
|
|
3466
|
+
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.",
|
|
3467
|
+
inputSchema: (0, import_ai9.jsonSchema)({
|
|
3468
|
+
type: "object",
|
|
3469
|
+
properties: {
|
|
3470
|
+
path: { type: "string", description: 'Absolute path, e.g. "/reports/summary.md"' },
|
|
3471
|
+
content: { type: "string", description: "The full file content" },
|
|
3472
|
+
mimeType: { type: "string", description: "Optional MIME type (guessed from the name)" }
|
|
3473
|
+
},
|
|
3474
|
+
required: ["path", "content"],
|
|
3475
|
+
additionalProperties: false
|
|
3476
|
+
}),
|
|
3477
|
+
execute: async ({ path, content, mimeType }) => {
|
|
3478
|
+
const info = await vfs.write(path, content, mimeType ? { mimeType } : {});
|
|
3479
|
+
return { written: info.path, size: info.size };
|
|
3480
|
+
}
|
|
3481
|
+
});
|
|
3482
|
+
tools[`${p}delete`] = (0, import_ai9.tool)({
|
|
3483
|
+
description: "Delete a file from the workspace.",
|
|
3484
|
+
inputSchema: (0, import_ai9.jsonSchema)({
|
|
3485
|
+
type: "object",
|
|
3486
|
+
properties: { path: { type: "string" } },
|
|
3487
|
+
required: ["path"],
|
|
3488
|
+
additionalProperties: false
|
|
3489
|
+
}),
|
|
3490
|
+
execute: async ({ path }) => ({ deleted: await vfs.delete(path) })
|
|
3491
|
+
});
|
|
3492
|
+
return tools;
|
|
3493
|
+
};
|
|
3494
|
+
|
|
3495
|
+
// src/index.ts
|
|
3496
|
+
init_webllm();
|
|
3497
|
+
|
|
1556
3498
|
// src/tools/define.ts
|
|
1557
|
-
var
|
|
3499
|
+
var import_ai10 = require("ai");
|
|
1558
3500
|
var defineTool = (config) => {
|
|
1559
|
-
const { promptHint, ...rest } = config;
|
|
1560
|
-
const t = (0,
|
|
3501
|
+
const { promptHint, readOnly, ...rest } = config;
|
|
3502
|
+
const t = (0, import_ai10.tool)(rest);
|
|
1561
3503
|
if (promptHint) t.promptHint = promptHint;
|
|
3504
|
+
if (readOnly) t.readOnly = true;
|
|
1562
3505
|
return t;
|
|
1563
3506
|
};
|
|
1564
3507
|
|
|
@@ -1613,6 +3556,10 @@ var IndexedDBStore = class {
|
|
|
1613
3556
|
};
|
|
1614
3557
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1615
3558
|
0 && (module.exports = {
|
|
3559
|
+
AttachmentsNotSupportedError,
|
|
3560
|
+
FILES_STORE,
|
|
3561
|
+
FIND_TOOLS_NAME,
|
|
3562
|
+
ImagesNotSupportedError,
|
|
1616
3563
|
IndexedDBStore,
|
|
1617
3564
|
IndexedDBVault,
|
|
1618
3565
|
KEYS_STORE,
|
|
@@ -1623,28 +3570,59 @@ var IndexedDBStore = class {
|
|
|
1623
3570
|
ReplanSchema,
|
|
1624
3571
|
SECRETS_STORE,
|
|
1625
3572
|
SESSIONS_STORE,
|
|
3573
|
+
SKILL_TOOL_NAMES,
|
|
3574
|
+
TOOL_APPROVAL_MODES,
|
|
3575
|
+
ToolBudgetError,
|
|
3576
|
+
ToolDeniedError,
|
|
1626
3577
|
VaultCredentialStore,
|
|
3578
|
+
VirtualFileSystem,
|
|
3579
|
+
attachmentKind,
|
|
1627
3580
|
buildModelFromStage,
|
|
3581
|
+
bytesToBase64,
|
|
3582
|
+
checkLimits,
|
|
3583
|
+
compactHistory,
|
|
3584
|
+
compactSteps,
|
|
1628
3585
|
compressHistory,
|
|
1629
3586
|
createAgent,
|
|
3587
|
+
createFileTools,
|
|
3588
|
+
createFindToolsTool,
|
|
1630
3589
|
createLogger,
|
|
1631
3590
|
createPlan,
|
|
3591
|
+
createSkillTools,
|
|
3592
|
+
createSubagentTool,
|
|
3593
|
+
createToolResultClearer,
|
|
1632
3594
|
createWebLLMModel,
|
|
1633
3595
|
decideReplan,
|
|
3596
|
+
decideToolPermission,
|
|
1634
3597
|
decryptJSON,
|
|
1635
3598
|
defaultPrompts,
|
|
3599
|
+
defineSkill,
|
|
1636
3600
|
defineTool,
|
|
1637
3601
|
directBrowserOk,
|
|
1638
3602
|
dispatch,
|
|
1639
3603
|
encryptJSON,
|
|
3604
|
+
estimateMessagesTokens,
|
|
3605
|
+
estimateTokens,
|
|
1640
3606
|
executeStep,
|
|
1641
3607
|
generate,
|
|
1642
3608
|
generateStructured,
|
|
1643
3609
|
getOrCreateVaultKey,
|
|
3610
|
+
isAttachmentRefusal,
|
|
1644
3611
|
isDirectModel,
|
|
3612
|
+
isImageRefusal,
|
|
1645
3613
|
isProviderSpec,
|
|
3614
|
+
isReadOnlyTool,
|
|
3615
|
+
isTextMime,
|
|
1646
3616
|
isWebGPUAvailable,
|
|
3617
|
+
limitModelOutput,
|
|
3618
|
+
loadSkillFromUrl,
|
|
1647
3619
|
looksLikeJson,
|
|
3620
|
+
markReadOnly,
|
|
3621
|
+
matchRule,
|
|
3622
|
+
mediaTypeOf,
|
|
3623
|
+
mergeProviderOptions,
|
|
3624
|
+
mimeFromPath,
|
|
3625
|
+
normalizePath,
|
|
1648
3626
|
normalizeSteps,
|
|
1649
3627
|
normalizeUsage,
|
|
1650
3628
|
openAgentWebDB,
|
|
@@ -1652,22 +3630,41 @@ var IndexedDBStore = class {
|
|
|
1652
3630
|
parsePlainText,
|
|
1653
3631
|
parsePlannerResponse,
|
|
1654
3632
|
parseReplannerResponse,
|
|
3633
|
+
parseSkillMarkdown,
|
|
1655
3634
|
preloadWebLLMModel,
|
|
1656
3635
|
promptHintOf,
|
|
3636
|
+
renderActiveSkills,
|
|
1657
3637
|
renderCatalog,
|
|
3638
|
+
renderSearchCatalog,
|
|
3639
|
+
renderSkillIndex,
|
|
1658
3640
|
replanWanted,
|
|
3641
|
+
resolveCaching,
|
|
3642
|
+
resolveCompaction,
|
|
1659
3643
|
resolveConfig,
|
|
1660
3644
|
resolveModel,
|
|
1661
3645
|
resolveStage,
|
|
3646
|
+
resolveThinking,
|
|
1662
3647
|
runToolLoop,
|
|
3648
|
+
schemaHintOf,
|
|
3649
|
+
searchTools,
|
|
1663
3650
|
selectToolMode,
|
|
3651
|
+
serveSubagentWorker,
|
|
1664
3652
|
shouldReplan,
|
|
1665
3653
|
splitBlocker,
|
|
1666
3654
|
stream,
|
|
3655
|
+
supportsImages,
|
|
1667
3656
|
supportsNativeTools,
|
|
3657
|
+
supportsPdf,
|
|
1668
3658
|
supportsStructuredOutput,
|
|
1669
3659
|
synthesizeAnswer,
|
|
3660
|
+
thinkingFor,
|
|
3661
|
+
toFilePart,
|
|
3662
|
+
toImagePart,
|
|
3663
|
+
tokenize,
|
|
3664
|
+
toolCatalogOf,
|
|
3665
|
+
toolRunContextOf,
|
|
1670
3666
|
unloadWebLLMModel,
|
|
3667
|
+
withRollingBreakpoint,
|
|
1671
3668
|
withSystem
|
|
1672
3669
|
});
|
|
1673
3670
|
//# sourceMappingURL=index.cjs.map
|