agent-accelerator 0.5.8 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +26 -5
  2. package/examples/prompts/SYSTEM_PROMPT.md +2 -0
  3. package/examples/prompts/SYSTEM_PROMPT_AGENT.md +4 -0
  4. package/examples/prompts/SYSTEM_PROMPT_TOOLS.md +1 -0
  5. package/package.json +4 -1
  6. package/src/agent/agent.ts +374 -167
  7. package/src/agent/architecture.md +14 -12
  8. package/src/agent/config-blocks.ts +39 -12
  9. package/src/agent/context.ts +8 -3
  10. package/src/agent/delegation.ts +82 -29
  11. package/src/agent/loop.ts +449 -373
  12. package/src/agent/subagent.ts +1 -3
  13. package/src/agent/subagents-tool.ts +45 -7
  14. package/src/agent/subagents.md +3 -0
  15. package/src/agent/turns.ts +3 -1
  16. package/src/agent/worker.ts +58 -28
  17. package/src/agent/workers.ts +113 -20
  18. package/src/architecture.md +16 -15
  19. package/src/index.ts +34 -10
  20. package/src/models/architecture.md +4 -3
  21. package/src/models/catalog-cache.ts +6 -5
  22. package/src/models/catalog.ts +46 -49
  23. package/src/providers/architecture.md +10 -8
  24. package/src/providers/google.ts +150 -62
  25. package/src/providers/openai-compat.ts +60 -58
  26. package/src/providers/openai.ts +57 -39
  27. package/src/providers/openrouter.ts +87 -32
  28. package/src/providers/registry.ts +12 -26
  29. package/src/providers/shared.ts +89 -39
  30. package/src/providers.ts +56 -115
  31. package/src/scheduler/architecture.md +9 -8
  32. package/src/scheduler/scheduled-actions.md +4 -0
  33. package/src/scheduler/scheduler.ts +186 -60
  34. package/src/scheduler/store.ts +23 -10
  35. package/src/scheduler/time.ts +17 -3
  36. package/src/scheduler/tool.ts +33 -7
  37. package/src/session/architecture.md +7 -6
  38. package/src/session/journal.ts +38 -12
  39. package/src/session/store.ts +122 -61
  40. package/src/session/usage.ts +58 -9
  41. package/src/streaming/architecture.md +4 -3
  42. package/src/streaming/event-stream.ts +26 -12
  43. package/src/telemetry.ts +6 -2
  44. package/src/tools/architecture.md +8 -7
  45. package/src/tools/executor.ts +50 -48
  46. package/src/tools/schema.ts +10 -6
  47. package/src/tools/subagent-result.ts +6 -1
  48. package/src/tools/tool.ts +9 -11
  49. package/src/types/agent.ts +2 -21
  50. package/src/types/architecture.md +11 -10
  51. package/src/types/core.ts +1 -9
  52. package/src/types/errors.ts +26 -4
  53. package/src/types/model.ts +0 -4
  54. package/src/types/tool.ts +12 -2
  55. package/src/update-models.ts +5 -5
  56. package/src/utils/architecture.md +16 -15
  57. package/src/utils/cache.ts +1 -3
  58. package/src/utils/durable.ts +17 -5
  59. package/src/utils/env.ts +9 -7
  60. package/src/utils/errors.ts +50 -44
  61. package/src/utils/headers.ts +3 -10
  62. package/src/utils/media.ts +23 -8
  63. package/src/utils/raw-capture.ts +9 -2
  64. package/src/utils/retry.ts +17 -31
  65. package/src/utils/safe-io.ts +29 -7
  66. package/src/utils/serialization.ts +1 -4
  67. package/src/utils/session.ts +1 -2
@@ -72,8 +72,17 @@ export function addTurn(totals: UsageTotals, usage: TokenUsage, priced: PricedCo
72
72
 
73
73
  /** Adds `other` into `into` (workers into a total). */
74
74
  export function mergeUsage(into: UsageTotals, other: UsageTotals): void {
75
- for (const k of ["turns", "inputTokens", "cacheReadTokens", "cacheWriteTokens", "outputTokens", "thinkingTokens"] as const) into[k] += other[k];
76
- for (const k of ["input", "output", "cacheRead", "cacheWrite", "total"] as const) into.costMicroUSD[k] += other.costMicroUSD[k];
75
+ for (const k of [
76
+ "turns",
77
+ "inputTokens",
78
+ "cacheReadTokens",
79
+ "cacheWriteTokens",
80
+ "outputTokens",
81
+ "thinkingTokens",
82
+ ] as const)
83
+ into[k] += other[k];
84
+ for (const k of ["input", "output", "cacheRead", "cacheWrite", "total"] as const)
85
+ into.costMicroUSD[k] += other.costMicroUSD[k];
77
86
  for (const k of ["provider", "catalog", "unpriced"] as const) into.turnsBy[k] += other.turnsBy[k];
78
87
  }
79
88
 
@@ -82,8 +91,17 @@ export function readUsage(saved: unknown): UsageTotals {
82
91
  const out = emptyUsage();
83
92
  const s = saved as Partial<UsageTotals> | null | undefined;
84
93
  if (!s || typeof s !== "object") return out;
85
- for (const k of ["turns", "inputTokens", "cacheReadTokens", "cacheWriteTokens", "outputTokens", "thinkingTokens"] as const) out[k] = count(s[k]);
86
- for (const k of ["input", "output", "cacheRead", "cacheWrite", "total"] as const) out.costMicroUSD[k] = count(s.costMicroUSD?.[k]);
94
+ for (const k of [
95
+ "turns",
96
+ "inputTokens",
97
+ "cacheReadTokens",
98
+ "cacheWriteTokens",
99
+ "outputTokens",
100
+ "thinkingTokens",
101
+ ] as const)
102
+ out[k] = count(s[k]);
103
+ for (const k of ["input", "output", "cacheRead", "cacheWrite", "total"] as const)
104
+ out.costMicroUSD[k] = count(s.costMicroUSD?.[k]);
87
105
  for (const k of ["provider", "catalog", "unpriced"] as const) out.turnsBy[k] = count(s.turnsBy?.[k]);
88
106
  return out;
89
107
  }
@@ -124,7 +142,14 @@ export interface UsageLine {
124
142
  /** Turns raw totals into a readable {@link UsageLine}. */
125
143
  export function usageLine(t: UsageTotals): UsageLine {
126
144
  const priced = t.turnsBy.provider + t.turnsBy.catalog;
127
- const source: CostSource = priced === 0 ? "unknown" : t.turnsBy.provider > 0 && t.turnsBy.catalog > 0 ? "mixed" : t.turnsBy.provider > 0 ? "provider" : "catalog";
145
+ const source: CostSource =
146
+ priced === 0
147
+ ? "unknown"
148
+ : t.turnsBy.provider > 0 && t.turnsBy.catalog > 0
149
+ ? "mixed"
150
+ : t.turnsBy.provider > 0
151
+ ? "provider"
152
+ : "catalog";
128
153
  const usd = (micro: number): number => micro / 1_000_000;
129
154
  const c = t.costMicroUSD;
130
155
  return {
@@ -143,7 +168,16 @@ export function usageLine(t: UsageTotals): UsageLine {
143
168
  source,
144
169
  unpricedTurns: t.turnsBy.unpriced,
145
170
  totalUsd: priced === 0 ? null : usd(c.total),
146
- ...(priced === 0 ? {} : { parts: { inputUsd: usd(c.input), outputUsd: usd(c.output), cacheReadUsd: usd(c.cacheRead), cacheWriteUsd: usd(c.cacheWrite) } }),
171
+ ...(priced === 0
172
+ ? {}
173
+ : {
174
+ parts: {
175
+ inputUsd: usd(c.input),
176
+ outputUsd: usd(c.output),
177
+ cacheReadUsd: usd(c.cacheRead),
178
+ cacheWriteUsd: usd(c.cacheWrite),
179
+ },
180
+ }),
147
181
  },
148
182
  };
149
183
  }
@@ -190,13 +224,28 @@ function sumTree(into: UsageTotals, nodes: UsageNode[]): void {
190
224
 
191
225
  // One node as a report line, children included; in: a usage node, out: UsageReport.workers.
192
226
  function nodeLine(n: UsageNode): WorkerUsage {
193
- return { id: n.id, name: n.name, ...(n.role ? { role: n.role } : {}), model: n.model, status: n.status, ...usageLine(n.totals), workers: n.workers.map(nodeLine) };
227
+ return {
228
+ id: n.id,
229
+ name: n.name,
230
+ ...(n.role ? { role: n.role } : {}),
231
+ model: n.model,
232
+ status: n.status,
233
+ ...usageLine(n.totals),
234
+ workers: n.workers.map(nodeLine),
235
+ };
194
236
  }
195
237
 
196
238
  /** Builds the report from the agent's own totals and its worker tree. */
197
- export function buildUsageReport(own: { id: string; name: string; model: string; totals: UsageTotals }, workers: UsageNode[]): UsageReport {
239
+ export function buildUsageReport(
240
+ own: { id: string; name: string; model: string; totals: UsageTotals },
241
+ workers: UsageNode[]
242
+ ): UsageReport {
198
243
  const total = emptyUsage();
199
244
  mergeUsage(total, own.totals);
200
245
  sumTree(total, workers);
201
- return { agent: { id: own.id, name: own.name, model: own.model, ...usageLine(own.totals) }, workers: workers.map(nodeLine), total: usageLine(total) };
246
+ return {
247
+ agent: { id: own.id, name: own.name, model: own.model, ...usageLine(own.totals) },
248
+ workers: workers.map(nodeLine),
249
+ total: usageLine(total),
250
+ };
202
251
  }
@@ -1,11 +1,12 @@
1
1
  # src/streaming/
2
2
 
3
- | File | What it is |
4
- | :-- | :-- |
3
+ | File | What it is |
4
+ | :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
5
5
  | `event-stream.ts` | `AssistantMessageEventStream`: an async-iterable event stream with listeners (`on`/`off`), a `result()` promise, `cancel()` and bounded buffering. Providers produce it; the loop and `Agent` consume it. |
6
- | `sse-parser.ts` | `SSEParser`: incremental Server-Sent Events framing that survives events split across network chunks. Internal (not exported from the barrel). |
6
+ | `sse-parser.ts` | `SSEParser`: incremental Server-Sent Events framing that survives events split across network chunks. Internal (not exported from the barrel). |
7
7
 
8
8
  ## Invariants
9
+
9
10
  - The buffer holds at most 1000 events (oldest dropped, reported on the bus); an event over 1 MB is dropped. `{ buffer: false }` keeps none (for a caller that only wants `result()`).
10
11
  - `end()` without a response fails the stream instead of leaving `result()` hanging.
11
12
  - A cancel is recognised by the `AbortError` name only, never by message text.
@@ -1,5 +1,6 @@
1
1
  import type { StreamEvent, AgentResponse } from "../types/response.ts";
2
2
  import { telemetryBus } from "../telemetry.ts";
3
+ import { ValidationError } from "../types/errors.ts";
3
4
 
4
5
  /** Listener invoked for a matching stream event type or `*`. */
5
6
  export type StreamEventListener = (event: StreamEvent) => void;
@@ -87,7 +88,9 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
87
88
  this.includePartial = opts?.includePartial !== false;
88
89
  this.buffered = opts?.buffer !== false;
89
90
  this.maxBufferedEvents =
90
- typeof opts?.maxBufferedEvents === "number" && Number.isFinite(opts.maxBufferedEvents) && opts.maxBufferedEvents > 0
91
+ typeof opts?.maxBufferedEvents === "number" &&
92
+ Number.isFinite(opts.maxBufferedEvents) &&
93
+ opts.maxBufferedEvents > 0
91
94
  ? Math.floor(opts.maxBufferedEvents)
92
95
  : MAX_BUFFERED_EVENTS;
93
96
  this.maxEventBytes =
@@ -131,12 +134,15 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
131
134
  }
132
135
 
133
136
  if (out.type === "error") {
134
- this.error = (out.error instanceof Error ? out.error : new Error(String(out.error))) ?? new Error("Unknown error in stream");
137
+ this.error =
138
+ (out.error instanceof Error ? out.error : new Error(String(out.error))) ?? new Error("Unknown error in stream");
135
139
  // Single error semantic: deliver the error event to listeners,
136
140
  // reject result(), mark finished, reject pending iterators, and queue
137
141
  // the error event so a late iterator observes it before failing.
138
142
  this.deliverToListeners(out);
139
- try { this.rejectFinalResult(this.error); } catch {
143
+ try {
144
+ this.rejectFinalResult(this.error);
145
+ } catch {
140
146
  // why: rejecting an already-settled promise helper must not throw.
141
147
  }
142
148
  this.finished = true;
@@ -168,7 +174,9 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
168
174
  const specificListeners = this.listeners.get(event.type);
169
175
  if (specificListeners) {
170
176
  for (const listener of specificListeners) {
171
- try { listener(event); } catch (e) {
177
+ try {
178
+ listener(event);
179
+ } catch (e) {
172
180
  // why: one bad listener must not break the stream or other
173
181
  // listeners; capture the first exception for inspection instead.
174
182
  if (!this.listenerError) {
@@ -180,7 +188,9 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
180
188
  const allListeners = this.listeners.get("*");
181
189
  if (allListeners) {
182
190
  for (const listener of allListeners) {
183
- try { listener(event); } catch (e) {
191
+ try {
192
+ listener(event);
193
+ } catch (e) {
184
194
  // why: same as above — isolate wildcard-listener failures.
185
195
  if (!this.listenerError) {
186
196
  this.listenerError = e instanceof Error ? e : new Error(String(e));
@@ -216,7 +226,7 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
216
226
  // Never hang result() — ending without a response is a
217
227
  // programming error, so fail loudly instead of leaving consumers
218
228
  // waiting forever.
219
- this.fail(new Error("end() requires response"));
229
+ this.fail(new ValidationError("end() requires response"));
220
230
  return;
221
231
  }
222
232
  this.finished = true;
@@ -245,13 +255,17 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
245
255
  /** Registers a callback invoked once on cancel(). Returns an unsubscribe fn. */
246
256
  onCancel(handler: () => void): () => void {
247
257
  if (this.cancelled) {
248
- try { handler(); } catch {
258
+ try {
259
+ handler();
260
+ } catch {
249
261
  // why: a throwing cancel handler must not break the canceller.
250
262
  }
251
263
  return () => {};
252
264
  }
253
265
  this.cancelHandlers.add(handler);
254
- return () => { this.cancelHandlers.delete(handler); };
266
+ return () => {
267
+ this.cancelHandlers.delete(handler);
268
+ };
255
269
  }
256
270
 
257
271
  /** Cancels the stream: stops background work and rejects result() with AbortError.
@@ -260,16 +274,16 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
260
274
  if (this.finished || this.cancelled) return;
261
275
  this.cancelled = true;
262
276
  for (const h of [...this.cancelHandlers]) {
263
- try { h(); } catch {
277
+ try {
278
+ h();
279
+ } catch {
264
280
  // why: isolate faulty cancel handlers so every handler still runs.
265
281
  }
266
282
  }
267
283
  this.cancelHandlers.clear();
268
284
  const err = reason instanceof Error ? reason : null;
269
285
  const abortErr: Error =
270
- err && err.name === "AbortError"
271
- ? err
272
- : Object.assign(new Error("Stream aborted"), { name: "AbortError" });
286
+ err && err.name === "AbortError" ? err : Object.assign(new Error("Stream aborted"), { name: "AbortError" });
273
287
  this.fail(abortErr);
274
288
  }
275
289
 
package/src/telemetry.ts CHANGED
@@ -35,7 +35,9 @@ export const telemetryBus: TelemetryBus = (() => {
35
35
  const listeners = new Set<TelemetryListener>();
36
36
  const deliver = (event: TelemetryEvent): void => {
37
37
  for (const l of [...listeners]) {
38
- try { l(event); } catch {
38
+ try {
39
+ l(event);
40
+ } catch {
39
41
  // why: a faulty telemetry listener must not break the emitting run.
40
42
  }
41
43
  }
@@ -43,7 +45,9 @@ export const telemetryBus: TelemetryBus = (() => {
43
45
  return {
44
46
  onEvent(listener: TelemetryListener): () => void {
45
47
  listeners.add(listener);
46
- return () => { listeners.delete(listener); };
48
+ return () => {
49
+ listeners.delete(listener);
50
+ };
47
51
  },
48
52
  emitWarning(code: string, message: string, data?: unknown): void {
49
53
  try {
@@ -2,16 +2,17 @@
2
2
 
3
3
  Defining tools and running them.
4
4
 
5
- | File | What it is |
6
- | :-- | :-- |
7
- | `tool.ts` | `tool()` factory, name validation, `toStandardToolDeclarations`, option normalizers. |
8
- | `schema.ts` | Zod to JSON Schema, `$ref` resolution, `stripSchemaForGoogle`. |
9
- | `executor.ts` | `executeToolCalls`: validation, concurrency (8 global + per-tool), attempts, timeout, result cap, error scrubbing, `ctx.stop`, `tool.*` events, per-call logs. |
10
- | `subagent-result.ts` | The private-Symbol brand that marks a result as made by the SDK's delegation tools. |
5
+ | File | What it is |
6
+ | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | `tool.ts` | `tool()` factory, name validation, `toStandardToolDeclarations`, option normalizers. |
8
+ | `schema.ts` | Zod to JSON Schema, `$ref` resolution, `stripSchemaForGoogle`. |
9
+ | `executor.ts` | `executeToolCalls`: validation, concurrency (8 global + per-tool), attempts, timeout, result cap, error scrubbing, `ctx.stop`, `tool.*` events, per-call logs. |
10
+ | `subagent-result.ts` | The private-Symbol brand that marks a result as made by the SDK's delegation tools. |
11
11
 
12
12
  ## Invariants
13
+
13
14
  - Tool names match `^[a-zA-Z0-9_-]{1,64}$` and must not collide after normalization (checked in the `Agent` constructor).
14
- - One attempt by default; only idempotent tools retry transient failures; the timeout is best effort (`ctx.signal` is cooperative), and an uncooperative tool no longer holds an aborted run.
15
+ - One attempt by default; only idempotent tools retry transient failures (what counts as transient is the one `isTransientError` in `utils/errors.ts`, shared with the provider retries); the timeout is best effort (`ctx.signal` is cooperative), and an uncooperative tool no longer holds an aborted run.
15
16
  - Results are capped by serialized size (default 20000 chars); nested error stacks and absolute paths are scrubbed. The telemetry bus gets full detail, the model gets the scrubbed text.
16
17
  - A truncated call (`{ __truncated_raw }`) never executes.
17
18
  - `ctx.stop(reason)` throws a private signal; the batch finishes, then the loop ends the run as `stopped`.
@@ -1,11 +1,7 @@
1
- import type {
2
- ToolDefinition,
3
- ToolCallRecord,
4
- ToolResultRecord,
5
- ToolExecutionContext,
6
- } from "../types/tool.ts";
1
+ import type { ToolDefinition, ToolCallRecord, ToolResultRecord, ToolExecutionContext } from "../types/tool.ts";
7
2
  import { toJsonSafe } from "../utils/serialization.ts";
8
3
  import { ConfigError } from "../types/errors.ts";
4
+ import { isTransientError } from "../utils/errors.ts";
9
5
  import { isSubagentResult } from "./subagent-result.ts";
10
6
  import { isTruncatedArguments } from "../types/tool.ts";
11
7
  import { telemetryBus } from "../telemetry.ts";
@@ -23,7 +19,13 @@ export interface ExecuteToolsOptions {
23
19
  /** Opens the log file of one call (only when the Agent persists to a folder); the executor writes its steps into it and `ctx.log` writes through it. */
24
20
  openToolLog?: (call: ToolCallRecord, sessionId: string | undefined) => ToolLogWriter | undefined;
25
21
  /** Forwards live sub-agent deltas (realtime worker thinking/text) to the caller. */
26
- onSubagentEvent?: (event: { trackingId: string; delta?: string; thinkingDelta?: string; partialText?: string; partialThinking?: string }) => void;
22
+ onSubagentEvent?: (event: {
23
+ trackingId: string;
24
+ delta?: string;
25
+ thinkingDelta?: string;
26
+ partialText?: string;
27
+ partialThinking?: string;
28
+ }) => void;
27
29
  }
28
30
 
29
31
  const DEFAULT_TOOL_CONCURRENCY = 8;
@@ -93,9 +95,7 @@ export function isTruncatedToolCall(call: ToolCallRecord): boolean {
93
95
  * same name (case/format-insensitive via {@link normalizeToolName}), which
94
96
  * would otherwise make model tool calls ambiguous.
95
97
  */
96
- export function assertNoToolCollisions(
97
- tools: Record<string, ToolDefinition>
98
- ): void {
98
+ export function assertNoToolCollisions(tools: Record<string, ToolDefinition>): void {
99
99
  if (!tools) return;
100
100
  const seen = new Map<string, string>();
101
101
  for (const [key, definition] of Object.entries(tools)) {
@@ -296,9 +296,7 @@ function levenshtein(a: string, b: string): number {
296
296
  row[0] = i;
297
297
  for (let j = 1; j <= b.length; j++) {
298
298
  const current = row[j]!;
299
- row[j] = a[i - 1] === b[j - 1]
300
- ? previous
301
- : Math.min(previous + 1, row[j - 1]! + 1, current + 1);
299
+ row[j] = a[i - 1] === b[j - 1] ? previous : Math.min(previous + 1, row[j - 1]! + 1, current + 1);
302
300
  previous = current;
303
301
  }
304
302
  }
@@ -339,10 +337,7 @@ function resolveTool(tools: Record<string, ToolDefinition>, requestedName: strin
339
337
  }
340
338
 
341
339
  /** Finds a tool definition by exact or case/format-insensitive name (no suggestion logic). */
342
- export function findToolDefinition(
343
- tools: Record<string, ToolDefinition>,
344
- name: string
345
- ): ToolDefinition | undefined {
340
+ export function findToolDefinition(tools: Record<string, ToolDefinition>, name: string): ToolDefinition | undefined {
346
341
  return resolveTool(tools, name).definition;
347
342
  }
348
343
 
@@ -401,21 +396,6 @@ class Semaphore {
401
396
  }
402
397
  }
403
398
 
404
- /**
405
- * True for retry-worthy failures: explicit opt-in flags (`transient` /
406
- * `retryable`), retryable HTTP statuses, or retryable network codes. Never
407
- * matches on message text — message content is untrusted and locale-fragile.
408
- */
409
- function isTransientError(error: any): boolean {
410
- if (!error) return false;
411
- if (error.transient === true || error.retryable === true) return true;
412
- const status = Number(error.status ?? error.statusCode ?? error.response?.status);
413
- if ([408, 425, 429, 500, 502, 503, 504].includes(status)) return true;
414
- const code = String(error.code || "").toUpperCase();
415
- if (["ECONNRESET", "ECONNREFUSED", "EAI_AGAIN", "ETIMEDOUT", "EPIPE", "UND_ERR_CONNECT_TIMEOUT"].includes(code)) return true;
416
- return false;
417
- }
418
-
419
399
  // Waits the retry backoff, rejecting `Tool execution aborted` as soon as the run aborts; in: runSingle's retry loop, out: nothing.
420
400
  function waitForRetry(ms: number, signal?: AbortSignal): Promise<void> {
421
401
  if (ms <= 0) return Promise.resolve();
@@ -505,10 +485,18 @@ async function executeAttempt(
505
485
  *
506
486
  * @example `const results = await executeToolCalls({ tools, toolCalls });`
507
487
  */
508
- export async function executeToolCalls(
509
- options: ExecuteToolsOptions
510
- ): Promise<ToolResultRecord[]> {
511
- const { tools, toolCalls, agentName, parallel = true, signal, sessionId, onSubagentEvent, onResult, openToolLog } = options;
488
+ export async function executeToolCalls(options: ExecuteToolsOptions): Promise<ToolResultRecord[]> {
489
+ const {
490
+ tools,
491
+ toolCalls,
492
+ agentName,
493
+ parallel = true,
494
+ signal,
495
+ sessionId,
496
+ onSubagentEvent,
497
+ onResult,
498
+ openToolLog,
499
+ } = options;
512
500
  assertNoToolCollisions(tools);
513
501
  const globalSemaphore = new Semaphore(DEFAULT_TOOL_CONCURRENCY);
514
502
  const perToolSemaphores = new Map<string, Semaphore>();
@@ -516,9 +504,7 @@ export async function executeToolCalls(
516
504
  const getToolSemaphore = (name: string, definition: ToolDefinition): Semaphore => {
517
505
  const existing = perToolSemaphores.get(name);
518
506
  if (existing) return existing;
519
- const configured = integerOption(
520
- definition.maxConcurrency
521
- );
507
+ const configured = integerOption(definition.maxConcurrency);
522
508
  const limit = configured && configured > 0 ? configured : DEFAULT_TOOL_CONCURRENCY;
523
509
  const semaphore = new Semaphore(limit);
524
510
  perToolSemaphores.set(name, semaphore);
@@ -578,7 +564,14 @@ export async function executeToolCalls(
578
564
  let stopReason: string | undefined;
579
565
  const stoppedResult = (): ToolResultRecord => {
580
566
  trace("call_stopped", "warn", { reason: stopReason, attempts, durationMs: elapsedMs(executionStartTime) });
581
- return { id: call.id, name: resolved.name, result: stopReason, isError: false, stopped: true, durationMs: elapsedMs(executionStartTime) };
567
+ return {
568
+ id: call.id,
569
+ name: resolved.name,
570
+ result: stopReason,
571
+ isError: false,
572
+ stopped: true,
573
+ durationMs: elapsedMs(executionStartTime),
574
+ };
582
575
  };
583
576
 
584
577
  try {
@@ -599,7 +592,8 @@ export async function executeToolCalls(
599
592
  stopReason ??= String(reason);
600
593
  throw new ToolStopSignal();
601
594
  },
602
- log: (message, data) => log?.write({ phase: "log", message, ...(data !== undefined ? { data: toJsonSafe(data) } : {}) }),
595
+ log: (message, data) =>
596
+ log?.write({ phase: "log", message, ...(data !== undefined ? { data: toJsonSafe(data) } : {}) }),
603
597
  emit: (event) => {
604
598
  if (event?.type === "progress") progress = event;
605
599
  trace(event?.type === "progress" ? "progress" : "event", "info", { event: capJson(event) });
@@ -620,9 +614,7 @@ export async function executeToolCalls(
620
614
  ? JSON.stringify(toolDef.parameters).slice(0, 800)
621
615
  : undefined;
622
616
  const detail = parseRes.error.message;
623
- const hint = expectedParams
624
- ? ` Expected parameters: ${expectedParams}.`
625
- : "";
617
+ const hint = expectedParams ? ` Expected parameters: ${expectedParams}.` : "";
626
618
  trace("call_blocked", "warn", { reason: "schema_validation", detail: parseRes.error.message });
627
619
  return {
628
620
  id: call.id,
@@ -648,7 +640,7 @@ export async function executeToolCalls(
648
640
  // Attempts run serially in the while loop below — never concurrently —
649
641
  // so a non-idempotent tool cannot execute twice at the same time.
650
642
  const isIdempotent = (toolDef as any).idempotent === true;
651
- const maxAttempts = configuredTries && configuredTries > 0 ? configuredTries : (isIdempotent ? 3 : 1);
643
+ const maxAttempts = configuredTries && configuredTries > 0 ? configuredTries : isIdempotent ? 3 : 1;
652
644
  const timeoutMs = numericOption(toolDef.timeoutMs);
653
645
  // Telemetry starts when the tool body is about to run, excluding queue
654
646
  // wait and schema validation. Retries/backoff remain part of this call.
@@ -668,8 +660,16 @@ export async function executeToolCalls(
668
660
  if (stopReason !== undefined) throw err; // ctx.stop: not a failure, never retried
669
661
  timedOut = err?.toolTimeout === true;
670
662
  const willRetry =
671
- isTransientError(err) && attempt < maxAttempts && !(err?.code === "ETIMEDOUT" && (!configuredTries || configuredTries <= 0));
672
- trace("attempt_failed", "warn", { attempt, maxAttempts, willRetry, error: describeError(err), progress: capJson(progress) });
663
+ isTransientError(err) &&
664
+ attempt < maxAttempts &&
665
+ !(err?.code === "ETIMEDOUT" && (!configuredTries || configuredTries <= 0));
666
+ trace("attempt_failed", "warn", {
667
+ attempt,
668
+ maxAttempts,
669
+ willRetry,
670
+ error: describeError(err),
671
+ progress: capJson(progress),
672
+ });
673
673
  if (!isTransientError(err) || attempt >= maxAttempts) throw err;
674
674
  // A timeout is a deliberate per-attempt deadline, not a parent
675
675
  // cancellation (which rejects via the abort signal instead and is
@@ -765,7 +765,9 @@ export async function executeToolCalls(
765
765
  error: {
766
766
  name: err?.name,
767
767
  ...(err?.code !== undefined ? { code: err.code } : {}),
768
- ...(err?.status !== undefined || err?.statusCode !== undefined ? { status: err.status ?? err.statusCode } : {}),
768
+ ...(err?.status !== undefined || err?.statusCode !== undefined
769
+ ? { status: err.status ?? err.statusCode }
770
+ : {}),
769
771
  },
770
772
  }),
771
773
  attempts,
@@ -65,7 +65,11 @@ export function zodToJsonSchema(schema: unknown): Record<string, unknown> {
65
65
  *
66
66
  * @example `const clean = cleanJsonSchema(rawSchema);`
67
67
  */
68
- export function cleanJsonSchema(schema: any, rootDefs?: Record<string, any>, seenRefs: Set<string> = new Set()): Record<string, unknown> {
68
+ export function cleanJsonSchema(
69
+ schema: any,
70
+ rootDefs?: Record<string, any>,
71
+ seenRefs: Set<string> = new Set()
72
+ ): Record<string, unknown> {
69
73
  if (typeof schema !== "object" || schema === null) {
70
74
  return schema;
71
75
  }
@@ -115,7 +119,8 @@ export function cleanJsonSchema(schema: any, rootDefs?: Record<string, any>, see
115
119
  if (rest.anyOf) rest.anyOf = (rest.anyOf as any[]).map((v: any) => cleanJsonSchema(v, defs, seenRefs));
116
120
  if (rest.oneOf) rest.oneOf = (rest.oneOf as any[]).map((v: any) => cleanJsonSchema(v, defs, seenRefs));
117
121
  if (rest.allOf) rest.allOf = (rest.allOf as any[]).map((v: any) => cleanJsonSchema(v, defs, seenRefs));
118
- if (rest.prefixItems) rest.prefixItems = (rest.prefixItems as any[]).map((v: any) => cleanJsonSchema(v, defs, seenRefs));
122
+ if (rest.prefixItems)
123
+ rest.prefixItems = (rest.prefixItems as any[]).map((v: any) => cleanJsonSchema(v, defs, seenRefs));
119
124
  // Recursively clean nested $ref inside properties that were not top-level
120
125
  for (const k of Object.keys(rest)) {
121
126
  if (rest[k] && typeof rest[k] === "object" && !Array.isArray(rest[k]) && (rest[k] as any).$ref) {
@@ -142,9 +147,7 @@ function extractZodObjectSchema(zodSchema: any): Record<string, unknown> {
142
147
 
143
148
  for (const [key, propSchema] of Object.entries(shape as Record<string, any>)) {
144
149
  const isOptional =
145
- propSchema.isOptional?.() ||
146
- propSchema._def?.typeName === "ZodOptional" ||
147
- propSchema._def?.type === "optional";
150
+ propSchema.isOptional?.() || propSchema._def?.typeName === "ZodOptional" || propSchema._def?.type === "optional";
148
151
 
149
152
  if (!isOptional) {
150
153
  required.push(key);
@@ -169,7 +172,8 @@ function inferZodPropertyType(prop: any): Record<string, unknown> {
169
172
  const tn = String(typeName).toLowerCase();
170
173
 
171
174
  if (tn.includes("number") || tn === "zodnumber") return { type: "number", ...(description ? { description } : {}) };
172
- if (tn.includes("boolean") || tn === "zodboolean") return { type: "boolean", ...(description ? { description } : {}) };
175
+ if (tn.includes("boolean") || tn === "zodboolean")
176
+ return { type: "boolean", ...(description ? { description } : {}) };
173
177
  if (tn.includes("integer")) return { type: "integer", ...(description ? { description } : {}) };
174
178
  if (tn.includes("enum") || tn === "zodenum") {
175
179
  const vals = unwrapped._def?.values || unwrapped._def?.entries || [];
@@ -49,7 +49,12 @@ export function normalizeSubagentMetadata(metas: unknown): SubAgentExecutionMeta
49
49
  if (!raw || typeof raw !== "object") continue;
50
50
  const m = raw as Record<string, unknown>;
51
51
  const u = (m.usage && typeof m.usage === "object" ? m.usage : {}) as Record<string, unknown>;
52
- const usage = { ...u, inputTokens: num(u.inputTokens), outputTokens: num(u.outputTokens), totalTokens: num(u.totalTokens) };
52
+ const usage = {
53
+ ...u,
54
+ inputTokens: num(u.inputTokens),
55
+ outputTokens: num(u.outputTokens),
56
+ totalTokens: num(u.totalTokens),
57
+ };
53
58
  out.push({
54
59
  ...(m as unknown as SubAgentExecutionMetadata),
55
60
  model: typeof m.model === "string" ? m.model : "unknown",
package/src/tools/tool.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  import { z } from "zod";
2
- import type {
3
- ToolDefinition,
4
- StandardToolDeclaration,
5
- ToolExecuteFn,
6
- } from "../types/tool.ts";
2
+ import type { ToolDefinition, StandardToolDeclaration, ToolExecuteFn } from "../types/tool.ts";
7
3
  import type { ThinkingLevel } from "../types/core.ts";
8
4
  import type { ThinkingOption } from "../types/agent.ts";
9
5
  import { zodToJsonSchema } from "./schema.ts";
@@ -54,9 +50,10 @@ export function normalizeThinkingV2(
54
50
  *
55
51
  * @example `resolveWhenBusy({ whenBusy: "queue", mode: "steer" }); // "queue"`
56
52
  */
57
- export function resolveWhenBusy(
58
- config?: { mode?: "steer" | "queue" | "auto"; whenBusy?: "steer" | "queue" | "auto" }
59
- ): "steer" | "queue" | "auto" {
53
+ export function resolveWhenBusy(config?: {
54
+ mode?: "steer" | "queue" | "auto";
55
+ whenBusy?: "steer" | "queue" | "auto";
56
+ }): "steer" | "queue" | "auto" {
60
57
  const v = config?.whenBusy ?? config?.mode ?? "auto";
61
58
  return v === "steer" || v === "queue" ? v : "auto";
62
59
  }
@@ -67,9 +64,10 @@ export function resolveWhenBusy(
67
64
  *
68
65
  * @example `normalizeMaxAttempts({ maxAttempts: 3 }); // 3`
69
66
  */
70
- export function normalizeMaxAttempts(
71
- config?: { maxTries?: number | string; maxAttempts?: number | string }
72
- ): number | string | undefined {
67
+ export function normalizeMaxAttempts(config?: {
68
+ maxTries?: number | string;
69
+ maxAttempts?: number | string;
70
+ }): number | string | undefined {
73
71
  return config?.maxTries ?? config?.maxAttempts;
74
72
  }
75
73
 
@@ -1,10 +1,6 @@
1
1
  import type { ModelSpec } from "./model.ts";
2
2
  import type { ToolDefinition } from "./tool.ts";
3
- import type {
4
- ThinkingLevel,
5
- CacheOption,
6
- ServiceTier,
7
- } from "./core.ts";
3
+ import type { ThinkingLevel, CacheOption, ServiceTier } from "./core.ts";
8
4
  import type { ModelProviderInstance } from "../providers/registry.ts";
9
5
  import type { Agent } from "../agent/agent.ts";
10
6
  import type { z } from "zod";
@@ -47,14 +43,7 @@ export interface AgentLogger {
47
43
  }
48
44
 
49
45
  /** Canonical tool-selection control. String form is the portable subset. */
50
- export type ToolChoiceOption =
51
- | "auto"
52
- | "none"
53
- | "required"
54
- | { mode?: "auto" | "none" | "required"; tool?: string };
55
-
56
- /** Canonical stop control: sequence(s) halting generation. */
57
- export type StopOption = string | string[];
46
+ export type ToolChoiceOption = "auto" | "none" | "required" | { mode?: "auto" | "none" | "required"; tool?: string };
58
47
 
59
48
  /** Canonical structured-output input. Zod, JSON Schema, or explicit form. */
60
49
  export type StructuredOutputOption =
@@ -167,10 +156,6 @@ export interface AgentConfig {
167
156
  googleTier?: "free" | "paid";
168
157
  /** Maximum output tokens for one model call. */
169
158
  maxOutputTokens?: number;
170
- /** Stop sequence(s). */
171
- stop?: StopOption;
172
- /** Deterministic seed, when the provider supports it. */
173
- seed?: number;
174
159
  /**
175
160
  * Optional output schema (Zod, JSON Schema, or `{ name, schema, strict }`,
176
161
  * see `StructuredOutputOption`, stored as `unknown` so `zod`
@@ -383,10 +368,6 @@ export interface AgentRunOptions {
383
368
  toolChoice?: ToolChoiceOption;
384
369
  /** Per-run output-token cap. */
385
370
  maxOutputTokens?: number;
386
- /** Per-run stop sequence(s). */
387
- stop?: StopOption;
388
- /** Per-run seed. */
389
- seed?: number;
390
371
  /** Per-run retry override. */
391
372
  retry?: RetryPolicyConfig;
392
373
  /** Per-run fetch override. */
@@ -2,18 +2,19 @@
2
2
 
3
3
  Shared contracts. Types only, plus a few tiny helpers that belong to a type.
4
4
 
5
- | File | What it defines |
6
- | :-- | :-- |
7
- | `agent.ts` | `AgentConfig`, `AgentRunOptions`, `DynamicSubagentsConfig`, `Budget`, `EditableAgentConfig`, run/turn event shapes. |
8
- | `core.ts` | `ThinkingLevel`, `CacheOption` (+ `CACHE_OPTIONS`), `ServiceTier`, `TokenUsage`. |
9
- | `message.ts` | `Message` and the content parts (text, image, audio, video, file, tool call/result, thinking), `ProviderSteps`. |
10
- | `model.ts` | `ModelSpec`, `Provider`, `ProviderRequestOptions`, `ProviderGenerateResult`, structured-output spec. |
11
- | `response.ts` | `AgentResponse`, `FinishReason` + `normalizeFinishReason`, `StreamEvent`, worker metadata. |
12
- | `tool.ts` | `ToolDefinition`, `ToolExecutionContext` (`log`, `stop`, `emit`), call/result records, the truncated-arguments marker. |
13
- | `scheduler.ts` | Scheduled-action input, record and delivery types. |
14
- | `errors.ts` | The typed error family (`AgentAccelError` and subclasses), `isAbortError`. |
5
+ | File | What it defines |
6
+ | :------------- | :--------------------------------------------------------------------------------------------------------------------- |
7
+ | `agent.ts` | `AgentConfig`, `AgentRunOptions`, `DynamicSubagentsConfig`, `Budget`, `EditableAgentConfig`, run/turn event shapes. |
8
+ | `core.ts` | `ThinkingLevel`, `CacheOption` (+ `CACHE_OPTIONS`), `ServiceTier`, `TokenUsage`. |
9
+ | `message.ts` | `Message` and the content parts (text, image, audio, video, file, tool call/result, thinking), `ProviderSteps`. |
10
+ | `model.ts` | `ModelSpec`, `Provider`, `ProviderRequestOptions`, `ProviderGenerateResult`, structured-output spec. |
11
+ | `response.ts` | `AgentResponse`, `FinishReason` + `normalizeFinishReason`, `StreamEvent`, worker metadata. |
12
+ | `tool.ts` | `ToolDefinition`, `ToolExecutionContext` (`log`, `stop`, `emit`), call/result records, the truncated-arguments marker. |
13
+ | `scheduler.ts` | Scheduled-action input, record and delivery types. |
14
+ | `errors.ts` | The typed error family (`AgentAccelError` and subclasses), `isAbortError`. |
15
15
 
16
16
  ## Invariants
17
+
17
18
  - Adding a field to a public type is a public API change: update `README.md` and `docs/`.
18
19
  - Abort means `signal.aborted` or `name === "AbortError"`, never a message match.
19
20
  - Error messages are one line and ANSI-free.