agent-accelerator 0.3.0 → 0.4.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/src/tools/tool.ts CHANGED
@@ -4,7 +4,90 @@ import type {
4
4
  StandardToolDeclaration,
5
5
  ToolExecuteFn,
6
6
  } from "../types/tool.ts";
7
+ import type { ThinkingLevel } from "../types/core.ts";
8
+ import type { ThinkingOption } from "../types/agent.ts";
7
9
  import { zodToJsonSchema } from "./schema.ts";
10
+ import { ConfigError } from "../types/errors.ts";
11
+
12
+ /**
13
+ * Valid tool-name pattern (Q-55): 1–64 chars of letters, digits, `_`, `-`.
14
+ * Names are validated at registration in {@link tool} (record-key fallback
15
+ * included); invalid names throw `ConfigError` with a fix.
16
+ */
17
+ export const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
18
+
19
+ /**
20
+ * Throws `ConfigError` when `name` is not a valid tool name.
21
+ *
22
+ * @example `assertValidToolName("get_status");`
23
+ */
24
+ export function assertValidToolName(name: string): void {
25
+ if (!TOOL_NAME_PATTERN.test(name)) {
26
+ throw new ConfigError(
27
+ `[Agent Accelerator] Invalid tool name "${name}": expected 1-64 characters of letters, digits, "_" or "-". ` +
28
+ `Fix: rename the tool (e.g. "get_status").`
29
+ );
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Normalizes the Q-52 canonical `thinking` option to a bare level.
35
+ * Accepts a level string or `{ level, budgetTokens }`; `budgetTokens` is
36
+ * carried separately by providers, so only the level is returned here.
37
+ * Resolution honors `thinking ?? thinkingLevel`.
38
+ *
39
+ * @example `normalizeThinkingV2({ level: "medium" }, "low"); // "medium"`
40
+ */
41
+ export function normalizeThinkingV2(
42
+ thinking?: ThinkingOption,
43
+ thinkingLevel?: ThinkingLevel
44
+ ): ThinkingLevel | undefined {
45
+ if (thinking !== undefined) {
46
+ if (typeof thinking === "string") return thinking;
47
+ return thinking.level ?? thinkingLevel;
48
+ }
49
+ return thinkingLevel;
50
+ }
51
+
52
+ /**
53
+ * Resolves the Q-51 canonical busy policy. `whenBusy` wins over `mode`.
54
+ *
55
+ * @example `resolveWhenBusy({ whenBusy: "queue", mode: "steer" }); // "queue"`
56
+ */
57
+ export function resolveWhenBusy(
58
+ config?: { mode?: "steer" | "queue" | "auto"; whenBusy?: "steer" | "queue" | "auto" }
59
+ ): "steer" | "queue" | "auto" {
60
+ const v = config?.whenBusy ?? config?.mode ?? "auto";
61
+ return v === "steer" || v === "queue" ? v : "auto";
62
+ }
63
+
64
+ /**
65
+ * Maps the Q-52 canonical worker-timeout knobs to the legacy `timeout` value
66
+ * (`0` = no limit, `-1` = model sets per-task `timeoutMs`, `>0` = fixed ms).
67
+ * An explicit `timeout`/`workerTimeoutMs` wins over `modelMaySetTimeout`.
68
+ *
69
+ * @example `normalizeWorkerTimeout({ modelMaySetTimeout: true }); // -1`
70
+ */
71
+ export function normalizeWorkerTimeout(
72
+ config?: { timeout?: number; workerTimeoutMs?: number; modelMaySetTimeout?: boolean }
73
+ ): number {
74
+ const explicit = config?.workerTimeoutMs ?? config?.timeout;
75
+ if (Number.isFinite(explicit as number)) return Math.floor(explicit as number);
76
+ if (config?.modelMaySetTimeout === true) return -1;
77
+ return 0;
78
+ }
79
+
80
+ /**
81
+ * Normalizes the Q-52 `maxAttempts` alias to a `maxTries` value.
82
+ * `maxTries` wins when both are set.
83
+ *
84
+ * @example `normalizeMaxAttempts({ maxAttempts: 3 }); // 3`
85
+ */
86
+ export function normalizeMaxAttempts(
87
+ config?: { maxTries?: number | string; maxAttempts?: number | string }
88
+ ): number | string | undefined {
89
+ return config?.maxTries ?? config?.maxAttempts;
90
+ }
8
91
 
9
92
  /** Complete configuration accepted by {@link tool}. */
10
93
  export interface CreateToolOptions<TInput = any, TOutput = any> {
@@ -22,8 +105,54 @@ export interface CreateToolOptions<TInput = any, TOutput = any> {
22
105
  timeoutMs?: number;
23
106
  /** Maximum total attempts for transient failures. `0`/omitted means no configured limit. */
24
107
  maxTries?: number | string;
108
+ /**
109
+ * Canonical alias for `maxTries` (Q-52). When both are set, `maxTries` wins.
110
+ * Normalized by `normalizeMaxAttempts`.
111
+ */
112
+ maxAttempts?: number | string;
25
113
  /** Maximum simultaneous executions of this tool. `0`/omitted uses the global pool. */
26
114
  maxConcurrency?: number | string;
115
+ /**
116
+ * Whether this tool is safe to retry on transient failures. Retry defaults
117
+ * to a single attempt unless this is `true` (bounded retries) or `maxTries`
118
+ * explicitly overrides the attempt count.
119
+ */
120
+ idempotent?: boolean;
121
+ /**
122
+ * Whether an identical call may repeat across consecutive turns. Defaults to
123
+ * `true` for idempotent tools, `false` otherwise. Honored by repeat guards.
124
+ */
125
+ repeatable?: boolean;
126
+ /**
127
+ * Maximum characters of a string tool result kept for model context.
128
+ * Longer strings are truncated with a `[Truncated: ...]` marker.
129
+ * `0`/omitted uses the executor default (20000).
130
+ */
131
+ maxResultChars?: number | string;
132
+ /** Optional hook to redact/replace the error message sent back to the model. */
133
+ redactError?: (err: unknown) => string;
134
+ /**
135
+ * Canonical alias for `redactError` (Q-55). When both are set,
136
+ * `redactError` wins.
137
+ */
138
+ redact?: (err: unknown) => string;
139
+ /**
140
+ * Explicit retry policy (Q-55). Preferred over bare `maxTries` for new
141
+ * code; the executor reads via `(toolDef as any).retry`.
142
+ */
143
+ retry?: { maxRetries?: number; baseDelayMs?: number; maxDelayMs?: number };
144
+ /** Free-form tags for grouping/filtering tools (e.g. `["web", "readonly"]`). */
145
+ tags?: string[];
146
+ /**
147
+ * When true, the host must approve this call before execution. Stored only;
148
+ * enforcement is wired by the host, default open.
149
+ */
150
+ needsApproval?: boolean;
151
+ /**
152
+ * Optional output schema (Zod or JSON Schema, stored as `unknown` so `zod`
153
+ * stays optional for type-only consumers).
154
+ */
155
+ output?: unknown;
27
156
  /** Implementation invoked with validated input and execution metadata. */
28
157
  execute: ToolExecuteFn<TInput, TOutput>;
29
158
  }
@@ -46,7 +175,7 @@ export interface CreateToolOptions<TInput = any, TOutput = any> {
46
175
  * maxTries: 2,
47
176
  * maxConcurrency: 2,
48
177
  * execute: async ({ username }, ctx) => {
49
- * return username === "Akshat Dwivedi" ? "Valid" : "Invalid";
178
+ * return username === "alice" ? "Valid" : "Invalid";
50
179
  * },
51
180
  * });
52
181
  * ```
@@ -57,6 +186,10 @@ export interface CreateToolOptions<TInput = any, TOutput = any> {
57
186
  export function tool<TInput = any, TOutput = any>(
58
187
  options: CreateToolOptions<TInput, TOutput>
59
188
  ): ToolDefinition<TInput, TOutput> {
189
+ // Q-55: names are validated at registration (record-key fallback included).
190
+ if (options.name !== undefined) assertValidToolName(options.name);
191
+ // ToolDefinition (src/types/tool.ts) carries the v2 policies as typed
192
+ // optionals; the executor additionally reads them via `(toolDef as any).*`.
60
193
  return {
61
194
  name: options.name,
62
195
  description: options.description,
@@ -64,10 +197,21 @@ export function tool<TInput = any, TOutput = any>(
64
197
  parameters: options.parameters,
65
198
  strict: options.strict,
66
199
  timeoutMs: options.timeoutMs,
67
- maxTries: options.maxTries,
200
+ maxTries: normalizeMaxAttempts(options),
68
201
  maxConcurrency: options.maxConcurrency,
69
202
  execute: options.execute,
70
- };
203
+ ...(options.idempotent !== undefined ? { idempotent: options.idempotent } : {}),
204
+ ...(options.repeatable !== undefined ? { repeatable: options.repeatable } : {}),
205
+ ...(options.maxResultChars !== undefined ? { maxResultChars: options.maxResultChars } : {}),
206
+ ...(options.redactError !== undefined ? { redactError: options.redactError } : {}),
207
+ ...(options.redactError === undefined && options.redact !== undefined ? { redactError: options.redact } : {}),
208
+ ...(options.redact !== undefined ? { redact: options.redact } : {}),
209
+ ...(options.retry !== undefined ? { retry: options.retry } : {}),
210
+ ...(options.tags !== undefined ? { tags: options.tags } : {}),
211
+ ...(options.needsApproval !== undefined ? { needsApproval: options.needsApproval } : {}),
212
+ ...(options.output !== undefined ? { output: options.output } : {}),
213
+ ...(options.maxAttempts !== undefined ? { maxAttempts: options.maxAttempts } : {}),
214
+ } as ToolDefinition<TInput, TOutput>;
71
215
  }
72
216
 
73
217
  /**
@@ -1,4 +1,4 @@
1
- import type { ProviderId, ModelSpec } from "./model.ts";
1
+ import type { ModelSpec } from "./model.ts";
2
2
  import type { ToolDefinition } from "./tool.ts";
3
3
  import type {
4
4
  ThinkingLevel,
@@ -7,6 +7,71 @@ import type {
7
7
  } from "./core.ts";
8
8
  import type { ModelProviderInstance } from "../providers/registry.ts";
9
9
  import type { Agent } from "../agent/agent.ts";
10
+ import type { z } from "zod";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Q-52/Q-53/Q-54 canonical option types (all additive; nothing renamed).
14
+ //
15
+ // Canonical homes (one place per setting):
16
+ // - Worker model: `dynamicSubagents.model` only. Top-level `subagentModel`
17
+ // is a deprecated alias (still honored: dynamicSubagents.model ??
18
+ // subagentModel ?? SUB_AGENT_MODEL ?? parent model).
19
+ // - Session identity: agent-level `sessionId` only (per-run `sessionId`
20
+ // selects a named session entry; `cache.sessionId` is a deprecated alias).
21
+ // - Model identity: `ModelRef` = string (`provider/model`) | catalog
22
+ // `ModelSpec` | `ModelProviderInstance` ({ provider, model, apiKey?,
23
+ // baseUrl? }). Explicit `Agent` config always wins over env; constructing
24
+ // an `Agent` with an explicit `model` reads no env for routing.
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /** Canonical model reference: `provider/model` string, catalog spec, or provider helper result. */
28
+ export type ModelRef = string | ModelSpec | ModelProviderInstance;
29
+
30
+ /** Canonical reasoning option (Q-52): bare level, or level + explicit token budget. */
31
+ export type ThinkingOption = ThinkingLevel | { level?: ThinkingLevel; budgetTokens?: number };
32
+
33
+ /** Canonical sampling knobs (Q-54). Read by the loop via `(config as any).sampling`. */
34
+ export interface SamplingConfig {
35
+ temperature?: number;
36
+ topP?: number;
37
+ }
38
+
39
+ /** Canonical retry policy (Q-54). Read by the loop via `(config as any).retry`. */
40
+ export interface RetryPolicyConfig {
41
+ maxRetries?: number;
42
+ baseDelayMs?: number;
43
+ maxDelayMs?: number;
44
+ budgetMs?: number;
45
+ }
46
+
47
+ /** Minimal logger surface for Agent/loop telemetry (Q-54). */
48
+ export interface AgentLogger {
49
+ debug?: (...args: unknown[]) => void;
50
+ info?: (...args: unknown[]) => void;
51
+ warn?: (...args: unknown[]) => void;
52
+ error?: (...args: unknown[]) => void;
53
+ }
54
+
55
+ /** Canonical tool-selection control (Q-54). String form is the portable subset. */
56
+ export type ToolChoiceOption =
57
+ | "auto"
58
+ | "none"
59
+ | "required"
60
+ | { mode?: "auto" | "none" | "required"; tool?: string };
61
+
62
+ /** Canonical stop control (Q-54): sequence(s) halting generation. */
63
+ export type StopOption = string | string[];
64
+
65
+ /** Canonical structured-output input (Q-61). Zod, JSON Schema, or explicit form. */
66
+ export type StructuredOutputOption =
67
+ | z.ZodType
68
+ | Record<string, unknown>
69
+ | {
70
+ name?: string;
71
+ description?: string;
72
+ schema: z.ZodType | Record<string, unknown>;
73
+ strict?: boolean;
74
+ };
10
75
 
11
76
  /** Configuration used to construct an {@link Agent}. */
12
77
  export interface AgentConfig {
@@ -30,6 +95,13 @@ export interface AgentConfig {
30
95
  tools?: Record<string, ToolDefinition> | ToolDefinition[];
31
96
  /** Requested reasoning level, validated against the model catalog. */
32
97
  thinkingLevel?: ThinkingLevel;
98
+ /**
99
+ * Canonical reasoning option (Q-52, preferred over `thinkingLevel`).
100
+ * Accepts a bare level or `{ level, budgetTokens }`. When both are set,
101
+ * `thinking` wins; resolution honors `thinking ?? thinkingLevel`.
102
+ * See `normalizeThinkingV2` (re-exported from the root entry).
103
+ */
104
+ thinking?: ThinkingOption;
33
105
  /** Prompt-cache retention and session-affinity settings. */
34
106
  cache?: CacheConfig;
35
107
  /** Provider service tier, when supported. */
@@ -45,12 +117,23 @@ export interface AgentConfig {
45
117
  sessionId?: string;
46
118
  /** Headers merged into every provider request. */
47
119
  headers?: Record<string, string>;
48
- /** Maximum model/tool turns per run. `0` or omitted means infinite. */
120
+ /**
121
+ * Maximum model/tool turns per run. `0` or omitted means infinite
122
+ * (legacy `0` alias, normalized to `Infinity`; prefer omitting).
123
+ */
49
124
  maxTurns?: number;
50
125
  /** Fixed worker agents exposed as delegation tools. */
51
126
  subagents?: (Agent | { name: string; description: string; agent: Agent })[];
52
127
  /** Clears conversation state before and after every run. */
53
128
  stateless?: boolean;
129
+ /**
130
+ * Mid-session queue policy for new queries arriving while a run is active.
131
+ * - `{ mode: "steer" }`: only steer (inject into current turn) is allowed.
132
+ * - `{ mode: "queue" }`: only queue (next-turn follow-up) is allowed.
133
+ * - `{ mode: "auto" }` (default): caller picks per message via `enqueue` or `steer()`/`queue()`.
134
+ * `interrupt` (cancel stream now) is always available via `interrupt()`/`cancel()`/`AbortSignal`.
135
+ */
136
+ midSession?: MidSessionConfig;
54
137
  /**
55
138
  * Durable session persistence: when set, the session file is rewritten
56
139
  * after every step (user turn, assistant turn, tool batch, sub-agent
@@ -59,6 +142,84 @@ export interface AgentConfig {
59
142
  * pretty `.session.json` file. Writes are atomic and never fail a turn.
60
143
  */
61
144
  persist?: { dir?: string; file?: string };
145
+ // Q-54 missing knobs (all optional, additive; loop reads via `(config as any).*`).
146
+ /** Canonical tool-selection control (`"auto"` default). */
147
+ toolChoice?: ToolChoiceOption;
148
+ /** Maximum output tokens for one model call. */
149
+ maxOutputTokens?: number;
150
+ /** Sampling knobs. */
151
+ sampling?: SamplingConfig;
152
+ /** Stop sequence(s). */
153
+ stop?: StopOption;
154
+ /** Deterministic seed, when the provider supports it. */
155
+ seed?: number;
156
+ /**
157
+ * Optional output schema (Zod, JSON Schema, or `{ name, schema, strict }`,
158
+ * see `StructuredOutputOption`, stored as `unknown` so `zod`
159
+ * stays optional for type-only consumers). Agent-level only; per-run
160
+ * overrides are intentionally unsupported. When set, the model answer is
161
+ * constrained to the schema on all providers and `AgentResponse.parsed`
162
+ * carries the validated value.
163
+ */
164
+ output?: unknown;
165
+ /** Retry policy for provider calls. */
166
+ retry?: RetryPolicyConfig;
167
+ /** Fetch implementation override (defaults to global `fetch`). */
168
+ fetch?: typeof fetch;
169
+ /** Provider/env overrides forwarded to key resolution. */
170
+ env?: Record<string, string>;
171
+ /** Structured logger for loop telemetry (defaults to no-op). */
172
+ logger?: AgentLogger;
173
+ /** Control hooks (Q-59). All optional, fail-open by default. */
174
+ hooks?: unknown;
175
+ /** Threshold compaction policy (Q-60a). Fully configurable/replaceable. */
176
+ contextManagement?: unknown;
177
+ /** Telemetry bus (Q-41). Default no-op; pretty logger lives in /cli. */
178
+ telemetry?: { onEvent?: (e: any) => void } | unknown;
179
+ /** Local file access roots (Q-08). Default deny; `path` sources must resolve inside roots. */
180
+ fileAccess?: { roots?: string[] };
181
+ /** Raw capture policy (Q-11). Default "redacted": no bodies. */
182
+ captureRaw?: false | "redacted" | true;
183
+ /** Opt-in run budgets (Q-39). All fields default unlimited. */
184
+ budget?: Budget;
185
+ /** Run + network limits (Q-22). Turn/budget caps unlimited by default; network timeouts finite. */
186
+ limits?: AgentRunLimits;
187
+ }
188
+
189
+ /** Per-request + run limits (Q-22). */
190
+ export interface AgentRunLimits {
191
+ maxTurns?: number;
192
+ deadlineMs?: number;
193
+ maxTotalTokens?: number;
194
+ maxToolCallsPerRun?: number;
195
+ requestTimeoutMs?: number;
196
+ firstByteTimeoutMs?: number;
197
+ streamIdleTimeoutMs?: number;
198
+ }
199
+
200
+ /** Developer policy for mid-session new-query insertion while a run is active. */
201
+ export type MidSessionMode = "steer" | "queue" | "auto";
202
+
203
+ /** Q-51 canonical busy-policy alias: prefer `whenBusy`, `mode` kept for compat. */
204
+ export type WhenBusyMode = MidSessionMode;
205
+
206
+ /** Developer-configured policy for LLM-spawned dynamic sub-agents. */
207
+ export interface MidSessionConfig {
208
+ /**
209
+ * Which mid-session method is allowed while a run is active:
210
+ * - `"steer"`: only steer (inject into current turn) is allowed.
211
+ * - `"queue"`: only queue (next-turn follow-up) is allowed.
212
+ * - `"auto"` (default): caller/user picks per message via `enqueue` or `steer()`/`queue()`.
213
+ */
214
+ mode?: MidSessionMode;
215
+ /**
216
+ * Canonical alias for `mode` (Q-51, preferred). When both are set,
217
+ * `whenBusy` wins. Accepted anywhere `mode` is read; resolution honors
218
+ * `whenBusy ?? mode ?? "auto"`.
219
+ */
220
+ whenBusy?: MidSessionMode;
221
+ /** Max queued (follow-up) requests buffered while busy. Extra `queue()` calls throw. Defaults to 20. */
222
+ maxQueued?: number;
62
223
  }
63
224
 
64
225
  /** Developer-configured policy for LLM-spawned dynamic sub-agents. */
@@ -75,12 +236,29 @@ export interface DynamicSubagentsConfig {
75
236
  tools?: Record<string, ToolDefinition> | ToolDefinition[];
76
237
  /** Per-worker timeout in ms. `0` (default) = no limit. `-1` = the Main Agent sets a per-task `timeoutMs`. `>0` = fixed timeout for every worker. */
77
238
  timeout?: number;
239
+ /**
240
+ * Canonical per-worker timeout in ms (Q-52, preferred over `timeout`).
241
+ * Maps to `timeout`: `workerTimeoutMs ?? timeout ?? 0`. See
242
+ * `normalizeWorkerTimeout` (re-exported from the root entry).
243
+ */
244
+ workerTimeoutMs?: number;
245
+ /**
246
+ * When true, the Main Agent sets a per-task `timeoutMs` (maps to
247
+ * `timeout: -1`). When both are set, an explicit `timeout`/`workerTimeoutMs`
248
+ * wins over `modelMaySetTimeout`.
249
+ */
250
+ modelMaySetTimeout?: boolean;
78
251
  }
79
252
 
80
253
  /** Per-run overrides for {@link Agent.run}, {@link Agent.ask}, and {@link Agent.stream}. */
81
254
  export interface AgentRunOptions {
82
255
  /** Per-run reasoning-level override, validated against the selected model. */
83
256
  thinkingLevel?: ThinkingLevel;
257
+ /**
258
+ * Canonical per-run reasoning option (Q-52, preferred over `thinkingLevel`).
259
+ * Resolution honors `thinking ?? thinkingLevel`.
260
+ */
261
+ thinking?: ThinkingOption;
84
262
  /** Return a streaming object instead of waiting for a final response. */
85
263
  stream?: boolean;
86
264
  /** Cancels provider, tool, and sub-agent work. */
@@ -89,6 +267,14 @@ export interface AgentRunOptions {
89
267
  sessionId?: string;
90
268
  /** Context added only to this user turn, preserving the stable system prompt. */
91
269
  additionalContext?: string;
270
+ /**
271
+ * Mid-session method to use when this call arrives while another run is active.
272
+ * Only honored when `midSession.mode` is `"auto"` (default). When the developer
273
+ * enforces `"steer"` or `"queue"`, that policy wins and this field is ignored.
274
+ * When idle, this field is ignored and the call runs immediately.
275
+ * Defaults to `"queue"` (finish current turn, run as next turn).
276
+ */
277
+ enqueue?: "steer" | "queue";
92
278
  /** Headers merged for this run only. */
93
279
  headers?: Record<string, string>;
94
280
  /** Called for each text delta as it streams (only when stream:true). */
@@ -101,6 +287,26 @@ export interface AgentRunOptions {
101
287
  wrapThinking?: boolean;
102
288
  /** Called after every model/tool turn with a turn summary (also drives sub-agent step logs + durable persistence). */
103
289
  onTurn?: (turn: AgentTurnEvent) => void;
290
+ // Q-54 per-run knobs (all optional, additive; `output` intentionally
291
+ // omitted here — it is agent-level only, see AgentConfig.output).
292
+ /** Per-run tool-selection override. */
293
+ toolChoice?: ToolChoiceOption;
294
+ /** Per-run output-token cap. */
295
+ maxOutputTokens?: number;
296
+ /** Per-run sampling override. */
297
+ sampling?: SamplingConfig;
298
+ /** Per-run stop sequence(s). */
299
+ stop?: StopOption;
300
+ /** Per-run seed. */
301
+ seed?: number;
302
+ /** Per-run retry override. */
303
+ retry?: RetryPolicyConfig;
304
+ /** Per-run fetch override. */
305
+ fetch?: typeof fetch;
306
+ /** Per-run env overrides. */
307
+ env?: Record<string, string>;
308
+ /** Per-run logger. */
309
+ logger?: AgentLogger;
104
310
  }
105
311
 
106
312
  /** Turn summary emitted to `AgentRunOptions.onTurn` after each loop turn. */
@@ -116,3 +322,39 @@ export interface AgentTurnEvent {
116
322
  /** Tool results executed this turn, if any. */
117
323
  toolResults?: Array<{ id: string; name: string; isError?: boolean }>;
118
324
  }
325
+
326
+ /** Optional run budgets enforced by delegation helpers (Q-39). All fields default to unlimited. */
327
+ export interface Budget {
328
+ /** Maximum total cost in USD across this agent + spawned workers. */
329
+ maxCostUsd?: number;
330
+ /** Maximum total tokens across this agent + spawned workers. */
331
+ maxTotalTokens?: number;
332
+ /** Maximum wall/monotonic duration in ms since budget tracking started. */
333
+ maxDurationMs?: number;
334
+ /** Maximum total dynamic sub-agents spawned via `spawn_subagents`. */
335
+ maxTotalSubagents?: number;
336
+ }
337
+
338
+ /** Mutable counters backing a {@link Budget}. Stored on the agent instance (untyped `budgetState`). */
339
+ export interface BudgetState {
340
+ /** Workers spawned so far (incremented by `reserveSpawn`). */
341
+ spawned: number;
342
+ /** Accumulated total tokens charged via `chargeBudgetUsage`. */
343
+ totalTokens: number;
344
+ /** Accumulated total cost in USD charged via `chargeBudgetUsage`. */
345
+ totalCostUsd: number;
346
+ /** Monotonic start time (`nowMs()` clock) for `maxDurationMs` checks. */
347
+ startedAtMs: number;
348
+ }
349
+
350
+ /** Run/turn lineage attached to sub-agent metadata via `(metadata as any)` (Q-42). */
351
+ export interface SubAgentRunIds {
352
+ /** Stable id for this worker batch/run (`newRunId`). */
353
+ runId?: string;
354
+ /** Stable id for this worker turn (`newTurnId`). */
355
+ turnId?: string;
356
+ /** Parent run id when known (opaque, forwarded when the host exposes one). */
357
+ parentRunId?: string;
358
+ /** Root run id when known (opaque, forwarded when the host exposes one). */
359
+ rootRunId?: string;
360
+ }
@@ -0,0 +1,133 @@
1
+ import type { TokenUsage } from "./core.ts";
2
+ import type { ToolCallRecord, ToolResultRecord } from "./tool.ts";
3
+
4
+ /** Machine-readable error codes. No ANSI in messages. */
5
+ export type AgentAccelErrorCode =
6
+ | "config"
7
+ | "provider"
8
+ | "timeout"
9
+ | "budget_exceeded"
10
+ | "tool"
11
+ | "validation"
12
+ | "session_load"
13
+ | "security_policy"
14
+ | "abort"
15
+ | "incomplete_stream"
16
+ | "context_overflow";
17
+
18
+ export interface AgentAccelErrorContext {
19
+ provider?: string;
20
+ model?: string;
21
+ status?: number;
22
+ requestId?: string;
23
+ retryable?: boolean;
24
+ runId?: string;
25
+ turnId?: string;
26
+ docs?: string;
27
+ fixes?: string[];
28
+ partial?: {
29
+ usage?: TokenUsage;
30
+ turns?: number;
31
+ text?: string;
32
+ toolCalls?: ToolCallRecord[];
33
+ toolResults?: ToolResultRecord[];
34
+ };
35
+ [k: string]: unknown;
36
+ }
37
+
38
+ /** Base for all SDK errors. Message is one line, no ANSI. */
39
+ export class AgentAccelError extends Error {
40
+ readonly code: AgentAccelErrorCode;
41
+ readonly retryable: boolean;
42
+ readonly context: AgentAccelErrorContext;
43
+ constructor(code: AgentAccelErrorCode, message: string, opts?: { retryable?: boolean; context?: AgentAccelErrorContext; cause?: unknown }) {
44
+ super(message);
45
+ this.name = "AgentAccelError";
46
+ this.code = code;
47
+ this.retryable = opts?.retryable ?? false;
48
+ this.context = opts?.context ?? {};
49
+ if (opts?.cause !== undefined) (this as { cause?: unknown }).cause = opts.cause;
50
+ }
51
+ }
52
+
53
+ export class ConfigError extends AgentAccelError {
54
+ constructor(message: string, context?: AgentAccelErrorContext) {
55
+ super("config", message, { retryable: false, context });
56
+ this.name = "ConfigError";
57
+ }
58
+ }
59
+
60
+ export class ProviderError extends AgentAccelError {
61
+ readonly status?: number;
62
+ constructor(message: string, opts?: { status?: number; provider?: string; model?: string; requestId?: string; retryable?: boolean; context?: AgentAccelErrorContext }) {
63
+ super("provider", message, {
64
+ retryable: opts?.retryable ?? false,
65
+ context: { ...opts?.context, provider: opts?.provider, model: opts?.model, status: opts?.status, requestId: opts?.requestId },
66
+ });
67
+ this.name = "ProviderError";
68
+ this.status = opts?.status;
69
+ }
70
+ }
71
+
72
+ export class TimeoutError extends AgentAccelError {
73
+ constructor(message: string, context?: AgentAccelErrorContext) {
74
+ super("timeout", message, { retryable: true, context });
75
+ this.name = "TimeoutError";
76
+ }
77
+ }
78
+
79
+ export class BudgetExceededError extends AgentAccelError {
80
+ constructor(message: string, context?: AgentAccelErrorContext) {
81
+ super("budget_exceeded", message, { retryable: false, context });
82
+ this.name = "BudgetExceededError";
83
+ }
84
+ }
85
+
86
+ export class ToolError extends AgentAccelError {
87
+ readonly expose: boolean;
88
+ constructor(message: string, opts?: { retryable?: boolean; expose?: boolean; context?: AgentAccelErrorContext }) {
89
+ super("tool", message, { retryable: opts?.retryable ?? false, context: opts?.context });
90
+ this.name = "ToolError";
91
+ this.expose = opts?.expose ?? true;
92
+ }
93
+ }
94
+
95
+ export class ValidationError extends AgentAccelError {
96
+ constructor(message: string, context?: AgentAccelErrorContext) {
97
+ super("validation", message, { retryable: false, context });
98
+ this.name = "ValidationError";
99
+ }
100
+ }
101
+
102
+ export class SessionLoadError extends AgentAccelError {
103
+ constructor(message: string, context?: AgentAccelErrorContext) {
104
+ super("session_load", message, { retryable: false, context });
105
+ this.name = "SessionLoadError";
106
+ }
107
+ }
108
+
109
+ export class SecurityPolicyError extends AgentAccelError {
110
+ constructor(message: string, context?: AgentAccelErrorContext) {
111
+ super("security_policy", message, { retryable: false, context });
112
+ this.name = "SecurityPolicyError";
113
+ }
114
+ }
115
+
116
+ /** Abort is signal.aborted or name === AbortError only. Never message regex. */
117
+ export function isAbortError(error: unknown): boolean {
118
+ const err = error as { name?: unknown } | null;
119
+ if (!err || typeof err !== "object") return false;
120
+ if ((err as { name?: unknown }).name === "AbortError") return true;
121
+ return false;
122
+ }
123
+
124
+ /** Format without ANSI for CLI pretty-printing. */
125
+ export function formatErrorPlain(err: unknown): string {
126
+ if (err instanceof AgentAccelError) {
127
+ const ctx = err.context?.provider ? ` [${err.context.provider}${err.context.model ? `/${err.context.model}` : ""}]` : "";
128
+ const status = err.context?.status ? ` (${err.context.status})` : "";
129
+ return `[${err.code}]${ctx}${status}: ${err.message}`;
130
+ }
131
+ if (err instanceof Error) return `${err.name}: ${err.message}`;
132
+ return String(err);
133
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ThinkingConfig, CacheConfig, ServiceTier, TokenUsage } from "./core.ts";
2
- import type { ProviderContext, Message, ContentPart } from "./message.ts";
2
+ import type { ProviderContext } from "./message.ts";
3
3
  import type { StandardToolDeclaration, ToolCallRecord } from "./tool.ts";
4
4
  import type { AssistantMessageEventStream } from "../streaming/event-stream.ts";
5
5
 
@@ -87,6 +87,7 @@ export interface ModelCapabilities {
87
87
  supportsLongCacheRetention?: boolean;
88
88
  supportsParallelToolCalls?: boolean;
89
89
  supportsStreaming?: boolean;
90
+ supportsStructuredOutput?: boolean;
90
91
  modalities?: ("text" | "image" | "audio" | "video" | "pdf")[];
91
92
  // derived
92
93
  supportsReasoningToggle?: boolean;
@@ -138,6 +139,24 @@ export interface ModelSpec {
138
139
  raw?: RawModelData;
139
140
  }
140
141
 
142
+ // ---------------------------------------------------------------------------
143
+ // Structured outputs — normalized, provider-agnostic spec.
144
+ // Canonical input is `AgentConfig.output` (Zod, JSON Schema, or explicit
145
+ // `{ name, schema, strict }`); `normalizeStructuredOutput` in
146
+ // `src/providers.ts` produces this shape for adapters.
147
+ // ---------------------------------------------------------------------------
148
+ /** Normalized structured-output request passed from Agent to providers. */
149
+ export interface StructuredOutputSpec {
150
+ /** Format name (1-64 chars, `[a-zA-Z0-9_-]`). Defaults to `structured_output`. */
151
+ name: string;
152
+ /** JSON Schema for the final answer (already converted from Zod when needed). */
153
+ schema: Record<string, unknown>;
154
+ /** Strict schema adherence (OpenAI/OpenRouter only; ignored by Google). Defaults to true. */
155
+ strict?: boolean;
156
+ /** Optional description of what the format is for. */
157
+ description?: string;
158
+ }
159
+
141
160
  // ---------------------------------------------------------------------------
142
161
  // Provider plumbing — unchanged API
143
162
  // ---------------------------------------------------------------------------
@@ -156,6 +175,8 @@ export interface ProviderRequestOptions {
156
175
  env?: Record<string, string>;
157
176
  maxRetries?: number;
158
177
  maxRetryDelayMs?: number;
178
+ /** Normalized structured-output spec (see `normalizeStructuredOutput`). */
179
+ output?: StructuredOutputSpec;
159
180
  }
160
181
 
161
182
  /** Auditable request/response wire payload captured in AgentResponse. */