agent-accelerator 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +174 -123
  2. package/{SYSTEM_PROMPT_AGENT.md → examples/prompts/SYSTEM_PROMPT_AGENT.md} +1 -1
  3. package/package.json +14 -9
  4. package/src/agent/agent.ts +385 -73
  5. package/src/agent/context.ts +1 -0
  6. package/src/agent/delegation.ts +443 -30
  7. package/src/agent/loop.ts +183 -53
  8. package/src/data/README.md +6 -6
  9. package/src/index.ts +163 -46
  10. package/src/models/catalog-cache.ts +60 -9
  11. package/src/models/catalog.ts +55 -8
  12. package/src/providers/google.ts +926 -0
  13. package/src/providers/openai-compat.ts +1147 -0
  14. package/src/providers/openai.ts +959 -0
  15. package/src/providers/openrouter-responses.ts +949 -0
  16. package/src/providers/openrouter.ts +1037 -0
  17. package/src/{ai-sdk → providers}/registry.ts +43 -55
  18. package/src/providers.ts +492 -0
  19. package/src/session/store.ts +806 -0
  20. package/src/streaming/sse-parser.ts +6 -4
  21. package/src/tools/executor.ts +23 -7
  22. package/src/tools/schema.ts +21 -11
  23. package/src/types/agent.ts +33 -2
  24. package/src/types/core.ts +3 -2
  25. package/src/types/message.ts +5 -0
  26. package/src/types/model.ts +3 -7
  27. package/src/types/provider-payloads.ts +2 -84
  28. package/src/types/response.ts +24 -0
  29. package/src/types/tool.ts +8 -0
  30. package/src/update-models.ts +56 -0
  31. package/src/utils/cache.ts +1 -1
  32. package/src/utils/documents.ts +517 -0
  33. package/src/utils/env.ts +0 -7
  34. package/src/{ai-sdk → utils}/errors.ts +61 -2
  35. package/src/utils/headers.ts +10 -20
  36. package/src/utils/media.ts +5 -2
  37. package/src/utils/retry.ts +89 -0
  38. package/src/utils/serialization.ts +15 -0
  39. package/src/utils/session.ts +36 -0
  40. package/src/ai-sdk/converters.ts +0 -342
  41. package/src/ai-sdk/executor.ts +0 -454
  42. package/src/ai-sdk/index.ts +0 -55
  43. package/src/ai-sdk/model-provider.ts +0 -303
  44. package/src/ai-sdk/options.ts +0 -306
  45. package/src/ai-sdk/provider.ts +0 -415
  46. package/src/tokens/counter.ts +0 -136
  47. /package/{SYSTEM_PROMPT.md → examples/prompts/SYSTEM_PROMPT.md} +0 -0
  48. /package/{SYSTEM_PROMPT_TOOLS.md → examples/prompts/SYSTEM_PROMPT_TOOLS.md} +0 -0
package/src/agent/loop.ts CHANGED
@@ -7,8 +7,9 @@ import { AssistantMessageEventStream } from "../streaming/event-stream.ts";
7
7
  import { AgentContext } from "./context.ts";
8
8
  import { toStandardToolDeclarations } from "../tools/tool.ts";
9
9
  import { executeToolCalls } from "../tools/executor.ts";
10
- import { getModelFromCatalog, ensureModelCatalogFresh } from "../models/catalog.ts";
11
- import { countTokens } from "../tokens/counter.ts";
10
+ import { getModelFromCatalog, ensureModelCatalogFresh, validateModelThinking } from "../models/catalog.ts";
11
+ import { preprocessFilePartsForBypass } from "../utils/documents.ts";
12
+ import { noteProviderTurn } from "../providers.ts";
12
13
 
13
14
  export interface AgentLoopConfig {
14
15
  agentName?: string;
@@ -19,6 +20,55 @@ export interface AgentLoopConfig {
19
20
  options?: ProviderRequestOptions;
20
21
  runOptions?: AgentRunOptions;
21
22
  maxTurns?: number;
23
+ /** Convert `file` parts client-side for pdf-incapable models (see Agent flag). */
24
+ bypassInputFileModality?: boolean;
25
+ /** Called after every turn for durable persistence checkpoints (never fails a turn). */
26
+ onProgress?: () => void;
27
+ }
28
+
29
+ function emitTurn(
30
+ runOptions: AgentRunOptions | undefined,
31
+ turn: { turns: number; text?: string; thinking?: string; toolCalls?: ToolCallRecord[]; toolResults?: ToolResultRecord[] }
32
+ ): void {
33
+ try {
34
+ runOptions?.onTurn?.({
35
+ turns: turn.turns,
36
+ text: turn.text,
37
+ thinking: turn.thinking,
38
+ toolCalls: turn.toolCalls?.map((c) => ({ id: c.id, name: c.name })),
39
+ toolResults: turn.toolResults?.map((r) => ({ id: r.id, name: r.name, isError: r.isError })),
40
+ });
41
+ } catch {}
42
+ }
43
+
44
+ function emitProgress(config: { onProgress?: () => void }): void {
45
+ try { config.onProgress?.(); } catch {}
46
+ }
47
+
48
+ /**
49
+ * Normalizes `maxTurns` to a safe positive integer (default Infinity).
50
+ * `0` means infinite (no limit), matching the `timeout: 0` convention.
51
+ * Prevents silent zero-turn runs from negative values (clamped to 1).
52
+ */
53
+ function normalizeMaxTurns(value?: number): number {
54
+ if (value === undefined) return Infinity;
55
+ if (value === 0 || value === Infinity) return Infinity;
56
+ if (!Number.isFinite(value as number)) return Infinity;
57
+ return Math.max(1, Math.floor(value as number));
58
+ }
59
+
60
+ /**
61
+ * Warns when an agent run stops because `maxTurns` was exhausted while
62
+ * the model still wanted to call tools. Without this, the loop simply
63
+ * returns the last tool-turn text (often empty) with `finishReason:
64
+ * "tool_calls"`, which looks like success while dropping pending work.
65
+ */
66
+ function warnMaxTurnsHit(agentName: string | undefined, maxTurns: number, pendingTools: string[]): void {
67
+ const who = agentName ? `"${agentName}"` : "Agent";
68
+ const tools = pendingTools.length > 0 ? ` Pending tool calls dropped: ${pendingTools.join(", ")}.` : "";
69
+ // Leading newline: runs often end mid-line on streamed output; without it
70
+ // the warning glues onto the last streamed chunk.
71
+ console.warn(`\n[Agent Accelerator] WARNING [agent] ${who} hit maxTurns (${maxTurns}) with unfinished tool calls.${tools} Increase maxTurns or split the task.`);
22
72
  }
23
73
 
24
74
  export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec): TokenUsage["cost"] {
@@ -67,6 +117,13 @@ function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: ModelSpe
67
117
  target.cacheWriteTokens = (target.cacheWriteTokens ?? 0) + (source.cacheWriteTokens ?? 0);
68
118
  target.thinkingTokens = (target.thinkingTokens ?? 0) + (source.thinkingTokens ?? 0);
69
119
 
120
+ // Canonical invariant (provider-agnostic): cache hits are a subset of
121
+ // input. Some providers occasionally report cached > input on long chained
122
+ // runs, which surfaces as >100% hit rates downstream. Clamp the aggregate
123
+ // so no consumer can observe an impossible ratio.
124
+ target.cachedTokens = Math.min(target.cachedTokens ?? 0, target.inputTokens);
125
+ target.cacheReadTokens = Math.min(target.cacheReadTokens ?? 0, target.inputTokens);
126
+
70
127
  const sourceCost = computeCostFromPricing(source, spec);
71
128
  if (sourceCost) {
72
129
  source.cost = sourceCost;
@@ -121,6 +178,7 @@ async function executeToolCallsWithRepeatGuard(options: {
121
178
  agentName?: string;
122
179
  signal?: AbortSignal;
123
180
  sessionId?: string;
181
+ onSubagentEvent?: (event: { trackingId: string; delta?: string; thinkingDelta?: string; partialText?: string; partialThinking?: string }) => void;
124
182
  }): Promise<{ results: ToolResultRecord[]; fingerprints: Set<string> }> {
125
183
  const { toolCalls, previousFingerprints } = options;
126
184
  const currentFingerprints = new Set<string>();
@@ -153,14 +211,29 @@ async function executeToolCallsWithRepeatGuard(options: {
153
211
  parallel: true,
154
212
  signal: options.signal,
155
213
  sessionId: options.sessionId,
214
+ onSubagentEvent: options.onSubagentEvent,
156
215
  })
157
216
  : [];
158
- const byId = new Map<string, ToolResultRecord>();
159
- for (const result of executed) byId.set(result.id, result);
160
- for (const [id, result] of blocked) byId.set(id, result);
217
+ const byId = new Map<string, ToolResultRecord[]>();
218
+ for (const result of executed) {
219
+ const queue = byId.get(result.id) ?? [];
220
+ queue.push(result);
221
+ byId.set(result.id, queue);
222
+ }
223
+ for (const [id, result] of blocked) {
224
+ const queue = byId.get(id) ?? [];
225
+ queue.push(result);
226
+ byId.set(id, queue);
227
+ }
161
228
 
162
229
  return {
163
- results: toolCalls.map((call) => byId.get(call.id)!).filter(Boolean),
230
+ // Consume per-id queues in call order: some open-weights models reuse one
231
+ // id for several distinct calls in a turn, and a plain id->result map
232
+ // would hand every call the last result. Queues keep each result aligned
233
+ // with its own call.
234
+ results: toolCalls
235
+ .map((call) => byId.get(call.id)?.shift())
236
+ .filter((r): r is ToolResultRecord => Boolean(r)),
164
237
  fingerprints: currentFingerprints,
165
238
  };
166
239
  }
@@ -178,8 +251,17 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
178
251
  tools,
179
252
  options,
180
253
  runOptions,
181
- maxTurns = 10,
254
+ maxTurns: rawMaxTurns = Infinity,
182
255
  } = config;
256
+ const maxTurns = normalizeMaxTurns(rawMaxTurns);
257
+
258
+ if (config.bypassInputFileModality) {
259
+ await preprocessFilePartsForBypass(config.context.messages, {
260
+ providerId: provider.id,
261
+ modelId,
262
+ signal: runOptions?.signal,
263
+ });
264
+ }
183
265
 
184
266
  const standardTools = toStandardToolDeclarations(tools);
185
267
  const startTime = Date.now();
@@ -204,30 +286,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
204
286
  while (turns < maxTurns) {
205
287
  turns++;
206
288
 
207
- // Battle-tested context window check — trim oldest history if needed, keep cached prefix (system+tools)
208
289
  const spec = getModelFromCatalog(provider.id, modelId);
209
- const contextWindow = spec?.limit?.context ?? spec?.contextWindow ?? 128000;
210
- const maxOutput = spec?.limit?.output ?? spec?.maxOutputTokens ?? 8192;
211
- // Reserve for output + 10% headroom, ensure first turn already cache-friendly
212
- const budgetForInput = Math.floor(contextWindow * 0.9) - maxOutput;
213
- let estimated = 0;
214
- try {
215
- estimated = countTokens({ systemPrompt: context.systemPrompt, messages: context.messages, tools: standardTools as any });
216
- } catch {}
217
- if (estimated > budgetForInput && context.messages.length > 2) {
218
- // Keep system + last 70% of history, drop oldest middle (preserve cached prefix stability)
219
- const keepCount = Math.max(2, Math.floor(context.messages.length * 0.7));
220
- const toKeep = context.messages.slice(-keepCount);
221
- // Preserve at least system and first user if possible
222
- const firstUserIdx = context.messages.findIndex((m) => m.role === "user");
223
- if (firstUserIdx >= 0 && firstUserIdx < context.messages.length - keepCount) {
224
- // Drop middle, keep head + tail for cache stability
225
- const head = context.messages.slice(0, 1);
226
- context.messages = [...head, ...toKeep];
227
- } else {
228
- context.messages = toKeep;
229
- }
230
- }
231
290
 
232
291
  const providerOptions: ProviderRequestOptions = {
233
292
  ...options,
@@ -250,6 +309,12 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
250
309
 
251
310
  finalResult = genResult;
252
311
 
312
+ // Canonical session routing: lets adapters detect provider switches.
313
+ noteProviderTurn(
314
+ runOptions?.sessionId || options?.sessionId || options?.cache?.sessionId,
315
+ provider.id
316
+ );
317
+
253
318
  // Safety fallback: if no tool calls and text is empty, rescue answer from thinking
254
319
  if ((!genResult.text || genResult.text.trim() === "") && (!genResult.toolCalls || genResult.toolCalls.length === 0) && genResult.thinking) {
255
320
  if (genResult.thinking.includes("</think>")) {
@@ -274,6 +339,8 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
274
339
 
275
340
  // If no tool calls, generation is complete!
276
341
  if (!genResult.toolCalls || genResult.toolCalls.length === 0) {
342
+ emitTurn(runOptions, { turns, text: genResult.text, thinking: genResult.thinking });
343
+ emitProgress(config);
277
344
  break;
278
345
  }
279
346
 
@@ -310,10 +377,31 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
310
377
 
311
378
  allToolResults.push(...sanitizedResults);
312
379
  context.addToolResults(sanitizedResults);
380
+ emitTurn(runOptions, {
381
+ turns,
382
+ text: genResult.text,
383
+ thinking: genResult.thinking,
384
+ toolCalls: genResult.toolCalls,
385
+ toolResults: sanitizedResults,
386
+ });
387
+ emitProgress(config);
313
388
 
314
389
  if (runOptions?.signal?.aborted) break;
315
390
  }
316
391
 
392
+ // maxTurns exhausted while the model still requested tools: the tool
393
+ // results above were never sent back, so the run is truncated — surface
394
+ // it via finishReason + warning instead of silently returning.
395
+ const truncatedByMaxTurns =
396
+ turns >= maxTurns && !!finalResult?.toolCalls && finalResult.toolCalls.length > 0;
397
+ if (truncatedByMaxTurns) {
398
+ warnMaxTurnsHit(
399
+ agentName,
400
+ maxTurns,
401
+ finalResult.toolCalls.map((c: ToolCallRecord) => c.name)
402
+ );
403
+ }
404
+
317
405
  return new AgentResponse({
318
406
  text: finalResult?.text || "",
319
407
  thinking: finalResult?.thinking,
@@ -325,7 +413,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
325
413
  responseId: finalResult?.responseId,
326
414
  model: modelId,
327
415
  provider: provider.id,
328
- finishReason: finalResult?.finishReason,
416
+ finishReason: truncatedByMaxTurns ? "max_turns" : finalResult?.finishReason,
329
417
  durationMs: Date.now() - startTime,
330
418
  raw: finalResult?.raw,
331
419
  turns,
@@ -340,7 +428,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
340
428
  const startTime = Date.now();
341
429
 
342
430
  // Cancellation: merge user signal + outer cancel() into one linked controller.
343
- // Vercel doStream honors abortSignal for all providers, so aborting this
431
+ // Native fetch honors abortSignal on every provider, so aborting this
344
432
  // stops the HTTP request; we also cancel the active inner provider stream.
345
433
  const linked = new AbortController();
346
434
  const userSignal = config.runOptions?.signal;
@@ -365,6 +453,27 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
365
453
  (async () => {
366
454
  try {
367
455
  await ensureModelCatalogFresh();
456
+ // Re-validate after refresh: Agent.stream() validates before the catalog
457
+ // is guaranteed fresh, so a cold cache validates permissively. A mismatch
458
+ // discovered here fails the stream instead of reaching the provider.
459
+ {
460
+ const lvl = config.runOptions?.thinkingLevel ?? config.options?.thinking?.level;
461
+ if (lvl) {
462
+ try {
463
+ validateModelThinking(config.provider.id, config.modelId, lvl);
464
+ } catch (err) {
465
+ outerStream.fail(err instanceof Error ? err : new Error(String(err)));
466
+ return;
467
+ }
468
+ }
469
+ }
470
+ if (config.bypassInputFileModality) {
471
+ await preprocessFilePartsForBypass(config.context.messages, {
472
+ providerId: config.provider.id,
473
+ modelId: config.modelId,
474
+ signal: config.runOptions?.signal,
475
+ });
476
+ }
368
477
  const {
369
478
  agentName,
370
479
  provider,
@@ -373,8 +482,9 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
373
482
  tools,
374
483
  options,
375
484
  runOptions,
376
- maxTurns = 10,
485
+ maxTurns: rawMaxTurns = Infinity,
377
486
  } = config;
487
+ const maxTurns = normalizeMaxTurns(rawMaxTurns);
378
488
 
379
489
  const standardTools = toStandardToolDeclarations(tools);
380
490
 
@@ -399,25 +509,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
399
509
  throwIfCancelled();
400
510
  turns++;
401
511
 
402
- // Same context-window trim as non-stream (ensure cache prefix stable)
403
512
  const spec2 = getModelFromCatalog(provider.id, modelId);
404
- const cw2 = spec2?.limit?.context ?? spec2?.contextWindow ?? 128000;
405
- const mo2 = spec2?.limit?.output ?? spec2?.maxOutputTokens ?? 8192;
406
- const budget2 = Math.floor(cw2 * 0.9) - mo2;
407
- try {
408
- const est2 = countTokens({ systemPrompt: context.systemPrompt, messages: context.messages, tools: standardTools as any });
409
- if (est2 > budget2 && context.messages.length > 2) {
410
- const keep2 = Math.max(2, Math.floor(context.messages.length * 0.7));
411
- const toKeep = context.messages.slice(-keep2);
412
- const firstUserIdx = context.messages.findIndex((m) => m.role === "user");
413
- if (firstUserIdx >= 0 && firstUserIdx < context.messages.length - keep2) {
414
- const head = context.messages.slice(0, 1);
415
- context.messages = [...head, ...toKeep];
416
- } else {
417
- context.messages = toKeep;
418
- }
419
- }
420
- } catch {}
421
513
 
422
514
  const providerOptions: ProviderRequestOptions = {
423
515
  ...options,
@@ -449,6 +541,11 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
449
541
  currentInner = null;
450
542
  throwIfCancelled();
451
543
  lastResponse = turnResponse;
544
+ // Canonical session routing: lets adapters detect provider switches.
545
+ noteProviderTurn(
546
+ runOptions?.sessionId || options?.sessionId || options?.cache?.sessionId,
547
+ provider.id
548
+ );
452
549
 
453
550
  // Safety fallback: if no tool calls and text is empty, rescue answer from thinking
454
551
  if ((!turnResponse.text || turnResponse.text.trim() === "") && (!turnResponse.toolCalls || turnResponse.toolCalls.length === 0) && turnResponse.thinking) {
@@ -472,6 +569,8 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
472
569
  );
473
570
 
474
571
  if (!turnResponse.toolCalls || turnResponse.toolCalls.length === 0) {
572
+ emitTurn(runOptions, { turns, text: turnResponse.text, thinking: turnResponse.thinking });
573
+ emitProgress(config);
475
574
  break;
476
575
  }
477
576
 
@@ -483,6 +582,18 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
483
582
  agentName,
484
583
  signal: linked.signal,
485
584
  sessionId: runOptions?.sessionId || options?.sessionId || options?.cache?.sessionId,
585
+ onSubagentEvent: (ev) => {
586
+ try {
587
+ outerStream.push({
588
+ type: "subagent_delta",
589
+ subagentTrackingId: ev.trackingId,
590
+ delta: ev.delta,
591
+ thinkingDelta: ev.thinkingDelta,
592
+ partialText: ev.partialText,
593
+ partialThinking: ev.partialThinking,
594
+ });
595
+ } catch {}
596
+ },
486
597
  });
487
598
  throwIfCancelled();
488
599
  const results = guarded.results;
@@ -518,7 +629,26 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
518
629
 
519
630
  allToolResults.push(...sanitizedResults);
520
631
  context.addToolResults(sanitizedResults);
632
+ emitTurn(runOptions, {
633
+ turns,
634
+ text: turnResponse.text,
635
+ thinking: turnResponse.thinking,
636
+ toolCalls: turnResponse.toolCalls,
637
+ toolResults: sanitizedResults,
638
+ });
639
+ emitProgress(config);
640
+ }
641
+
642
+ const truncatedByMaxTurns =
643
+ turns >= maxTurns && !!lastResponse?.toolCalls && lastResponse.toolCalls.length > 0;
644
+ if (truncatedByMaxTurns) {
645
+ warnMaxTurnsHit(
646
+ agentName,
647
+ maxTurns,
648
+ lastResponse!.toolCalls.map((c) => c.name)
649
+ );
521
650
  }
651
+ const truncatedFinishReason = truncatedByMaxTurns ? "max_turns" : lastResponse?.finishReason;
522
652
 
523
653
  const finalAgentResponse = new AgentResponse({
524
654
  text: lastResponse?.text || "",
@@ -531,7 +661,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
531
661
  responseId: lastResponse?.responseId,
532
662
  model: modelId,
533
663
  provider: provider.id,
534
- finishReason: lastResponse?.finishReason,
664
+ finishReason: truncatedFinishReason,
535
665
  durationMs: Date.now() - startTime,
536
666
  raw: lastResponse?.raw as any,
537
667
  turns,
@@ -541,7 +671,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
541
671
  type: "done",
542
672
  delta: "",
543
673
  usage: accumulatedUsage,
544
- finishReason: lastResponse?.finishReason,
674
+ finishReason: truncatedFinishReason,
545
675
  responseId: lastResponse?.responseId,
546
676
  });
547
677
 
@@ -11,13 +11,13 @@ Agent Accelerator references a catalog of **7,500+ models across 200+ providers*
11
11
  To keep the repository and published npm/Bun package lightweight and blazing fast:
12
12
 
13
13
  1. **Excluded from Production Package & Git**:
14
- * The ~4.5 MB full catalog snapshot (`models-cache.json` / `models.dev.json`) is **never** committed to git and is **excluded** from the npm/Bun distribution bundle.
15
- * Both files are tracked in `.gitignore` and `.npmignore`.
14
+ * The ~4.5 MB full catalog snapshot (`models.dev.json`) is **never** committed to git and is **excluded** from the npm/Bun distribution bundle.
15
+ * The file is tracked in `.gitignore` and `.npmignore`.
16
16
 
17
17
  2. **Dynamic 12-Hour Automated TTL Cache**:
18
- * On agent execution (`runAgentLoop` / `streamAgentLoop`), Agent Accelerator checks the status of `src/data/models-cache.json`.
18
+ * On agent execution (`runAgentLoop` / `streamAgentLoop`), Agent Accelerator checks the status of `src/data/models.dev.json`.
19
19
  * **Cache Hit (< 12h)**: Instant memory/disk load with zero network overhead.
20
- * **Cache Miss / Expired (≥ 12h)**: Automatically downloads the latest catalog directly from `https://models.dev/api.json`, caches it to `src/data/models-cache.json`, and loads the catalog into memory.
20
+ * **Cache Miss / Expired (≥ 12h)**: Automatically downloads the latest catalog directly from `https://models.dev/api.json`, caches it to `src/data/models.dev.json`, and loads the catalog into memory.
21
21
  * **Resilience**: If an existing cache exists on disk, temporary upstream network issues will safely continue using the local cached copy.
22
22
 
23
23
  ---
@@ -36,7 +36,7 @@ bun run update-models
36
36
  bun run update-models --force
37
37
 
38
38
  # Custom TTL (e.g., 24 hours)
39
- bun scripts/update-models.ts --ttl=24h
39
+ bun src/update-models.ts --ttl=24h
40
40
  ```
41
41
 
42
42
  ---
@@ -80,5 +80,5 @@ setCatalogTTL(6 * 60 * 60 * 1000);
80
80
  ```text
81
81
  src/data/
82
82
  ├── README.md # This documentation
83
- └── models-cache.json # Auto-generated on first run (gitignored, excluded from bundle)
83
+ └── models.dev.json # Auto-generated on first run (gitignored, excluded from bundle)
84
84
  ```