agent-accelerator 0.5.9 → 0.6.5
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 +64 -16
- package/examples/prompts/SYSTEM_PROMPT.md +2 -0
- package/examples/prompts/SYSTEM_PROMPT_AGENT.md +4 -0
- package/examples/prompts/SYSTEM_PROMPT_TOOLS.md +1 -0
- package/package.json +4 -1
- package/src/agent/agent.ts +447 -172
- package/src/agent/architecture.md +16 -12
- package/src/agent/config-blocks.ts +39 -12
- package/src/agent/context.ts +9 -3
- package/src/agent/defaults.ts +88 -0
- package/src/agent/delegation.ts +86 -28
- package/src/agent/loop.ts +474 -374
- package/src/agent/subagent.ts +1 -3
- package/src/agent/subagents-tool.ts +50 -19
- package/src/agent/turns.ts +3 -1
- package/src/agent/usage-limit.ts +92 -0
- package/src/agent/worker.ts +73 -30
- package/src/agent/workers.ts +126 -21
- package/src/architecture.md +17 -15
- package/src/index.ts +43 -19
- package/src/models/architecture.md +4 -3
- package/src/models/catalog-cache.ts +6 -5
- package/src/models/catalog.ts +39 -30
- package/src/prompts/additional-context.md +2 -0
- package/src/prompts/architecture.md +19 -0
- package/src/prompts/prompts.ts +81 -0
- package/src/prompts/reasoning-directive.md +5 -0
- package/src/{scheduler/scheduled-actions.md → prompts/scheduled-actions-tool.md} +4 -0
- package/src/prompts/structured-output.md +2 -0
- package/src/{agent/subagents.md → prompts/subagents-tool.md} +3 -0
- package/src/prompts/tool-bridge.md +1 -0
- package/src/prompts/tool-interrupted.md +1 -0
- package/src/prompts/tool-not-found.md +1 -0
- package/src/prompts/tool-repeat-blocked.md +1 -0
- package/src/prompts/tool-schema-error.md +1 -0
- package/src/prompts/tool-truncated-arguments.md +1 -0
- package/src/prompts/worker-failed.md +1 -0
- package/src/prompts/worker-replied.md +2 -0
- package/src/providers/architecture.md +10 -8
- package/src/providers/google.ts +228 -69
- package/src/providers/openai-compat.ts +74 -56
- package/src/providers/openai.ts +64 -41
- package/src/providers/openrouter.ts +270 -48
- package/src/providers/registry.ts +6 -21
- package/src/providers/shared.ts +87 -33
- package/src/providers.ts +115 -124
- package/src/scheduler/architecture.md +8 -7
- package/src/scheduler/scheduler.ts +226 -60
- package/src/scheduler/store.ts +23 -10
- package/src/scheduler/time.ts +17 -3
- package/src/scheduler/tool.ts +38 -16
- package/src/session/architecture.md +7 -6
- package/src/session/journal.ts +39 -12
- package/src/session/store.ts +126 -61
- package/src/session/usage.ts +58 -9
- package/src/streaming/architecture.md +4 -3
- package/src/streaming/event-stream.ts +26 -12
- package/src/telemetry.ts +6 -2
- package/src/tools/architecture.md +9 -7
- package/src/tools/executor.ts +109 -62
- package/src/tools/schema.ts +10 -6
- package/src/tools/subagent-result.ts +6 -1
- package/src/tools/tool-content.ts +32 -0
- package/src/tools/tool.ts +9 -11
- package/src/types/agent.ts +37 -22
- package/src/types/architecture.md +11 -10
- package/src/types/core.ts +5 -9
- package/src/types/errors.ts +26 -4
- package/src/types/message.ts +7 -0
- package/src/types/model.ts +4 -5
- package/src/types/scheduler.ts +4 -4
- package/src/types/tool.ts +14 -2
- package/src/update-models.ts +5 -5
- package/src/utils/architecture.md +16 -15
- package/src/utils/cache.ts +1 -3
- package/src/utils/durable.ts +17 -5
- package/src/utils/env.ts +9 -7
- package/src/utils/errors.ts +9 -17
- package/src/utils/headers.ts +3 -10
- package/src/utils/media.ts +23 -8
- package/src/utils/raw-capture.ts +9 -2
- package/src/utils/retry.ts +15 -3
- package/src/utils/safe-io.ts +29 -7
- package/src/utils/serialization.ts +1 -4
- package/src/utils/session.ts +1 -2
package/src/agent/workers.ts
CHANGED
|
@@ -15,9 +15,15 @@ import { sessionDirFor, loadSessionDir } from "../session/store.ts";
|
|
|
15
15
|
import { telemetryBus } from "../telemetry.ts";
|
|
16
16
|
import { hashSessionPart, newRunId, newTurnId, nowMs } from "../utils/session.ts";
|
|
17
17
|
import { buildDynamicWorkerConfig, grantWorkerTools, sanitizeToolName } from "./worker.ts";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
createFixedChildSessionId,
|
|
20
|
+
openWorkerTrace,
|
|
21
|
+
reserveSpawn,
|
|
22
|
+
workerMetadata,
|
|
23
|
+
type WorkerRunResult,
|
|
24
|
+
} from "./delegation.ts";
|
|
19
25
|
import { finishedTurns, historyUpTo, type Turn } from "./turns.ts";
|
|
20
|
-
import { modelName, readUsage, type UsageNode } from "../session/usage.ts";
|
|
26
|
+
import { mergeUsage, modelName, readUsage, type UsageNode } from "../session/usage.ts";
|
|
21
27
|
|
|
22
28
|
/** What `spawn` needs to start a worker. */
|
|
23
29
|
export interface WorkerSpec {
|
|
@@ -59,7 +65,13 @@ export interface WorkerCallOptions {
|
|
|
59
65
|
/** The calling tool call's id: makes the call exactly-once across a crash and replay (the `subagents` tool passes it). */
|
|
60
66
|
callId?: string;
|
|
61
67
|
/** Receives the worker's live text and thinking deltas. */
|
|
62
|
-
onSubagentEvent?: (event: {
|
|
68
|
+
onSubagentEvent?: (event: {
|
|
69
|
+
trackingId: string;
|
|
70
|
+
delta?: string;
|
|
71
|
+
thinkingDelta?: string;
|
|
72
|
+
partialText?: string;
|
|
73
|
+
partialThinking?: string;
|
|
74
|
+
}) => void;
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
// Saved in `worker.json`: everything needed to rebuild a worker after a restart.
|
|
@@ -104,7 +116,11 @@ function lastAssistantText(messages: Message[]): string | undefined {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
// Reads a worker that is not in memory from its folder: its saved spending, model, and the workers saved inside it; in: a worker's session folder, out: usageNodes.
|
|
107
|
-
function readSavedWorker(dir: string | undefined): {
|
|
119
|
+
function readSavedWorker(dir: string | undefined): {
|
|
120
|
+
model: string;
|
|
121
|
+
totals: ReturnType<typeof readUsage>;
|
|
122
|
+
workers: UsageNode[];
|
|
123
|
+
} {
|
|
108
124
|
if (!dir) return { model: "unknown", totals: readUsage(undefined), workers: [] };
|
|
109
125
|
let session: ReturnType<typeof loadSessionDir>;
|
|
110
126
|
try {
|
|
@@ -125,7 +141,15 @@ function readSavedWorker(dir: string | undefined): { model: string; totals: Retu
|
|
|
125
141
|
try {
|
|
126
142
|
const meta = JSON.parse(fs.readFileSync(path.join(root, d.name, META_FILE), "utf8")) as WorkerMeta;
|
|
127
143
|
const inner = readSavedWorker(path.join(root, d.name));
|
|
128
|
-
workers.push({
|
|
144
|
+
workers.push({
|
|
145
|
+
id: meta.id,
|
|
146
|
+
name: meta.name,
|
|
147
|
+
...(meta.role ? { role: meta.role } : {}),
|
|
148
|
+
model: inner.model,
|
|
149
|
+
status: meta.status,
|
|
150
|
+
totals: inner.totals,
|
|
151
|
+
workers: inner.workers,
|
|
152
|
+
});
|
|
129
153
|
} catch {
|
|
130
154
|
// a damaged worker.json is skipped, as in load()
|
|
131
155
|
}
|
|
@@ -133,6 +157,17 @@ function readSavedWorker(dir: string | undefined): { model: string; totals: Retu
|
|
|
133
157
|
return { model: session?.session.model || "unknown", totals: readUsage(session?.session.usage), workers };
|
|
134
158
|
}
|
|
135
159
|
|
|
160
|
+
/** What a saved session folder and every worker saved below it spent in total (read from disk): the starting point of a chat-wide usage cap after a restart; in: Agent.beginChat, out: usage totals. */
|
|
161
|
+
export function savedSpend(dir: string): ReturnType<typeof readUsage> {
|
|
162
|
+
const total = readUsage(undefined);
|
|
163
|
+
const add = (node: { totals: ReturnType<typeof readUsage>; workers: UsageNode[] }): void => {
|
|
164
|
+
mergeUsage(total, node.totals);
|
|
165
|
+
for (const w of node.workers) add(w);
|
|
166
|
+
};
|
|
167
|
+
add(readSavedWorker(dir));
|
|
168
|
+
return total;
|
|
169
|
+
}
|
|
170
|
+
|
|
136
171
|
/** The workers of one parent `Agent` (`agent.workers`). */
|
|
137
172
|
export class WorkerRegistry {
|
|
138
173
|
private readonly entries = new Map<string, Entry>();
|
|
@@ -167,10 +202,15 @@ export class WorkerRegistry {
|
|
|
167
202
|
const dir = path.join(root, d.name);
|
|
168
203
|
try {
|
|
169
204
|
const meta = JSON.parse(fs.readFileSync(path.join(dir, META_FILE), "utf8")) as WorkerMeta;
|
|
170
|
-
if (typeof meta.id !== "string" || typeof meta.sessionId !== "string" || !Array.isArray(meta.tools))
|
|
205
|
+
if (typeof meta.id !== "string" || typeof meta.sessionId !== "string" || !Array.isArray(meta.tools))
|
|
206
|
+
throw new Error("missing fields");
|
|
171
207
|
this.entries.set(meta.id, { meta, dir, busy: false });
|
|
172
208
|
} catch (err) {
|
|
173
|
-
telemetryBus.emitWarning(
|
|
209
|
+
telemetryBus.emitWarning(
|
|
210
|
+
"worker.invalid",
|
|
211
|
+
`[Agent Accelerator] ${path.join(dir, META_FILE)} is not a valid worker and is skipped: ${err instanceof Error ? err.message : String(err)}`,
|
|
212
|
+
{ dir }
|
|
213
|
+
);
|
|
174
214
|
}
|
|
175
215
|
}
|
|
176
216
|
}
|
|
@@ -219,11 +259,17 @@ export class WorkerRegistry {
|
|
|
219
259
|
/** Closes a worker: it can no longer be messaged, its session stays on disk (and in `usageReport`). */
|
|
220
260
|
close(id: string): WorkerInfo {
|
|
221
261
|
const entry = this.open(id);
|
|
222
|
-
if (entry.busy)
|
|
262
|
+
if (entry.busy)
|
|
263
|
+
throw new ValidationError(`Worker "${id}" is busy with a call; wait for it to finish before closing it.`);
|
|
223
264
|
entry.meta.status = "closed";
|
|
224
265
|
entry.meta.closedAt = Date.now();
|
|
225
266
|
this.save(entry);
|
|
226
|
-
if (entry.agent)
|
|
267
|
+
if (entry.agent)
|
|
268
|
+
entry.kept = {
|
|
269
|
+
model: modelName(entry.agent.modelStringOrSpec),
|
|
270
|
+
totals: readUsage(entry.agent.usageTotals),
|
|
271
|
+
workers: entry.agent.workers.usageNodes(),
|
|
272
|
+
};
|
|
227
273
|
entry.agent?.disposeSession(entry.meta.sessionId); // frees its provider chain; never reset(): that could rewrite the saved history
|
|
228
274
|
entry.agent = undefined;
|
|
229
275
|
return this.info(entry);
|
|
@@ -244,7 +290,13 @@ export class WorkerRegistry {
|
|
|
244
290
|
return [...this.entries.values()].map((entry) => {
|
|
245
291
|
const { id, name, role, status } = entry.meta;
|
|
246
292
|
const base = { id, name, ...(role ? { role } : {}), status };
|
|
247
|
-
if (entry.agent)
|
|
293
|
+
if (entry.agent)
|
|
294
|
+
return {
|
|
295
|
+
...base,
|
|
296
|
+
model: modelName(entry.agent.modelStringOrSpec),
|
|
297
|
+
totals: entry.agent.usageTotals,
|
|
298
|
+
workers: entry.agent.workers.usageNodes(),
|
|
299
|
+
};
|
|
248
300
|
return { ...base, ...(entry.kept ?? readSavedWorker(entry.dir)) };
|
|
249
301
|
});
|
|
250
302
|
}
|
|
@@ -253,7 +305,8 @@ export class WorkerRegistry {
|
|
|
253
305
|
private open(id: string): Entry {
|
|
254
306
|
const entry = this.entries.get(id);
|
|
255
307
|
if (!entry) throw new ValidationError(`No worker with id "${id}". Use list to see the open workers.`);
|
|
256
|
-
if (entry.meta.status === "closed")
|
|
308
|
+
if (entry.meta.status === "closed")
|
|
309
|
+
throw new ValidationError(`Worker "${id}" is closed. Spawn a new worker instead.`);
|
|
257
310
|
return entry;
|
|
258
311
|
}
|
|
259
312
|
|
|
@@ -264,7 +317,13 @@ export class WorkerRegistry {
|
|
|
264
317
|
}
|
|
265
318
|
|
|
266
319
|
// Checks a new worker's request, builds its Agent (seeded with the copied history) and saves `worker.json`; in: spawn, out: a new open entry.
|
|
267
|
-
private async create(
|
|
320
|
+
private async create(
|
|
321
|
+
id: string,
|
|
322
|
+
name: string,
|
|
323
|
+
task: string,
|
|
324
|
+
key: string | undefined,
|
|
325
|
+
spec: WorkerSpec
|
|
326
|
+
): Promise<Entry> {
|
|
268
327
|
const parent = this.parent;
|
|
269
328
|
if (!parent.subagentModel) {
|
|
270
329
|
throw new ConfigError(
|
|
@@ -273,7 +332,11 @@ export class WorkerRegistry {
|
|
|
273
332
|
}
|
|
274
333
|
const maxOpen = parent.dynamicSubagents?.maxSpawn ?? Infinity;
|
|
275
334
|
if (this.list().length >= maxOpen) {
|
|
276
|
-
throw new ValidationError(
|
|
335
|
+
throw new ValidationError(
|
|
336
|
+
`Too many open workers (${maxOpen} is the limit). Close one first; open workers: ${this.list()
|
|
337
|
+
.map((w) => w.id)
|
|
338
|
+
.join(", ")}.`
|
|
339
|
+
);
|
|
277
340
|
}
|
|
278
341
|
const pool = parent.dynamicSubagents?.tools ?? {};
|
|
279
342
|
const requested = spec.tools ?? [];
|
|
@@ -281,7 +344,8 @@ export class WorkerRegistry {
|
|
|
281
344
|
if (unknown.length > 0) {
|
|
282
345
|
const names = Object.keys(pool);
|
|
283
346
|
throw new ValidationError(
|
|
284
|
-
`tools: unknown worker tool(s) ${unknown.map((n) => `"${n}"`).join(", ")}; ` +
|
|
347
|
+
`tools: unknown worker tool(s) ${unknown.map((n) => `"${n}"`).join(", ")}; ` +
|
|
348
|
+
(names.length > 0 ? `the pool is [${names.join(", ")}].` : "no worker tools are available, omit tools.")
|
|
285
349
|
);
|
|
286
350
|
}
|
|
287
351
|
const history = spec.history ?? "none";
|
|
@@ -331,6 +395,7 @@ export class WorkerRegistry {
|
|
|
331
395
|
})
|
|
332
396
|
);
|
|
333
397
|
(agent as unknown as { parentSessionId?: string }).parentSessionId = this.parent.sessionId;
|
|
398
|
+
agent.chat = this.parent.chat; // the worker's turns count into the chat-wide cap and are checked against it
|
|
334
399
|
// A cut-off run was already loaded by the constructor; otherwise bring back what the worker said so far.
|
|
335
400
|
const saved = entry.dir && agent.context.messages.length === 0 ? loadSessionDir(entry.dir)?.session : undefined;
|
|
336
401
|
if (saved) agent.importSession(saved);
|
|
@@ -338,8 +403,16 @@ export class WorkerRegistry {
|
|
|
338
403
|
}
|
|
339
404
|
|
|
340
405
|
// Sends one text to a worker and turns what happens into a reply: runs it, or after a crash continues the cut-off run or returns the answer it had already given; in: spawn and message, out: a WorkerReply.
|
|
341
|
-
private async deliver(
|
|
342
|
-
|
|
406
|
+
private async deliver(
|
|
407
|
+
entry: Entry,
|
|
408
|
+
text: string,
|
|
409
|
+
key: string | undefined,
|
|
410
|
+
opts: WorkerCallOptions
|
|
411
|
+
): Promise<WorkerReply> {
|
|
412
|
+
if (entry.busy)
|
|
413
|
+
throw new ValidationError(
|
|
414
|
+
`Worker "${entry.meta.id}" is busy with another call; wait for it, or message another worker.`
|
|
415
|
+
);
|
|
343
416
|
entry.busy = true;
|
|
344
417
|
const { meta } = entry;
|
|
345
418
|
const startTime = nowMs();
|
|
@@ -354,14 +427,24 @@ export class WorkerRegistry {
|
|
|
354
427
|
parent: this.parent,
|
|
355
428
|
emit: opts.onSubagentEvent,
|
|
356
429
|
});
|
|
357
|
-
const lineage = {
|
|
430
|
+
const lineage = {
|
|
431
|
+
name: meta.name,
|
|
432
|
+
displayId: live.displayId,
|
|
433
|
+
sessionId: meta.sessionId,
|
|
434
|
+
parentSessionId: this.parent.sessionId,
|
|
435
|
+
role: meta.role,
|
|
436
|
+
task: text,
|
|
437
|
+
runId: newRunId(),
|
|
438
|
+
turnId: newTurnId(),
|
|
439
|
+
};
|
|
358
440
|
const replayed = this.isReplay(entry, key, text) && agent.runMarks.length > meta.last!.marks;
|
|
359
441
|
if (!replayed) {
|
|
360
442
|
if (key) meta.last = { key, text: hashSessionPart(text), marks: agent.runMarks.length };
|
|
361
443
|
else delete meta.last;
|
|
362
444
|
this.save(entry);
|
|
363
445
|
}
|
|
364
|
-
const onAbort = (): void =>
|
|
446
|
+
const onAbort = (): void =>
|
|
447
|
+
agent.interrupt(opts.signal?.reason instanceof Error ? opts.signal.reason : undefined);
|
|
365
448
|
try {
|
|
366
449
|
if (opts.signal?.aborted) throw new Error("aborted before the worker started");
|
|
367
450
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -387,14 +470,36 @@ export class WorkerRegistry {
|
|
|
387
470
|
if (!response) throw new Error("the worker had nothing to resume");
|
|
388
471
|
result = response;
|
|
389
472
|
}
|
|
390
|
-
const reply = result.text.trim()
|
|
473
|
+
const reply = result.text.trim()
|
|
474
|
+
? result.text
|
|
475
|
+
: `(the worker produced no text${result.finishReason ? `; finish reason: ${result.finishReason}` : ""})`;
|
|
391
476
|
live.finish(result);
|
|
392
|
-
return {
|
|
477
|
+
return {
|
|
478
|
+
id: meta.id,
|
|
479
|
+
text: reply,
|
|
480
|
+
metadata: workerMetadata({
|
|
481
|
+
...lineage,
|
|
482
|
+
durationMs: Math.max(0, nowMs() - startTime),
|
|
483
|
+
result,
|
|
484
|
+
steps: live.steps(),
|
|
485
|
+
}),
|
|
486
|
+
};
|
|
393
487
|
} catch (err) {
|
|
394
488
|
const message = err instanceof Error ? err.message : String(err);
|
|
395
489
|
live.fail(message);
|
|
396
490
|
const errorText = `Error in worker ${meta.id}: ${message}`;
|
|
397
|
-
return {
|
|
491
|
+
return {
|
|
492
|
+
id: meta.id,
|
|
493
|
+
text: errorText,
|
|
494
|
+
error: message,
|
|
495
|
+
metadata: workerMetadata({
|
|
496
|
+
...lineage,
|
|
497
|
+
durationMs: Math.max(0, nowMs() - startTime),
|
|
498
|
+
error: message,
|
|
499
|
+
errorText,
|
|
500
|
+
steps: live.steps(),
|
|
501
|
+
}),
|
|
502
|
+
};
|
|
398
503
|
} finally {
|
|
399
504
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
400
505
|
}
|
package/src/architecture.md
CHANGED
|
@@ -2,24 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
The whole SDK. `index.ts` is the only public entry (`main`/`module`/`types`); everything else is reached through it.
|
|
4
4
|
|
|
5
|
-
| File / folder
|
|
6
|
-
|
|
|
7
|
-
| `index.ts`
|
|
8
|
-
| `telemetry.ts`
|
|
5
|
+
| File / folder | What it is |
|
|
6
|
+
| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| `index.ts` | The public barrel. Adding or removing an export is a public API change. |
|
|
8
|
+
| `telemetry.ts` | The in-process event bus (`telemetryBus`). Imports nothing from the SDK, so any module may use it. |
|
|
9
9
|
| `telemetry-file.ts` | Opt-in NDJSON sink for the bus: a persisted session's events go to `<session folder>/telemetry.ndjson`, the rest to `<home>/telemetry/<date>.ndjson`; never outside a home. |
|
|
10
|
-
| `update-models.ts`
|
|
11
|
-
| `providers.ts`
|
|
12
|
-
| `agent/`
|
|
13
|
-
| `providers/`
|
|
14
|
-
| `models/`
|
|
15
|
-
| `tools/`
|
|
16
|
-
| `streaming/`
|
|
17
|
-
| `session/`
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
10
|
+
| `update-models.ts` | CLI behind `bun run update-models` (refreshes the model catalog cache). |
|
|
11
|
+
| `providers.ts` | Provider-agnostic mappers (thinking, cache, tool choice, structured output, generation params). No HTTP. |
|
|
12
|
+
| `agent/` | `Agent`, the model-to-tools loop, workers, delegation. |
|
|
13
|
+
| `providers/` | One adapter per wire (Google, OpenAI, OpenRouter, OpenAI-compatible), the registry, shared helpers. |
|
|
14
|
+
| `models/` | The model catalog (models.dev) and its cache. |
|
|
15
|
+
| `tools/` | `tool()`, schemas, the executor. |
|
|
16
|
+
| `streaming/` | The event stream and the SSE parser. |
|
|
17
|
+
| `session/` | Persistence (snapshot + journal), usage accounting, tool logs. |
|
|
18
|
+
| `prompts/` | The SDK's own prompts as `.md` files, `DEFAULT_PROMPTS`, `AgentConfig.prompts`. |
|
|
19
|
+
| `scheduler/` | Scheduled actions. |
|
|
20
|
+
| `types/` | Shared contracts (no logic except small helpers). |
|
|
21
|
+
| `utils/` | Small shared helpers (durable writes, home, env, media, retry, safe I/O). |
|
|
21
22
|
|
|
22
23
|
## Rules for every file here
|
|
24
|
+
|
|
23
25
|
- Imports use `.ts` extensions; type-only imports use `import type`.
|
|
24
26
|
- No `console.*`: warnings and traces go to `telemetryBus` (exceptions: the `update-models.ts` CLI and one compat `console.warn` in `providers.ts`).
|
|
25
27
|
- All state is written under the one home folder (`utils/home.ts`). Files are replaced through `utils/durable.ts`; the append-only ones are the session journal and tool logs (fsynced per line) and the opt-in telemetry file (not fsynced).
|
package/src/index.ts
CHANGED
|
@@ -31,13 +31,24 @@ export {
|
|
|
31
31
|
} from "./tools/tool.ts";
|
|
32
32
|
export type { CreateToolOptions } from "./tools/tool.ts";
|
|
33
33
|
export { zodToJsonSchema, cleanJsonSchema } from "./tools/schema.ts";
|
|
34
|
+
export { toolContent } from "./tools/tool-content.ts";
|
|
35
|
+
export { DEFAULT_PROMPTS } from "./prompts/prompts.ts";
|
|
36
|
+
export type { PromptName, PromptOverrides } from "./prompts/prompts.ts";
|
|
37
|
+
export type { ToolContent, ToolContentPart } from "./tools/tool-content.ts";
|
|
34
38
|
/**
|
|
35
39
|
* @deprecated Internal executor surface. Prefer `Agent` runs; for direct
|
|
36
40
|
* execution import from the future `/internal` entry.
|
|
37
41
|
*/
|
|
38
42
|
export { executeToolCalls } from "./tools/executor.ts";
|
|
39
43
|
|
|
40
|
-
export {
|
|
44
|
+
export {
|
|
45
|
+
assertPathAllowed,
|
|
46
|
+
enforceFileAccess,
|
|
47
|
+
fetchRemoteBytes,
|
|
48
|
+
isPrivateAddress,
|
|
49
|
+
RemoteFetchError,
|
|
50
|
+
MAX_REMOTE_FETCH_BYTES,
|
|
51
|
+
} from "./utils/safe-io.ts";
|
|
41
52
|
|
|
42
53
|
// Multi-Agent Delegation & Tools
|
|
43
54
|
export {
|
|
@@ -53,17 +64,12 @@ export {
|
|
|
53
64
|
export type { AgentAsToolTarget, SubAgentTrace } from "./agent/delegation.ts";
|
|
54
65
|
export type { WorkerRegistry, WorkerSpec, WorkerInfo, WorkerReply, WorkerCallOptions } from "./agent/workers.ts";
|
|
55
66
|
|
|
56
|
-
// Scheduled actions: dynamic sub-agents that run later and save their answer to a file
|
|
57
|
-
export {
|
|
58
|
-
createScheduledAction,
|
|
59
|
-
listScheduledActions,
|
|
60
|
-
updateScheduledAction,
|
|
61
|
-
runScheduledAction,
|
|
62
|
-
skipScheduledAction,
|
|
63
|
-
deleteScheduledAction,
|
|
64
|
-
} from "./scheduler/scheduler.ts";
|
|
67
|
+
// Scheduled actions: dynamic sub-agents that run later and save their answer to a file (use `agent.scheduled`)
|
|
68
|
+
export type { ScheduledActions } from "./scheduler/scheduler.ts";
|
|
65
69
|
export { getDatetimeTool } from "./scheduler/tool.ts";
|
|
66
70
|
export { setHome, resolveHome } from "./utils/home.ts";
|
|
71
|
+
export { configure } from "./agent/defaults.ts";
|
|
72
|
+
export type { AgentDefaults } from "./agent/defaults.ts";
|
|
67
73
|
export type {
|
|
68
74
|
ScheduledActionInput,
|
|
69
75
|
ScheduledActionPatch,
|
|
@@ -126,12 +132,8 @@ export {
|
|
|
126
132
|
GoogleInteractionsProvider,
|
|
127
133
|
clearInteractionChains,
|
|
128
134
|
} from "./providers/google.ts";
|
|
129
|
-
export {
|
|
130
|
-
|
|
131
|
-
} from "./providers/openrouter.ts";
|
|
132
|
-
export {
|
|
133
|
-
OpenAIResponsesProvider,
|
|
134
|
-
} from "./providers/openai.ts";
|
|
135
|
+
export { OpenRouterChatCompletionsProvider } from "./providers/openrouter.ts";
|
|
136
|
+
export { OpenAIResponsesProvider } from "./providers/openai.ts";
|
|
135
137
|
/**
|
|
136
138
|
* @deprecated Internal provider mappers/routing helpers. Prefer the
|
|
137
139
|
* provider-agnostic registry (`resolveModel`/`getProvider`); direct mapper
|
|
@@ -251,14 +253,36 @@ export {
|
|
|
251
253
|
emptyTotals,
|
|
252
254
|
} from "./session/store.ts";
|
|
253
255
|
export type { UsageReport, UsageLine, UsageTotals, AgentUsage, WorkerUsage, CostSource } from "./session/usage.ts";
|
|
254
|
-
export type {
|
|
256
|
+
export type {
|
|
257
|
+
SessionLimits,
|
|
258
|
+
SessionPayloadStatus,
|
|
259
|
+
PersistedAgentSession,
|
|
260
|
+
SessionTotals,
|
|
261
|
+
SessionAgentLike,
|
|
262
|
+
LoadedSessionDir,
|
|
263
|
+
PersistedSubAgentTrace,
|
|
264
|
+
SessionRunMark,
|
|
265
|
+
SessionRunEnd,
|
|
266
|
+
SessionPending,
|
|
267
|
+
SessionPendingPrompt,
|
|
268
|
+
SessionToolRecord,
|
|
269
|
+
} from "./session/store.ts";
|
|
255
270
|
|
|
256
271
|
// Utilities
|
|
257
272
|
/**
|
|
258
273
|
* @deprecated Session-routing internals. Prefer agent-level `sessionId`;
|
|
259
274
|
* direct use moves to the future `/internal` entry.
|
|
260
275
|
*/
|
|
261
|
-
export {
|
|
276
|
+
export {
|
|
277
|
+
createSessionId,
|
|
278
|
+
hashSessionPart,
|
|
279
|
+
createTrackingId,
|
|
280
|
+
nowMs,
|
|
281
|
+
newRunId,
|
|
282
|
+
newTurnId,
|
|
283
|
+
previewPrompt,
|
|
284
|
+
parseModelRef,
|
|
285
|
+
} from "./utils/session.ts";
|
|
262
286
|
export type { ParsedModelRef } from "./utils/session.ts";
|
|
263
287
|
/**
|
|
264
288
|
* @deprecated Raw env access. Prefer explicit `Agent` config (which wins over
|
|
@@ -298,6 +322,7 @@ export type {
|
|
|
298
322
|
ProviderContext,
|
|
299
323
|
ProviderSteps,
|
|
300
324
|
MediaResolution,
|
|
325
|
+
VideoProcessing,
|
|
301
326
|
} from "./types/message.ts";
|
|
302
327
|
|
|
303
328
|
export type {
|
|
@@ -344,7 +369,6 @@ export type {
|
|
|
344
369
|
RetryPolicyConfig,
|
|
345
370
|
AgentLogger,
|
|
346
371
|
ToolChoiceOption,
|
|
347
|
-
StopOption,
|
|
348
372
|
Budget,
|
|
349
373
|
BudgetState,
|
|
350
374
|
SubAgentRunIds,
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
The model catalog: context limits, prices, modalities and reasoning options come from models.dev, not from code.
|
|
4
4
|
|
|
5
|
-
| File
|
|
6
|
-
|
|
|
5
|
+
| File | What it is |
|
|
6
|
+
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
7
7
|
| `catalog-cache.ts` | Downloads and caches `models.dev/api.json` in `<home>/catalog/` (or `AGENT_CACHE_DIR`). Stale-while-revalidate: an expired catalog is served at once and refreshed in the background (8 s timeout, 10 min backoff after a failure); only a cold start waits. `AGENT_CATALOG_REFRESH=off` stops automatic refreshes. |
|
|
8
|
-
| `catalog.ts`
|
|
8
|
+
| `catalog.ts` | Lookups (`getModelFromCatalog`), `registerModel`, thinking validation (`validateModelThinking`, which throws `ThinkingLevelError`, a one-line `ConfigError`), and the exported `GOOGLE_/OPENAI_/OPENROUTER_MODELS` arrays (rebuilt in place when the catalog changes). |
|
|
9
9
|
|
|
10
10
|
## Invariants
|
|
11
|
+
|
|
11
12
|
- Never hand-edit the cache file; register custom models with `registerModel()`. Verified modality fixes go in `MODALITY_OVERRIDES`.
|
|
12
13
|
- The global bare-id fallback applies to first-class providers and explicit bare-id lookups only. A custom prefix never borrows another provider's price or modalities: unknown stays unknown.
|
|
13
14
|
- A payload that is not a catalog (fewer than 3 providers with models) never overwrites the cache.
|
|
@@ -313,7 +313,7 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
|
|
|
313
313
|
fallbackToDiskCache();
|
|
314
314
|
return getCatalogStatus();
|
|
315
315
|
}
|
|
316
|
-
} catch
|
|
316
|
+
} catch {
|
|
317
317
|
clearTimeout(timer);
|
|
318
318
|
// If download fails, retain disk cache if available
|
|
319
319
|
fallbackToDiskCache();
|
|
@@ -356,7 +356,6 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
|
|
|
356
356
|
return getCatalogStatus();
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
|
|
360
359
|
/** True when `AGENT_CATALOG_REFRESH` is `off`/`0`/`false`: never touch the network automatically. */
|
|
361
360
|
function automaticRefreshDisabled(): boolean {
|
|
362
361
|
const v = getEnv("AGENT_CATALOG_REFRESH")?.trim().toLowerCase();
|
|
@@ -388,9 +387,11 @@ export async function ensureModelCatalogFresh(options: RefreshCatalogOptions = {
|
|
|
388
387
|
const start = (): Promise<CatalogStatus> => {
|
|
389
388
|
if (!inFlightRefresh) {
|
|
390
389
|
const timeoutMs = options.timeoutMs ?? (current.loaded ? BACKGROUND_REFRESH_TIMEOUT_MS : undefined);
|
|
391
|
-
inFlightRefresh = refreshModelCatalog({ ...options, ...(timeoutMs !== undefined ? { timeoutMs } : {}) }).finally(
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
inFlightRefresh = refreshModelCatalog({ ...options, ...(timeoutMs !== undefined ? { timeoutMs } : {}) }).finally(
|
|
391
|
+
() => {
|
|
392
|
+
inFlightRefresh = null;
|
|
393
|
+
}
|
|
394
|
+
);
|
|
394
395
|
}
|
|
395
396
|
return inFlightRefresh;
|
|
396
397
|
};
|
package/src/models/catalog.ts
CHANGED
|
@@ -5,21 +5,10 @@
|
|
|
5
5
|
* rebuilt in place whenever it changes.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
getActiveCatalog,
|
|
10
|
-
registerCatalogUpdateListener,
|
|
11
|
-
refreshModelCatalog,
|
|
12
|
-
ensureModelCatalogFresh,
|
|
13
|
-
getCatalogStatus,
|
|
14
|
-
setCatalogTTL,
|
|
15
|
-
getCatalogTTL,
|
|
16
|
-
DEFAULT_CATALOG_TTL_MS,
|
|
17
|
-
type CatalogStatus,
|
|
18
|
-
type RefreshCatalogOptions,
|
|
19
|
-
} from "./catalog-cache.ts";
|
|
8
|
+
import { getActiveCatalog, registerCatalogUpdateListener } from "./catalog-cache.ts";
|
|
20
9
|
import type { ModelSpec, ProviderId, ModelLimit, ModelCost, ModelModalities } from "../types/model.ts";
|
|
21
10
|
import type { ThinkingLevel } from "../types/core.ts";
|
|
22
|
-
import { ConfigError } from "../types/errors.ts";
|
|
11
|
+
import { ConfigError, ValidationError } from "../types/errors.ts";
|
|
23
12
|
|
|
24
13
|
// ---------------------------------------------------------------------------
|
|
25
14
|
// Provider aliases — maps our internal ids to models.dev keys
|
|
@@ -49,8 +38,8 @@ function applyModalityOverrides(provider: string, modelId: string, base: ModelMo
|
|
|
49
38
|
MODALITY_OVERRIDES[`${provider}/${normalizeModelIdForLookup(modelId)}`];
|
|
50
39
|
if (!hit) return base;
|
|
51
40
|
return {
|
|
52
|
-
input: (
|
|
53
|
-
output: (
|
|
41
|
+
input: (hit.input ?? base.input) as ModelModalities["input"],
|
|
42
|
+
output: (hit.output ?? base.output) as ModelModalities["output"],
|
|
54
43
|
};
|
|
55
44
|
}
|
|
56
45
|
|
|
@@ -107,7 +96,7 @@ function customRegistryKey(provider: string, modelId: string): string {
|
|
|
107
96
|
*/
|
|
108
97
|
export function registerModel(spec: ModelSpec): void {
|
|
109
98
|
if (!spec || typeof spec.id !== "string" || typeof spec.provider !== "string") {
|
|
110
|
-
throw new
|
|
99
|
+
throw new ValidationError("[Agent Accelerator] registerModel requires spec.id and spec.provider strings");
|
|
111
100
|
}
|
|
112
101
|
customModelRegistry.set(customRegistryKey(String(spec.provider), String(spec.id)), { ...spec });
|
|
113
102
|
// Invalidate derived caches so the custom model is visible immediately.
|
|
@@ -136,30 +125,38 @@ export function getModelSource(provider: string, modelId: string): "catalog" | "
|
|
|
136
125
|
if (customModelRegistry.has(customRegistryKey(p, m)) || customModelRegistry.has(customRegistryKey(p, norm))) {
|
|
137
126
|
return "override";
|
|
138
127
|
}
|
|
139
|
-
if (
|
|
140
|
-
MODALITY_OVERRIDES[`${p}/${m}`] !== undefined ||
|
|
141
|
-
MODALITY_OVERRIDES[`${p}/${norm}`] !== undefined
|
|
142
|
-
) {
|
|
128
|
+
if (MODALITY_OVERRIDES[`${p}/${m}`] !== undefined || MODALITY_OVERRIDES[`${p}/${norm}`] !== undefined) {
|
|
143
129
|
return "override";
|
|
144
130
|
}
|
|
145
131
|
return getModelFromCatalog(provider, modelId) ? "catalog" : "unknown";
|
|
146
132
|
}
|
|
147
133
|
|
|
148
134
|
// Derives capability flags (thinking, caching, tools, structured output, modalities) from a raw catalog entry; in: mapModelSpec, out: ModelCapabilities.
|
|
149
|
-
function mapCapabilities(
|
|
135
|
+
function mapCapabilities(
|
|
136
|
+
raw: any,
|
|
137
|
+
cost: ModelCost,
|
|
138
|
+
reasoning: boolean,
|
|
139
|
+
toolCall: boolean,
|
|
140
|
+
modalities: ModelModalities
|
|
141
|
+
) {
|
|
150
142
|
return {
|
|
151
143
|
supportsThinking: reasoning,
|
|
152
144
|
supportsThinkingLevel: !!raw.reasoning_options,
|
|
153
|
-
supportsThinkingBudget:
|
|
145
|
+
supportsThinkingBudget:
|
|
146
|
+
Array.isArray(raw.reasoning_options) && raw.reasoning_options.some((o: any) => o.type === "budget_tokens"),
|
|
154
147
|
supportsImplicitCaching: cost.cache_read !== undefined || cost.cache_write !== undefined,
|
|
155
148
|
supportsLongCacheRetention: cost.cache_read !== undefined,
|
|
156
149
|
supportsExplicitCaching: cost.cache_read !== undefined,
|
|
157
150
|
supportsParallelToolCalls: toolCall,
|
|
158
151
|
supportsStreaming: true,
|
|
159
152
|
supportsStructuredOutput: raw.structured_output === undefined ? true : !!raw.structured_output,
|
|
160
|
-
supportsReasoningToggle:
|
|
161
|
-
|
|
162
|
-
|
|
153
|
+
supportsReasoningToggle:
|
|
154
|
+
Array.isArray(raw.reasoning_options) && raw.reasoning_options.some((o: any) => o.type === "toggle"),
|
|
155
|
+
supportsReasoningEffort:
|
|
156
|
+
Array.isArray(raw.reasoning_options) && raw.reasoning_options.some((o: any) => o.type === "effort"),
|
|
157
|
+
modalities: [...(modalities.input || []), ...(modalities.output || [])].filter(
|
|
158
|
+
(v: any, i: any, a: any) => a.indexOf(v) === i
|
|
159
|
+
),
|
|
163
160
|
};
|
|
164
161
|
}
|
|
165
162
|
|
|
@@ -268,7 +265,9 @@ export function getModelFromCatalog(providerInput: string, modelIdInput: string)
|
|
|
268
265
|
}
|
|
269
266
|
|
|
270
267
|
const aliasProviders = PROVIDER_ALIASES[provider] || [provider];
|
|
271
|
-
const tryProviders = [
|
|
268
|
+
const tryProviders = [
|
|
269
|
+
...new Set([...aliasProviders, provider, provider.replace("-zen", ""), provider.replace("-go", "")]),
|
|
270
|
+
];
|
|
272
271
|
const catalog = getActiveCatalog();
|
|
273
272
|
|
|
274
273
|
for (const p of tryProviders) {
|
|
@@ -292,7 +291,11 @@ export function getModelFromCatalog(providerInput: string, modelIdInput: string)
|
|
|
292
291
|
// or normalized equality. The old loose `modelId.endsWith(key)` is removed
|
|
293
292
|
// (e.g. "gpt-4o-mini-extra" must not match "mini").
|
|
294
293
|
for (const [key, raw] of Object.entries(providerData.models as Record<string, any>)) {
|
|
295
|
-
if (
|
|
294
|
+
if (
|
|
295
|
+
key === modelId ||
|
|
296
|
+
key.endsWith(`/${modelId}`) ||
|
|
297
|
+
normalizeModelIdForLookup(key) === normalizeModelIdForLookup(modelId)
|
|
298
|
+
) {
|
|
296
299
|
result = mapModelSpec(p as ProviderId, key, raw as any);
|
|
297
300
|
setLookupCache(cacheKey, result);
|
|
298
301
|
return result;
|
|
@@ -411,7 +414,8 @@ export function getModelThinkingInfo(provider: string, modelId: string): ModelTh
|
|
|
411
414
|
allowed.add("dynamic");
|
|
412
415
|
|
|
413
416
|
const allowedLevels = Array.from(allowed);
|
|
414
|
-
const supportsDisable =
|
|
417
|
+
const supportsDisable =
|
|
418
|
+
allowed.has("none") || !!toggleOpt || Boolean(budgetOpt && (budgetOpt.min === undefined || budgetOpt.min <= 0));
|
|
415
419
|
|
|
416
420
|
return {
|
|
417
421
|
supportsThinking: true,
|
|
@@ -444,7 +448,8 @@ export class ThinkingLevelError extends ConfigError {
|
|
|
444
448
|
reason: string;
|
|
445
449
|
remedy?: string;
|
|
446
450
|
}) {
|
|
447
|
-
const allowed =
|
|
451
|
+
const allowed =
|
|
452
|
+
opts.allowedLevels.length > 0 ? ` Allowed levels: ${opts.allowedLevels.map((l) => `"${l}"`).join(", ")}.` : "";
|
|
448
453
|
const remedy = opts.remedy ? ` How to fix: ${opts.remedy}` : "";
|
|
449
454
|
super(
|
|
450
455
|
`[Agent Accelerator] Thinking level "${opts.requestedLevel}" does not fit "${opts.provider}/${opts.modelId}": ${opts.reason}${allowed}${remedy}`,
|
|
@@ -464,7 +469,11 @@ export class ThinkingLevelError extends ConfigError {
|
|
|
464
469
|
*
|
|
465
470
|
* @example `validateModelThinking("google", "gemini-3.5-flash-lite", "medium");`
|
|
466
471
|
*/
|
|
467
|
-
export function validateModelThinking(
|
|
472
|
+
export function validateModelThinking(
|
|
473
|
+
provider: string,
|
|
474
|
+
modelId: string,
|
|
475
|
+
requestedLevel?: ThinkingLevel | string
|
|
476
|
+
): void {
|
|
468
477
|
if (!requestedLevel) return;
|
|
469
478
|
const level = String(requestedLevel).toLowerCase().trim();
|
|
470
479
|
const info = getModelThinkingInfo(provider, modelId);
|