agent-accelerator 0.5.9 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +64 -16
  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 +447 -172
  7. package/src/agent/architecture.md +16 -12
  8. package/src/agent/config-blocks.ts +39 -12
  9. package/src/agent/context.ts +9 -3
  10. package/src/agent/defaults.ts +88 -0
  11. package/src/agent/delegation.ts +86 -28
  12. package/src/agent/loop.ts +474 -374
  13. package/src/agent/subagent.ts +1 -3
  14. package/src/agent/subagents-tool.ts +50 -19
  15. package/src/agent/turns.ts +3 -1
  16. package/src/agent/usage-limit.ts +92 -0
  17. package/src/agent/worker.ts +73 -30
  18. package/src/agent/workers.ts +126 -21
  19. package/src/architecture.md +17 -15
  20. package/src/index.ts +43 -19
  21. package/src/models/architecture.md +4 -3
  22. package/src/models/catalog-cache.ts +6 -5
  23. package/src/models/catalog.ts +39 -30
  24. package/src/prompts/additional-context.md +2 -0
  25. package/src/prompts/architecture.md +19 -0
  26. package/src/prompts/prompts.ts +81 -0
  27. package/src/prompts/reasoning-directive.md +5 -0
  28. package/src/{scheduler/scheduled-actions.md → prompts/scheduled-actions-tool.md} +4 -0
  29. package/src/prompts/structured-output.md +2 -0
  30. package/src/{agent/subagents.md → prompts/subagents-tool.md} +3 -0
  31. package/src/prompts/tool-bridge.md +1 -0
  32. package/src/prompts/tool-interrupted.md +1 -0
  33. package/src/prompts/tool-not-found.md +1 -0
  34. package/src/prompts/tool-repeat-blocked.md +1 -0
  35. package/src/prompts/tool-schema-error.md +1 -0
  36. package/src/prompts/tool-truncated-arguments.md +1 -0
  37. package/src/prompts/worker-failed.md +1 -0
  38. package/src/prompts/worker-replied.md +2 -0
  39. package/src/providers/architecture.md +10 -8
  40. package/src/providers/google.ts +228 -69
  41. package/src/providers/openai-compat.ts +74 -56
  42. package/src/providers/openai.ts +64 -41
  43. package/src/providers/openrouter.ts +270 -48
  44. package/src/providers/registry.ts +6 -21
  45. package/src/providers/shared.ts +87 -33
  46. package/src/providers.ts +115 -124
  47. package/src/scheduler/architecture.md +8 -7
  48. package/src/scheduler/scheduler.ts +226 -60
  49. package/src/scheduler/store.ts +23 -10
  50. package/src/scheduler/time.ts +17 -3
  51. package/src/scheduler/tool.ts +38 -16
  52. package/src/session/architecture.md +7 -6
  53. package/src/session/journal.ts +39 -12
  54. package/src/session/store.ts +126 -61
  55. package/src/session/usage.ts +58 -9
  56. package/src/streaming/architecture.md +4 -3
  57. package/src/streaming/event-stream.ts +26 -12
  58. package/src/telemetry.ts +6 -2
  59. package/src/tools/architecture.md +9 -7
  60. package/src/tools/executor.ts +109 -62
  61. package/src/tools/schema.ts +10 -6
  62. package/src/tools/subagent-result.ts +6 -1
  63. package/src/tools/tool-content.ts +32 -0
  64. package/src/tools/tool.ts +9 -11
  65. package/src/types/agent.ts +37 -22
  66. package/src/types/architecture.md +11 -10
  67. package/src/types/core.ts +5 -9
  68. package/src/types/errors.ts +26 -4
  69. package/src/types/message.ts +7 -0
  70. package/src/types/model.ts +4 -5
  71. package/src/types/scheduler.ts +4 -4
  72. package/src/types/tool.ts +14 -2
  73. package/src/update-models.ts +5 -5
  74. package/src/utils/architecture.md +16 -15
  75. package/src/utils/cache.ts +1 -3
  76. package/src/utils/durable.ts +17 -5
  77. package/src/utils/env.ts +9 -7
  78. package/src/utils/errors.ts +9 -17
  79. package/src/utils/headers.ts +3 -10
  80. package/src/utils/media.ts +23 -8
  81. package/src/utils/raw-capture.ts +9 -2
  82. package/src/utils/retry.ts +15 -3
  83. package/src/utils/safe-io.ts +29 -7
  84. package/src/utils/serialization.ts +1 -4
  85. 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";
@@ -12,22 +28,33 @@ import { AgentContext } from "./context.ts";
12
28
  import { resolveModel } from "../providers/registry.ts";
13
29
  import { buildAgentTools } from "./delegation.ts";
14
30
  import { createSubagentsTool } from "./subagents-tool.ts";
15
- import { WorkerRegistry } from "./workers.ts";
31
+ import { WorkerRegistry, savedSpend } from "./workers.ts";
16
32
  import {
17
33
  DEFAULT_MAX_DEPTH,
34
+ isWorkerConfig,
18
35
  normalizeToolMap,
19
36
  resolveDynamicModelInput,
20
37
  resolveMainModel,
21
38
  } from "./worker.ts";
22
39
  import * as path from "node:path";
23
- import { registerAgent } from "../scheduler/scheduler.ts";
24
- import { resolveHome } from "../scheduler/store.ts";
40
+ import { registerAgent, ScheduledActions } from "../scheduler/scheduler.ts";
41
+ import { resolveHome } from "../utils/home.ts";
25
42
  import { persistTelemetry, registerSessionFolder, unregisterSessionFolder } from "../telemetry-file.ts";
26
43
  import { lockFolder } from "../utils/durable.ts";
27
44
  import { createScheduledActionsTool, getDatetimeTool } from "../scheduler/tool.ts";
45
+ import { fillPrompt, resolvePrompts, type Prompts } from "../prompts/prompts.ts";
46
+ import { UsageLimit, checkUsageLimitConfig } from "./usage-limit.ts";
47
+ import { mergeCap } from "./defaults.ts";
28
48
  import { applyEditable, assertModelAllowed, modelKey, toEditableConfig } from "./config-blocks.ts";
29
49
  import { assertNoToolCollisions } from "../tools/executor.ts";
30
- import { runAgentLoop, streamAgentLoop, unfinishedToolCalls, endsWithStop, accumulateUsage, type SteerEntry } from "./loop.ts";
50
+ import {
51
+ runAgentLoop,
52
+ streamAgentLoop,
53
+ unfinishedToolCalls,
54
+ endsWithStop,
55
+ accumulateUsage,
56
+ type SteerEntry,
57
+ } from "./loop.ts";
31
58
  import { isRetryableRun, readRetryAfterMs, sleep } from "../utils/retry.ts";
32
59
  import { createSessionId, nowMs, newRunId, previewPrompt } from "../utils/session.ts";
33
60
  import {
@@ -38,25 +65,43 @@ import {
38
65
  listSubAgentTraceIdsForSessions,
39
66
  type SubAgentTrace,
40
67
  } 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";
68
+ import {
69
+ addTurn,
70
+ buildUsageReport,
71
+ emptyUsage,
72
+ mergeUsage,
73
+ modelName,
74
+ readUsage,
75
+ type UsageNode,
76
+ type UsageReport,
77
+ type UsageTotals,
78
+ } from "../session/usage.ts";
79
+ import {
80
+ saveSessionFile,
81
+ sessionDirFor,
82
+ loadSessionDir,
83
+ findInvalidMessage,
84
+ priceUsage,
85
+ telemetryBus,
86
+ type SessionRunMark,
87
+ type SessionRunEnd,
88
+ type SessionPending,
89
+ type SessionToolRecord,
90
+ } from "../session/store.ts";
43
91
  import { SessionJournal } from "../session/journal.ts";
44
92
  import { ToolLogs, type ToolLogWriter } from "../session/tool-log.ts";
45
93
  import { isLocalPathInput } from "../utils/safe-io.ts";
46
- import { clearSessionRouting, normalizeStructuredOutput, normalizeStop } from "../providers.ts";
94
+ import { clearSessionRouting, normalizeStructuredOutput } from "../providers.ts";
47
95
  import type { StructuredOutputSpec } from "../types/model.ts";
48
96
  import { clearInteractionChains } from "../providers/google.ts";
49
97
  import { validateModelThinking, ensureModelCatalogFresh } from "../models/catalog.ts";
50
- import { buildSessionData, type PersistedAgentSession, type SessionTotals,} from "../session/store.ts";
98
+ import { buildSessionData, type PersistedAgentSession, type SessionTotals } from "../session/store.ts";
51
99
  import { SessionTelemetry } from "../session/store.ts";
52
100
 
53
101
  /**
54
102
  * Resolves a per-run thinking override without mutating agent config.
55
103
  */
56
- export function resolveEffectiveThinking(
57
- base?: ThinkingConfig,
58
- overrideLevel?: string
59
- ): ThinkingConfig | undefined {
104
+ export function resolveEffectiveThinking(base?: ThinkingConfig, overrideLevel?: string): ThinkingConfig | undefined {
60
105
  if (!overrideLevel) return base;
61
106
  if (overrideLevel === "none") return { enabled: false, level: "none", budgetTokens: 0 };
62
107
  if (overrideLevel === "dynamic") return { enabled: true, level: "dynamic", budgetTokens: -1 };
@@ -94,8 +139,7 @@ export interface AgentRunLimits {
94
139
  /** Resolves `(config as any).limits` with defaults (timeouts tolerate reasoning). */
95
140
  function resolveAgentLimits(config: { limits?: Record<string, unknown> }): AgentRunLimits {
96
141
  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;
142
+ const num = (v: unknown): number | undefined => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
99
143
  // A network timeout: `0` or `Infinity` turns it off, any other number is milliseconds, anything else is unset (the default applies).
100
144
  const timeout = (v: unknown): number | undefined => (typeof v === "number" && !Number.isNaN(v) ? v : undefined);
101
145
  return {
@@ -104,7 +148,9 @@ function resolveAgentLimits(config: { limits?: Record<string, unknown> }): Agent
104
148
  ...(num(raw.maxToolCallsPerRun) !== undefined
105
149
  ? { maxToolCallsPerRun: Math.max(1, Math.floor(num(raw.maxToolCallsPerRun)!)) }
106
150
  : {}),
107
- ...(num(raw.maxTotalTokens) !== undefined ? { maxTotalTokens: Math.max(1, Math.floor(num(raw.maxTotalTokens)!)) } : {}),
151
+ ...(num(raw.maxTotalTokens) !== undefined
152
+ ? { maxTotalTokens: Math.max(1, Math.floor(num(raw.maxTotalTokens)!)) }
153
+ : {}),
108
154
  requestTimeoutMs: timeout(raw.requestTimeoutMs) ?? 300_000,
109
155
  firstByteTimeoutMs: timeout(raw.firstByteTimeoutMs) ?? 120_000,
110
156
  streamIdleTimeoutMs: timeout(raw.streamIdleTimeoutMs) ?? 300_000,
@@ -160,6 +206,8 @@ export class Agent {
160
206
  readonly description: string;
161
207
  /** Stable system instructions. */
162
208
  instructions: string;
209
+ /** The SDK prompts in use: the shipped defaults with `AgentConfig.prompts` applied. */
210
+ readonly prompts: Prompts;
163
211
  /** Original model string or catalog spec used for resolution. */
164
212
  readonly modelStringOrSpec: string | any;
165
213
  /** Configured dynamic sub-agent model, if enabled. */
@@ -180,6 +228,8 @@ export class Agent {
180
228
  maxDepth: number;
181
229
  /** Per-run spending cap of each worker (unset = unlimited). */
182
230
  workerBudget?: { maxCostUsd?: number; maxTotalTokens?: number };
231
+ /** Per-run spending cap of each scheduled-action run (unset = unlimited). */
232
+ scheduledActionBudget?: { maxCostUsd?: number; maxTotalTokens?: number };
183
233
  };
184
234
  /** Home folder for everything written to disk (`sessions/`, `scheduled/`, `telemetry/`). */
185
235
  readonly home: string;
@@ -216,8 +266,6 @@ export class Agent {
216
266
  private readonly callDefaults: {
217
267
  toolChoice?: ToolChoiceOption;
218
268
  maxOutputTokens?: number;
219
- stop?: string | string[];
220
- seed?: number;
221
269
  retry?: RetryPolicyConfig;
222
270
  env?: Record<string, string>;
223
271
  fetch?: typeof fetch;
@@ -289,6 +337,11 @@ export class Agent {
289
337
  /** The resume in flight, so a second `resume()` returns the same promise. */
290
338
  private resuming?: Promise<AgentResponse | undefined>;
291
339
  private workerRegistry?: WorkerRegistry;
340
+ /** The chat-wide spending cap this agent counts into and checks against: its own (`usageLimit`) or the root's, handed down to workers. @internal */
341
+ chat?: UsageLimit;
342
+ /** True for the agent that created `chat` from its own `usageLimit` (the root of the chat). */
343
+ private ownsChat = false;
344
+ private scheduledActions?: ScheduledActions;
292
345
 
293
346
  /**
294
347
  * Creates an agent and registers its model, tools, cache, and delegation settings.
@@ -314,6 +367,18 @@ export class Agent {
314
367
  this.description = config.description || "AI Agent powered by Agent Accelerator";
315
368
  // DX1: only instructions (no systemPrompt alias)
316
369
  this.instructions = config.instructions || "";
370
+ this.prompts = resolvePrompts(config.prompts);
371
+ // Workers and scheduled runs the SDK builds never take developer defaults (`configure()`, env vars).
372
+ const takesDefaults = !isWorkerConfig(config);
373
+ if (config.usageLimit !== undefined) {
374
+ const problem = checkUsageLimitConfig(config.usageLimit);
375
+ if (problem) throw new ConfigError(`[Agent Accelerator] Invalid usageLimit: ${problem}.`);
376
+ }
377
+ const chatCap = mergeCap("usageLimit", config.usageLimit, takesDefaults);
378
+ if (chatCap) {
379
+ this.chat = new UsageLimit(chatCap);
380
+ this.ownsChat = true;
381
+ }
317
382
  // Unified model resolution (shared with SubAgent/workers): supports
318
383
  // ModelProviderInstance {model,apiKey,...} and ModelSpec {id,provider}.
319
384
  const { modelStringOrSpec, rawModel } = resolveMainModel(config.model);
@@ -325,34 +390,60 @@ export class Agent {
325
390
  this.subagentModel = dynModel as string | any | undefined;
326
391
  const dynEnabled = dynRaw ? (dynRaw.enabled ?? true) : false;
327
392
  if (dynRaw) {
328
- const dynTools = normalizeToolMap((dynRaw as { tools?: Record<string, ToolDefinition> | ToolDefinition[] }).tools);
393
+ const dynTools = normalizeToolMap(
394
+ (dynRaw as { tools?: Record<string, ToolDefinition> | ToolDefinition[] }).tools
395
+ );
329
396
  const rawDepth = (dynRaw as { maxDepth?: unknown }).maxDepth;
330
397
  // 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.`);
398
+ if (
399
+ rawDepth !== undefined &&
400
+ rawDepth !== Infinity &&
401
+ !(Number.isInteger(rawDepth) && (rawDepth as number) >= 1)
402
+ ) {
403
+ throw new ConfigError(
404
+ `[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.`
405
+ );
333
406
  }
334
- const rawBudget = (dynRaw as { workerBudget?: { maxCostUsd?: unknown; maxTotalTokens?: unknown } }).workerBudget;
335
- for (const key of ["maxCostUsd", "maxTotalTokens"] as const) {
336
- const v = rawBudget?.[key];
337
- 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.`);
407
+ type RawBudget = { maxCostUsd?: unknown; maxTotalTokens?: unknown } | undefined;
408
+ const checkBudgetKeys = (name: "workerBudget" | "scheduledActionBudget"): RawBudget => {
409
+ const raw = (dynRaw as Record<string, RawBudget>)[name];
410
+ for (const key of ["maxCostUsd", "maxTotalTokens"] as const) {
411
+ const v = raw?.[key];
412
+ if (v !== undefined && !(typeof v === "number" && Number.isFinite(v) && v > 0)) {
413
+ throw new ConfigError(
414
+ `[Agent Accelerator] Invalid dynamicSubagents.${name}.${key} ${JSON.stringify(v)}: expected a number greater than 0, or omit it for no limit.`
415
+ );
416
+ }
339
417
  }
340
- }
418
+ return raw;
419
+ };
420
+ const rawBudget = checkBudgetKeys("workerBudget");
421
+ const rawScheduledBudget = mergeCap(
422
+ "scheduledActionBudget",
423
+ checkBudgetKeys("scheduledActionBudget") as { maxCostUsd?: number; maxTotalTokens?: number } | undefined,
424
+ takesDefaults
425
+ );
341
426
  // Open workers are unlimited unless the developer sets a cap (a number >= 1; Infinity also means unlimited).
342
427
  const rawMax = (dynRaw as { maxSpawn?: unknown }).maxSpawn;
343
428
  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.`);
429
+ throw new ConfigError(
430
+ `[Agent Accelerator] Invalid dynamicSubagents.maxSpawn ${JSON.stringify(rawMax)}: expected a whole number >= 1, or omit it for no limit.`
431
+ );
345
432
  }
346
433
  const maxSpawn = rawMax === undefined || rawMax === Infinity ? Infinity : Math.max(1, Math.floor(rawMax));
347
434
  // Only scheduled actions are timed: no limit unless the developer sets one (0 and Infinity mean none).
348
435
  for (const removed of ["timeout", "modelMaySetTimeout"]) {
349
436
  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.`);
437
+ throw new ConfigError(
438
+ `[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.`
439
+ );
351
440
  }
352
441
  }
353
442
  const rawTimeout = (dynRaw as { workerTimeoutMs?: unknown }).workerTimeoutMs;
354
443
  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).`);
444
+ throw new ConfigError(
445
+ `[Agent Accelerator] Invalid dynamicSubagents.workerTimeoutMs ${JSON.stringify(rawTimeout)}: expected a number of milliseconds > 0, or omit it (0 and Infinity mean no limit).`
446
+ );
356
447
  }
357
448
  const workerTimeoutMs = !rawTimeout ? Infinity : Math.max(1, Math.floor(rawTimeout));
358
449
  this.dynamicSubagents = {
@@ -365,6 +456,9 @@ export class Agent {
365
456
  allowScheduledActions: dynRaw.allowScheduledActions === true,
366
457
  maxDepth: (rawDepth as number | undefined) ?? DEFAULT_MAX_DEPTH,
367
458
  ...(rawBudget ? { workerBudget: { ...(rawBudget as { maxCostUsd?: number; maxTotalTokens?: number }) } } : {}),
459
+ ...(rawScheduledBudget
460
+ ? { scheduledActionBudget: { ...(rawScheduledBudget as { maxCostUsd?: number; maxTotalTokens?: number }) } }
461
+ : {}),
368
462
  };
369
463
  }
370
464
  this.apiKey = (rawModel as any)?.apiKey || config.apiKey;
@@ -419,17 +513,20 @@ export class Agent {
419
513
  }
420
514
  this.cache = config.cache;
421
515
  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".`);
516
+ throw new ConfigError(
517
+ `[Agent Accelerator] Invalid resume ${JSON.stringify(config.resume)}: expected "automatic" (default) or "explicit".`
518
+ );
423
519
  }
424
520
  this.resumeMode = config.resume ?? "automatic";
425
521
  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).`);
522
+ throw new ConfigError(
523
+ `[Agent Accelerator] Invalid runRetries ${JSON.stringify(config.runRetries)}: expected a whole number >= 0 (default 3, 0 turns it off).`
524
+ );
427
525
  }
428
526
  this.runRetries = config.runRetries ?? 3;
429
527
 
430
528
  this.serviceTier = config.serviceTier;
431
- this.onFailure =
432
- config.onFailure === "rollback" || config.onFailure === "keep" ? config.onFailure : "auto";
529
+ this.onFailure = config.onFailure === "rollback" || config.onFailure === "keep" ? config.onFailure : "auto";
433
530
  this.forkConfig = { ...config };
434
531
  this.captureRaw = config.captureRaw;
435
532
  this.budget = config.budget ? { ...config.budget } : undefined;
@@ -448,8 +545,6 @@ export class Agent {
448
545
  this.callDefaults = {
449
546
  toolChoice: config.toolChoice,
450
547
  maxOutputTokens: config.maxOutputTokens,
451
- stop: config.stop,
452
- seed: config.seed,
453
548
  retry: config.retry,
454
549
  env: config.env,
455
550
  fetch: config.fetch,
@@ -466,9 +561,8 @@ export class Agent {
466
561
  Object.assign(this.tools, normalizeToolMap(config.tools));
467
562
  }
468
563
 
469
-
470
564
  if (Array.isArray(config.subagents)) {
471
- const subagentTools = buildAgentTools(config.subagents);
565
+ const subagentTools = buildAgentTools(config.subagents, { chat: this.chat });
472
566
  for (const [toolName, toolDef] of Object.entries(subagentTools)) {
473
567
  this.tools[toolName] = toolDef;
474
568
  }
@@ -508,6 +602,11 @@ export class Agent {
508
602
  return (this.workerRegistry ??= new WorkerRegistry(this));
509
603
  }
510
604
 
605
+ /** The scheduled actions of this agent's home: `create`, `list`, `update`, `run`, `skip`, `delete` (see `ScheduledActions`). */
606
+ get scheduled(): ScheduledActions {
607
+ return (this.scheduledActions ??= new ScheduledActions(this));
608
+ }
609
+
511
610
  // Takes the one-process lock of the folder this Agent persists to: its own session folder, or the folder of a single `persist.file`; throws ConfigError when another live process holds it; in: the constructor and importSession (a new session id is a new folder), out: a `.lock` file there.
512
611
  private lockSessionFolder(): void {
513
612
  const cfg = this.persistConfig;
@@ -570,11 +669,14 @@ export class Agent {
570
669
  // Puts saved spending totals back after a restart or import (zeros when the file has none); in: a loaded session's `usage`, out: this.usageTotals.
571
670
  private restoreUsage(saved: unknown): void {
572
671
  Object.assign(this.usageTotals, readUsage(saved));
672
+ if (this.ownsChat) this.chat!.restart(); // a restored session changes the starting point of the chat total
573
673
  }
574
674
 
575
675
  // 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
676
  private recordTurnUsage(usage: TokenUsage, spec: Parameters<typeof priceUsage>[1]): void {
577
- addTurn(this.usageTotals, usage, priceUsage(usage, spec, this.serviceTier ? { serviceTier: this.serviceTier } : undefined));
677
+ const priced = priceUsage(usage, spec, this.serviceTier ? { serviceTier: this.serviceTier } : undefined);
678
+ addTurn(this.usageTotals, usage, priced);
679
+ this.chat?.add((usage.inputTokens || 0) + (usage.outputTokens || 0), priced?.breakdown.totalMicroUSD ?? 0);
578
680
  }
579
681
 
580
682
  /** Adds another agent's spending to this one (a fixed worker's per-call copy into its template). @internal */
@@ -589,7 +691,10 @@ export class Agent {
589
691
  * below it. Spending of one-shot scheduled-action workers is in their run files, not here.
590
692
  */
591
693
  usageReport(): UsageReport {
592
- return buildUsageReport({ id: this.sessionId, name: this.name, model: modelName(this.modelStringOrSpec), totals: this.usageTotals }, [...this.workers.usageNodes(), ...this.fixedWorkerNodes()]);
694
+ return buildUsageReport(
695
+ { id: this.sessionId, name: this.name, model: modelName(this.modelStringOrSpec), totals: this.usageTotals },
696
+ [...this.workers.usageNodes(), ...this.fixedWorkerNodes()]
697
+ );
593
698
  }
594
699
 
595
700
  // The `subagents: [...]` workers as usage nodes (their per-call copies' spending is merged into them); in: the constructor config, out: usageReport.
@@ -598,14 +703,25 @@ export class Agent {
598
703
  for (const entry of this.forkConfig.subagents ?? []) {
599
704
  const worker = "agent" in entry ? entry.agent : entry;
600
705
  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: [] });
706
+ nodes.push({
707
+ id: name,
708
+ name,
709
+ model: modelName(worker.modelStringOrSpec),
710
+ status: "fixed",
711
+ totals: worker.usageTotals,
712
+ workers: [],
713
+ });
602
714
  }
603
715
  return nodes;
604
716
  }
605
717
 
606
718
  // Says on the telemetry bus that resuming (or a prompt that was waiting) failed; in: a rejected resume, out: telemetryBus.
607
719
  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 });
720
+ telemetryBus.emitWarning(
721
+ "agent.resume_failed",
722
+ `[Agent Accelerator] Resuming session ${this.sessionId} failed: ${err instanceof Error ? err.message : String(err)}`,
723
+ { sessionId: this.sessionId }
724
+ );
609
725
  }
610
726
 
611
727
  /** 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 +745,8 @@ export class Agent {
629
745
  */
630
746
  resume(): Promise<AgentResponse | undefined> {
631
747
  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.");
748
+ if (this.currentRun)
749
+ throw new ValidationError("[Agent Accelerator] resume() needs an idle agent: a run is already active.");
633
750
  const mark = this.runMarks.filter((m) => !m.end && m !== this.activeMark).pop();
634
751
  const last = this.context.messages[this.context.messages.length - 1];
635
752
  const nothingOpen = unfinishedToolCalls(this.context.messages).length === 0;
@@ -642,7 +759,11 @@ export class Agent {
642
759
  telemetryBus.emit({
643
760
  code: "agent.resumed",
644
761
  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) },
762
+ data: {
763
+ sessionId: this.sessionId,
764
+ runId: mark.id,
765
+ pendingToolCalls: unfinishedToolCalls(this.context.messages).map((c) => c.name),
766
+ },
646
767
  });
647
768
  const run = this.startNonStreamingRun("", undefined, mark);
648
769
  const clear = (): void => {
@@ -655,7 +776,19 @@ export class Agent {
655
776
 
656
777
  // The finished tool calls of the current run as results the loop can reuse; in: toolRecords, out: loopConfig.resume.stored.
657
778
  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]));
779
+ return new Map(
780
+ this.toolRecords.map((t) => [
781
+ t.id,
782
+ {
783
+ id: t.id,
784
+ name: t.name,
785
+ result: t.result,
786
+ ...(t.isError ? { isError: true } : {}),
787
+ ...(t.stopped ? { stopped: true } : {}),
788
+ ...(t.parts && t.parts.length > 0 ? { parts: t.parts } : {}),
789
+ } as ToolResultRecord,
790
+ ])
791
+ );
659
792
  }
660
793
 
661
794
  // 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.
@@ -674,6 +807,7 @@ export class Agent {
674
807
  result: isSubagentResult(result.result) ? result.result.xml : result.result,
675
808
  ...(result.isError ? { isError: true } : {}),
676
809
  ...(result.stopped ? { stopped: true } : {}),
810
+ ...(result.parts && result.parts.length > 0 ? { parts: result.parts } : {}),
677
811
  });
678
812
  this.doPersist();
679
813
  }
@@ -683,15 +817,9 @@ export class Agent {
683
817
  const base = this.instructions || "";
684
818
  let out = base;
685
819
  const isThinkingEnabled =
686
- this.thinkingConfig?.enabled !== false &&
687
- this.thinkingConfig?.level &&
688
- this.thinkingConfig?.level !== "none";
689
- if (isThinkingEnabled && !out.includes("[Reasoning Directive]")) {
690
- const guidance =
691
- "[Reasoning Directive]\n" +
692
- "1. Use internal reasoning strictly for private planning and step-by-step thinking.\n" +
693
- "2. Never attempt to execute tools or output final user deliverables inside reasoning.\n" +
694
- "3. Once your reasoning is complete, output your final response or function calls directly in the standard response output.";
820
+ this.thinkingConfig?.enabled !== false && this.thinkingConfig?.level && this.thinkingConfig?.level !== "none";
821
+ const guidance = this.prompts.reasoningDirective;
822
+ if (isThinkingEnabled && !out.includes(guidance)) {
695
823
  out = out ? `${out}\n\n${guidance}` : guidance;
696
824
  }
697
825
  return out || undefined;
@@ -707,8 +835,12 @@ export class Agent {
707
835
  this.toolRecords.length = 0; // a cleared conversation has no run to resume, so its finished tool results go too
708
836
  this.activeMark = undefined;
709
837
  // Reset also drops session-scoped provider chaining/routing.
710
- try { clearInteractionChains(sid); } catch {}
711
- try { clearSessionRouting(sid); } catch {}
838
+ try {
839
+ clearInteractionChains(sid);
840
+ } catch {}
841
+ try {
842
+ clearSessionRouting(sid);
843
+ } catch {}
712
844
  }
713
845
 
714
846
  /**
@@ -727,17 +859,27 @@ export class Agent {
727
859
  /** Drops one named session. No-op for unknown ids. */
728
860
  disposeSession(id: string): void {
729
861
  unregisterSessionFolder(id);
730
- try { this.sessions.delete(id); } catch {}
731
- try { clearInteractionChains(id); } catch {}
732
- try { clearSessionRouting(id); } catch {}
862
+ try {
863
+ this.sessions.delete(id);
864
+ } catch {}
865
+ try {
866
+ clearInteractionChains(id);
867
+ } catch {}
868
+ try {
869
+ clearSessionRouting(id);
870
+ } catch {}
733
871
  }
734
872
 
735
873
  /** Drops all named sessions except the default context. */
736
874
  clearSessions(): void {
737
875
  try {
738
876
  for (const id of [...this.sessions.keys()]) {
739
- try { clearInteractionChains(id); } catch {}
740
- try { clearSessionRouting(id); } catch {}
877
+ try {
878
+ clearInteractionChains(id);
879
+ } catch {}
880
+ try {
881
+ clearSessionRouting(id);
882
+ } catch {}
741
883
  }
742
884
  this.sessions.clear();
743
885
  } catch {}
@@ -753,8 +895,12 @@ export class Agent {
753
895
  for (const [key, entry] of [...this.sessions.entries()]) {
754
896
  if (now - entry.lastAccess > SESSION_TTL_MS) {
755
897
  this.sessions.delete(key);
756
- try { clearInteractionChains(key); } catch {}
757
- try { clearSessionRouting(key); } catch {}
898
+ try {
899
+ clearInteractionChains(key);
900
+ } catch {}
901
+ try {
902
+ clearSessionRouting(key);
903
+ } catch {}
758
904
  }
759
905
  }
760
906
  } catch {}
@@ -771,12 +917,19 @@ export class Agent {
771
917
  let oldest: string | undefined;
772
918
  let oldestTs = Infinity;
773
919
  for (const [key, entry] of this.sessions) {
774
- if (entry.lastAccess < oldestTs) { oldestTs = entry.lastAccess; oldest = key; }
920
+ if (entry.lastAccess < oldestTs) {
921
+ oldestTs = entry.lastAccess;
922
+ oldest = key;
923
+ }
775
924
  }
776
925
  if (!oldest) break;
777
926
  this.sessions.delete(oldest);
778
- try { clearInteractionChains(oldest); } catch {}
779
- try { clearSessionRouting(oldest); } catch {}
927
+ try {
928
+ clearInteractionChains(oldest);
929
+ } catch {}
930
+ try {
931
+ clearSessionRouting(oldest);
932
+ } catch {}
780
933
  }
781
934
  } catch {}
782
935
  return fresh;
@@ -789,8 +942,12 @@ export class Agent {
789
942
  const err = Object.assign(new Error("Queue cleared"), { name: "AbortError" });
790
943
  const pending = this.pendingQueue.splice(0);
791
944
  for (const entry of pending) {
792
- try { entry.reject(err); } catch {}
793
- try { entry.deferredStream?.fail(err instanceof Error ? err : new Error(String(err))); } catch {}
945
+ try {
946
+ entry.reject(err);
947
+ } catch {}
948
+ try {
949
+ entry.deferredStream?.fail(err instanceof Error ? err : new Error(String(err)));
950
+ } catch {}
794
951
  }
795
952
  this.persistNow();
796
953
  return n;
@@ -800,7 +957,7 @@ export class Agent {
800
957
  private assertSteerRoom(inbox: SteerEntry[]): void {
801
958
  const max = this.maxSteers ?? 20;
802
959
  if (inbox.length >= max) {
803
- throw new Error(
960
+ throw new ValidationError(
804
961
  `[Agent Accelerator] Steer inbox is full (${inbox.length}/${max}). Wait for the active run to drain or increase midSession.maxSteers.`
805
962
  );
806
963
  }
@@ -858,7 +1015,9 @@ export class Agent {
858
1015
  interrupt(arg?: Error | string | ContentPart[], options?: AgentRunOptions): void | Promise<AgentResponse> {
859
1016
  if (typeof arg === "string" || Array.isArray(arg)) {
860
1017
  if (options?.stream === true) {
861
- throw new Error('[Agent Accelerator] interrupt(message) is non-streaming. Use stream(prompt, { enqueue: "interrupt" }) for a streaming interrupt.');
1018
+ throw new ValidationError(
1019
+ '[Agent Accelerator] interrupt(message) is non-streaming. Use stream(prompt, { enqueue: "interrupt" }) for a streaming interrupt.'
1020
+ );
862
1021
  }
863
1022
  return this.run(arg, { ...options, enqueue: "interrupt" });
864
1023
  }
@@ -900,12 +1059,12 @@ export class Agent {
900
1059
  steer(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
901
1060
  this.assertPromptValid(prompt);
902
1061
  if (options?.stream === true) {
903
- throw new Error(
1062
+ throw new ValidationError(
904
1063
  '[Agent Accelerator] steer() is non-streaming. Use stream(prompt, { enqueue: "steer" }) for a streaming steer.'
905
1064
  );
906
1065
  }
907
1066
  if (this.midSessionConfig.mode === "queue") {
908
- throw new Error(
1067
+ throw new ConfigError(
909
1068
  '[Agent Accelerator] steer() is disabled by midSession.mode "queue". Use queue() or set mode to "auto"/"steer".'
910
1069
  );
911
1070
  }
@@ -958,19 +1117,19 @@ export class Agent {
958
1117
  queue(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
959
1118
  this.assertPromptValid(prompt);
960
1119
  if (options?.stream === true) {
961
- throw new Error(
1120
+ throw new ValidationError(
962
1121
  '[Agent Accelerator] queue() is non-streaming. Use stream(prompt, { enqueue: "queue" }) for a streaming follow-up.'
963
1122
  );
964
1123
  }
965
1124
  if (this.midSessionConfig.mode === "steer") {
966
- throw new Error(
1125
+ throw new ConfigError(
967
1126
  '[Agent Accelerator] queue() is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
968
1127
  );
969
1128
  }
970
1129
  if (!this.currentRun) return this.run(prompt, options) as Promise<AgentResponse>;
971
1130
  const max = this.midSessionConfig.maxQueued ?? 20;
972
1131
  if (this.pendingQueue.length >= max) {
973
- throw new Error(
1132
+ throw new ValidationError(
974
1133
  `[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
975
1134
  );
976
1135
  }
@@ -1001,8 +1160,12 @@ export class Agent {
1001
1160
  reason instanceof Error ? reason : new Error("Queued run aborted before start"),
1002
1161
  { name: "AbortError" }
1003
1162
  );
1004
- try { next.reject(abortErr); } catch {}
1005
- try { next.deferredStream?.fail(abortErr instanceof Error ? abortErr : new Error(String(abortErr))); } catch {}
1163
+ try {
1164
+ next.reject(abortErr);
1165
+ } catch {}
1166
+ try {
1167
+ next.deferredStream?.fail(abortErr instanceof Error ? abortErr : new Error(String(abortErr)));
1168
+ } catch {}
1006
1169
  queueMicrotask(() => this.pumpQueue());
1007
1170
  return;
1008
1171
  }
@@ -1011,30 +1174,48 @@ export class Agent {
1011
1174
  const deferred = next.deferredStream;
1012
1175
  try {
1013
1176
  const real = this.startStreamingRun(next.prompt, next.options);
1014
- try { (deferred as any).__piped = true; } catch {}
1177
+ try {
1178
+ (deferred as any).__piped = true;
1179
+ } catch {}
1015
1180
  const forward = (e: StreamEvent) => {
1016
- try { deferred.push(e); } catch {}
1181
+ try {
1182
+ deferred.push(e);
1183
+ } catch {}
1017
1184
  };
1018
1185
  real.on("*", forward as any);
1019
1186
  try {
1020
1187
  deferred.onCancel(() => {
1021
- try { real.cancel(); } catch {}
1188
+ try {
1189
+ real.cancel();
1190
+ } catch {}
1022
1191
  });
1023
1192
  } catch {}
1024
1193
  real.result().then(
1025
1194
  (res) => {
1026
- try { next.resolve(res); } catch {}
1027
- try { deferred.end(res); } catch {}
1195
+ try {
1196
+ next.resolve(res);
1197
+ } catch {}
1198
+ try {
1199
+ deferred.end(res);
1200
+ } catch {}
1028
1201
  },
1029
1202
  (err) => {
1030
- try { next.reject(err); } catch {}
1203
+ try {
1204
+ next.reject(err);
1205
+ } catch {}
1031
1206
  // Pipe failures into the deferred stream so for-await sees them.
1032
- try { deferred.fail(err instanceof Error ? err : new Error(String(err))); } catch {}
1207
+ try {
1208
+ deferred.fail(err instanceof Error ? err : new Error(String(err)));
1209
+ } catch {}
1033
1210
  }
1034
1211
  );
1035
1212
  } catch (err) {
1036
- try { next.reject(err); } catch {}
1037
- try { deferred.fail(err instanceof Error ? err : new Error(String(err))); } catch {}
1213
+ try {
1214
+ next.reject(err);
1215
+ } catch {}
1216
+ try {
1217
+ deferred.fail(err instanceof Error ? err : new Error(String(err)));
1218
+ } catch {}
1038
1219
  // Keep draining even if starting failed.
1039
1220
  queueMicrotask(() => this.pumpQueue());
1040
1221
  }
@@ -1047,7 +1228,11 @@ export class Agent {
1047
1228
  private finishCurrentRun(): void {
1048
1229
  // Leftover steer entries become queued follow-ups instead of dropping.
1049
1230
  let leftover: SteerEntry[] = [];
1050
- try { leftover = this.currentRun?.steerInbox?.splice(0) ?? []; } catch { leftover = []; }
1231
+ try {
1232
+ leftover = this.currentRun?.steerInbox?.splice(0) ?? [];
1233
+ } catch {
1234
+ leftover = [];
1235
+ }
1051
1236
  this.currentRun = null;
1052
1237
  if (leftover.length > 0) {
1053
1238
  try {
@@ -1057,12 +1242,18 @@ export class Agent {
1057
1242
  for (const entry of leftover) {
1058
1243
  try {
1059
1244
  if ((entry.options as { signal?: AbortSignal } | undefined)?.signal?.aborted) {
1060
- entry.settle?.reject(Object.assign(new Error("Steer aborted before it could run"), { name: "AbortError" }));
1245
+ entry.settle?.reject(
1246
+ Object.assign(new Error("Steer aborted before it could run"), { name: "AbortError" })
1247
+ );
1061
1248
  continue;
1062
1249
  }
1063
- } catch { continue; }
1250
+ } catch {
1251
+ continue;
1252
+ }
1064
1253
  if (this.pendingQueue.length + lateSteers.length >= max) {
1065
- entry.settle?.reject(new Error("[Agent Accelerator] Steer dropped: the follow-up queue is full."));
1254
+ entry.settle?.reject(
1255
+ new ValidationError("[Agent Accelerator] Steer dropped: the follow-up queue is full.")
1256
+ );
1066
1257
  continue;
1067
1258
  }
1068
1259
  // The steer was never injected: it now runs as a follow-up, and the
@@ -1091,7 +1282,9 @@ export class Agent {
1091
1282
  try {
1092
1283
  const dispose = mergeDisposers.get(runController);
1093
1284
  if (dispose) {
1094
- try { dispose(); } catch {}
1285
+ try {
1286
+ dispose();
1287
+ } catch {}
1095
1288
  mergeDisposers.delete(runController);
1096
1289
  }
1097
1290
  } catch {}
@@ -1105,7 +1298,9 @@ export class Agent {
1105
1298
  try {
1106
1299
  runController.abort((userSignal as any).reason);
1107
1300
  } catch {
1108
- try { runController.abort(); } catch {}
1301
+ try {
1302
+ runController.abort();
1303
+ } catch {}
1109
1304
  }
1110
1305
  return runController.signal;
1111
1306
  }
@@ -1121,13 +1316,19 @@ export class Agent {
1121
1316
  try {
1122
1317
  runController.abort((userSignal as any).reason);
1123
1318
  } catch {
1124
- try { runController.abort(); } catch {}
1319
+ try {
1320
+ runController.abort();
1321
+ } catch {}
1125
1322
  }
1126
1323
  };
1127
- try { userSignal.addEventListener("abort", forward, { once: true }); } catch {}
1324
+ try {
1325
+ userSignal.addEventListener("abort", forward, { once: true });
1326
+ } catch {}
1128
1327
  try {
1129
1328
  mergeDisposers.set(runController, () => {
1130
- try { userSignal.removeEventListener("abort", forward); } catch {}
1329
+ try {
1330
+ userSignal.removeEventListener("abort", forward);
1331
+ } catch {}
1131
1332
  });
1132
1333
  } catch {}
1133
1334
  return runController.signal;
@@ -1143,6 +1344,7 @@ export class Agent {
1143
1344
  const d = this.callDefaults;
1144
1345
  return {
1145
1346
  limits: { ...this.runLimits },
1347
+ ...(this.forkConfig.prompts ? { prompts: { ...this.forkConfig.prompts } } : {}),
1146
1348
  ...(d.retry ? { retry: d.retry } : {}),
1147
1349
  ...(d.env ? { env: d.env } : {}),
1148
1350
  ...(d.fetch ? { fetch: d.fetch } : {}),
@@ -1194,13 +1396,34 @@ export class Agent {
1194
1396
  return new Agent(applyEditable(editable, base));
1195
1397
  }
1196
1398
 
1399
+ // Takes the chat total's starting point (what the saved sessions of this agent and its workers already spent) once, before the first turn of the chat's root agent; in: the run starters, out: this.chat.
1400
+ private beginChat(): void {
1401
+ if (!this.ownsChat) return;
1402
+ this.chat!.begin(() => {
1403
+ // A persisted chat starts from its saved sessions (this agent's and every worker's); a memory-only one from what it holds now.
1404
+ const root = this.persistConfig?.dir;
1405
+ if (root && !this.stateless) {
1406
+ try {
1407
+ const saved = savedSpend(sessionDirFor(root, this.sessionId));
1408
+ return { tokens: saved.inputTokens + saved.outputTokens, microUSD: saved.costMicroUSD.total };
1409
+ } catch {
1410
+ // not a usable session folder name: nothing was saved there
1411
+ }
1412
+ }
1413
+ const total = this.usageReport().total;
1414
+ return { tokens: total.tokens.total, microUSD: Math.round((total.cost.totalUsd ?? 0) * 1_000_000) };
1415
+ });
1416
+ }
1417
+
1197
1418
  /** Budgets are per run: counters restart whenever a new run begins. */
1198
1419
  private resetBudgetState(): void {
1420
+ this.beginChat();
1199
1421
  if (this.budget) this.budgetState = { spawned: 0, totalTokens: 0, totalCostUsd: 0, startedAtMs: nowMs() };
1200
1422
  }
1201
1423
 
1202
1424
  /** Loop hook: charge `usage` (when given) and throw `BudgetExceededError` once any cap is passed. */
1203
1425
  private enforceBudget(usage?: TokenUsage): void {
1426
+ this.chat?.check(); // the chat-wide cap: before every turn and right after each one
1204
1427
  if (!this.budget) return;
1205
1428
  if (usage) chargeBudgetUsage(this as unknown as Parameters<typeof chargeBudgetUsage>[0], usage);
1206
1429
  checkBudget(this.budget, this.budgetState);
@@ -1223,9 +1446,7 @@ export class Agent {
1223
1446
  const toolChoice = normalizeToolChoiceOption(runOptions?.toolChoice ?? d.toolChoice);
1224
1447
  const retry = { ...d.retry, ...runOptions?.retry };
1225
1448
  const env = { ...d.env, ...runOptions?.env };
1226
- const stop = normalizeStop(runOptions?.stop ?? d.stop);
1227
1449
  const maxOutputTokens = runOptions?.maxOutputTokens ?? d.maxOutputTokens;
1228
- const seed = runOptions?.seed ?? d.seed;
1229
1450
  const fetchImpl = runOptions?.fetch ?? d.fetch;
1230
1451
  return {
1231
1452
  apiKey: this.apiKey,
@@ -1236,11 +1457,10 @@ export class Agent {
1236
1457
  serviceTier: this.serviceTier,
1237
1458
  sessionId,
1238
1459
  ...(this.outputSpec ? { output: this.outputSpec } : {}),
1460
+ prompts: { structuredOutput: this.prompts.structuredOutput, toolBridge: this.prompts.toolBridge },
1239
1461
  ...(this.googleTier ? { googleTier: this.googleTier } : {}),
1240
1462
  ...(toolChoice !== undefined ? { toolChoice } : {}),
1241
1463
  ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
1242
- ...(stop ? { stop } : {}),
1243
- ...(seed !== undefined ? { seed } : {}),
1244
1464
  ...(Object.keys(env).length > 0 ? { env } : {}),
1245
1465
  ...(fetchImpl ? { fetch: fetchImpl } : {}),
1246
1466
  ...(retry.maxRetries !== undefined ? { maxRetries: retry.maxRetries } : {}),
@@ -1295,7 +1515,7 @@ export class Agent {
1295
1515
  ...(this.fileAccess ? { fileAccess: this.fileAccess } : {}),
1296
1516
  onFailure: this.onFailure,
1297
1517
  ...(this.captureRaw !== undefined ? { captureRaw: this.captureRaw } : {}),
1298
- ...(this.budget ? { enforceBudget: (u?: TokenUsage) => this.enforceBudget(u) } : {}),
1518
+ ...(this.budget || this.chat ? { enforceBudget: (u?: TokenUsage) => this.enforceBudget(u) } : {}),
1299
1519
  onProgress: () => this.persistNow(),
1300
1520
  onTurnUsage: (u: TokenUsage, spec: Parameters<typeof priceUsage>[1]) => this.recordTurnUsage(u, spec),
1301
1521
  onToolResult: (r: ToolResultRecord) => this.recordTool(r),
@@ -1305,6 +1525,7 @@ export class Agent {
1305
1525
  onSteerInjected: () => this.persistNow(),
1306
1526
  // Loop enforces these (plus local timeout races).
1307
1527
  limits: { ...this.runLimits },
1528
+ prompts: this.prompts,
1308
1529
  ...(this.output !== undefined ? { output: this.output } : {}),
1309
1530
  };
1310
1531
 
@@ -1314,13 +1535,18 @@ export class Agent {
1314
1535
  const failedAttempts: FailedAttempt[] = [];
1315
1536
  for (let attempt = 0; ; attempt++) {
1316
1537
  try {
1317
- res = await runAgentLoop(attempt === 0 ? loopConfig : { ...loopConfig, resume: { stored: this.storedToolResults() } });
1538
+ res = await runAgentLoop(
1539
+ attempt === 0 ? loopConfig : { ...loopConfig, resume: { stored: this.storedToolResults() } }
1540
+ );
1318
1541
  break;
1319
1542
  } catch (err) {
1320
1543
  if (attempt >= this.runRetries || runOptions?.signal?.aborted || !isRetryableRun(err)) throw err;
1321
1544
  const partial = (err as { partial?: FailedAttempt }).partial;
1322
1545
  if (partial?.usage) failedAttempts.push(partial);
1323
- const delayMs = Math.max(RUN_RETRY_DELAYS_MS[Math.min(attempt, RUN_RETRY_DELAYS_MS.length - 1)]!, readRetryAfterMs(err) ?? 0);
1546
+ const delayMs = Math.max(
1547
+ RUN_RETRY_DELAYS_MS[Math.min(attempt, RUN_RETRY_DELAYS_MS.length - 1)]!,
1548
+ readRetryAfterMs(err) ?? 0
1549
+ );
1324
1550
  telemetryBus.emitWarning(
1325
1551
  "agent.run_retry",
1326
1552
  `[Agent Accelerator] Run failed (${err instanceof Error ? err.message : String(err)}); retry ${attempt + 1} of ${this.runRetries} in ${delayMs} ms.`,
@@ -1345,7 +1571,9 @@ export class Agent {
1345
1571
  (err) => this.endRun(mark, isAbortError(err) ? "aborted" : "failed")
1346
1572
  );
1347
1573
  const cleanup = () => {
1348
- try { this.cleanupMerge(runController); } catch {}
1574
+ try {
1575
+ this.cleanupMerge(runController);
1576
+ } catch {}
1349
1577
  if (this.currentRun?.runPromise === task) this.finishCurrentRun();
1350
1578
  };
1351
1579
  task.then(cleanup, cleanup);
@@ -1353,10 +1581,7 @@ export class Agent {
1353
1581
  }
1354
1582
 
1355
1583
  // 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.
1356
- private startStreamingRun(
1357
- prompt: string | ContentPart[],
1358
- options?: AgentRunOptions
1359
- ): AssistantMessageEventStream {
1584
+ private startStreamingRun(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
1360
1585
  const resolved = resolveModel(this.modelStringOrSpec);
1361
1586
  const effectiveLevel = normalizeThinkingV2(options?.thinking, options?.thinkingLevel) || this.thinkingConfig?.level;
1362
1587
  if (effectiveLevel) {
@@ -1387,7 +1612,7 @@ export class Agent {
1387
1612
  ...(this.fileAccess ? { fileAccess: this.fileAccess } : {}),
1388
1613
  onFailure: this.onFailure,
1389
1614
  ...(this.captureRaw !== undefined ? { captureRaw: this.captureRaw } : {}),
1390
- ...(this.budget ? { enforceBudget: (u?: TokenUsage) => this.enforceBudget(u) } : {}),
1615
+ ...(this.budget || this.chat ? { enforceBudget: (u?: TokenUsage) => this.enforceBudget(u) } : {}),
1391
1616
  onProgress: () => this.persistNow(),
1392
1617
  onTurnUsage: (u: TokenUsage, spec: Parameters<typeof priceUsage>[1]) => this.recordTurnUsage(u, spec),
1393
1618
  onToolResult: (r: ToolResultRecord) => this.recordTool(r),
@@ -1396,6 +1621,7 @@ export class Agent {
1396
1621
  onSteerInjected: () => this.persistNow(),
1397
1622
  // Loop enforces these (plus local timeout races).
1398
1623
  limits: { ...this.runLimits },
1624
+ prompts: this.prompts,
1399
1625
  ...(this.output !== undefined ? { output: this.output } : {}),
1400
1626
  };
1401
1627
 
@@ -1408,7 +1634,9 @@ export class Agent {
1408
1634
  this.mergeSubagentTraces(res.subagents as any);
1409
1635
  this.persistNow();
1410
1636
  } catch {}
1411
- try { this.cleanupMerge(runController); } catch {}
1637
+ try {
1638
+ this.cleanupMerge(runController);
1639
+ } catch {}
1412
1640
  if (this.stateless) {
1413
1641
  targetContext.messages = [];
1414
1642
  targetContext.thoughtSignatures = [];
@@ -1417,7 +1645,9 @@ export class Agent {
1417
1645
  },
1418
1646
  (err) => {
1419
1647
  this.endRun(mark, isAbortError(err) ? "aborted" : "failed");
1420
- try { this.cleanupMerge(runController); } catch {}
1648
+ try {
1649
+ this.cleanupMerge(runController);
1650
+ } catch {}
1421
1651
  if (this.currentRun?.outerStream === s) this.finishCurrentRun();
1422
1652
  }
1423
1653
  );
@@ -1534,12 +1764,24 @@ export class Agent {
1534
1764
  else if (cfg.file) saveSessionFile(cfg.file, this as any, null);
1535
1765
  } catch (err) {
1536
1766
  // Surface via hook instead of swallowing silently.
1537
- try { (cfg as unknown as { onPersistError?: (e: unknown) => void }).onPersistError?.(err); } catch {}
1767
+ try {
1768
+ (cfg as unknown as { onPersistError?: (e: unknown) => void }).onPersistError?.(err);
1769
+ } catch {}
1538
1770
  }
1539
1771
  }
1540
1772
 
1541
1773
  /** Merges completed worker metadata (incl. steps) into this agent's trace registry. */
1542
- private mergeSubagentTraces(subagents: Array<{ trackingId?: string; name: string; sessionId?: string; parentSessionId?: string; task: string; steps?: SubAgentTrace["steps"]; [k: string]: unknown }>): void {
1774
+ private mergeSubagentTraces(
1775
+ subagents: Array<{
1776
+ trackingId?: string;
1777
+ name: string;
1778
+ sessionId?: string;
1779
+ parentSessionId?: string;
1780
+ task: string;
1781
+ steps?: SubAgentTrace["steps"];
1782
+ [k: string]: unknown;
1783
+ }>
1784
+ ): void {
1543
1785
  if (!subagents || subagents.length === 0) return;
1544
1786
  const rawOf = (id?: string): string | undefined => {
1545
1787
  if (!id) return undefined;
@@ -1641,15 +1883,19 @@ export class Agent {
1641
1883
  try {
1642
1884
  const g: any = globalThis as any;
1643
1885
  this.context.messages = Array.isArray(data.messages)
1644
- ? (typeof g.structuredClone === "function"
1645
- ? g.structuredClone(data.messages)
1646
- : JSON.parse(JSON.stringify(data.messages)))
1886
+ ? typeof g.structuredClone === "function"
1887
+ ? g.structuredClone(data.messages)
1888
+ : JSON.parse(JSON.stringify(data.messages))
1647
1889
  : [];
1648
1890
  } catch {
1649
1891
  this.context.messages = Array.isArray(data.messages) ? [...data.messages] : [];
1650
1892
  }
1651
1893
  this.context.thoughtSignatures = [];
1652
- this.runMarks.splice(0, this.runMarks.length, ...(data.runs ?? []).filter((r) => r.i < this.context.messages.length));
1894
+ this.runMarks.splice(
1895
+ 0,
1896
+ this.runMarks.length,
1897
+ ...(data.runs ?? []).filter((r) => r.i < this.context.messages.length)
1898
+ );
1653
1899
  this.restoreUsage(data.usage);
1654
1900
  if (data.systemPrompt) {
1655
1901
  this.context.systemPrompt = data.systemPrompt;
@@ -1658,10 +1904,18 @@ export class Agent {
1658
1904
  }
1659
1905
  // Importing a snapshot invalidates prior provider chaining/routing
1660
1906
  // for both the previous and the restored session.
1661
- try { clearInteractionChains(prevSid); } catch {}
1662
- try { clearSessionRouting(prevSid); } catch {}
1663
- try { clearInteractionChains(this.sessionId); } catch {}
1664
- try { clearSessionRouting(this.sessionId); } catch {}
1907
+ try {
1908
+ clearInteractionChains(prevSid);
1909
+ } catch {}
1910
+ try {
1911
+ clearSessionRouting(prevSid);
1912
+ } catch {}
1913
+ try {
1914
+ clearInteractionChains(this.sessionId);
1915
+ } catch {}
1916
+ try {
1917
+ clearSessionRouting(this.sessionId);
1918
+ } catch {}
1665
1919
  }
1666
1920
 
1667
1921
  /**
@@ -1691,7 +1945,11 @@ export class Agent {
1691
1945
  }
1692
1946
 
1693
1947
  // 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.
1694
- private prepareTurn(prompt: string | ContentPart[], options?: AgentRunOptions, target?: AgentContext): SessionRunMark | undefined {
1948
+ private prepareTurn(
1949
+ prompt: string | ContentPart[],
1950
+ options?: AgentRunOptions,
1951
+ target?: AgentContext
1952
+ ): SessionRunMark | undefined {
1695
1953
  // Prompt validation throws synchronously in both run() and
1696
1954
  // stream() (same timing; documented here).
1697
1955
  if (Array.isArray(prompt) && prompt.length === 0) {
@@ -1710,7 +1968,7 @@ export class Agent {
1710
1968
  if (options?.additionalContext) {
1711
1969
  // Preserve stable instructions as systemPrompt
1712
1970
  ctx.systemPrompt = fullInstructions;
1713
- const prefix = `[Additional Context]\n${options.additionalContext}\n\n`;
1971
+ const prefix = `${fillPrompt(this.prompts.additionalContext, { context: options.additionalContext })}\n\n`;
1714
1972
  if (typeof prompt === "string") {
1715
1973
  prompt = prefix + prompt;
1716
1974
  } else if (Array.isArray(prompt)) {
@@ -1750,23 +2008,14 @@ export class Agent {
1750
2008
  run(
1751
2009
  prompt: string | ContentPart[],
1752
2010
  options: AgentRunOptions & { stream: true } & (
1753
- | { onDelta: NonNullable<AgentRunOptions["onDelta"]> }
1754
- | { onThinkingDelta: NonNullable<AgentRunOptions["onThinkingDelta"]> }
1755
- | { onEvent: NonNullable<AgentRunOptions["onEvent"]> }
1756
- )
2011
+ | { onDelta: NonNullable<AgentRunOptions["onDelta"]> }
2012
+ | { onThinkingDelta: NonNullable<AgentRunOptions["onThinkingDelta"]> }
2013
+ | { onEvent: NonNullable<AgentRunOptions["onEvent"]> }
2014
+ )
1757
2015
  ): Promise<AgentResponse> & AsyncIterable<StreamEvent>;
1758
- run(
1759
- prompt: string | ContentPart[],
1760
- options: AgentRunOptions & { stream: true }
1761
- ): AssistantMessageEventStream;
1762
- run(
1763
- prompt: string | ContentPart[],
1764
- options?: AgentRunOptions
1765
- ): Promise<AgentResponse>;
1766
- run(
1767
- prompt: string | ContentPart[],
1768
- options?: AgentRunOptions
1769
- ): Promise<AgentResponse> | AssistantMessageEventStream {
2016
+ run(prompt: string | ContentPart[], options: AgentRunOptions & { stream: true }): AssistantMessageEventStream;
2017
+ run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse>;
2018
+ run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> | AssistantMessageEventStream {
1770
2019
  // Prompt validation throws synchronously in both run() and
1771
2020
  // stream() (same timing; async work starts only after validation).
1772
2021
  this.assertPromptValid(prompt);
@@ -1795,12 +2044,14 @@ export class Agent {
1795
2044
  // Mid-session: busy agent serializes via steer (same turn) or queue (next turn), or stops the active run and starts this prompt (interrupt).
1796
2045
  if (this.currentRun) {
1797
2046
  if (options?.enqueue === "interrupt") {
1798
- return new Promise<AgentResponse>((resolve, reject) => this.queueUrgent({ prompt, options, isStream: false, resolve, reject }));
2047
+ return new Promise<AgentResponse>((resolve, reject) =>
2048
+ this.queueUrgent({ prompt, options, isStream: false, resolve, reject })
2049
+ );
1799
2050
  }
1800
2051
  const enforced = this.midSessionConfig.mode;
1801
2052
  const requested = options?.enqueue;
1802
2053
  if (requested && (enforced === "steer" || enforced === "queue") && requested !== enforced) {
1803
- throw new Error(
2054
+ throw new ConfigError(
1804
2055
  `[Agent Accelerator] enqueue "${requested}" is disabled by midSession.mode "${enforced}". Use ${enforced}() or set mode to "auto"/"${requested}".`
1805
2056
  );
1806
2057
  }
@@ -1810,14 +2061,14 @@ export class Agent {
1810
2061
  return this.steerInto(this.currentRun, prompt, options);
1811
2062
  } else {
1812
2063
  if (this.midSessionConfig.mode === "steer") {
1813
- throw new Error(
2064
+ throw new ConfigError(
1814
2065
  '[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
1815
2066
  );
1816
2067
  }
1817
2068
  this.assertPromptValid(prompt);
1818
2069
  const max = this.midSessionConfig.maxQueued ?? 20;
1819
2070
  if (this.pendingQueue.length >= max) {
1820
- throw new Error(
2071
+ throw new ValidationError(
1821
2072
  `[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
1822
2073
  );
1823
2074
  }
@@ -1846,13 +2097,9 @@ export class Agent {
1846
2097
  prompt: string | ContentPart[],
1847
2098
  optionsOrStream?: boolean | AgentRunOptions
1848
2099
  ): Promise<AgentResponse> & AsyncIterable<string | StreamEvent> {
1849
- const isStream =
1850
- typeof optionsOrStream === "boolean"
1851
- ? optionsOrStream
1852
- : optionsOrStream?.stream === true;
2100
+ const isStream = typeof optionsOrStream === "boolean" ? optionsOrStream : optionsOrStream?.stream === true;
1853
2101
 
1854
- const runOpts: AgentRunOptions =
1855
- typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
2102
+ const runOpts: AgentRunOptions = typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
1856
2103
 
1857
2104
  if (isStream) {
1858
2105
  const stream = this.stream(prompt, runOpts);
@@ -1860,7 +2107,8 @@ export class Agent {
1860
2107
  if (runOpts.onDelta || runOpts.onThinkingDelta || runOpts.onEvent) {
1861
2108
  if (runOpts.onEvent) stream.on("*", runOpts.onEvent as any);
1862
2109
  if (runOpts.onDelta) stream.on("text_delta", (e: any) => runOpts.onDelta!(e.delta!, e));
1863
- if (runOpts.onThinkingDelta) stream.on("thinking_delta", (e: any) => runOpts.onThinkingDelta!(e.thinkingDelta!, e));
2110
+ if (runOpts.onThinkingDelta)
2111
+ stream.on("thinking_delta", (e: any) => runOpts.onThinkingDelta!(e.thinkingDelta!, e));
1864
2112
  }
1865
2113
  const stringStream: any = {
1866
2114
  [Symbol.asyncIterator]: async function* () {
@@ -1901,10 +2149,7 @@ export class Agent {
1901
2149
  * }
1902
2150
  * ```
1903
2151
  */
1904
- stream(
1905
- prompt: string | ContentPart[],
1906
- options?: AgentRunOptions
1907
- ): AssistantMessageEventStream {
2152
+ stream(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
1908
2153
  // Same sync validation timing as run().
1909
2154
  this.assertPromptValid(prompt);
1910
2155
  // Mid-session: busy agent serializes via steer (same turn) or queue (next turn).
@@ -1912,8 +2157,12 @@ export class Agent {
1912
2157
  const enforcedStream = this.midSessionConfig.mode;
1913
2158
  const urgent = options?.enqueue === "interrupt"; // stops the active run and goes first, in every mode and whatever the queue holds
1914
2159
  const requestedStream = options?.enqueue === "interrupt" ? undefined : options?.enqueue;
1915
- if (requestedStream && (enforcedStream === "steer" || enforcedStream === "queue") && requestedStream !== enforcedStream) {
1916
- throw new Error(
2160
+ if (
2161
+ requestedStream &&
2162
+ (enforcedStream === "steer" || enforcedStream === "queue") &&
2163
+ requestedStream !== enforcedStream
2164
+ ) {
2165
+ throw new ConfigError(
1917
2166
  `[Agent Accelerator] enqueue "${requestedStream}" is disabled by midSession.mode "${enforcedStream}". Use ${enforcedStream}() or set mode to "auto"/"${requestedStream}".`
1918
2167
  );
1919
2168
  }
@@ -1937,28 +2186,40 @@ export class Agent {
1937
2186
  activePromise.then(
1938
2187
  (res) => {
1939
2188
  try {
1940
- stub.push({ type: "done", delta: "", usage: res.usage, finishReason: res.finishReason, responseId: res.responseId } as any);
2189
+ stub.push({
2190
+ type: "done",
2191
+ delta: "",
2192
+ usage: res.usage,
2193
+ finishReason: res.finishReason,
2194
+ responseId: res.responseId,
2195
+ } as any);
2196
+ } catch {}
2197
+ try {
2198
+ stub.end(res);
1941
2199
  } catch {}
1942
- try { stub.end(res); } catch {}
1943
2200
  },
1944
2201
  (err) => {
1945
- try { stub.fail(err instanceof Error ? err : new Error(String(err))); } catch {}
2202
+ try {
2203
+ stub.fail(err instanceof Error ? err : new Error(String(err)));
2204
+ } catch {}
1946
2205
  }
1947
2206
  );
1948
2207
  } else {
1949
- try { stub.fail(new Error("[Agent Accelerator] No active run to steer.")); } catch {}
2208
+ try {
2209
+ stub.fail(new ValidationError("[Agent Accelerator] No active run to steer."));
2210
+ } catch {}
1950
2211
  }
1951
2212
  return stub;
1952
2213
  }
1953
2214
  if (!urgent && this.midSessionConfig.mode === "steer") {
1954
- throw new Error(
2215
+ throw new ConfigError(
1955
2216
  '[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
1956
2217
  );
1957
2218
  }
1958
2219
  this.assertPromptValid(prompt);
1959
2220
  const max = this.midSessionConfig.maxQueued ?? 20;
1960
2221
  if (!urgent && this.pendingQueue.length >= max) {
1961
- throw new Error(
2222
+ throw new ValidationError(
1962
2223
  `[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
1963
2224
  );
1964
2225
  }
@@ -2005,7 +2266,9 @@ export class Agent {
2005
2266
  if (idx >= 0) {
2006
2267
  this.pendingQueue.splice(idx, 1);
2007
2268
  queuedEntry.reject(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
2008
- try { deferred.fail(Object.assign(new Error("Stream aborted"), { name: "AbortError" })); } catch {}
2269
+ try {
2270
+ deferred.fail(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
2271
+ } catch {}
2009
2272
  }
2010
2273
  } catch {}
2011
2274
  });
@@ -2023,7 +2286,13 @@ export class Agent {
2023
2286
  // Otherwise (edge), end it here.
2024
2287
  try {
2025
2288
  if ((deferred as any).__piped !== true) {
2026
- deferred.push({ type: "done", delta: "", usage: res.usage, finishReason: res.finishReason, responseId: res.responseId } as any);
2289
+ deferred.push({
2290
+ type: "done",
2291
+ delta: "",
2292
+ usage: res.usage,
2293
+ finishReason: res.finishReason,
2294
+ responseId: res.responseId,
2295
+ } as any);
2027
2296
  deferred.end(res);
2028
2297
  }
2029
2298
  } catch {}
@@ -2045,10 +2314,7 @@ export class Agent {
2045
2314
  }
2046
2315
 
2047
2316
  // 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.
2048
- private wireStreamCallbacks(
2049
- s: AssistantMessageEventStream,
2050
- options?: AgentRunOptions
2051
- ): void {
2317
+ private wireStreamCallbacks(s: AssistantMessageEventStream, options?: AgentRunOptions): void {
2052
2318
  // Wire one-liner callbacks so `stream:true` + onDelta is enough — no manual for-await needed
2053
2319
  if (options?.wrapThinking) {
2054
2320
  // Auto-wrap reasoning as <think>…</think> per-turn — clean boundaries across multi-turn agent runs.
@@ -2125,7 +2391,9 @@ export class Agent {
2125
2391
  const raw: string = e.delta ?? "";
2126
2392
  if (!justClosedThink) return raw;
2127
2393
  const stripped = raw.replace(/^\n+/, "");
2128
- try { e.delta = stripped; } catch {}
2394
+ try {
2395
+ e.delta = stripped;
2396
+ } catch {}
2129
2397
  if (!stripped) return "";
2130
2398
  justClosedThink = false;
2131
2399
  return stripped;
@@ -2222,11 +2490,13 @@ export class Agent {
2222
2490
  closeSynthetic();
2223
2491
  if (justClosedThink) {
2224
2492
  const stripped: string = (e.delta ?? "").replace(/^\n+/, "");
2225
- try { e.delta = stripped; } catch {}
2493
+ try {
2494
+ e.delta = stripped;
2495
+ } catch {}
2226
2496
  if (!stripped) return;
2227
2497
  justClosedThink = false;
2228
2498
  }
2229
- needThinkNewline = !((e.delta ?? "").endsWith("\n"));
2499
+ needThinkNewline = !(e.delta ?? "").endsWith("\n");
2230
2500
  });
2231
2501
  s.on("tool_call_start" as any, () => {
2232
2502
  closeSynthetic();
@@ -2304,7 +2574,12 @@ export function normalizeThinking(config: AgentConfig, fallbackLevel?: string):
2304
2574
  try {
2305
2575
  const rawModel: any = (config.model as any)?.model ? (config.model as any).model : config.model;
2306
2576
  const modelStr = typeof rawModel === "string" ? rawModel : rawModel?.id || "";
2307
- const provStr = typeof rawModel === "object" && rawModel?.provider ? rawModel.provider : modelStr.includes("/") ? modelStr.split("/")[0] : "";
2577
+ const provStr =
2578
+ typeof rawModel === "object" && rawModel?.provider
2579
+ ? rawModel.provider
2580
+ : modelStr.includes("/")
2581
+ ? modelStr.split("/")[0]
2582
+ : "";
2308
2583
  const actualModelId = modelStr.includes("/") ? modelStr.split("/").slice(1).join("/") : modelStr;
2309
2584
  if (actualModelId) {
2310
2585
  validateModelThinking(provStr || "openrouter", actualModelId, level);