agent-accelerator 0.2.0 → 0.4.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.
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,
@@ -8,6 +8,59 @@ import type {
8
8
  import type { ModelProviderInstance } from "../providers/registry.ts";
9
9
  import type { Agent } from "../agent/agent.ts";
10
10
 
11
+ // ---------------------------------------------------------------------------
12
+ // Q-52/Q-53/Q-54 canonical option types (all additive; nothing renamed).
13
+ //
14
+ // Canonical homes (one place per setting):
15
+ // - Worker model: `dynamicSubagents.model` only. Top-level `subagentModel`
16
+ // is a deprecated alias (still honored: dynamicSubagents.model ??
17
+ // subagentModel ?? SUB_AGENT_MODEL ?? parent model).
18
+ // - Session identity: agent-level `sessionId` only (per-run `sessionId`
19
+ // selects a named session entry; `cache.sessionId` is a deprecated alias).
20
+ // - Model identity: `ModelRef` = string (`provider/model`) | catalog
21
+ // `ModelSpec` | `ModelProviderInstance` ({ provider, model, apiKey?,
22
+ // baseUrl? }). Explicit `Agent` config always wins over env; constructing
23
+ // an `Agent` with an explicit `model` reads no env for routing.
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /** Canonical model reference: `provider/model` string, catalog spec, or provider helper result. */
27
+ export type ModelRef = string | ModelSpec | ModelProviderInstance;
28
+
29
+ /** Canonical reasoning option (Q-52): bare level, or level + explicit token budget. */
30
+ export type ThinkingOption = ThinkingLevel | { level?: ThinkingLevel; budgetTokens?: number };
31
+
32
+ /** Canonical sampling knobs (Q-54). Read by the loop via `(config as any).sampling`. */
33
+ export interface SamplingConfig {
34
+ temperature?: number;
35
+ topP?: number;
36
+ }
37
+
38
+ /** Canonical retry policy (Q-54). Read by the loop via `(config as any).retry`. */
39
+ export interface RetryPolicyConfig {
40
+ maxRetries?: number;
41
+ baseDelayMs?: number;
42
+ maxDelayMs?: number;
43
+ budgetMs?: number;
44
+ }
45
+
46
+ /** Minimal logger surface for Agent/loop telemetry (Q-54). */
47
+ export interface AgentLogger {
48
+ debug?: (...args: unknown[]) => void;
49
+ info?: (...args: unknown[]) => void;
50
+ warn?: (...args: unknown[]) => void;
51
+ error?: (...args: unknown[]) => void;
52
+ }
53
+
54
+ /** Canonical tool-selection control (Q-54). String form is the portable subset. */
55
+ export type ToolChoiceOption =
56
+ | "auto"
57
+ | "none"
58
+ | "required"
59
+ | { mode?: "auto" | "none" | "required"; tool?: string };
60
+
61
+ /** Canonical stop control (Q-54): sequence(s) halting generation. */
62
+ export type StopOption = string | string[];
63
+
11
64
  /** Configuration used to construct an {@link Agent}. */
12
65
  export interface AgentConfig {
13
66
  /** Display name used in tool and sub-agent metadata. */
@@ -30,6 +83,13 @@ export interface AgentConfig {
30
83
  tools?: Record<string, ToolDefinition> | ToolDefinition[];
31
84
  /** Requested reasoning level, validated against the model catalog. */
32
85
  thinkingLevel?: ThinkingLevel;
86
+ /**
87
+ * Canonical reasoning option (Q-52, preferred over `thinkingLevel`).
88
+ * Accepts a bare level or `{ level, budgetTokens }`. When both are set,
89
+ * `thinking` wins; resolution honors `thinking ?? thinkingLevel`.
90
+ * See `normalizeThinkingV2` (re-exported from the root entry).
91
+ */
92
+ thinking?: ThinkingOption;
33
93
  /** Prompt-cache retention and session-affinity settings. */
34
94
  cache?: CacheConfig;
35
95
  /** Provider service tier, when supported. */
@@ -45,12 +105,106 @@ export interface AgentConfig {
45
105
  sessionId?: string;
46
106
  /** Headers merged into every provider request. */
47
107
  headers?: Record<string, string>;
48
- /** Maximum model/tool turns per run. Defaults to 10. */
108
+ /**
109
+ * Maximum model/tool turns per run. `0` or omitted means infinite
110
+ * (legacy `0` alias, normalized to `Infinity`; prefer omitting).
111
+ */
49
112
  maxTurns?: number;
50
113
  /** Fixed worker agents exposed as delegation tools. */
51
114
  subagents?: (Agent | { name: string; description: string; agent: Agent })[];
52
115
  /** Clears conversation state before and after every run. */
53
116
  stateless?: boolean;
117
+ /**
118
+ * Mid-session queue policy for new queries arriving while a run is active.
119
+ * - `{ mode: "steer" }`: only steer (inject into current turn) is allowed.
120
+ * - `{ mode: "queue" }`: only queue (next-turn follow-up) is allowed.
121
+ * - `{ mode: "auto" }` (default): caller picks per message via `enqueue` or `steer()`/`queue()`.
122
+ * `interrupt` (cancel stream now) is always available via `interrupt()`/`cancel()`/`AbortSignal`.
123
+ */
124
+ midSession?: MidSessionConfig;
125
+ /**
126
+ * Durable session persistence: when set, the session file is rewritten
127
+ * after every step (user turn, assistant turn, tool batch, sub-agent
128
+ * step) so a crash loses at most the in-flight step. Pass `{ dir }` for
129
+ * `sessions/<sessionId>/session.json` layout or `{ file }` for a single
130
+ * pretty `.session.json` file. Writes are atomic and never fail a turn.
131
+ */
132
+ persist?: { dir?: string; file?: string };
133
+ // Q-54 missing knobs (all optional, additive; loop reads via `(config as any).*`).
134
+ /** Canonical tool-selection control (`"auto"` default). */
135
+ toolChoice?: ToolChoiceOption;
136
+ /** Maximum output tokens for one model call. */
137
+ maxOutputTokens?: number;
138
+ /** Sampling knobs. */
139
+ sampling?: SamplingConfig;
140
+ /** Stop sequence(s). */
141
+ stop?: StopOption;
142
+ /** Deterministic seed, when the provider supports it. */
143
+ seed?: number;
144
+ /**
145
+ * Optional output schema (Zod or JSON Schema, stored as `unknown` so `zod`
146
+ * stays optional for type-only consumers). Agent-level only; per-run
147
+ * overrides are intentionally unsupported.
148
+ */
149
+ output?: unknown;
150
+ /** Retry policy for provider calls. */
151
+ retry?: RetryPolicyConfig;
152
+ /** Fetch implementation override (defaults to global `fetch`). */
153
+ fetch?: typeof fetch;
154
+ /** Provider/env overrides forwarded to key resolution. */
155
+ env?: Record<string, string>;
156
+ /** Structured logger for loop telemetry (defaults to no-op). */
157
+ logger?: AgentLogger;
158
+ /** Control hooks (Q-59). All optional, fail-open by default. */
159
+ hooks?: unknown;
160
+ /** Threshold compaction policy (Q-60a). Fully configurable/replaceable. */
161
+ contextManagement?: unknown;
162
+ /** Telemetry bus (Q-41). Default no-op; pretty logger lives in /cli. */
163
+ telemetry?: { onEvent?: (e: any) => void } | unknown;
164
+ /** Local file access roots (Q-08). Default deny; `path` sources must resolve inside roots. */
165
+ fileAccess?: { roots?: string[] };
166
+ /** Raw capture policy (Q-11). Default "redacted": no bodies. */
167
+ captureRaw?: false | "redacted" | true;
168
+ /** Opt-in run budgets (Q-39). All fields default unlimited. */
169
+ budget?: Budget;
170
+ /** Run + network limits (Q-22). Turn/budget caps unlimited by default; network timeouts finite. */
171
+ limits?: AgentRunLimits;
172
+ }
173
+
174
+ /** Per-request + run limits (Q-22). */
175
+ export interface AgentRunLimits {
176
+ maxTurns?: number;
177
+ deadlineMs?: number;
178
+ maxTotalTokens?: number;
179
+ maxToolCallsPerRun?: number;
180
+ requestTimeoutMs?: number;
181
+ firstByteTimeoutMs?: number;
182
+ streamIdleTimeoutMs?: number;
183
+ }
184
+
185
+ /** Developer policy for mid-session new-query insertion while a run is active. */
186
+ export type MidSessionMode = "steer" | "queue" | "auto";
187
+
188
+ /** Q-51 canonical busy-policy alias: prefer `whenBusy`, `mode` kept for compat. */
189
+ export type WhenBusyMode = MidSessionMode;
190
+
191
+ /** Developer-configured policy for LLM-spawned dynamic sub-agents. */
192
+ export interface MidSessionConfig {
193
+ /**
194
+ * Which mid-session method is allowed while a run is active:
195
+ * - `"steer"`: only steer (inject into current turn) is allowed.
196
+ * - `"queue"`: only queue (next-turn follow-up) is allowed.
197
+ * - `"auto"` (default): caller/user picks per message via `enqueue` or `steer()`/`queue()`.
198
+ */
199
+ mode?: MidSessionMode;
200
+ /**
201
+ * Canonical alias for `mode` (Q-51, preferred). When both are set,
202
+ * `whenBusy` wins. Accepted anywhere `mode` is read; resolution honors
203
+ * `whenBusy ?? mode ?? "auto"`.
204
+ */
205
+ whenBusy?: MidSessionMode;
206
+ /** Max queued (follow-up) requests buffered while busy. Extra `queue()` calls throw. Defaults to 20. */
207
+ maxQueued?: number;
54
208
  }
55
209
 
56
210
  /** Developer-configured policy for LLM-spawned dynamic sub-agents. */
@@ -67,12 +221,29 @@ export interface DynamicSubagentsConfig {
67
221
  tools?: Record<string, ToolDefinition> | ToolDefinition[];
68
222
  /** Per-worker timeout in ms. `0` (default) = no limit. `-1` = the Main Agent sets a per-task `timeoutMs`. `>0` = fixed timeout for every worker. */
69
223
  timeout?: number;
224
+ /**
225
+ * Canonical per-worker timeout in ms (Q-52, preferred over `timeout`).
226
+ * Maps to `timeout`: `workerTimeoutMs ?? timeout ?? 0`. See
227
+ * `normalizeWorkerTimeout` (re-exported from the root entry).
228
+ */
229
+ workerTimeoutMs?: number;
230
+ /**
231
+ * When true, the Main Agent sets a per-task `timeoutMs` (maps to
232
+ * `timeout: -1`). When both are set, an explicit `timeout`/`workerTimeoutMs`
233
+ * wins over `modelMaySetTimeout`.
234
+ */
235
+ modelMaySetTimeout?: boolean;
70
236
  }
71
237
 
72
238
  /** Per-run overrides for {@link Agent.run}, {@link Agent.ask}, and {@link Agent.stream}. */
73
239
  export interface AgentRunOptions {
74
240
  /** Per-run reasoning-level override, validated against the selected model. */
75
241
  thinkingLevel?: ThinkingLevel;
242
+ /**
243
+ * Canonical per-run reasoning option (Q-52, preferred over `thinkingLevel`).
244
+ * Resolution honors `thinking ?? thinkingLevel`.
245
+ */
246
+ thinking?: ThinkingOption;
76
247
  /** Return a streaming object instead of waiting for a final response. */
77
248
  stream?: boolean;
78
249
  /** Cancels provider, tool, and sub-agent work. */
@@ -81,6 +252,14 @@ export interface AgentRunOptions {
81
252
  sessionId?: string;
82
253
  /** Context added only to this user turn, preserving the stable system prompt. */
83
254
  additionalContext?: string;
255
+ /**
256
+ * Mid-session method to use when this call arrives while another run is active.
257
+ * Only honored when `midSession.mode` is `"auto"` (default). When the developer
258
+ * enforces `"steer"` or `"queue"`, that policy wins and this field is ignored.
259
+ * When idle, this field is ignored and the call runs immediately.
260
+ * Defaults to `"queue"` (finish current turn, run as next turn).
261
+ */
262
+ enqueue?: "steer" | "queue";
84
263
  /** Headers merged for this run only. */
85
264
  headers?: Record<string, string>;
86
265
  /** Called for each text delta as it streams (only when stream:true). */
@@ -91,4 +270,76 @@ export interface AgentRunOptions {
91
270
  onEvent?: (event: import("./response.ts").StreamEvent) => void;
92
271
  /** When true, wraps reasoning stream as <think>\n...\n</think>\n\n — no manual isThinking needed. */
93
272
  wrapThinking?: boolean;
273
+ /** Called after every model/tool turn with a turn summary (also drives sub-agent step logs + durable persistence). */
274
+ onTurn?: (turn: AgentTurnEvent) => void;
275
+ // Q-54 per-run knobs (all optional, additive; `output` intentionally
276
+ // omitted here — it is agent-level only, see AgentConfig.output).
277
+ /** Per-run tool-selection override. */
278
+ toolChoice?: ToolChoiceOption;
279
+ /** Per-run output-token cap. */
280
+ maxOutputTokens?: number;
281
+ /** Per-run sampling override. */
282
+ sampling?: SamplingConfig;
283
+ /** Per-run stop sequence(s). */
284
+ stop?: StopOption;
285
+ /** Per-run seed. */
286
+ seed?: number;
287
+ /** Per-run retry override. */
288
+ retry?: RetryPolicyConfig;
289
+ /** Per-run fetch override. */
290
+ fetch?: typeof fetch;
291
+ /** Per-run env overrides. */
292
+ env?: Record<string, string>;
293
+ /** Per-run logger. */
294
+ logger?: AgentLogger;
295
+ }
296
+
297
+ /** Turn summary emitted to `AgentRunOptions.onTurn` after each loop turn. */
298
+ export interface AgentTurnEvent {
299
+ /** 1-based turn count within this run. */
300
+ turns: number;
301
+ /** Assistant text produced this turn, if any. */
302
+ text?: string;
303
+ /** Reasoning produced this turn, if any. */
304
+ thinking?: string;
305
+ /** Tool calls requested this turn, if any. */
306
+ toolCalls?: Array<{ id: string; name: string }>;
307
+ /** Tool results executed this turn, if any. */
308
+ toolResults?: Array<{ id: string; name: string; isError?: boolean }>;
309
+ }
310
+
311
+ /** Optional run budgets enforced by delegation helpers (Q-39). All fields default to unlimited. */
312
+ export interface Budget {
313
+ /** Maximum total cost in USD across this agent + spawned workers. */
314
+ maxCostUsd?: number;
315
+ /** Maximum total tokens across this agent + spawned workers. */
316
+ maxTotalTokens?: number;
317
+ /** Maximum wall/monotonic duration in ms since budget tracking started. */
318
+ maxDurationMs?: number;
319
+ /** Maximum total dynamic sub-agents spawned via `spawn_subagents`. */
320
+ maxTotalSubagents?: number;
321
+ }
322
+
323
+ /** Mutable counters backing a {@link Budget}. Stored on the agent instance (untyped `budgetState`). */
324
+ export interface BudgetState {
325
+ /** Workers spawned so far (incremented by `reserveSpawn`). */
326
+ spawned: number;
327
+ /** Accumulated total tokens charged via `chargeBudgetUsage`. */
328
+ totalTokens: number;
329
+ /** Accumulated total cost in USD charged via `chargeBudgetUsage`. */
330
+ totalCostUsd: number;
331
+ /** Monotonic start time (`nowMs()` clock) for `maxDurationMs` checks. */
332
+ startedAtMs: number;
333
+ }
334
+
335
+ /** Run/turn lineage attached to sub-agent metadata via `(metadata as any)` (Q-42). */
336
+ export interface SubAgentRunIds {
337
+ /** Stable id for this worker batch/run (`newRunId`). */
338
+ runId?: string;
339
+ /** Stable id for this worker turn (`newTurnId`). */
340
+ turnId?: string;
341
+ /** Parent run id when known (opaque, forwarded when the host exposes one). */
342
+ parentRunId?: string;
343
+ /** Root run id when known (opaque, forwarded when the host exposes one). */
344
+ rootRunId?: string;
94
345
  }
package/src/types/core.ts CHANGED
@@ -9,7 +9,8 @@ export type ThinkingLevel =
9
9
  | "low"
10
10
  | "medium"
11
11
  | "high"
12
- | "xhigh";
12
+ | "xhigh"
13
+ | "max";
13
14
 
14
15
  // Internal normalized config (SDK-internal, not exposed as Agent flag)
15
16
  /** Internal normalized reasoning configuration passed to providers. */
@@ -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