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
@@ -1,10 +1,26 @@
1
- import type { AgentConfig, EditableAgentConfig, AgentRunOptions, MidSessionConfig, ToolChoiceOption, RetryPolicyConfig, Budget, BudgetState, InterruptedRun } from "../types/agent.ts";
1
+ import type {
2
+ AgentConfig,
3
+ EditableAgentConfig,
4
+ AgentRunOptions,
5
+ MidSessionConfig,
6
+ ToolChoiceOption,
7
+ RetryPolicyConfig,
8
+ Budget,
9
+ BudgetState,
10
+ InterruptedRun,
11
+ } from "../types/agent.ts";
2
12
  import type { TokenUsage } from "../types/core.ts";
3
13
  import type { ProviderRequestOptions } from "../types/model.ts";
4
14
  import { normalizeThinkingV2, resolveWhenBusy } from "../tools/tool.ts";
5
15
  import type { ToolDefinition, ToolResultRecord } from "../types/tool.ts";
6
16
  import { isSubagentResult } from "../tools/subagent-result.ts";
7
- import { CACHE_OPTIONS, type ThinkingLevel, type ThinkingConfig, type CacheOption, type ServiceTier } from "../types/core.ts";
17
+ import {
18
+ CACHE_OPTIONS,
19
+ type ThinkingLevel,
20
+ type ThinkingConfig,
21
+ type CacheOption,
22
+ type ServiceTier,
23
+ } from "../types/core.ts";
8
24
  import type { ContentPart } from "../types/message.ts";
9
25
  import { AgentResponse, type StreamEvent } from "../types/response.ts";
10
26
  import { AssistantMessageEventStream } from "../streaming/event-stream.ts";
@@ -13,21 +29,23 @@ import { resolveModel } from "../providers/registry.ts";
13
29
  import { buildAgentTools } from "./delegation.ts";
14
30
  import { createSubagentsTool } from "./subagents-tool.ts";
15
31
  import { WorkerRegistry } from "./workers.ts";
16
- import {
17
- DEFAULT_MAX_DEPTH,
18
- normalizeToolMap,
19
- resolveDynamicModelInput,
20
- resolveMainModel,
21
- } from "./worker.ts";
32
+ import { DEFAULT_MAX_DEPTH, normalizeToolMap, resolveDynamicModelInput, resolveMainModel } from "./worker.ts";
22
33
  import * as path from "node:path";
23
34
  import { registerAgent } from "../scheduler/scheduler.ts";
24
- import { resolveHome } from "../scheduler/store.ts";
35
+ import { resolveHome } from "../utils/home.ts";
25
36
  import { persistTelemetry, registerSessionFolder, unregisterSessionFolder } from "../telemetry-file.ts";
26
37
  import { lockFolder } from "../utils/durable.ts";
27
38
  import { createScheduledActionsTool, getDatetimeTool } from "../scheduler/tool.ts";
28
39
  import { applyEditable, assertModelAllowed, modelKey, toEditableConfig } from "./config-blocks.ts";
29
40
  import { assertNoToolCollisions } from "../tools/executor.ts";
30
- import { runAgentLoop, streamAgentLoop, unfinishedToolCalls, endsWithStop, accumulateUsage, type SteerEntry } from "./loop.ts";
41
+ import {
42
+ runAgentLoop,
43
+ streamAgentLoop,
44
+ unfinishedToolCalls,
45
+ endsWithStop,
46
+ accumulateUsage,
47
+ type SteerEntry,
48
+ } from "./loop.ts";
31
49
  import { isRetryableRun, readRetryAfterMs, sleep } from "../utils/retry.ts";
32
50
  import { createSessionId, nowMs, newRunId, previewPrompt } from "../utils/session.ts";
33
51
  import {
@@ -38,32 +56,50 @@ import {
38
56
  listSubAgentTraceIdsForSessions,
39
57
  type SubAgentTrace,
40
58
  } from "./delegation.ts";
41
- import { addTurn, buildUsageReport, emptyUsage, mergeUsage, modelName, readUsage, type UsageNode, type UsageReport, type UsageTotals } from "../session/usage.ts";
42
- import { saveSessionFile, sessionDirFor, loadSessionDir, findInvalidMessage, priceUsage, telemetryBus, type SessionRunMark, type SessionRunEnd, type SessionPending, type SessionToolRecord } from "../session/store.ts";
59
+ import {
60
+ addTurn,
61
+ buildUsageReport,
62
+ emptyUsage,
63
+ mergeUsage,
64
+ modelName,
65
+ readUsage,
66
+ type UsageNode,
67
+ type UsageReport,
68
+ type UsageTotals,
69
+ } from "../session/usage.ts";
70
+ import {
71
+ saveSessionFile,
72
+ sessionDirFor,
73
+ loadSessionDir,
74
+ findInvalidMessage,
75
+ priceUsage,
76
+ telemetryBus,
77
+ type SessionRunMark,
78
+ type SessionRunEnd,
79
+ type SessionPending,
80
+ type SessionToolRecord,
81
+ } from "../session/store.ts";
43
82
  import { SessionJournal } from "../session/journal.ts";
44
83
  import { ToolLogs, type ToolLogWriter } from "../session/tool-log.ts";
45
84
  import { isLocalPathInput } from "../utils/safe-io.ts";
46
- import { clearSessionRouting, normalizeStructuredOutput, normalizeStop } from "../providers.ts";
85
+ import { clearSessionRouting, normalizeStructuredOutput } from "../providers.ts";
47
86
  import type { StructuredOutputSpec } from "../types/model.ts";
48
87
  import { clearInteractionChains } from "../providers/google.ts";
49
88
  import { validateModelThinking, ensureModelCatalogFresh } from "../models/catalog.ts";
50
- import { buildSessionData, type PersistedAgentSession, type SessionTotals,} from "../session/store.ts";
89
+ import { buildSessionData, type PersistedAgentSession, type SessionTotals } from "../session/store.ts";
51
90
  import { SessionTelemetry } from "../session/store.ts";
52
91
 
53
92
  /**
54
93
  * Resolves a per-run thinking override without mutating agent config.
55
94
  */
56
- export function resolveEffectiveThinking(
57
- base?: ThinkingConfig,
58
- overrideLevel?: string
59
- ): ThinkingConfig | undefined {
95
+ export function resolveEffectiveThinking(base?: ThinkingConfig, overrideLevel?: string): ThinkingConfig | undefined {
60
96
  if (!overrideLevel) return base;
61
97
  if (overrideLevel === "none") return { enabled: false, level: "none", budgetTokens: 0 };
62
98
  if (overrideLevel === "dynamic") return { enabled: true, level: "dynamic", budgetTokens: -1 };
63
99
  return { enabled: true, level: overrideLevel as ThinkingConfig["level"] };
64
100
  }
65
101
 
66
- import { ConfigError, SessionLoadError, isAbortError } from "../types/errors.ts";
102
+ import { ConfigError, SessionLoadError, ValidationError, isAbortError } from "../types/errors.ts";
67
103
 
68
104
  /**
69
105
  * Disposers for the manual user-signal forward listeners (fallback when
@@ -94,8 +130,7 @@ export interface AgentRunLimits {
94
130
  /** Resolves `(config as any).limits` with defaults (timeouts tolerate reasoning). */
95
131
  function resolveAgentLimits(config: { limits?: Record<string, unknown> }): AgentRunLimits {
96
132
  const raw = (config as { limits?: Record<string, unknown> }).limits ?? {};
97
- const num = (v: unknown): number | undefined =>
98
- typeof v === "number" && Number.isFinite(v) ? v : undefined;
133
+ const num = (v: unknown): number | undefined => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
99
134
  // A network timeout: `0` or `Infinity` turns it off, any other number is milliseconds, anything else is unset (the default applies).
100
135
  const timeout = (v: unknown): number | undefined => (typeof v === "number" && !Number.isNaN(v) ? v : undefined);
101
136
  return {
@@ -104,7 +139,9 @@ function resolveAgentLimits(config: { limits?: Record<string, unknown> }): Agent
104
139
  ...(num(raw.maxToolCallsPerRun) !== undefined
105
140
  ? { maxToolCallsPerRun: Math.max(1, Math.floor(num(raw.maxToolCallsPerRun)!)) }
106
141
  : {}),
107
- ...(num(raw.maxTotalTokens) !== undefined ? { maxTotalTokens: Math.max(1, Math.floor(num(raw.maxTotalTokens)!)) } : {}),
142
+ ...(num(raw.maxTotalTokens) !== undefined
143
+ ? { maxTotalTokens: Math.max(1, Math.floor(num(raw.maxTotalTokens)!)) }
144
+ : {}),
108
145
  requestTimeoutMs: timeout(raw.requestTimeoutMs) ?? 300_000,
109
146
  firstByteTimeoutMs: timeout(raw.firstByteTimeoutMs) ?? 120_000,
110
147
  streamIdleTimeoutMs: timeout(raw.streamIdleTimeoutMs) ?? 300_000,
@@ -216,8 +253,6 @@ export class Agent {
216
253
  private readonly callDefaults: {
217
254
  toolChoice?: ToolChoiceOption;
218
255
  maxOutputTokens?: number;
219
- stop?: string | string[];
220
- seed?: number;
221
256
  retry?: RetryPolicyConfig;
222
257
  env?: Record<string, string>;
223
258
  fetch?: typeof fetch;
@@ -325,34 +360,50 @@ export class Agent {
325
360
  this.subagentModel = dynModel as string | any | undefined;
326
361
  const dynEnabled = dynRaw ? (dynRaw.enabled ?? true) : false;
327
362
  if (dynRaw) {
328
- const dynTools = normalizeToolMap((dynRaw as { tools?: Record<string, ToolDefinition> | ToolDefinition[] }).tools);
363
+ const dynTools = normalizeToolMap(
364
+ (dynRaw as { tools?: Record<string, ToolDefinition> | ToolDefinition[] }).tools
365
+ );
329
366
  const rawDepth = (dynRaw as { maxDepth?: unknown }).maxDepth;
330
367
  // Depth is unlimited unless the developer sets a cap (a whole number >= 1; Infinity also means unlimited).
331
- if (rawDepth !== undefined && rawDepth !== Infinity && !(Number.isInteger(rawDepth) && (rawDepth as number) >= 1)) {
332
- throw new ConfigError(`[Agent Accelerator] Invalid dynamicSubagents.maxDepth ${JSON.stringify(rawDepth)}: expected a whole number >= 1, or omit it for no limit; set dynamicSubagents.enabled: false to turn workers off.`);
368
+ if (
369
+ rawDepth !== undefined &&
370
+ rawDepth !== Infinity &&
371
+ !(Number.isInteger(rawDepth) && (rawDepth as number) >= 1)
372
+ ) {
373
+ throw new ConfigError(
374
+ `[Agent Accelerator] Invalid dynamicSubagents.maxDepth ${JSON.stringify(rawDepth)}: expected a whole number >= 1, or omit it for no limit; set dynamicSubagents.enabled: false to turn workers off.`
375
+ );
333
376
  }
334
377
  const rawBudget = (dynRaw as { workerBudget?: { maxCostUsd?: unknown; maxTotalTokens?: unknown } }).workerBudget;
335
378
  for (const key of ["maxCostUsd", "maxTotalTokens"] as const) {
336
379
  const v = rawBudget?.[key];
337
380
  if (v !== undefined && !(typeof v === "number" && Number.isFinite(v) && v > 0)) {
338
- throw new ConfigError(`[Agent Accelerator] Invalid dynamicSubagents.workerBudget.${key} ${JSON.stringify(v)}: expected a number greater than 0, or omit it for no limit.`);
381
+ throw new ConfigError(
382
+ `[Agent Accelerator] Invalid dynamicSubagents.workerBudget.${key} ${JSON.stringify(v)}: expected a number greater than 0, or omit it for no limit.`
383
+ );
339
384
  }
340
385
  }
341
386
  // Open workers are unlimited unless the developer sets a cap (a number >= 1; Infinity also means unlimited).
342
387
  const rawMax = (dynRaw as { maxSpawn?: unknown }).maxSpawn;
343
388
  if (rawMax !== undefined && (typeof rawMax !== "number" || Number.isNaN(rawMax))) {
344
- throw new ConfigError(`[Agent Accelerator] Invalid dynamicSubagents.maxSpawn ${JSON.stringify(rawMax)}: expected a whole number >= 1, or omit it for no limit.`);
389
+ throw new ConfigError(
390
+ `[Agent Accelerator] Invalid dynamicSubagents.maxSpawn ${JSON.stringify(rawMax)}: expected a whole number >= 1, or omit it for no limit.`
391
+ );
345
392
  }
346
393
  const maxSpawn = rawMax === undefined || rawMax === Infinity ? Infinity : Math.max(1, Math.floor(rawMax));
347
394
  // Only scheduled actions are timed: no limit unless the developer sets one (0 and Infinity mean none).
348
395
  for (const removed of ["timeout", "modelMaySetTimeout"]) {
349
396
  if ((dynRaw as Record<string, unknown>)[removed] !== undefined) {
350
- throw new ConfigError(`[Agent Accelerator] dynamicSubagents.${removed} was removed: set dynamicSubagents.workerTimeoutMs (the longest a scheduled run may take, optional) and let each scheduled action carry its own timeoutMs.`);
397
+ throw new ConfigError(
398
+ `[Agent Accelerator] dynamicSubagents.${removed} was removed: set dynamicSubagents.workerTimeoutMs (the longest a scheduled run may take, optional) and let each scheduled action carry its own timeoutMs.`
399
+ );
351
400
  }
352
401
  }
353
402
  const rawTimeout = (dynRaw as { workerTimeoutMs?: unknown }).workerTimeoutMs;
354
403
  if (rawTimeout !== undefined && (typeof rawTimeout !== "number" || Number.isNaN(rawTimeout) || rawTimeout < 0)) {
355
- throw new ConfigError(`[Agent Accelerator] Invalid dynamicSubagents.workerTimeoutMs ${JSON.stringify(rawTimeout)}: expected a number of milliseconds > 0, or omit it (0 and Infinity mean no limit).`);
404
+ throw new ConfigError(
405
+ `[Agent Accelerator] Invalid dynamicSubagents.workerTimeoutMs ${JSON.stringify(rawTimeout)}: expected a number of milliseconds > 0, or omit it (0 and Infinity mean no limit).`
406
+ );
356
407
  }
357
408
  const workerTimeoutMs = !rawTimeout ? Infinity : Math.max(1, Math.floor(rawTimeout));
358
409
  this.dynamicSubagents = {
@@ -419,17 +470,20 @@ export class Agent {
419
470
  }
420
471
  this.cache = config.cache;
421
472
  if (config.resume !== undefined && config.resume !== "automatic" && config.resume !== "explicit") {
422
- throw new ConfigError(`[Agent Accelerator] Invalid resume ${JSON.stringify(config.resume)}: expected "automatic" (default) or "explicit".`);
473
+ throw new ConfigError(
474
+ `[Agent Accelerator] Invalid resume ${JSON.stringify(config.resume)}: expected "automatic" (default) or "explicit".`
475
+ );
423
476
  }
424
477
  this.resumeMode = config.resume ?? "automatic";
425
478
  if (config.runRetries !== undefined && !(Number.isInteger(config.runRetries) && config.runRetries >= 0)) {
426
- throw new ConfigError(`[Agent Accelerator] Invalid runRetries ${JSON.stringify(config.runRetries)}: expected a whole number >= 0 (default 3, 0 turns it off).`);
479
+ throw new ConfigError(
480
+ `[Agent Accelerator] Invalid runRetries ${JSON.stringify(config.runRetries)}: expected a whole number >= 0 (default 3, 0 turns it off).`
481
+ );
427
482
  }
428
483
  this.runRetries = config.runRetries ?? 3;
429
484
 
430
485
  this.serviceTier = config.serviceTier;
431
- this.onFailure =
432
- config.onFailure === "rollback" || config.onFailure === "keep" ? config.onFailure : "auto";
486
+ this.onFailure = config.onFailure === "rollback" || config.onFailure === "keep" ? config.onFailure : "auto";
433
487
  this.forkConfig = { ...config };
434
488
  this.captureRaw = config.captureRaw;
435
489
  this.budget = config.budget ? { ...config.budget } : undefined;
@@ -448,8 +502,6 @@ export class Agent {
448
502
  this.callDefaults = {
449
503
  toolChoice: config.toolChoice,
450
504
  maxOutputTokens: config.maxOutputTokens,
451
- stop: config.stop,
452
- seed: config.seed,
453
505
  retry: config.retry,
454
506
  env: config.env,
455
507
  fetch: config.fetch,
@@ -466,7 +518,6 @@ export class Agent {
466
518
  Object.assign(this.tools, normalizeToolMap(config.tools));
467
519
  }
468
520
 
469
-
470
521
  if (Array.isArray(config.subagents)) {
471
522
  const subagentTools = buildAgentTools(config.subagents);
472
523
  for (const [toolName, toolDef] of Object.entries(subagentTools)) {
@@ -574,7 +625,11 @@ export class Agent {
574
625
 
575
626
  // Records one model call of THIS agent (priced per turn, with the service tier); in: the loop's onTurnUsage, out: this.usageTotals (saved by the next persist).
576
627
  private recordTurnUsage(usage: TokenUsage, spec: Parameters<typeof priceUsage>[1]): void {
577
- addTurn(this.usageTotals, usage, priceUsage(usage, spec, this.serviceTier ? { serviceTier: this.serviceTier } : undefined));
628
+ addTurn(
629
+ this.usageTotals,
630
+ usage,
631
+ priceUsage(usage, spec, this.serviceTier ? { serviceTier: this.serviceTier } : undefined)
632
+ );
578
633
  }
579
634
 
580
635
  /** Adds another agent's spending to this one (a fixed worker's per-call copy into its template). @internal */
@@ -589,7 +644,10 @@ export class Agent {
589
644
  * below it. Spending of one-shot scheduled-action workers is in their run files, not here.
590
645
  */
591
646
  usageReport(): UsageReport {
592
- return buildUsageReport({ id: this.sessionId, name: this.name, model: modelName(this.modelStringOrSpec), totals: this.usageTotals }, [...this.workers.usageNodes(), ...this.fixedWorkerNodes()]);
647
+ return buildUsageReport(
648
+ { id: this.sessionId, name: this.name, model: modelName(this.modelStringOrSpec), totals: this.usageTotals },
649
+ [...this.workers.usageNodes(), ...this.fixedWorkerNodes()]
650
+ );
593
651
  }
594
652
 
595
653
  // The `subagents: [...]` workers as usage nodes (their per-call copies' spending is merged into them); in: the constructor config, out: usageReport.
@@ -598,14 +656,25 @@ export class Agent {
598
656
  for (const entry of this.forkConfig.subagents ?? []) {
599
657
  const worker = "agent" in entry ? entry.agent : entry;
600
658
  const name = "agent" in entry ? entry.name : worker.name;
601
- nodes.push({ id: name, name, model: modelName(worker.modelStringOrSpec), status: "fixed", totals: worker.usageTotals, workers: [] });
659
+ nodes.push({
660
+ id: name,
661
+ name,
662
+ model: modelName(worker.modelStringOrSpec),
663
+ status: "fixed",
664
+ totals: worker.usageTotals,
665
+ workers: [],
666
+ });
602
667
  }
603
668
  return nodes;
604
669
  }
605
670
 
606
671
  // Says on the telemetry bus that resuming (or a prompt that was waiting) failed; in: a rejected resume, out: telemetryBus.
607
672
  private reportResumeFailure(err: unknown): void {
608
- telemetryBus.emitWarning("agent.resume_failed", `[Agent Accelerator] Resuming session ${this.sessionId} failed: ${err instanceof Error ? err.message : String(err)}`, { sessionId: this.sessionId });
673
+ telemetryBus.emitWarning(
674
+ "agent.resume_failed",
675
+ `[Agent Accelerator] Resuming session ${this.sessionId} failed: ${err instanceof Error ? err.message : String(err)}`,
676
+ { sessionId: this.sessionId }
677
+ );
609
678
  }
610
679
 
611
680
  /** Runs that a crash cut off (they began, never ended and are not running now), oldest first. Needs `persist: { dir }` and the same `sessionId` as before the restart. */
@@ -629,7 +698,8 @@ export class Agent {
629
698
  */
630
699
  resume(): Promise<AgentResponse | undefined> {
631
700
  if (this.resuming) return this.resuming;
632
- if (this.currentRun) throw new Error("[Agent Accelerator] resume() needs an idle agent: a run is already active.");
701
+ if (this.currentRun)
702
+ throw new ValidationError("[Agent Accelerator] resume() needs an idle agent: a run is already active.");
633
703
  const mark = this.runMarks.filter((m) => !m.end && m !== this.activeMark).pop();
634
704
  const last = this.context.messages[this.context.messages.length - 1];
635
705
  const nothingOpen = unfinishedToolCalls(this.context.messages).length === 0;
@@ -642,7 +712,11 @@ export class Agent {
642
712
  telemetryBus.emit({
643
713
  code: "agent.resumed",
644
714
  message: `[Agent Accelerator] resuming run ${mark.id} of session ${this.sessionId}`,
645
- data: { sessionId: this.sessionId, runId: mark.id, pendingToolCalls: unfinishedToolCalls(this.context.messages).map((c) => c.name) },
715
+ data: {
716
+ sessionId: this.sessionId,
717
+ runId: mark.id,
718
+ pendingToolCalls: unfinishedToolCalls(this.context.messages).map((c) => c.name),
719
+ },
646
720
  });
647
721
  const run = this.startNonStreamingRun("", undefined, mark);
648
722
  const clear = (): void => {
@@ -655,7 +729,18 @@ export class Agent {
655
729
 
656
730
  // The finished tool calls of the current run as results the loop can reuse; in: toolRecords, out: loopConfig.resume.stored.
657
731
  private storedToolResults(): Map<string, ToolResultRecord> {
658
- return new Map(this.toolRecords.map((t) => [t.id, { id: t.id, name: t.name, result: t.result, ...(t.isError ? { isError: true } : {}), ...(t.stopped ? { stopped: true } : {}) } as ToolResultRecord]));
732
+ return new Map(
733
+ this.toolRecords.map((t) => [
734
+ t.id,
735
+ {
736
+ id: t.id,
737
+ name: t.name,
738
+ result: t.result,
739
+ ...(t.isError ? { isError: true } : {}),
740
+ ...(t.stopped ? { stopped: true } : {}),
741
+ } as ToolResultRecord,
742
+ ])
743
+ );
659
744
  }
660
745
 
661
746
  // Closes a run's mark with how it ended and saves that at once (not on the next tick), so the disk is up to date when `await run()` returns; in: the run's promise handlers, out: runMarks + journal.
@@ -683,9 +768,7 @@ export class Agent {
683
768
  const base = this.instructions || "";
684
769
  let out = base;
685
770
  const isThinkingEnabled =
686
- this.thinkingConfig?.enabled !== false &&
687
- this.thinkingConfig?.level &&
688
- this.thinkingConfig?.level !== "none";
771
+ this.thinkingConfig?.enabled !== false && this.thinkingConfig?.level && this.thinkingConfig?.level !== "none";
689
772
  if (isThinkingEnabled && !out.includes("[Reasoning Directive]")) {
690
773
  const guidance =
691
774
  "[Reasoning Directive]\n" +
@@ -697,16 +780,22 @@ export class Agent {
697
780
  return out || undefined;
698
781
  }
699
782
 
700
- /** Clears conversation messages and provider thought signatures while keeping configuration. */
783
+ /** Starts a fresh conversation: clears messages, signatures, run marks and the run's saved tool results, keeps config and spending totals. */
701
784
  reset(): void {
702
785
  const sid = this.sessionId;
703
786
  this.context.messages = [];
704
787
  this.context.thoughtSignatures = [];
705
788
  this.context.systemPrompt = this.getFullInstructions();
706
789
  this.runMarks.length = 0;
790
+ this.toolRecords.length = 0; // a cleared conversation has no run to resume, so its finished tool results go too
791
+ this.activeMark = undefined;
707
792
  // Reset also drops session-scoped provider chaining/routing.
708
- try { clearInteractionChains(sid); } catch {}
709
- try { clearSessionRouting(sid); } catch {}
793
+ try {
794
+ clearInteractionChains(sid);
795
+ } catch {}
796
+ try {
797
+ clearSessionRouting(sid);
798
+ } catch {}
710
799
  }
711
800
 
712
801
  /**
@@ -725,17 +814,27 @@ export class Agent {
725
814
  /** Drops one named session. No-op for unknown ids. */
726
815
  disposeSession(id: string): void {
727
816
  unregisterSessionFolder(id);
728
- try { this.sessions.delete(id); } catch {}
729
- try { clearInteractionChains(id); } catch {}
730
- try { clearSessionRouting(id); } catch {}
817
+ try {
818
+ this.sessions.delete(id);
819
+ } catch {}
820
+ try {
821
+ clearInteractionChains(id);
822
+ } catch {}
823
+ try {
824
+ clearSessionRouting(id);
825
+ } catch {}
731
826
  }
732
827
 
733
828
  /** Drops all named sessions except the default context. */
734
829
  clearSessions(): void {
735
830
  try {
736
831
  for (const id of [...this.sessions.keys()]) {
737
- try { clearInteractionChains(id); } catch {}
738
- try { clearSessionRouting(id); } catch {}
832
+ try {
833
+ clearInteractionChains(id);
834
+ } catch {}
835
+ try {
836
+ clearSessionRouting(id);
837
+ } catch {}
739
838
  }
740
839
  this.sessions.clear();
741
840
  } catch {}
@@ -751,8 +850,12 @@ export class Agent {
751
850
  for (const [key, entry] of [...this.sessions.entries()]) {
752
851
  if (now - entry.lastAccess > SESSION_TTL_MS) {
753
852
  this.sessions.delete(key);
754
- try { clearInteractionChains(key); } catch {}
755
- try { clearSessionRouting(key); } catch {}
853
+ try {
854
+ clearInteractionChains(key);
855
+ } catch {}
856
+ try {
857
+ clearSessionRouting(key);
858
+ } catch {}
756
859
  }
757
860
  }
758
861
  } catch {}
@@ -769,12 +872,19 @@ export class Agent {
769
872
  let oldest: string | undefined;
770
873
  let oldestTs = Infinity;
771
874
  for (const [key, entry] of this.sessions) {
772
- if (entry.lastAccess < oldestTs) { oldestTs = entry.lastAccess; oldest = key; }
875
+ if (entry.lastAccess < oldestTs) {
876
+ oldestTs = entry.lastAccess;
877
+ oldest = key;
878
+ }
773
879
  }
774
880
  if (!oldest) break;
775
881
  this.sessions.delete(oldest);
776
- try { clearInteractionChains(oldest); } catch {}
777
- try { clearSessionRouting(oldest); } catch {}
882
+ try {
883
+ clearInteractionChains(oldest);
884
+ } catch {}
885
+ try {
886
+ clearSessionRouting(oldest);
887
+ } catch {}
778
888
  }
779
889
  } catch {}
780
890
  return fresh;
@@ -787,8 +897,12 @@ export class Agent {
787
897
  const err = Object.assign(new Error("Queue cleared"), { name: "AbortError" });
788
898
  const pending = this.pendingQueue.splice(0);
789
899
  for (const entry of pending) {
790
- try { entry.reject(err); } catch {}
791
- try { entry.deferredStream?.fail(err instanceof Error ? err : new Error(String(err))); } catch {}
900
+ try {
901
+ entry.reject(err);
902
+ } catch {}
903
+ try {
904
+ entry.deferredStream?.fail(err instanceof Error ? err : new Error(String(err)));
905
+ } catch {}
792
906
  }
793
907
  this.persistNow();
794
908
  return n;
@@ -798,7 +912,7 @@ export class Agent {
798
912
  private assertSteerRoom(inbox: SteerEntry[]): void {
799
913
  const max = this.maxSteers ?? 20;
800
914
  if (inbox.length >= max) {
801
- throw new Error(
915
+ throw new ValidationError(
802
916
  `[Agent Accelerator] Steer inbox is full (${inbox.length}/${max}). Wait for the active run to drain or increase midSession.maxSteers.`
803
917
  );
804
918
  }
@@ -837,7 +951,7 @@ export class Agent {
837
951
  // Throws synchronously on an empty content-part array so run() and stream() fail at the same moment; in: any prompt entry point, out: nothing.
838
952
  private assertPromptValid(prompt: string | ContentPart[]): void {
839
953
  if (Array.isArray(prompt) && prompt.length === 0) {
840
- throw new Error(
954
+ throw new ValidationError(
841
955
  "[Agent Accelerator] Prompt cannot be empty: pass a non-empty string or at least one content part."
842
956
  );
843
957
  }
@@ -856,7 +970,9 @@ export class Agent {
856
970
  interrupt(arg?: Error | string | ContentPart[], options?: AgentRunOptions): void | Promise<AgentResponse> {
857
971
  if (typeof arg === "string" || Array.isArray(arg)) {
858
972
  if (options?.stream === true) {
859
- throw new Error('[Agent Accelerator] interrupt(message) is non-streaming. Use stream(prompt, { enqueue: "interrupt" }) for a streaming interrupt.');
973
+ throw new ValidationError(
974
+ '[Agent Accelerator] interrupt(message) is non-streaming. Use stream(prompt, { enqueue: "interrupt" }) for a streaming interrupt.'
975
+ );
860
976
  }
861
977
  return this.run(arg, { ...options, enqueue: "interrupt" });
862
978
  }
@@ -898,12 +1014,12 @@ export class Agent {
898
1014
  steer(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
899
1015
  this.assertPromptValid(prompt);
900
1016
  if (options?.stream === true) {
901
- throw new Error(
1017
+ throw new ValidationError(
902
1018
  '[Agent Accelerator] steer() is non-streaming. Use stream(prompt, { enqueue: "steer" }) for a streaming steer.'
903
1019
  );
904
1020
  }
905
1021
  if (this.midSessionConfig.mode === "queue") {
906
- throw new Error(
1022
+ throw new ConfigError(
907
1023
  '[Agent Accelerator] steer() is disabled by midSession.mode "queue". Use queue() or set mode to "auto"/"steer".'
908
1024
  );
909
1025
  }
@@ -956,19 +1072,19 @@ export class Agent {
956
1072
  queue(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
957
1073
  this.assertPromptValid(prompt);
958
1074
  if (options?.stream === true) {
959
- throw new Error(
1075
+ throw new ValidationError(
960
1076
  '[Agent Accelerator] queue() is non-streaming. Use stream(prompt, { enqueue: "queue" }) for a streaming follow-up.'
961
1077
  );
962
1078
  }
963
1079
  if (this.midSessionConfig.mode === "steer") {
964
- throw new Error(
1080
+ throw new ConfigError(
965
1081
  '[Agent Accelerator] queue() is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
966
1082
  );
967
1083
  }
968
1084
  if (!this.currentRun) return this.run(prompt, options) as Promise<AgentResponse>;
969
1085
  const max = this.midSessionConfig.maxQueued ?? 20;
970
1086
  if (this.pendingQueue.length >= max) {
971
- throw new Error(
1087
+ throw new ValidationError(
972
1088
  `[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
973
1089
  );
974
1090
  }
@@ -999,8 +1115,12 @@ export class Agent {
999
1115
  reason instanceof Error ? reason : new Error("Queued run aborted before start"),
1000
1116
  { name: "AbortError" }
1001
1117
  );
1002
- try { next.reject(abortErr); } catch {}
1003
- try { next.deferredStream?.fail(abortErr instanceof Error ? abortErr : new Error(String(abortErr))); } catch {}
1118
+ try {
1119
+ next.reject(abortErr);
1120
+ } catch {}
1121
+ try {
1122
+ next.deferredStream?.fail(abortErr instanceof Error ? abortErr : new Error(String(abortErr)));
1123
+ } catch {}
1004
1124
  queueMicrotask(() => this.pumpQueue());
1005
1125
  return;
1006
1126
  }
@@ -1009,30 +1129,48 @@ export class Agent {
1009
1129
  const deferred = next.deferredStream;
1010
1130
  try {
1011
1131
  const real = this.startStreamingRun(next.prompt, next.options);
1012
- try { (deferred as any).__piped = true; } catch {}
1132
+ try {
1133
+ (deferred as any).__piped = true;
1134
+ } catch {}
1013
1135
  const forward = (e: StreamEvent) => {
1014
- try { deferred.push(e); } catch {}
1136
+ try {
1137
+ deferred.push(e);
1138
+ } catch {}
1015
1139
  };
1016
1140
  real.on("*", forward as any);
1017
1141
  try {
1018
1142
  deferred.onCancel(() => {
1019
- try { real.cancel(); } catch {}
1143
+ try {
1144
+ real.cancel();
1145
+ } catch {}
1020
1146
  });
1021
1147
  } catch {}
1022
1148
  real.result().then(
1023
1149
  (res) => {
1024
- try { next.resolve(res); } catch {}
1025
- try { deferred.end(res); } catch {}
1150
+ try {
1151
+ next.resolve(res);
1152
+ } catch {}
1153
+ try {
1154
+ deferred.end(res);
1155
+ } catch {}
1026
1156
  },
1027
1157
  (err) => {
1028
- try { next.reject(err); } catch {}
1158
+ try {
1159
+ next.reject(err);
1160
+ } catch {}
1029
1161
  // Pipe failures into the deferred stream so for-await sees them.
1030
- try { deferred.fail(err instanceof Error ? err : new Error(String(err))); } catch {}
1162
+ try {
1163
+ deferred.fail(err instanceof Error ? err : new Error(String(err)));
1164
+ } catch {}
1031
1165
  }
1032
1166
  );
1033
1167
  } catch (err) {
1034
- try { next.reject(err); } catch {}
1035
- try { deferred.fail(err instanceof Error ? err : new Error(String(err))); } catch {}
1168
+ try {
1169
+ next.reject(err);
1170
+ } catch {}
1171
+ try {
1172
+ deferred.fail(err instanceof Error ? err : new Error(String(err)));
1173
+ } catch {}
1036
1174
  // Keep draining even if starting failed.
1037
1175
  queueMicrotask(() => this.pumpQueue());
1038
1176
  }
@@ -1045,7 +1183,11 @@ export class Agent {
1045
1183
  private finishCurrentRun(): void {
1046
1184
  // Leftover steer entries become queued follow-ups instead of dropping.
1047
1185
  let leftover: SteerEntry[] = [];
1048
- try { leftover = this.currentRun?.steerInbox?.splice(0) ?? []; } catch { leftover = []; }
1186
+ try {
1187
+ leftover = this.currentRun?.steerInbox?.splice(0) ?? [];
1188
+ } catch {
1189
+ leftover = [];
1190
+ }
1049
1191
  this.currentRun = null;
1050
1192
  if (leftover.length > 0) {
1051
1193
  try {
@@ -1055,12 +1197,18 @@ export class Agent {
1055
1197
  for (const entry of leftover) {
1056
1198
  try {
1057
1199
  if ((entry.options as { signal?: AbortSignal } | undefined)?.signal?.aborted) {
1058
- entry.settle?.reject(Object.assign(new Error("Steer aborted before it could run"), { name: "AbortError" }));
1200
+ entry.settle?.reject(
1201
+ Object.assign(new Error("Steer aborted before it could run"), { name: "AbortError" })
1202
+ );
1059
1203
  continue;
1060
1204
  }
1061
- } catch { continue; }
1205
+ } catch {
1206
+ continue;
1207
+ }
1062
1208
  if (this.pendingQueue.length + lateSteers.length >= max) {
1063
- entry.settle?.reject(new Error("[Agent Accelerator] Steer dropped: the follow-up queue is full."));
1209
+ entry.settle?.reject(
1210
+ new ValidationError("[Agent Accelerator] Steer dropped: the follow-up queue is full.")
1211
+ );
1064
1212
  continue;
1065
1213
  }
1066
1214
  // The steer was never injected: it now runs as a follow-up, and the
@@ -1089,7 +1237,9 @@ export class Agent {
1089
1237
  try {
1090
1238
  const dispose = mergeDisposers.get(runController);
1091
1239
  if (dispose) {
1092
- try { dispose(); } catch {}
1240
+ try {
1241
+ dispose();
1242
+ } catch {}
1093
1243
  mergeDisposers.delete(runController);
1094
1244
  }
1095
1245
  } catch {}
@@ -1103,7 +1253,9 @@ export class Agent {
1103
1253
  try {
1104
1254
  runController.abort((userSignal as any).reason);
1105
1255
  } catch {
1106
- try { runController.abort(); } catch {}
1256
+ try {
1257
+ runController.abort();
1258
+ } catch {}
1107
1259
  }
1108
1260
  return runController.signal;
1109
1261
  }
@@ -1119,13 +1271,19 @@ export class Agent {
1119
1271
  try {
1120
1272
  runController.abort((userSignal as any).reason);
1121
1273
  } catch {
1122
- try { runController.abort(); } catch {}
1274
+ try {
1275
+ runController.abort();
1276
+ } catch {}
1123
1277
  }
1124
1278
  };
1125
- try { userSignal.addEventListener("abort", forward, { once: true }); } catch {}
1279
+ try {
1280
+ userSignal.addEventListener("abort", forward, { once: true });
1281
+ } catch {}
1126
1282
  try {
1127
1283
  mergeDisposers.set(runController, () => {
1128
- try { userSignal.removeEventListener("abort", forward); } catch {}
1284
+ try {
1285
+ userSignal.removeEventListener("abort", forward);
1286
+ } catch {}
1129
1287
  });
1130
1288
  } catch {}
1131
1289
  return runController.signal;
@@ -1221,9 +1379,7 @@ export class Agent {
1221
1379
  const toolChoice = normalizeToolChoiceOption(runOptions?.toolChoice ?? d.toolChoice);
1222
1380
  const retry = { ...d.retry, ...runOptions?.retry };
1223
1381
  const env = { ...d.env, ...runOptions?.env };
1224
- const stop = normalizeStop(runOptions?.stop ?? d.stop);
1225
1382
  const maxOutputTokens = runOptions?.maxOutputTokens ?? d.maxOutputTokens;
1226
- const seed = runOptions?.seed ?? d.seed;
1227
1383
  const fetchImpl = runOptions?.fetch ?? d.fetch;
1228
1384
  return {
1229
1385
  apiKey: this.apiKey,
@@ -1237,8 +1393,6 @@ export class Agent {
1237
1393
  ...(this.googleTier ? { googleTier: this.googleTier } : {}),
1238
1394
  ...(toolChoice !== undefined ? { toolChoice } : {}),
1239
1395
  ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
1240
- ...(stop ? { stop } : {}),
1241
- ...(seed !== undefined ? { seed } : {}),
1242
1396
  ...(Object.keys(env).length > 0 ? { env } : {}),
1243
1397
  ...(fetchImpl ? { fetch: fetchImpl } : {}),
1244
1398
  ...(retry.maxRetries !== undefined ? { maxRetries: retry.maxRetries } : {}),
@@ -1312,13 +1466,18 @@ export class Agent {
1312
1466
  const failedAttempts: FailedAttempt[] = [];
1313
1467
  for (let attempt = 0; ; attempt++) {
1314
1468
  try {
1315
- res = await runAgentLoop(attempt === 0 ? loopConfig : { ...loopConfig, resume: { stored: this.storedToolResults() } });
1469
+ res = await runAgentLoop(
1470
+ attempt === 0 ? loopConfig : { ...loopConfig, resume: { stored: this.storedToolResults() } }
1471
+ );
1316
1472
  break;
1317
1473
  } catch (err) {
1318
1474
  if (attempt >= this.runRetries || runOptions?.signal?.aborted || !isRetryableRun(err)) throw err;
1319
1475
  const partial = (err as { partial?: FailedAttempt }).partial;
1320
1476
  if (partial?.usage) failedAttempts.push(partial);
1321
- const delayMs = Math.max(RUN_RETRY_DELAYS_MS[Math.min(attempt, RUN_RETRY_DELAYS_MS.length - 1)]!, readRetryAfterMs(err) ?? 0);
1477
+ const delayMs = Math.max(
1478
+ RUN_RETRY_DELAYS_MS[Math.min(attempt, RUN_RETRY_DELAYS_MS.length - 1)]!,
1479
+ readRetryAfterMs(err) ?? 0
1480
+ );
1322
1481
  telemetryBus.emitWarning(
1323
1482
  "agent.run_retry",
1324
1483
  `[Agent Accelerator] Run failed (${err instanceof Error ? err.message : String(err)}); retry ${attempt + 1} of ${this.runRetries} in ${delayMs} ms.`,
@@ -1343,7 +1502,9 @@ export class Agent {
1343
1502
  (err) => this.endRun(mark, isAbortError(err) ? "aborted" : "failed")
1344
1503
  );
1345
1504
  const cleanup = () => {
1346
- try { this.cleanupMerge(runController); } catch {}
1505
+ try {
1506
+ this.cleanupMerge(runController);
1507
+ } catch {}
1347
1508
  if (this.currentRun?.runPromise === task) this.finishCurrentRun();
1348
1509
  };
1349
1510
  task.then(cleanup, cleanup);
@@ -1351,10 +1512,7 @@ export class Agent {
1351
1512
  }
1352
1513
 
1353
1514
  // Starts one streaming run: validates the model and thinking level synchronously, adds the prompt, builds the loop config and returns the loop's event stream, recording the run ending when it settles; in: stream()/pumpQueue, out: an AssistantMessageEventStream.
1354
- private startStreamingRun(
1355
- prompt: string | ContentPart[],
1356
- options?: AgentRunOptions
1357
- ): AssistantMessageEventStream {
1515
+ private startStreamingRun(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
1358
1516
  const resolved = resolveModel(this.modelStringOrSpec);
1359
1517
  const effectiveLevel = normalizeThinkingV2(options?.thinking, options?.thinkingLevel) || this.thinkingConfig?.level;
1360
1518
  if (effectiveLevel) {
@@ -1406,7 +1564,9 @@ export class Agent {
1406
1564
  this.mergeSubagentTraces(res.subagents as any);
1407
1565
  this.persistNow();
1408
1566
  } catch {}
1409
- try { this.cleanupMerge(runController); } catch {}
1567
+ try {
1568
+ this.cleanupMerge(runController);
1569
+ } catch {}
1410
1570
  if (this.stateless) {
1411
1571
  targetContext.messages = [];
1412
1572
  targetContext.thoughtSignatures = [];
@@ -1415,7 +1575,9 @@ export class Agent {
1415
1575
  },
1416
1576
  (err) => {
1417
1577
  this.endRun(mark, isAbortError(err) ? "aborted" : "failed");
1418
- try { this.cleanupMerge(runController); } catch {}
1578
+ try {
1579
+ this.cleanupMerge(runController);
1580
+ } catch {}
1419
1581
  if (this.currentRun?.outerStream === s) this.finishCurrentRun();
1420
1582
  }
1421
1583
  );
@@ -1532,12 +1694,24 @@ export class Agent {
1532
1694
  else if (cfg.file) saveSessionFile(cfg.file, this as any, null);
1533
1695
  } catch (err) {
1534
1696
  // Surface via hook instead of swallowing silently.
1535
- try { (cfg as unknown as { onPersistError?: (e: unknown) => void }).onPersistError?.(err); } catch {}
1697
+ try {
1698
+ (cfg as unknown as { onPersistError?: (e: unknown) => void }).onPersistError?.(err);
1699
+ } catch {}
1536
1700
  }
1537
1701
  }
1538
1702
 
1539
1703
  /** Merges completed worker metadata (incl. steps) into this agent's trace registry. */
1540
- private mergeSubagentTraces(subagents: Array<{ trackingId?: string; name: string; sessionId?: string; parentSessionId?: string; task: string; steps?: SubAgentTrace["steps"]; [k: string]: unknown }>): void {
1704
+ private mergeSubagentTraces(
1705
+ subagents: Array<{
1706
+ trackingId?: string;
1707
+ name: string;
1708
+ sessionId?: string;
1709
+ parentSessionId?: string;
1710
+ task: string;
1711
+ steps?: SubAgentTrace["steps"];
1712
+ [k: string]: unknown;
1713
+ }>
1714
+ ): void {
1541
1715
  if (!subagents || subagents.length === 0) return;
1542
1716
  const rawOf = (id?: string): string | undefined => {
1543
1717
  if (!id) return undefined;
@@ -1639,15 +1813,19 @@ export class Agent {
1639
1813
  try {
1640
1814
  const g: any = globalThis as any;
1641
1815
  this.context.messages = Array.isArray(data.messages)
1642
- ? (typeof g.structuredClone === "function"
1643
- ? g.structuredClone(data.messages)
1644
- : JSON.parse(JSON.stringify(data.messages)))
1816
+ ? typeof g.structuredClone === "function"
1817
+ ? g.structuredClone(data.messages)
1818
+ : JSON.parse(JSON.stringify(data.messages))
1645
1819
  : [];
1646
1820
  } catch {
1647
1821
  this.context.messages = Array.isArray(data.messages) ? [...data.messages] : [];
1648
1822
  }
1649
1823
  this.context.thoughtSignatures = [];
1650
- this.runMarks.splice(0, this.runMarks.length, ...(data.runs ?? []).filter((r) => r.i < this.context.messages.length));
1824
+ this.runMarks.splice(
1825
+ 0,
1826
+ this.runMarks.length,
1827
+ ...(data.runs ?? []).filter((r) => r.i < this.context.messages.length)
1828
+ );
1651
1829
  this.restoreUsage(data.usage);
1652
1830
  if (data.systemPrompt) {
1653
1831
  this.context.systemPrompt = data.systemPrompt;
@@ -1656,10 +1834,18 @@ export class Agent {
1656
1834
  }
1657
1835
  // Importing a snapshot invalidates prior provider chaining/routing
1658
1836
  // for both the previous and the restored session.
1659
- try { clearInteractionChains(prevSid); } catch {}
1660
- try { clearSessionRouting(prevSid); } catch {}
1661
- try { clearInteractionChains(this.sessionId); } catch {}
1662
- try { clearSessionRouting(this.sessionId); } catch {}
1837
+ try {
1838
+ clearInteractionChains(prevSid);
1839
+ } catch {}
1840
+ try {
1841
+ clearSessionRouting(prevSid);
1842
+ } catch {}
1843
+ try {
1844
+ clearInteractionChains(this.sessionId);
1845
+ } catch {}
1846
+ try {
1847
+ clearSessionRouting(this.sessionId);
1848
+ } catch {}
1663
1849
  }
1664
1850
 
1665
1851
  /**
@@ -1689,11 +1875,15 @@ export class Agent {
1689
1875
  }
1690
1876
 
1691
1877
  // Adds the user prompt to the target context (fresh context when stateless, stable system prompt, additionalContext as a user prefix) and marks the run start for the default session; in: a run start, out: the new SessionRunMark.
1692
- private prepareTurn(prompt: string | ContentPart[], options?: AgentRunOptions, target?: AgentContext): SessionRunMark | undefined {
1878
+ private prepareTurn(
1879
+ prompt: string | ContentPart[],
1880
+ options?: AgentRunOptions,
1881
+ target?: AgentContext
1882
+ ): SessionRunMark | undefined {
1693
1883
  // Prompt validation throws synchronously in both run() and
1694
1884
  // stream() (same timing; documented here).
1695
1885
  if (Array.isArray(prompt) && prompt.length === 0) {
1696
- throw new Error(
1886
+ throw new ValidationError(
1697
1887
  "[Agent Accelerator] Prompt cannot be empty: pass a non-empty string or at least one content part."
1698
1888
  );
1699
1889
  }
@@ -1748,23 +1938,14 @@ export class Agent {
1748
1938
  run(
1749
1939
  prompt: string | ContentPart[],
1750
1940
  options: AgentRunOptions & { stream: true } & (
1751
- | { onDelta: NonNullable<AgentRunOptions["onDelta"]> }
1752
- | { onThinkingDelta: NonNullable<AgentRunOptions["onThinkingDelta"]> }
1753
- | { onEvent: NonNullable<AgentRunOptions["onEvent"]> }
1754
- )
1941
+ | { onDelta: NonNullable<AgentRunOptions["onDelta"]> }
1942
+ | { onThinkingDelta: NonNullable<AgentRunOptions["onThinkingDelta"]> }
1943
+ | { onEvent: NonNullable<AgentRunOptions["onEvent"]> }
1944
+ )
1755
1945
  ): Promise<AgentResponse> & AsyncIterable<StreamEvent>;
1756
- run(
1757
- prompt: string | ContentPart[],
1758
- options: AgentRunOptions & { stream: true }
1759
- ): AssistantMessageEventStream;
1760
- run(
1761
- prompt: string | ContentPart[],
1762
- options?: AgentRunOptions
1763
- ): Promise<AgentResponse>;
1764
- run(
1765
- prompt: string | ContentPart[],
1766
- options?: AgentRunOptions
1767
- ): Promise<AgentResponse> | AssistantMessageEventStream {
1946
+ run(prompt: string | ContentPart[], options: AgentRunOptions & { stream: true }): AssistantMessageEventStream;
1947
+ run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse>;
1948
+ run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> | AssistantMessageEventStream {
1768
1949
  // Prompt validation throws synchronously in both run() and
1769
1950
  // stream() (same timing; async work starts only after validation).
1770
1951
  this.assertPromptValid(prompt);
@@ -1793,12 +1974,14 @@ export class Agent {
1793
1974
  // Mid-session: busy agent serializes via steer (same turn) or queue (next turn), or stops the active run and starts this prompt (interrupt).
1794
1975
  if (this.currentRun) {
1795
1976
  if (options?.enqueue === "interrupt") {
1796
- return new Promise<AgentResponse>((resolve, reject) => this.queueUrgent({ prompt, options, isStream: false, resolve, reject }));
1977
+ return new Promise<AgentResponse>((resolve, reject) =>
1978
+ this.queueUrgent({ prompt, options, isStream: false, resolve, reject })
1979
+ );
1797
1980
  }
1798
1981
  const enforced = this.midSessionConfig.mode;
1799
1982
  const requested = options?.enqueue;
1800
1983
  if (requested && (enforced === "steer" || enforced === "queue") && requested !== enforced) {
1801
- throw new Error(
1984
+ throw new ConfigError(
1802
1985
  `[Agent Accelerator] enqueue "${requested}" is disabled by midSession.mode "${enforced}". Use ${enforced}() or set mode to "auto"/"${requested}".`
1803
1986
  );
1804
1987
  }
@@ -1808,14 +1991,14 @@ export class Agent {
1808
1991
  return this.steerInto(this.currentRun, prompt, options);
1809
1992
  } else {
1810
1993
  if (this.midSessionConfig.mode === "steer") {
1811
- throw new Error(
1994
+ throw new ConfigError(
1812
1995
  '[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
1813
1996
  );
1814
1997
  }
1815
1998
  this.assertPromptValid(prompt);
1816
1999
  const max = this.midSessionConfig.maxQueued ?? 20;
1817
2000
  if (this.pendingQueue.length >= max) {
1818
- throw new Error(
2001
+ throw new ValidationError(
1819
2002
  `[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
1820
2003
  );
1821
2004
  }
@@ -1844,13 +2027,9 @@ export class Agent {
1844
2027
  prompt: string | ContentPart[],
1845
2028
  optionsOrStream?: boolean | AgentRunOptions
1846
2029
  ): Promise<AgentResponse> & AsyncIterable<string | StreamEvent> {
1847
- const isStream =
1848
- typeof optionsOrStream === "boolean"
1849
- ? optionsOrStream
1850
- : optionsOrStream?.stream === true;
2030
+ const isStream = typeof optionsOrStream === "boolean" ? optionsOrStream : optionsOrStream?.stream === true;
1851
2031
 
1852
- const runOpts: AgentRunOptions =
1853
- typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
2032
+ const runOpts: AgentRunOptions = typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
1854
2033
 
1855
2034
  if (isStream) {
1856
2035
  const stream = this.stream(prompt, runOpts);
@@ -1858,7 +2037,8 @@ export class Agent {
1858
2037
  if (runOpts.onDelta || runOpts.onThinkingDelta || runOpts.onEvent) {
1859
2038
  if (runOpts.onEvent) stream.on("*", runOpts.onEvent as any);
1860
2039
  if (runOpts.onDelta) stream.on("text_delta", (e: any) => runOpts.onDelta!(e.delta!, e));
1861
- if (runOpts.onThinkingDelta) stream.on("thinking_delta", (e: any) => runOpts.onThinkingDelta!(e.thinkingDelta!, e));
2040
+ if (runOpts.onThinkingDelta)
2041
+ stream.on("thinking_delta", (e: any) => runOpts.onThinkingDelta!(e.thinkingDelta!, e));
1862
2042
  }
1863
2043
  const stringStream: any = {
1864
2044
  [Symbol.asyncIterator]: async function* () {
@@ -1899,10 +2079,7 @@ export class Agent {
1899
2079
  * }
1900
2080
  * ```
1901
2081
  */
1902
- stream(
1903
- prompt: string | ContentPart[],
1904
- options?: AgentRunOptions
1905
- ): AssistantMessageEventStream {
2082
+ stream(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
1906
2083
  // Same sync validation timing as run().
1907
2084
  this.assertPromptValid(prompt);
1908
2085
  // Mid-session: busy agent serializes via steer (same turn) or queue (next turn).
@@ -1910,8 +2087,12 @@ export class Agent {
1910
2087
  const enforcedStream = this.midSessionConfig.mode;
1911
2088
  const urgent = options?.enqueue === "interrupt"; // stops the active run and goes first, in every mode and whatever the queue holds
1912
2089
  const requestedStream = options?.enqueue === "interrupt" ? undefined : options?.enqueue;
1913
- if (requestedStream && (enforcedStream === "steer" || enforcedStream === "queue") && requestedStream !== enforcedStream) {
1914
- throw new Error(
2090
+ if (
2091
+ requestedStream &&
2092
+ (enforcedStream === "steer" || enforcedStream === "queue") &&
2093
+ requestedStream !== enforcedStream
2094
+ ) {
2095
+ throw new ConfigError(
1915
2096
  `[Agent Accelerator] enqueue "${requestedStream}" is disabled by midSession.mode "${enforcedStream}". Use ${enforcedStream}() or set mode to "auto"/"${requestedStream}".`
1916
2097
  );
1917
2098
  }
@@ -1935,28 +2116,40 @@ export class Agent {
1935
2116
  activePromise.then(
1936
2117
  (res) => {
1937
2118
  try {
1938
- stub.push({ type: "done", delta: "", usage: res.usage, finishReason: res.finishReason, responseId: res.responseId } as any);
2119
+ stub.push({
2120
+ type: "done",
2121
+ delta: "",
2122
+ usage: res.usage,
2123
+ finishReason: res.finishReason,
2124
+ responseId: res.responseId,
2125
+ } as any);
2126
+ } catch {}
2127
+ try {
2128
+ stub.end(res);
1939
2129
  } catch {}
1940
- try { stub.end(res); } catch {}
1941
2130
  },
1942
2131
  (err) => {
1943
- try { stub.fail(err instanceof Error ? err : new Error(String(err))); } catch {}
2132
+ try {
2133
+ stub.fail(err instanceof Error ? err : new Error(String(err)));
2134
+ } catch {}
1944
2135
  }
1945
2136
  );
1946
2137
  } else {
1947
- try { stub.fail(new Error("[Agent Accelerator] No active run to steer.")); } catch {}
2138
+ try {
2139
+ stub.fail(new ValidationError("[Agent Accelerator] No active run to steer."));
2140
+ } catch {}
1948
2141
  }
1949
2142
  return stub;
1950
2143
  }
1951
2144
  if (!urgent && this.midSessionConfig.mode === "steer") {
1952
- throw new Error(
2145
+ throw new ConfigError(
1953
2146
  '[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
1954
2147
  );
1955
2148
  }
1956
2149
  this.assertPromptValid(prompt);
1957
2150
  const max = this.midSessionConfig.maxQueued ?? 20;
1958
2151
  if (!urgent && this.pendingQueue.length >= max) {
1959
- throw new Error(
2152
+ throw new ValidationError(
1960
2153
  `[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
1961
2154
  );
1962
2155
  }
@@ -2003,7 +2196,9 @@ export class Agent {
2003
2196
  if (idx >= 0) {
2004
2197
  this.pendingQueue.splice(idx, 1);
2005
2198
  queuedEntry.reject(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
2006
- try { deferred.fail(Object.assign(new Error("Stream aborted"), { name: "AbortError" })); } catch {}
2199
+ try {
2200
+ deferred.fail(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
2201
+ } catch {}
2007
2202
  }
2008
2203
  } catch {}
2009
2204
  });
@@ -2021,7 +2216,13 @@ export class Agent {
2021
2216
  // Otherwise (edge), end it here.
2022
2217
  try {
2023
2218
  if ((deferred as any).__piped !== true) {
2024
- deferred.push({ type: "done", delta: "", usage: res.usage, finishReason: res.finishReason, responseId: res.responseId } as any);
2219
+ deferred.push({
2220
+ type: "done",
2221
+ delta: "",
2222
+ usage: res.usage,
2223
+ finishReason: res.finishReason,
2224
+ responseId: res.responseId,
2225
+ } as any);
2025
2226
  deferred.end(res);
2026
2227
  }
2027
2228
  } catch {}
@@ -2043,10 +2244,7 @@ export class Agent {
2043
2244
  }
2044
2245
 
2045
2246
  // Attaches onEvent/onDelta/onThinkingDelta to a stream and, with wrapThinking, adds display-only <think> tags and whitespace hygiene on the outer events; in: stream()/pumpQueue, out: listeners on the stream.
2046
- private wireStreamCallbacks(
2047
- s: AssistantMessageEventStream,
2048
- options?: AgentRunOptions
2049
- ): void {
2247
+ private wireStreamCallbacks(s: AssistantMessageEventStream, options?: AgentRunOptions): void {
2050
2248
  // Wire one-liner callbacks so `stream:true` + onDelta is enough — no manual for-await needed
2051
2249
  if (options?.wrapThinking) {
2052
2250
  // Auto-wrap reasoning as <think>…</think> per-turn — clean boundaries across multi-turn agent runs.
@@ -2123,7 +2321,9 @@ export class Agent {
2123
2321
  const raw: string = e.delta ?? "";
2124
2322
  if (!justClosedThink) return raw;
2125
2323
  const stripped = raw.replace(/^\n+/, "");
2126
- try { e.delta = stripped; } catch {}
2324
+ try {
2325
+ e.delta = stripped;
2326
+ } catch {}
2127
2327
  if (!stripped) return "";
2128
2328
  justClosedThink = false;
2129
2329
  return stripped;
@@ -2220,11 +2420,13 @@ export class Agent {
2220
2420
  closeSynthetic();
2221
2421
  if (justClosedThink) {
2222
2422
  const stripped: string = (e.delta ?? "").replace(/^\n+/, "");
2223
- try { e.delta = stripped; } catch {}
2423
+ try {
2424
+ e.delta = stripped;
2425
+ } catch {}
2224
2426
  if (!stripped) return;
2225
2427
  justClosedThink = false;
2226
2428
  }
2227
- needThinkNewline = !((e.delta ?? "").endsWith("\n"));
2429
+ needThinkNewline = !(e.delta ?? "").endsWith("\n");
2228
2430
  });
2229
2431
  s.on("tool_call_start" as any, () => {
2230
2432
  closeSynthetic();
@@ -2254,7 +2456,7 @@ export class Agent {
2254
2456
  /** What a failed run attempt got done before it failed (the `partial` the loop attaches to its error). */
2255
2457
  type FailedAttempt = Partial<AgentResponse> & { usage: TokenUsage };
2256
2458
 
2257
- // Adds what failed attempts already did (tool calls, results, turns, token usage) to the response of the attempt that finally worked, so a retry never hides side effects or cost; in: the final response + each failed attempt's `err.partial`, out: the run's AgentResponse.
2459
+ // Adds what failed attempts already did (tool calls, results, workers, turns, token usage) to the response of the attempt that finally worked, so a retry never hides side effects or cost; in: the final response + each failed attempt's `err.partial`, out: the run's AgentResponse.
2258
2460
  function withFailedAttempts(res: AgentResponse, failed: FailedAttempt[]): AgentResponse {
2259
2461
  const usage: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
2260
2462
  for (const f of failed) accumulateUsage(usage, f.usage);
@@ -2266,7 +2468,7 @@ function withFailedAttempts(res: AgentResponse, failed: FailedAttempt[]): AgentR
2266
2468
  thoughtSignature: res.thoughtSignature,
2267
2469
  toolCalls: [...failed.flatMap((f) => f.toolCalls ?? []), ...res.toolCalls],
2268
2470
  toolResults: [...failed.flatMap((f) => f.toolResults ?? []), ...res.toolResults],
2269
- subagents: res.subagents,
2471
+ subagents: [...failed.flatMap((f) => f.subagents ?? []), ...res.subagents],
2270
2472
  usage,
2271
2473
  responseId: res.responseId,
2272
2474
  model: res.model,
@@ -2302,7 +2504,12 @@ export function normalizeThinking(config: AgentConfig, fallbackLevel?: string):
2302
2504
  try {
2303
2505
  const rawModel: any = (config.model as any)?.model ? (config.model as any).model : config.model;
2304
2506
  const modelStr = typeof rawModel === "string" ? rawModel : rawModel?.id || "";
2305
- const provStr = typeof rawModel === "object" && rawModel?.provider ? rawModel.provider : modelStr.includes("/") ? modelStr.split("/")[0] : "";
2507
+ const provStr =
2508
+ typeof rawModel === "object" && rawModel?.provider
2509
+ ? rawModel.provider
2510
+ : modelStr.includes("/")
2511
+ ? modelStr.split("/")[0]
2512
+ : "";
2306
2513
  const actualModelId = modelStr.includes("/") ? modelStr.split("/").slice(1).join("/") : modelStr;
2307
2514
  if (actualModelId) {
2308
2515
  validateModelThinking(provStr || "openrouter", actualModelId, level);