agent-accelerator 0.5.7 → 0.5.9

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 (44) hide show
  1. package/README.md +27 -8
  2. package/package.json +1 -1
  3. package/src/agent/agent.ts +84 -45
  4. package/src/agent/architecture.md +1 -0
  5. package/src/agent/context.ts +3 -3
  6. package/src/agent/delegation.ts +12 -12
  7. package/src/agent/loop.ts +50 -53
  8. package/src/agent/subagent.ts +4 -4
  9. package/src/agent/worker.ts +2 -1
  10. package/src/architecture.md +1 -1
  11. package/src/index.ts +7 -7
  12. package/src/models/architecture.md +1 -1
  13. package/src/models/catalog-cache.ts +7 -7
  14. package/src/models/catalog.ts +22 -34
  15. package/src/providers/google.ts +7 -7
  16. package/src/providers/openai-compat.ts +10 -16
  17. package/src/providers/openai.ts +8 -7
  18. package/src/providers/openrouter.ts +4 -4
  19. package/src/providers/registry.ts +6 -5
  20. package/src/providers/shared.ts +6 -10
  21. package/src/scheduler/architecture.md +6 -3
  22. package/src/scheduler/scheduler.ts +111 -57
  23. package/src/scheduler/store.ts +273 -59
  24. package/src/session/architecture.md +1 -1
  25. package/src/session/store.ts +22 -22
  26. package/src/streaming/event-stream.ts +11 -11
  27. package/src/telemetry-file.ts +74 -24
  28. package/src/telemetry.ts +2 -2
  29. package/src/tools/executor.ts +1 -1
  30. package/src/tools/tool.ts +8 -8
  31. package/src/types/agent.ts +22 -20
  32. package/src/types/response.ts +8 -8
  33. package/src/types/scheduler.ts +2 -2
  34. package/src/types/tool.ts +5 -5
  35. package/src/update-models.ts +2 -2
  36. package/src/utils/architecture.md +3 -3
  37. package/src/utils/cache.ts +4 -4
  38. package/src/utils/durable.ts +78 -3
  39. package/src/utils/env.ts +1 -1
  40. package/src/utils/errors.ts +45 -31
  41. package/src/utils/headers.ts +4 -4
  42. package/src/utils/home.ts +1 -1
  43. package/src/utils/retry.ts +2 -28
  44. package/src/utils/session.ts +5 -5
package/src/agent/loop.ts CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  import { getModelFromCatalog, ensureModelCatalogFresh, validateModelThinking } from "../models/catalog.ts";
24
24
  import { enforceFileAccess } from "../utils/safe-io.ts";
25
25
  import { isSubagentResult, normalizeSubagentMetadata } from "../tools/subagent-result.ts";
26
- import { BudgetExceededError } from "../types/errors.ts";
26
+ import { BudgetExceededError, TimeoutError } from "../types/errors.ts";
27
27
  import { applyCaptureRaw, type CaptureRawMode } from "../utils/raw-capture.ts";
28
28
  import { previewPrompt } from "../utils/session.ts";
29
29
  import { noteProviderTurn, normalizeStructuredOutput, parseStructuredOutput } from "../providers.ts";
@@ -223,7 +223,7 @@ function normalizeMaxTurns(value?: number): number {
223
223
  function warnMaxTurnsHit(agentName: string | undefined, maxTurns: number, pendingTools: string[]): void {
224
224
  const who = agentName ? `"${agentName}"` : "Agent";
225
225
  const tools = pendingTools.length > 0 ? ` Pending tool calls dropped: ${pendingTools.join(", ")}.` : "";
226
- // Q-41: no console.* in library code — route via the telemetry bus
226
+ // No console.* in library code — route via the telemetry bus
227
227
  // (default no-op; CLIs attach a pretty logger via telemetryBus.onEvent).
228
228
  try {
229
229
  telemetryBus.emitWarning(
@@ -237,11 +237,11 @@ function warnMaxTurnsHit(agentName: string | undefined, maxTurns: number, pendin
237
237
  }
238
238
 
239
239
  // ---------------------------------------------------------------------------
240
- // Q-19: tool-call/result pairing repair.
240
+ // Tool-call/result pairing repair.
241
241
  // ---------------------------------------------------------------------------
242
242
 
243
243
  /**
244
- * Repairs tool call/result pairing before each provider request (Q-19).
244
+ * Repairs tool call/result pairing before each provider request.
245
245
  *
246
246
  * Providers reject a `tool_result` without its `tool_call`, a `tool_call`
247
247
  * without its result, AND (Chat-Completions style APIs) results that are not
@@ -306,7 +306,7 @@ export function ensureToolPairing(context: AgentContext): void {
306
306
  }
307
307
 
308
308
  // ---------------------------------------------------------------------------
309
- // Q-21/Q-22: abort + limits helpers.
309
+ // Abort + limits helpers.
310
310
  // ---------------------------------------------------------------------------
311
311
 
312
312
  export interface LoopLimits {
@@ -319,7 +319,7 @@ export interface LoopLimits {
319
319
  streamIdleTimeoutMs: number;
320
320
  }
321
321
 
322
- /** Resolves `(config as any).limits` with Q-22 defaults (timeouts tolerate reasoning). */
322
+ /** Resolves `(config as any).limits` with defaults (timeouts tolerate reasoning). */
323
323
  function resolveLoopLimits(config: AgentLoopConfig, fallbackMaxTurns: number): LoopLimits & { maxTurns: number; deadlineMs: number; maxToolCallsPerRun: number; maxTotalTokens: number } {
324
324
  const raw = ((config as unknown as { limits?: Record<string, unknown> }).limits ?? {}) as Record<string, unknown>;
325
325
  const num = (v: unknown): number | undefined =>
@@ -389,7 +389,7 @@ function checkTokenLimit(usage: TokenUsage, maxTotalTokens: number): void {
389
389
  // Throws when the run has been going longer than limits.deadlineMs; in: both loops each turn, out: nothing or a thrown error.
390
390
  function checkDeadline(startTime: number, deadlineMs: number): void {
391
391
  if (Number.isFinite(deadlineMs) && deadlineMs !== Infinity && Date.now() - startTime > deadlineMs) {
392
- throw new Error(`[Agent Accelerator] Run deadline exceeded after ${deadlineMs}ms.`);
392
+ throw new BudgetExceededError(`[Agent Accelerator] Run deadline exceeded after ${deadlineMs}ms.`);
393
393
  }
394
394
  }
395
395
 
@@ -400,9 +400,7 @@ async function generateWithTimeout<T>(task: () => Promise<T>, timeoutMs: number)
400
400
  try {
401
401
  const timeoutP = new Promise<never>((_, reject) => {
402
402
  timer = setTimeout(() => {
403
- const e = new Error(`[Agent Accelerator] Request timed out after ${timeoutMs}ms.`);
404
- e.name = "TimeoutError";
405
- reject(e);
403
+ reject(new TimeoutError(`[Agent Accelerator] Request timed out after ${timeoutMs}ms.`));
406
404
  }, timeoutMs);
407
405
  try { (timer as unknown as { unref?: () => void }).unref?.(); } catch {}
408
406
  });
@@ -436,7 +434,7 @@ export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec, serv
436
434
  }
437
435
 
438
436
  // ---------------------------------------------------------------------------
439
- // Q-35: canonical usage accumulation (pure w.r.t. source).
437
+ // Canonical usage accumulation (pure w.r.t. source).
440
438
  // Invariants (see assertUsageInvariants in session/store.ts):
441
439
  // - token counts are finite integers >= 0;
442
440
  // - cachedTokens/cacheReadTokens are a SUBSET of inputTokens (clamped below
@@ -444,7 +442,7 @@ export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec, serv
444
442
  // - thinkingTokens is a SUBSET of outputTokens when both are reported
445
443
  // (reasoning is generated output; enforced in the test helper, documented
446
444
  // here because providers vary on which side they omit).
447
- // This function never mutates `source` (Q-35): the priced cost is cloned
445
+ // This function never mutates `source`: the priced cost is cloned
448
446
  // into a local before folding into the target.
449
447
  // ---------------------------------------------------------------------------
450
448
  export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: ModelSpec, serviceTier?: string): void {
@@ -465,7 +463,7 @@ export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: M
465
463
  target.cachedTokens = Math.min(preCached, target.inputTokens);
466
464
  target.cacheReadTokens = Math.min(preRead, target.inputTokens);
467
465
  if ((target.cachedTokens ?? 0) < preCached || (target.cacheReadTokens ?? 0) < preRead) {
468
- // Q-35: no console — report via the anomaly hook + telemetry bus.
466
+ // No console — report via the anomaly hook + telemetry bus.
469
467
  reportUsageAnomaly({
470
468
  type: "cache_clamp",
471
469
  inputTokens: target.inputTokens,
@@ -475,7 +473,7 @@ export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: M
475
473
  }
476
474
 
477
475
  const priced = computeCostFromPricing(source, spec, serviceTier);
478
- // Q-35: pure — read `source` only; never assign back to `source.cost`.
476
+ // Pure — read `source` only; never assign back to `source.cost`.
479
477
  // Clone the priced cost so later target folds cannot alias source state.
480
478
  const sourceCost = priced ? { ...priced } : undefined;
481
479
 
@@ -673,11 +671,11 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
673
671
  const maxTurns = limits.maxTurns;
674
672
  // Structured outputs: normalize once (throws ValidationError for bad shapes).
675
673
  const loopOutput = resolveLoopOutput(config);
676
- // Q-19: pre-run checkpoint for onFailure rollback.
674
+ // Pre-run checkpoint for onFailure rollback.
677
675
  const preRunCp = context.checkpoint();
678
676
  const sessionIdForRollback = loopSessionId(config);
679
677
 
680
- // Q-37: partial-run state hoisted outside try so the catch block can attach
678
+ // Partial-run state hoisted outside try so the catch block can attach
681
679
  // {usage,cost,turns,toolCalls,toolResults,text} to ANY thrown error.
682
680
  const startTime = Date.now();
683
681
  const accumulatedUsage: TokenUsage = {
@@ -717,11 +715,11 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
717
715
  }
718
716
 
719
717
  while (!stoppedByTool && turns < maxTurns) {
720
- // Q-21: fail fast on abort (throws AbortError with partial).
718
+ // Fail fast on abort (throws AbortError with partial).
721
719
  throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: finalResult?.text });
722
720
  checkDeadline(startTime, limits.deadlineMs);
723
721
  config.enforceBudget?.();
724
- // Q-19: repair orphan tool pairs before every request.
722
+ // Repair orphan tool pairs before every request.
725
723
  ensureToolPairing(context);
726
724
  if (config.fileAccess?.roots) await enforceFileAccess(context.messages, config.fileAccess.roots);
727
725
  turns++;
@@ -737,13 +735,13 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
737
735
  toolChoice: toolChoiceForTurn(options?.toolChoice, turns),
738
736
  ...(loopOutput.spec ? { output: loopOutput.spec } : {}),
739
737
  };
740
- // Q-22: per-request timeout pass-through (providers honor timeoutMs).
738
+ // Per-request timeout pass-through (providers honor timeoutMs).
741
739
  try {
742
740
  if (Number.isFinite(limits.requestTimeoutMs) && limits.requestTimeoutMs !== Infinity) {
743
741
  (providerOptions as unknown as { timeoutMs?: number }).timeoutMs = limits.requestTimeoutMs;
744
742
  }
745
743
  } catch {}
746
- // Q-25-partial: google:{store:false} opt-out pass-through (provider
744
+ // google:{store:false} opt-out pass-through (provider
747
745
  // already respects body.store===false for chaining).
748
746
  try {
749
747
  const gStore =
@@ -784,8 +782,8 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
784
782
  provider.id
785
783
  );
786
784
 
787
- // Thinking is exposed via events/traces and onTurn. Promoting it to answer text is gated (Q-15,
788
- // default OFF): without the opt-in the run returns the provider's empty text and finishReason.
785
+ // Thinking is exposed via events/traces and onTurn. Promoting it to answer text is gated (default
786
+ // OFF): without the opt-in the run returns the provider's empty text and finishReason.
789
787
  if ((!genResult.text || genResult.text.trim() === "") && (!genResult.toolCalls || genResult.toolCalls.length === 0) && genResult.thinking) {
790
788
  if (shouldPromoteThinkingToAnswer(config, runOptions)) {
791
789
  if (genResult.thinking.includes("</think>")) {
@@ -853,13 +851,13 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
853
851
  }
854
852
 
855
853
  // Execute tool calls — always parallel (model-driven, bloatfree DX7)
856
- // Q-21: check abort before tools (throws AbortError with partial).
854
+ // Check abort before tools (throws AbortError with partial).
857
855
  throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: genResult.text });
858
856
  emitProgress(config); // the calls are on record BEFORE they run, so a crash can finish them instead of forgetting them
859
857
  allToolCalls.push(...genResult.toolCalls);
860
- // Q-22: per-run tool-call budget.
858
+ // Per-run tool-call budget.
861
859
  if (allToolCalls.length > limits.maxToolCallsPerRun) {
862
- throw new Error(
860
+ throw new BudgetExceededError(
863
861
  `[Agent Accelerator] maxToolCallsPerRun exceeded (${allToolCalls.length} > ${limits.maxToolCallsPerRun}).`
864
862
  );
865
863
  }
@@ -901,7 +899,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
901
899
  allToolResults.push(...sanitizedResults);
902
900
  context.addToolResults(sanitizedResults);
903
901
  checkTokenLimit(accumulatedUsage, limits.maxTotalTokens);
904
- // Q-19: results are stored BEFORE the cancellation check so an abort
902
+ // Results are stored BEFORE the cancellation check so an abort
905
903
  // never drops executed tool work from history.
906
904
  throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: genResult.text });
907
905
  emitTurn(runOptions, {
@@ -926,7 +924,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
926
924
  }
927
925
  emitProgress(config);
928
926
 
929
- // Q-21: abort always throws AbortError with partial attached (never
927
+ // Abort always throws AbortError with partial attached (never
930
928
  // silently breaks).
931
929
  throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: finalResult?.text });
932
930
  }
@@ -963,7 +961,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
963
961
  responseId: finalResult?.responseId,
964
962
  model: modelId,
965
963
  provider: provider.id,
966
- // Q-04: canonical finish reason + raw vendor passthrough.
964
+ // Canonical finish reason + raw vendor passthrough.
967
965
  ...(stoppedByTool
968
966
  ? { finishReason: "stopped" as const, ...(finalResult?.finishReason ? { rawFinishReason: finalResult.finishReason } : {}) }
969
967
  : canonicalFinishReason(finalResult?.finishReason, truncatedByMaxTurns)),
@@ -972,7 +970,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
972
970
  turns,
973
971
  });
974
972
  } catch (err) {
975
- // Q-19: run-level onFailure rollback (default restores pre-run
973
+ // Run-level onFailure rollback (default restores pre-run
976
974
  // checkpoint; opt out with (config as any).onFailure === "keep").
977
975
  // Aborts are excluded so partial history survives interruption.
978
976
  const isAbort =
@@ -983,14 +981,14 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
983
981
  // why: rollback is best-effort — the original error takes precedence.
984
982
  }
985
983
  }
986
- // Q-21: ensure aborts surface as AbortError with partial attached.
984
+ // Ensure aborts surface as AbortError with partial attached.
987
985
  if (isAbort && (err as { name?: string } | null)?.name !== "AbortError") {
988
986
  throw makeAbortError(
989
987
  { usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, turns: 0 },
990
988
  err
991
989
  );
992
990
  }
993
- // Q-37: every failure carries its partial progress for observability.
991
+ // Every failure carries its partial progress for observability.
994
992
  try {
995
993
  (err as unknown as { partial?: unknown }).partial ??= {
996
994
  usage: accumulatedUsage,
@@ -998,6 +996,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
998
996
  turns,
999
997
  toolCalls: allToolCalls,
1000
998
  toolResults: allToolResults,
999
+ subagents: allSubagents,
1001
1000
  text: finalResult?.text,
1002
1001
  };
1003
1002
  } catch {
@@ -1042,7 +1041,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1042
1041
  }
1043
1042
  };
1044
1043
 
1045
- // Q-19/Q-22: per-run checkpoint + limits for the streaming loop.
1044
+ // Per-run checkpoint + limits for the streaming loop.
1046
1045
  const streamPreCp = config.context.checkpoint();
1047
1046
  const streamSessionId = loopSessionId(config);
1048
1047
  const streamFallbackMax = normalizeMaxTurns((config as { maxTurns?: number }).maxTurns);
@@ -1050,7 +1049,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1050
1049
  const streamStartTime = startTime;
1051
1050
 
1052
1051
  (async () => {
1053
- // Hoisted for catch-block partial reporting (Q-21/Q-37).
1052
+ // Hoisted for catch-block partial reporting.
1054
1053
  let accumulatedUsage: TokenUsage = {
1055
1054
  inputTokens: 0,
1056
1055
  outputTokens: 0,
@@ -1062,7 +1061,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1062
1061
  };
1063
1062
  let lastResponse: AgentResponse | null = null;
1064
1063
  let turns = 0;
1065
- // Q-37: tool progress hoisted so failures carry toolCalls/toolResults.
1064
+ // Tool progress hoisted so failures carry toolCalls/toolResults.
1066
1065
  const allToolCalls: ToolCallRecord[] = [];
1067
1066
  const allToolResults: ToolResultRecord[] = [];
1068
1067
  const allSubagents: SubAgentExecutionMetadata[] = [];
@@ -1118,7 +1117,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1118
1117
  thinkingTokens: 0,
1119
1118
  };
1120
1119
 
1121
- // Q-37: allToolCalls/allToolResults/allSubagents are hoisted above (fresh
1120
+ // allToolCalls/allToolResults/allSubagents are hoisted above (fresh
1122
1121
  // per stream run); just reset turn counters here.
1123
1122
  lastResponse = null;
1124
1123
  turns = 0;
@@ -1176,7 +1175,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1176
1175
  );
1177
1176
  currentInner = innerStream as AssistantMessageEventStream;
1178
1177
 
1179
- // Q-22: first-byte + idle timeouts around SSE (tolerate reasoning).
1178
+ // First-byte + idle timeouts around SSE (tolerate reasoning).
1180
1179
  // Implemented as races on the inner iterator so a hung provider
1181
1180
  // surfaces as TimeoutError instead of hanging the run forever.
1182
1181
  {
@@ -1191,11 +1190,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1191
1190
  Number.isFinite(waitMs) && waitMs !== Infinity && waitMs > 0
1192
1191
  ? new Promise<never>((_, reject) => {
1193
1192
  timer = setTimeout(() => {
1194
- const e = new Error(
1195
- `[Agent Accelerator] Stream ${first ? "first-byte" : "idle"} timed out after ${waitMs}ms.`
1196
- );
1197
- e.name = "TimeoutError";
1198
- reject(e);
1193
+ reject(new TimeoutError(`[Agent Accelerator] Stream ${first ? "first-byte" : "idle"} timed out after ${waitMs}ms.`));
1199
1194
  }, waitMs);
1200
1195
  try { (timer as unknown as { unref?: () => void }).unref?.(); } catch {}
1201
1196
  })
@@ -1238,7 +1233,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1238
1233
  );
1239
1234
 
1240
1235
  // Thinking stays on events/traces + onTurn (default exposed). Promoting
1241
- // it to answer text is gated (Q-15, default OFF): without the opt-in
1236
+ // it to answer text is gated (default OFF): without the opt-in
1242
1237
  // the turn keeps the provider's empty text and finishReason.
1243
1238
  if ((!turnResponse.text || turnResponse.text.trim() === "") && (!turnResponse.toolCalls || turnResponse.toolCalls.length === 0) && turnResponse.thinking) {
1244
1239
  if (shouldPromoteThinkingToAnswer(config, runOptions)) {
@@ -1311,11 +1306,11 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1311
1306
  emitProgress(config); // the calls are on record BEFORE they run, so a crash can finish them instead of forgetting them
1312
1307
  allToolCalls.push(...turnResponse.toolCalls);
1313
1308
  if (allToolCalls.length > streamLimits.maxToolCallsPerRun) {
1314
- throw new Error(
1309
+ throw new BudgetExceededError(
1315
1310
  `[Agent Accelerator] maxToolCallsPerRun exceeded (${allToolCalls.length} > ${streamLimits.maxToolCallsPerRun}).`
1316
1311
  );
1317
1312
  }
1318
- // Q-40: typed tool lifecycle for streaming consumers.
1313
+ // Typed tool lifecycle for streaming consumers.
1319
1314
  try {
1320
1315
  outerStream.push({ type: "tool_start", toolCalls: turnResponse.toolCalls });
1321
1316
  } catch {
@@ -1379,14 +1374,14 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1379
1374
  toolResult: res,
1380
1375
  });
1381
1376
  }
1382
- // Q-40: close the tool lifecycle opened by `tool_start` above.
1377
+ // Close the tool lifecycle opened by `tool_start` above.
1383
1378
  try {
1384
1379
  outerStream.push({ type: "tool_end", toolResults: sanitizedResults });
1385
1380
  } catch {
1386
1381
  // why: stream push never throws, but guard anyway to protect the loop.
1387
1382
  }
1388
1383
 
1389
- // Q-19: store tool results BEFORE the cancellation check.
1384
+ // Store tool results BEFORE the cancellation check.
1390
1385
  allToolResults.push(...sanitizedResults);
1391
1386
  context.addToolResults(sanitizedResults);
1392
1387
  throwIfCancelled({ usage: accumulatedUsage, turns, text: turnResponse.text });
@@ -1444,6 +1439,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1444
1439
  turns,
1445
1440
  toolCalls: allToolCalls,
1446
1441
  toolResults: allToolResults,
1442
+ subagents: allSubagents,
1447
1443
  text: lastResponse?.text,
1448
1444
  }; } catch {}
1449
1445
  outerStream.fail(raw);
@@ -1463,7 +1459,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1463
1459
  responseId: lastResponse?.responseId,
1464
1460
  model: modelId,
1465
1461
  provider: provider.id,
1466
- // Q-04: canonical finish reason + raw vendor passthrough.
1462
+ // Canonical finish reason + raw vendor passthrough.
1467
1463
  ...truncatedFinish,
1468
1464
  durationMs: Date.now() - startTime,
1469
1465
  raw: applyCaptureRaw(lastResponse?.raw, config.captureRaw) as ProviderRawData,
@@ -1486,7 +1482,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1486
1482
  linked.signal.aborted ||
1487
1483
  outerStream.isCancelled() ||
1488
1484
  (raw as any)?.name === "AbortError";
1489
- // Q-19: streaming onFailure rollback (same policy as runAgentLoop;
1485
+ // Streaming onFailure rollback (same policy as runAgentLoop;
1490
1486
  // aborts excluded so partial history survives).
1491
1487
  try {
1492
1488
  if (shouldRollbackOnFailure(config.onFailure, isAbort, toolsExecuted)) {
@@ -1497,16 +1493,17 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1497
1493
  } catch {
1498
1494
  // why: rollback policy itself must never mask the original error.
1499
1495
  }
1500
- // Q-37: every failure carries its partial progress for observability.
1496
+ // Every failure carries its partial progress for observability.
1501
1497
  const partial = {
1502
1498
  usage: accumulatedUsage,
1503
1499
  cost: accumulatedUsage.cost,
1504
1500
  turns,
1505
1501
  toolCalls: allToolCalls,
1506
1502
  toolResults: allToolResults,
1503
+ subagents: allSubagents,
1507
1504
  text: lastResponse?.text,
1508
1505
  };
1509
- // Q-21: aborts always surface as AbortError with partial attached.
1506
+ // Aborts always surface as AbortError with partial attached.
1510
1507
  if (isAbort) {
1511
1508
  const abortErr =
1512
1509
  raw.name === "AbortError" ? raw : makeAbortError(partial, raw);
@@ -1537,12 +1534,12 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1537
1534
  }
1538
1535
 
1539
1536
  // ---------------------------------------------------------------------------
1540
- // Q-15: thinking-promotion policy + Q-04 canonical finish helper.
1537
+ // Thinking-promotion policy + canonical finish helper.
1541
1538
  // (Appended here to avoid conflicts with other loop sections.)
1542
1539
  // ---------------------------------------------------------------------------
1543
1540
 
1544
1541
  /**
1545
- * Whether an empty-text turn may promote `thinking` to answer `text` (Q-15).
1542
+ * Whether an empty-text turn may promote `thinking` to answer `text`.
1546
1543
  * Default false: the run returns the provider's empty text with its
1547
1544
  * finishReason instead of fabricating an answer from reasoning. Opt in via
1548
1545
  * `(runOptions as any).thinkingAsAnswerFallback === true` or
@@ -1567,7 +1564,7 @@ export function shouldPromoteThinkingToAnswer(config?: unknown, runOptions?: unk
1567
1564
  }
1568
1565
 
1569
1566
  /**
1570
- * Canonicalizes a provider finish reason for AgentResponse (Q-04).
1567
+ * Canonicalizes a provider finish reason for AgentResponse.
1571
1568
  * When `truncated` (maxTurns exhausted with pending tools), forces
1572
1569
  * `"max_turns"` and preserves the provider's reason in `rawFinishReason`.
1573
1570
  * Otherwise delegates to `normalizeFinishReason` (unknown vendor strings map
@@ -9,7 +9,7 @@ import { getModel, getSubModel } from "../utils/env.ts";
9
9
  export interface SubAgentConfig extends AgentConfig {}
10
10
 
11
11
  /**
12
- * Canonical env-based construction (Q-56): builds an `Agent` reading
12
+ * Canonical env-based construction: builds an `Agent` reading
13
13
  * `MODEL`/`SUB_AGENT_MODEL` only for the fields the caller omits, via
14
14
  * `getModel()`/`getSubModel()`. Explicit config always wins; constructing an
15
15
  * `Agent` with an explicit `model` reads no env for routing. The static
@@ -36,12 +36,12 @@ export function agentFromEnv(
36
36
  * It inherits 100% of the capabilities of Agent, defaults to SUB_AGENT_MODEL,
37
37
  * and provides .asTool() and .toTool() for seamless registration into parent agents.
38
38
  *
39
- * Model fallback chain (Q-56, canonical): explicit `config.model` ??
39
+ * Model fallback chain (canonical): explicit `config.model` ??
40
40
  * `SUB_AGENT_MODEL` (`getSubModel()`) ?? `MODEL` (`getModel()`). Missing
41
41
  * everything throws. Constructing any `Agent`/`SubAgent` with an explicit
42
42
  * `model` reads no env for routing.
43
43
  *
44
- * Q-47 decomposition boundary: this class stays a thin wrapper. The run/stream
44
+ * Decomposition boundary: this class stays a thin wrapper. The run/stream
45
45
  * loops live in `agent.ts`/`loop.ts`, spawning in `delegation.ts`
46
46
  * (`agentToTool`), and model parsing in `utils/session.ts` (`parseModelRef`).
47
47
  * No loop, transport, or formatting logic is duplicated here.
@@ -104,7 +104,7 @@ export class SubAgent extends Agent {
104
104
  }
105
105
  }
106
106
 
107
- // Q-56: `Agent.fromEnv()` static without touching `agent.ts` (owned by
107
+ // `Agent.fromEnv()` static without touching `agent.ts` (owned by
108
108
  // another lane). Wired at module load; typed via the declaration below so
109
109
  // `Agent.fromEnv({...})` typechecks wherever this module is imported.
110
110
  declare module "./agent.ts" {
@@ -5,6 +5,7 @@ import type { Agent } from "./agent.ts";
5
5
  import { normalizeToolName } from "../tools/executor.ts";
6
6
  import { getModel, getSubModel } from "../utils/env.ts";
7
7
  import { resolveModel } from "../providers/registry.ts";
8
+ import { ConfigError } from "../types/errors.ts";
8
9
 
9
10
  /** Minimal live-trace shape shared by fixed + dynamic workers (structural subset of `SubAgentTrace`). */
10
11
  export interface LiveWorkerTrace {
@@ -134,7 +135,7 @@ export function resolveDynamicModelInput(
134
135
  export function resolveSubAgentModel(config: { name?: string; model?: AgentConfig["model"] }): NonNullable<AgentConfig["model"]> {
135
136
  const resolvedModel = config.model ?? getSubModel() ?? getModel();
136
137
  if (!resolvedModel) {
137
- throw new Error(
138
+ throw new ConfigError(
138
139
  `[Agent Accelerator] SubAgent '${config.name || "subagent"}' requires a model. ` +
139
140
  `Specify 'model' in SubAgent config, or set SUB_AGENT_MODEL or MODEL in environment variables.`
140
141
  );
@@ -6,7 +6,7 @@ The whole SDK. `index.ts` is the only public entry (`main`/`module`/`types`); ev
6
6
  | :-- | :-- |
7
7
  | `index.ts` | The public barrel. Adding or removing an export is a public API change. |
8
8
  | `telemetry.ts` | The in-process event bus (`telemetryBus`). Imports nothing from the SDK, so any module may use it. |
9
- | `telemetry-file.ts` | Opt-in NDJSON sink for the bus (`<home>/telemetry/<date>.ndjson`). |
9
+ | `telemetry-file.ts` | Opt-in NDJSON sink for the bus: a persisted session's events go to `<session folder>/telemetry.ndjson`, the rest to `<home>/telemetry/<date>.ndjson`; never outside a home. |
10
10
  | `update-models.ts` | CLI behind `bun run update-models` (refreshes the model catalog cache). |
11
11
  | `providers.ts` | Provider-agnostic mappers (thinking, cache, tool choice, structured output, generation params). No HTTP. |
12
12
  | `agent/` | `Agent`, the model-to-tools loop, workers, delegation. |
package/src/index.ts CHANGED
@@ -15,7 +15,7 @@ export { Agent, SubAgentModelError } from "./agent/agent.ts";
15
15
  /**
16
16
  * @deprecated Compat alias: prefer composing a plain `Agent` and exposing it
17
17
  * via `agentToTool()`/`buildAgentTools()` (or `subagents: [...]`). `SubAgent`
18
- * stays for compat and receives no new features (Q-56).
18
+ * stays for compat and receives no new features.
19
19
  */
20
20
  export { SubAgent, agentFromEnv } from "./agent/subagent.ts";
21
21
  export type { SubAgentConfig } from "./agent/subagent.ts";
@@ -33,7 +33,7 @@ export type { CreateToolOptions } from "./tools/tool.ts";
33
33
  export { zodToJsonSchema, cleanJsonSchema } from "./tools/schema.ts";
34
34
  /**
35
35
  * @deprecated Internal executor surface. Prefer `Agent` runs; for direct
36
- * execution import from the future `/internal` entry (Q-50).
36
+ * execution import from the future `/internal` entry.
37
37
  */
38
38
  export { executeToolCalls } from "./tools/executor.ts";
39
39
 
@@ -135,7 +135,7 @@ export {
135
135
  /**
136
136
  * @deprecated Internal provider mappers/routing helpers. Prefer the
137
137
  * provider-agnostic registry (`resolveModel`/`getProvider`); direct mapper
138
- * use moves to the future `/internal` entry (Q-50).
138
+ * use moves to the future `/internal` entry.
139
139
  */
140
140
  export {
141
141
  emitProviderWarning,
@@ -204,7 +204,7 @@ export type { SteerEntry } from "./agent/loop.ts";
204
204
  // Responses
205
205
  export { AgentResponse } from "./types/response.ts";
206
206
 
207
- // Core error taxonomy (Q-50 core surface)
207
+ // Core error taxonomy (core surface)
208
208
  export {
209
209
  AgentAccelError,
210
210
  ConfigError,
@@ -219,7 +219,7 @@ export {
219
219
  formatErrorPlain,
220
220
  } from "./types/errors.ts";
221
221
  export type { AgentAccelErrorCode, AgentAccelErrorContext } from "./types/errors.ts";
222
- // Normalized run limits (Q-22; sub-agents inherit unless overridden)
222
+ // Normalized run limits (sub-agents inherit unless overridden)
223
223
  export type { AgentRunLimits } from "./agent/agent.ts";
224
224
 
225
225
  // Telemetry bus: warnings, anomalies and per-turn thinking traces (attach with `telemetryBus.onEvent`)
@@ -256,14 +256,14 @@ export type { SessionLimits, SessionPayloadStatus, PersistedAgentSession, Sessio
256
256
  // Utilities
257
257
  /**
258
258
  * @deprecated Session-routing internals. Prefer agent-level `sessionId`;
259
- * direct use moves to the future `/internal` entry (Q-50).
259
+ * direct use moves to the future `/internal` entry.
260
260
  */
261
261
  export { createSessionId, hashSessionPart, createTrackingId, nowMs, newRunId, newTurnId, previewPrompt, parseModelRef } from "./utils/session.ts";
262
262
  export type { ParsedModelRef } from "./utils/session.ts";
263
263
  /**
264
264
  * @deprecated Raw env access. Prefer explicit `Agent` config (which wins over
265
265
  * env) and `Agent.fromEnv()`/`agentFromEnv()`; direct use moves to the future
266
- * `/internal` entry (Q-50).
266
+ * `/internal` entry.
267
267
  */
268
268
  export { getApiKey, getEnv } from "./utils/env.ts";
269
269
  export { buildSessionHeaders, setAttribution, getAttribution } from "./utils/headers.ts";
@@ -5,7 +5,7 @@ The model catalog: context limits, prices, modalities and reasoning options come
5
5
  | File | What it is |
6
6
  | :-- | :-- |
7
7
  | `catalog-cache.ts` | Downloads and caches `models.dev/api.json` in `<home>/catalog/` (or `AGENT_CACHE_DIR`). Stale-while-revalidate: an expired catalog is served at once and refreshed in the background (8 s timeout, 10 min backoff after a failure); only a cold start waits. `AGENT_CATALOG_REFRESH=off` stops automatic refreshes. |
8
- | `catalog.ts` | Lookups (`getModelFromCatalog`), `registerModel`, thinking validation (`validateModelThinking`), and the exported `GOOGLE_/OPENAI_/OPENROUTER_MODELS` arrays (rebuilt in place when the catalog changes). |
8
+ | `catalog.ts` | Lookups (`getModelFromCatalog`), `registerModel`, thinking validation (`validateModelThinking`, which throws `ThinkingLevelError`, a one-line `ConfigError`), and the exported `GOOGLE_/OPENAI_/OPENROUTER_MODELS` arrays (rebuilt in place when the catalog changes). |
9
9
 
10
10
  ## Invariants
11
11
  - Never hand-edit the cache file; register custom models with `registerModel()`. Verified modality fixes go in `MODALITY_OVERRIDES`.
@@ -9,13 +9,13 @@ import { writeFileDurable } from "../utils/durable.ts";
9
9
  /** Default TTL: 12 Hours (in milliseconds). */
10
10
  export const DEFAULT_CATALOG_TTL_MS = 12 * 60 * 60 * 1000;
11
11
 
12
- /** Max accepted models.dev payload size: 15MB (Q-16a). Larger payloads are rejected before parse. */
12
+ /** Max accepted models.dev payload size: 15MB. Larger payloads are rejected before parse. */
13
13
  export const MAX_CATALOG_PAYLOAD_BYTES = 15 * 1024 * 1024;
14
14
 
15
15
  let globalCatalogTtlMs = DEFAULT_CATALOG_TTL_MS;
16
16
 
17
17
  /** Sets the global model catalog cache TTL in milliseconds.
18
- * Note (Q-16a): cache files carry their own `ttlMs`. A global `setCatalogTTL`
18
+ * Note: cache files carry their own `ttlMs`. A global `setCatalogTTL`
19
19
  * does not rewrite in-flight file TTLs; it takes effect on the next refresh
20
20
  * (fresh download) or when the cached file has no `ttlMs` of its own.
21
21
  */
@@ -90,7 +90,7 @@ export function getCacheDir(): string {
90
90
  return path.join(resolveHome(), "catalog");
91
91
  }
92
92
 
93
- /** Cache file path: `<cacheDir>/models.dev.json`, honoring AGENT_CACHE_DIR (Q-16a/Q-34). */
93
+ /** Cache file path: `<cacheDir>/models.dev.json`, honoring AGENT_CACHE_DIR. */
94
94
  export function getCacheFilePath(): string {
95
95
  return path.join(getCacheDir(), "models.dev.json");
96
96
  }
@@ -111,7 +111,7 @@ function writeJsonFileSync(filePath: string, data: any): void {
111
111
  writeFileDurable(filePath, JSON.stringify(data), { backup: false });
112
112
  }
113
113
 
114
- /** Verifies optional sha256 pin from MODELS_DEV_SHA256 (Q-16a). Returns false on mismatch. */
114
+ /** Verifies optional sha256 pin from MODELS_DEV_SHA256. Returns false on mismatch. */
115
115
  export function verifyCatalogSha256(text: string): boolean {
116
116
  const pin = getEnv("MODELS_DEV_SHA256")?.trim().toLowerCase();
117
117
  if (!pin) return true;
@@ -283,14 +283,14 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
283
283
  throw new Error(`HTTP ${res.status} ${res.statusText}`);
284
284
  }
285
285
 
286
- // Q-16a: size-cap before parse (check text length, reject >15MB).
286
+ // Size-cap before parse (check text length, reject >15MB).
287
287
  const text = await res.text();
288
288
  if (text.length > MAX_CATALOG_PAYLOAD_BYTES) {
289
289
  clearTimeout(timer);
290
290
  fallbackToDiskCache();
291
291
  return getCatalogStatus();
292
292
  }
293
- // Q-16a: optional sha256 pin via MODELS_DEV_SHA256.
293
+ // Optional sha256 pin via MODELS_DEV_SHA256.
294
294
  if (!verifyCatalogSha256(text)) {
295
295
  clearTimeout(timer);
296
296
  fallbackToDiskCache();
@@ -334,7 +334,7 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
334
334
 
335
335
  // Update in-memory state FIRST so a read-only filesystem still serves the
336
336
  // fresh catalog for this process; disk persistence below is best-effort.
337
- // Q-34: a real download is NOT from cache.
337
+ // A real download is NOT from cache.
338
338
  activeCatalog = freshData;
339
339
  activeFetchedAt = fetchedAt;
340
340
  activeTtlMs = effectiveTtl;