agent-accelerator 0.5.7 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/agent/loop.ts CHANGED
@@ -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 =>
@@ -436,7 +436,7 @@ export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec, serv
436
436
  }
437
437
 
438
438
  // ---------------------------------------------------------------------------
439
- // Q-35: canonical usage accumulation (pure w.r.t. source).
439
+ // Canonical usage accumulation (pure w.r.t. source).
440
440
  // Invariants (see assertUsageInvariants in session/store.ts):
441
441
  // - token counts are finite integers >= 0;
442
442
  // - cachedTokens/cacheReadTokens are a SUBSET of inputTokens (clamped below
@@ -444,7 +444,7 @@ export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec, serv
444
444
  // - thinkingTokens is a SUBSET of outputTokens when both are reported
445
445
  // (reasoning is generated output; enforced in the test helper, documented
446
446
  // here because providers vary on which side they omit).
447
- // This function never mutates `source` (Q-35): the priced cost is cloned
447
+ // This function never mutates `source`: the priced cost is cloned
448
448
  // into a local before folding into the target.
449
449
  // ---------------------------------------------------------------------------
450
450
  export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: ModelSpec, serviceTier?: string): void {
@@ -465,7 +465,7 @@ export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: M
465
465
  target.cachedTokens = Math.min(preCached, target.inputTokens);
466
466
  target.cacheReadTokens = Math.min(preRead, target.inputTokens);
467
467
  if ((target.cachedTokens ?? 0) < preCached || (target.cacheReadTokens ?? 0) < preRead) {
468
- // Q-35: no console — report via the anomaly hook + telemetry bus.
468
+ // No console — report via the anomaly hook + telemetry bus.
469
469
  reportUsageAnomaly({
470
470
  type: "cache_clamp",
471
471
  inputTokens: target.inputTokens,
@@ -475,7 +475,7 @@ export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: M
475
475
  }
476
476
 
477
477
  const priced = computeCostFromPricing(source, spec, serviceTier);
478
- // Q-35: pure — read `source` only; never assign back to `source.cost`.
478
+ // Pure — read `source` only; never assign back to `source.cost`.
479
479
  // Clone the priced cost so later target folds cannot alias source state.
480
480
  const sourceCost = priced ? { ...priced } : undefined;
481
481
 
@@ -673,11 +673,11 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
673
673
  const maxTurns = limits.maxTurns;
674
674
  // Structured outputs: normalize once (throws ValidationError for bad shapes).
675
675
  const loopOutput = resolveLoopOutput(config);
676
- // Q-19: pre-run checkpoint for onFailure rollback.
676
+ // Pre-run checkpoint for onFailure rollback.
677
677
  const preRunCp = context.checkpoint();
678
678
  const sessionIdForRollback = loopSessionId(config);
679
679
 
680
- // Q-37: partial-run state hoisted outside try so the catch block can attach
680
+ // Partial-run state hoisted outside try so the catch block can attach
681
681
  // {usage,cost,turns,toolCalls,toolResults,text} to ANY thrown error.
682
682
  const startTime = Date.now();
683
683
  const accumulatedUsage: TokenUsage = {
@@ -717,11 +717,11 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
717
717
  }
718
718
 
719
719
  while (!stoppedByTool && turns < maxTurns) {
720
- // Q-21: fail fast on abort (throws AbortError with partial).
720
+ // Fail fast on abort (throws AbortError with partial).
721
721
  throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: finalResult?.text });
722
722
  checkDeadline(startTime, limits.deadlineMs);
723
723
  config.enforceBudget?.();
724
- // Q-19: repair orphan tool pairs before every request.
724
+ // Repair orphan tool pairs before every request.
725
725
  ensureToolPairing(context);
726
726
  if (config.fileAccess?.roots) await enforceFileAccess(context.messages, config.fileAccess.roots);
727
727
  turns++;
@@ -737,13 +737,13 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
737
737
  toolChoice: toolChoiceForTurn(options?.toolChoice, turns),
738
738
  ...(loopOutput.spec ? { output: loopOutput.spec } : {}),
739
739
  };
740
- // Q-22: per-request timeout pass-through (providers honor timeoutMs).
740
+ // Per-request timeout pass-through (providers honor timeoutMs).
741
741
  try {
742
742
  if (Number.isFinite(limits.requestTimeoutMs) && limits.requestTimeoutMs !== Infinity) {
743
743
  (providerOptions as unknown as { timeoutMs?: number }).timeoutMs = limits.requestTimeoutMs;
744
744
  }
745
745
  } catch {}
746
- // Q-25-partial: google:{store:false} opt-out pass-through (provider
746
+ // google:{store:false} opt-out pass-through (provider
747
747
  // already respects body.store===false for chaining).
748
748
  try {
749
749
  const gStore =
@@ -784,8 +784,8 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
784
784
  provider.id
785
785
  );
786
786
 
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.
787
+ // Thinking is exposed via events/traces and onTurn. Promoting it to answer text is gated (default
788
+ // OFF): without the opt-in the run returns the provider's empty text and finishReason.
789
789
  if ((!genResult.text || genResult.text.trim() === "") && (!genResult.toolCalls || genResult.toolCalls.length === 0) && genResult.thinking) {
790
790
  if (shouldPromoteThinkingToAnswer(config, runOptions)) {
791
791
  if (genResult.thinking.includes("</think>")) {
@@ -853,11 +853,11 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
853
853
  }
854
854
 
855
855
  // Execute tool calls — always parallel (model-driven, bloatfree DX7)
856
- // Q-21: check abort before tools (throws AbortError with partial).
856
+ // Check abort before tools (throws AbortError with partial).
857
857
  throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: genResult.text });
858
858
  emitProgress(config); // the calls are on record BEFORE they run, so a crash can finish them instead of forgetting them
859
859
  allToolCalls.push(...genResult.toolCalls);
860
- // Q-22: per-run tool-call budget.
860
+ // Per-run tool-call budget.
861
861
  if (allToolCalls.length > limits.maxToolCallsPerRun) {
862
862
  throw new Error(
863
863
  `[Agent Accelerator] maxToolCallsPerRun exceeded (${allToolCalls.length} > ${limits.maxToolCallsPerRun}).`
@@ -901,7 +901,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
901
901
  allToolResults.push(...sanitizedResults);
902
902
  context.addToolResults(sanitizedResults);
903
903
  checkTokenLimit(accumulatedUsage, limits.maxTotalTokens);
904
- // Q-19: results are stored BEFORE the cancellation check so an abort
904
+ // Results are stored BEFORE the cancellation check so an abort
905
905
  // never drops executed tool work from history.
906
906
  throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: genResult.text });
907
907
  emitTurn(runOptions, {
@@ -926,7 +926,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
926
926
  }
927
927
  emitProgress(config);
928
928
 
929
- // Q-21: abort always throws AbortError with partial attached (never
929
+ // Abort always throws AbortError with partial attached (never
930
930
  // silently breaks).
931
931
  throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: finalResult?.text });
932
932
  }
@@ -963,7 +963,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
963
963
  responseId: finalResult?.responseId,
964
964
  model: modelId,
965
965
  provider: provider.id,
966
- // Q-04: canonical finish reason + raw vendor passthrough.
966
+ // Canonical finish reason + raw vendor passthrough.
967
967
  ...(stoppedByTool
968
968
  ? { finishReason: "stopped" as const, ...(finalResult?.finishReason ? { rawFinishReason: finalResult.finishReason } : {}) }
969
969
  : canonicalFinishReason(finalResult?.finishReason, truncatedByMaxTurns)),
@@ -972,7 +972,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
972
972
  turns,
973
973
  });
974
974
  } catch (err) {
975
- // Q-19: run-level onFailure rollback (default restores pre-run
975
+ // Run-level onFailure rollback (default restores pre-run
976
976
  // checkpoint; opt out with (config as any).onFailure === "keep").
977
977
  // Aborts are excluded so partial history survives interruption.
978
978
  const isAbort =
@@ -983,14 +983,14 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
983
983
  // why: rollback is best-effort — the original error takes precedence.
984
984
  }
985
985
  }
986
- // Q-21: ensure aborts surface as AbortError with partial attached.
986
+ // Ensure aborts surface as AbortError with partial attached.
987
987
  if (isAbort && (err as { name?: string } | null)?.name !== "AbortError") {
988
988
  throw makeAbortError(
989
989
  { usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, turns: 0 },
990
990
  err
991
991
  );
992
992
  }
993
- // Q-37: every failure carries its partial progress for observability.
993
+ // Every failure carries its partial progress for observability.
994
994
  try {
995
995
  (err as unknown as { partial?: unknown }).partial ??= {
996
996
  usage: accumulatedUsage,
@@ -1042,7 +1042,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1042
1042
  }
1043
1043
  };
1044
1044
 
1045
- // Q-19/Q-22: per-run checkpoint + limits for the streaming loop.
1045
+ // Per-run checkpoint + limits for the streaming loop.
1046
1046
  const streamPreCp = config.context.checkpoint();
1047
1047
  const streamSessionId = loopSessionId(config);
1048
1048
  const streamFallbackMax = normalizeMaxTurns((config as { maxTurns?: number }).maxTurns);
@@ -1050,7 +1050,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1050
1050
  const streamStartTime = startTime;
1051
1051
 
1052
1052
  (async () => {
1053
- // Hoisted for catch-block partial reporting (Q-21/Q-37).
1053
+ // Hoisted for catch-block partial reporting.
1054
1054
  let accumulatedUsage: TokenUsage = {
1055
1055
  inputTokens: 0,
1056
1056
  outputTokens: 0,
@@ -1062,7 +1062,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1062
1062
  };
1063
1063
  let lastResponse: AgentResponse | null = null;
1064
1064
  let turns = 0;
1065
- // Q-37: tool progress hoisted so failures carry toolCalls/toolResults.
1065
+ // Tool progress hoisted so failures carry toolCalls/toolResults.
1066
1066
  const allToolCalls: ToolCallRecord[] = [];
1067
1067
  const allToolResults: ToolResultRecord[] = [];
1068
1068
  const allSubagents: SubAgentExecutionMetadata[] = [];
@@ -1118,7 +1118,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1118
1118
  thinkingTokens: 0,
1119
1119
  };
1120
1120
 
1121
- // Q-37: allToolCalls/allToolResults/allSubagents are hoisted above (fresh
1121
+ // allToolCalls/allToolResults/allSubagents are hoisted above (fresh
1122
1122
  // per stream run); just reset turn counters here.
1123
1123
  lastResponse = null;
1124
1124
  turns = 0;
@@ -1176,7 +1176,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1176
1176
  );
1177
1177
  currentInner = innerStream as AssistantMessageEventStream;
1178
1178
 
1179
- // Q-22: first-byte + idle timeouts around SSE (tolerate reasoning).
1179
+ // First-byte + idle timeouts around SSE (tolerate reasoning).
1180
1180
  // Implemented as races on the inner iterator so a hung provider
1181
1181
  // surfaces as TimeoutError instead of hanging the run forever.
1182
1182
  {
@@ -1238,7 +1238,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1238
1238
  );
1239
1239
 
1240
1240
  // Thinking stays on events/traces + onTurn (default exposed). Promoting
1241
- // it to answer text is gated (Q-15, default OFF): without the opt-in
1241
+ // it to answer text is gated (default OFF): without the opt-in
1242
1242
  // the turn keeps the provider's empty text and finishReason.
1243
1243
  if ((!turnResponse.text || turnResponse.text.trim() === "") && (!turnResponse.toolCalls || turnResponse.toolCalls.length === 0) && turnResponse.thinking) {
1244
1244
  if (shouldPromoteThinkingToAnswer(config, runOptions)) {
@@ -1315,7 +1315,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1315
1315
  `[Agent Accelerator] maxToolCallsPerRun exceeded (${allToolCalls.length} > ${streamLimits.maxToolCallsPerRun}).`
1316
1316
  );
1317
1317
  }
1318
- // Q-40: typed tool lifecycle for streaming consumers.
1318
+ // Typed tool lifecycle for streaming consumers.
1319
1319
  try {
1320
1320
  outerStream.push({ type: "tool_start", toolCalls: turnResponse.toolCalls });
1321
1321
  } catch {
@@ -1379,14 +1379,14 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1379
1379
  toolResult: res,
1380
1380
  });
1381
1381
  }
1382
- // Q-40: close the tool lifecycle opened by `tool_start` above.
1382
+ // Close the tool lifecycle opened by `tool_start` above.
1383
1383
  try {
1384
1384
  outerStream.push({ type: "tool_end", toolResults: sanitizedResults });
1385
1385
  } catch {
1386
1386
  // why: stream push never throws, but guard anyway to protect the loop.
1387
1387
  }
1388
1388
 
1389
- // Q-19: store tool results BEFORE the cancellation check.
1389
+ // Store tool results BEFORE the cancellation check.
1390
1390
  allToolResults.push(...sanitizedResults);
1391
1391
  context.addToolResults(sanitizedResults);
1392
1392
  throwIfCancelled({ usage: accumulatedUsage, turns, text: turnResponse.text });
@@ -1463,7 +1463,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1463
1463
  responseId: lastResponse?.responseId,
1464
1464
  model: modelId,
1465
1465
  provider: provider.id,
1466
- // Q-04: canonical finish reason + raw vendor passthrough.
1466
+ // Canonical finish reason + raw vendor passthrough.
1467
1467
  ...truncatedFinish,
1468
1468
  durationMs: Date.now() - startTime,
1469
1469
  raw: applyCaptureRaw(lastResponse?.raw, config.captureRaw) as ProviderRawData,
@@ -1486,7 +1486,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1486
1486
  linked.signal.aborted ||
1487
1487
  outerStream.isCancelled() ||
1488
1488
  (raw as any)?.name === "AbortError";
1489
- // Q-19: streaming onFailure rollback (same policy as runAgentLoop;
1489
+ // Streaming onFailure rollback (same policy as runAgentLoop;
1490
1490
  // aborts excluded so partial history survives).
1491
1491
  try {
1492
1492
  if (shouldRollbackOnFailure(config.onFailure, isAbort, toolsExecuted)) {
@@ -1497,7 +1497,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1497
1497
  } catch {
1498
1498
  // why: rollback policy itself must never mask the original error.
1499
1499
  }
1500
- // Q-37: every failure carries its partial progress for observability.
1500
+ // Every failure carries its partial progress for observability.
1501
1501
  const partial = {
1502
1502
  usage: accumulatedUsage,
1503
1503
  cost: accumulatedUsage.cost,
@@ -1506,7 +1506,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1506
1506
  toolResults: allToolResults,
1507
1507
  text: lastResponse?.text,
1508
1508
  };
1509
- // Q-21: aborts always surface as AbortError with partial attached.
1509
+ // Aborts always surface as AbortError with partial attached.
1510
1510
  if (isAbort) {
1511
1511
  const abortErr =
1512
1512
  raw.name === "AbortError" ? raw : makeAbortError(partial, raw);
@@ -1537,12 +1537,12 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
1537
1537
  }
1538
1538
 
1539
1539
  // ---------------------------------------------------------------------------
1540
- // Q-15: thinking-promotion policy + Q-04 canonical finish helper.
1540
+ // Thinking-promotion policy + canonical finish helper.
1541
1541
  // (Appended here to avoid conflicts with other loop sections.)
1542
1542
  // ---------------------------------------------------------------------------
1543
1543
 
1544
1544
  /**
1545
- * Whether an empty-text turn may promote `thinking` to answer `text` (Q-15).
1545
+ * Whether an empty-text turn may promote `thinking` to answer `text`.
1546
1546
  * Default false: the run returns the provider's empty text with its
1547
1547
  * finishReason instead of fabricating an answer from reasoning. Opt in via
1548
1548
  * `(runOptions as any).thinkingAsAnswerFallback === true` or
@@ -1567,7 +1567,7 @@ export function shouldPromoteThinkingToAnswer(config?: unknown, runOptions?: unk
1567
1567
  }
1568
1568
 
1569
1569
  /**
1570
- * Canonicalizes a provider finish reason for AgentResponse (Q-04).
1570
+ * Canonicalizes a provider finish reason for AgentResponse.
1571
1571
  * When `truncated` (maxTurns exhausted with pending tools), forces
1572
1572
  * `"max_turns"` and preserves the provider's reason in `rawFinishReason`.
1573
1573
  * 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" {
@@ -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";
@@ -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;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Model catalog — battle-tested single source of truth (models.dev)
3
- * Dynamically loaded and synchronized via ~/.cache with ~12h TTL.
4
- * All context length, pricing, limits, reasoning, modalities come from here.
5
- * Providers' hardcoded GOOGLE_MODELS etc. are DEPRECATED fallbacks only.
2
+ * Model catalog: the single source of truth (models.dev) for context limits, prices, modalities and reasoning options.
3
+ * The data is cached in `<home>/catalog/models.dev.json` (or `AGENT_CACHE_DIR`) with a 12 hour TTL and refreshed in the
4
+ * background (see `catalog-cache.ts`). `GOOGLE_MODELS`, `OPENAI_MODELS` and `OPENROUTER_MODELS` are views of it,
5
+ * rebuilt in place whenever it changes.
6
6
  */
7
7
 
8
8
  import {
@@ -77,12 +77,12 @@ function normalizeModelIdForLookup(modelId: string): string {
77
77
 
78
78
  // ---------------------------------------------------------------------------
79
79
  // Internal cache — battle-tested: avoid repeated global scans (7487 models)
80
- // Bound lookupCache to 1000 entries LRU (Q-33): evict oldest on overflow.
80
+ // Bound lookupCache to 1000 entries LRU: evict oldest on overflow.
81
81
  // ---------------------------------------------------------------------------
82
82
  const lookupCache = new Map<string, ModelSpec | undefined>();
83
83
  const providerModelsCache = new Map<string, ModelSpec[]>();
84
84
 
85
- /** Bounded LRU set for lookupCache (Q-33): keeps at most 1000 entries. */
85
+ /** Bounded LRU set for lookupCache: keeps at most 1000 entries. */
86
86
  function setLookupCache(key: string, value: ModelSpec | undefined): void {
87
87
  if (lookupCache.has(key)) lookupCache.delete(key);
88
88
  else if (lookupCache.size >= 1000) {
@@ -92,7 +92,7 @@ function setLookupCache(key: string, value: ModelSpec | undefined): void {
92
92
  lookupCache.set(key, value);
93
93
  }
94
94
 
95
- // Custom-registered models (Q-33): checked first in getModelFromCatalog.
95
+ // Custom-registered models: checked first in getModelFromCatalog.
96
96
  const customModelRegistry = new Map<string, ModelSpec>();
97
97
 
98
98
  // Case-insensitive key of a registered custom model; in: registerModel and lookups, out: a string.
@@ -101,7 +101,7 @@ function customRegistryKey(provider: string, modelId: string): string {
101
101
  }
102
102
 
103
103
  /**
104
- * Registers a custom model spec that takes precedence over the catalog (Q-33).
104
+ * Registers a custom model spec that takes precedence over the catalog.
105
105
  * Stored in a separate map, checked first in `getModelFromCatalog`.
106
106
  */
107
107
  export function registerModel(spec: ModelSpec): void {
@@ -124,7 +124,7 @@ export function clearCustomModels(): void {
124
124
  }
125
125
 
126
126
  /**
127
- * Returns where a model resolves from without touching frozen types (Q-33):
127
+ * Returns where a model resolves from without touching frozen types:
128
128
  * `"override"` for custom-registered or MODALITY_OVERRIDES-covered models,
129
129
  * `"catalog"` for plain catalog hits, `"unknown"` when unresolvable.
130
130
  */
@@ -256,7 +256,7 @@ export function getModelFromCatalog(providerInput: string, modelIdInput: string)
256
256
  const modelId = modelIdInput.trim();
257
257
  let result: ModelSpec | undefined;
258
258
 
259
- // Q-33: custom-registered models win over the catalog.
259
+ // Custom-registered models win over the catalog.
260
260
  const customHit =
261
261
  customModelRegistry.get(customRegistryKey(provider, modelId)) ??
262
262
  customModelRegistry.get(customRegistryKey(provider, normalizeModelIdForLookup(modelId)));
@@ -287,7 +287,7 @@ export function getModelFromCatalog(providerInput: string, modelIdInput: string)
287
287
  return result;
288
288
  }
289
289
  }
290
- // Q-33: tightened fuzzy match — exact, `endsWith("/"+modelId)` boundary,
290
+ // Tightened fuzzy match — exact, `endsWith("/"+modelId)` boundary,
291
291
  // or normalized equality. The old loose `modelId.endsWith(key)` is removed
292
292
  // (e.g. "gpt-4o-mini-extra" must not match "mini").
293
293
  for (const [key, raw] of Object.entries(providerData.models as Record<string, any>)) {
@@ -563,7 +563,7 @@ export function getModelsForProvider(provider: string): ModelSpec[] {
563
563
  out.push(mapModelSpec(alias as ProviderId, key, raw));
564
564
  }
565
565
  }
566
- // Q-33: include custom-registered models for this provider/alias set.
566
+ // Include custom-registered models for this provider/alias set.
567
567
  for (const spec of customModelRegistry.values()) {
568
568
  if (aliases.includes(String(spec.provider)) || String(spec.provider) === provider) {
569
569
  if (!seen.has(spec.id)) {
@@ -573,7 +573,7 @@ export function getModelsForProvider(provider: string): ModelSpec[] {
573
573
  }
574
574
  }
575
575
  // Also include our internal provider id mapping
576
- // Q-33: return copies — cache the canonical array, hand out a fresh copy.
576
+ // Return copies — cache the canonical array, hand out a fresh copy.
577
577
  providerModelsCache.set(provider, out);
578
578
  return [...out];
579
579
  }
@@ -609,7 +609,7 @@ async function fullHistorySteps(context: ProviderContext): Promise<InteractionIn
609
609
  const turnStart = steps.length;
610
610
  for (const part of m.content) {
611
611
  if (part.type === "thinking" && part.thinking) {
612
- // Q-18: preserve namespaced signatures only — foreign-provider
612
+ // Preserve namespaced signatures only — foreign-provider
613
613
  // signatures never echo here; untagged legacy echoes (compat).
614
614
  // Absent signatures are omitted (never sent as empty strings).
615
615
  const partProvider = (part as { thoughtSignatureProvider?: unknown }).thoughtSignatureProvider;
@@ -642,7 +642,7 @@ async function fullHistorySteps(context: ProviderContext): Promise<InteractionIn
642
642
  } else if (m.thoughtSignature && !steps.slice(turnStart).some((s) => s.type === "thought")) {
643
643
  // Preserve a signature-only thought so stateless chaining keeps
644
644
  // reasoning continuity even when no summary text was retained.
645
- // Q-18: only for google/untagged signatures.
645
+ // Only for google/untagged signatures.
646
646
  const msgProvider = (m as { thoughtSignatureProvider?: unknown }).thoughtSignatureProvider;
647
647
  if (shouldEchoSignature(msgProvider, "google")) {
648
648
  steps.push({ type: "thought", signature: m.thoughtSignature });
@@ -910,7 +910,7 @@ export class GoogleInteractionsProvider implements Provider {
910
910
  signal?: AbortSignal
911
911
  ): RequestInit {
912
912
  const headers: Record<string, string> = { "Content-Type": "application/json", ...options?.headers };
913
- // Q-17: key travels as the `x-goog-api-key` header (never `?key=` in the
913
+ // Key travels as the `x-goog-api-key` header (never `?key=` in the
914
914
  // URL, which would leak into logs). Case-insensitive merge so a custom
915
915
  // `X-Goog-Api-Key` never duplicates the canonical header. Proxy flows
916
916
  // (`apiKey === "unused"`) preserve custom auth instead of overwriting.
@@ -1392,7 +1392,7 @@ export class GoogleInteractionsProvider implements Provider {
1392
1392
  noteProviderTurn(sessionId, "google");
1393
1393
  checkTurnTokens(sessionId, usage);
1394
1394
 
1395
- // Q-23: terminal-state guard — fail `incomplete_stream` (retryable)
1395
+ // Terminal-state guard — fail `incomplete_stream` (retryable)
1396
1396
  // instead of resolving empty success. Skipped for non-SSE payloads
1397
1397
  // (JSON mocks) where no SSE frames were ever seen.
1398
1398
  const hasTerminal = completedBody !== undefined || sawFinishReason || sawUsage;