@theokit/sdk 5.10.0 → 5.11.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 (58) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/{agent-TLRURB7W.cjs → agent-5DOR5T3E.cjs} +7 -6
  3. package/dist/{agent-TLRURB7W.cjs.map → agent-5DOR5T3E.cjs.map} +1 -1
  4. package/dist/{agent-XTLAVVYC.js → agent-GWKRARAL.js} +6 -5
  5. package/dist/{agent-XTLAVVYC.js.map → agent-GWKRARAL.js.map} +1 -1
  6. package/dist/{chunk-RTE2DD4S.js → chunk-4PHSVR2R.js} +50 -32
  7. package/dist/chunk-4PHSVR2R.js.map +1 -0
  8. package/dist/{chunk-PCLRIQPX.cjs → chunk-5H2GJXGC.cjs} +4 -4
  9. package/dist/{chunk-PCLRIQPX.cjs.map → chunk-5H2GJXGC.cjs.map} +1 -1
  10. package/dist/{chunk-CVEQ5366.cjs → chunk-5OHB5HJF.cjs} +90 -72
  11. package/dist/chunk-5OHB5HJF.cjs.map +1 -0
  12. package/dist/{chunk-EVH3OUMY.js → chunk-74OA5DIM.js} +189 -43
  13. package/dist/chunk-74OA5DIM.js.map +1 -0
  14. package/dist/{chunk-EXI7EHDN.js → chunk-7Y23B7XX.js} +3 -3
  15. package/dist/{chunk-EXI7EHDN.js.map → chunk-7Y23B7XX.js.map} +1 -1
  16. package/dist/{chunk-QY3CIMUV.cjs → chunk-BZKF7TXX.cjs} +192 -44
  17. package/dist/chunk-BZKF7TXX.cjs.map +1 -0
  18. package/dist/chunk-LZJXEPEQ.js +58 -0
  19. package/dist/chunk-LZJXEPEQ.js.map +1 -0
  20. package/dist/{chunk-FR6COQXZ.cjs → chunk-N7ZIPW2N.cjs} +3 -3
  21. package/dist/{chunk-FR6COQXZ.cjs.map → chunk-N7ZIPW2N.cjs.map} +1 -1
  22. package/dist/chunk-NWS2CS3Y.cjs +61 -0
  23. package/dist/chunk-NWS2CS3Y.cjs.map +1 -0
  24. package/dist/{chunk-523O5UYB.js → chunk-Z5BAFHPU.js} +3 -3
  25. package/dist/{chunk-523O5UYB.js.map → chunk-Z5BAFHPU.js.map} +1 -1
  26. package/dist/{compact-session-4QHBBGEY.cjs → compact-session-2AETONOS.cjs} +12 -12
  27. package/dist/{compact-session-4QHBBGEY.cjs.map → compact-session-2AETONOS.cjs.map} +1 -1
  28. package/dist/{compact-session-PVAWTKKL.js → compact-session-F2UDGZCN.js} +3 -3
  29. package/dist/{compact-session-PVAWTKKL.js.map → compact-session-F2UDGZCN.js.map} +1 -1
  30. package/dist/cron.cjs +6 -5
  31. package/dist/cron.js +5 -4
  32. package/dist/eval.cjs +5 -4
  33. package/dist/eval.cjs.map +1 -1
  34. package/dist/eval.js +4 -3
  35. package/dist/eval.js.map +1 -1
  36. package/dist/index.cjs +38 -128
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.js +9 -104
  39. package/dist/index.js.map +1 -1
  40. package/dist/{inject-session-O24XGRR6.js → inject-session-E5UDFVLI.js} +3 -3
  41. package/dist/{inject-session-O24XGRR6.js.map → inject-session-E5UDFVLI.js.map} +1 -1
  42. package/dist/{inject-session-OMLFL2V3.cjs → inject-session-W4UKACYJ.cjs} +4 -4
  43. package/dist/{inject-session-OMLFL2V3.cjs.map → inject-session-W4UKACYJ.cjs.map} +1 -1
  44. package/dist/internal/budget/normalize-usage.d.ts +81 -0
  45. package/dist/internal/llm/anthropic-shared.d.ts +6 -0
  46. package/dist/internal/llm/openai.d.ts +3 -5
  47. package/dist/internal/llm/openrouter-system.d.ts +1 -0
  48. package/dist/internal/llm/types.d.ts +10 -5
  49. package/dist/models.cjs +5 -50
  50. package/dist/models.cjs.map +1 -1
  51. package/dist/models.js +3 -51
  52. package/dist/models.js.map +1 -1
  53. package/docs/error-codes.md +3 -2
  54. package/package.json +1 -1
  55. package/dist/chunk-CVEQ5366.cjs.map +0 -1
  56. package/dist/chunk-EVH3OUMY.js.map +0 -1
  57. package/dist/chunk-QY3CIMUV.cjs.map +0 -1
  58. package/dist/chunk-RTE2DD4S.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { enqueueSessionWrite } from './chunk-EXI7EHDN.js';
1
+ import { enqueueSessionWrite } from './chunk-7Y23B7XX.js';
2
2
  import { invalidateSessionCache } from './chunk-2XC6KDJM.js';
3
3
  import './chunk-XERM7PDQ.js';
4
4
  import { SessionTranscript } from './chunk-FTZTZBCU.js';
@@ -23,5 +23,5 @@ async function injectSessionTurn(opts) {
23
23
  }
24
24
 
25
25
  export { injectSessionTurn };
26
- //# sourceMappingURL=inject-session-O24XGRR6.js.map
27
- //# sourceMappingURL=inject-session-O24XGRR6.js.map
26
+ //# sourceMappingURL=inject-session-E5UDFVLI.js.map
27
+ //# sourceMappingURL=inject-session-E5UDFVLI.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/session/inject-session.ts"],"names":[],"mappings":";;;;;;;;;AAWA,eAAsB,kBAAkB,IAAA,EAMtB;AAChB,EAAA,MAAM,oBAAoB,IAAA,CAAK,GAAA,CAAI,KAAK,IAAA,CAAK,GAAA,CAAI,SAAS,YAAY;AACpE,IAAA,MAAM,QAAQ,MAAM,IAAA,CAAK,MAAM,WAAA,CAAY,IAAA,CAAK,IAAI,OAAO,CAAA;AAC3D,IAAA,MAAM,UAAA,GAAa,iBAAA,CAAkB,WAAA,CAAY,KAAA,EAAO;AAAA,MACtD,GAAA,EAAK,KAAK,GAAA,CAAI,GAAA;AAAA,MACd,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,KAAA,IAAS;AAAA,KAC1B,CAAA;AACD,IAAA,UAAA,CAAW,cAAA,CAAe,KAAK,QAAQ,CAAA;AACvC,IAAA,UAAA,CAAW,mBAAA,CAAoB,EAAE,IAAA,EAAM,IAAA,CAAK,eAAe,CAAA;AAC3D,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,aAAA,CAAc,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,UAAA,CAAW,OAAA,EAAQ,CAAE,KAAA,CAAM,KAAA,CAAM,MAAM,CAAC,CAAA;AACzF,IAAA,sBAAA,CAAuB,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,IAAI,OAAO,CAAA;AAAA,EACvD,CAAC,CAAA;AACH","file":"inject-session-O24XGRR6.js","sourcesContent":["/**\n * M51 (agent-builder) — inject a SYNTHETIC user+assistant pair into a session's persisted transcript\n * WITHOUT running an LLM turn. This is the Codex review-exit mechanism (`exit_success.xml` appended to\n * the parent thread history so follow-ups like \"fix finding 2\" work): the pair chains onto the DAG\n * leaf (M50 pattern) and the in-memory cache is invalidated so the NEXT send re-hydrates with it.\n */\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport { SessionTranscript } from \"../persistence/session-transcript.js\";\nimport { enqueueSessionWrite, invalidateSessionCache } from \"./agent-session.js\";\nimport type { CompactLocation } from \"./compact-session.js\";\n\nexport async function injectSessionTurn(opts: {\n store: SessionStore;\n loc: CompactLocation;\n sessionId: string;\n userText: string;\n assistantText: string;\n}): Promise<void> {\n await enqueueSessionWrite(opts.loc.cwd, opts.loc.agentId, async () => {\n const prior = await opts.store.readRecords(opts.loc.agentId);\n const transcript = SessionTranscript.fromRecords(prior, {\n cwd: opts.loc.cwd,\n sessionId: opts.sessionId,\n model: opts.loc.model ?? \"unknown\",\n });\n transcript.appendUserTurn(opts.userText);\n transcript.appendAssistantTurn({ text: opts.assistantText });\n await opts.store.appendRecords(opts.loc.agentId, transcript.records().slice(prior.length));\n invalidateSessionCache(opts.loc.cwd, opts.loc.agentId);\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/session/inject-session.ts"],"names":[],"mappings":";;;;;;;;;AAWA,eAAsB,kBAAkB,IAAA,EAMtB;AAChB,EAAA,MAAM,oBAAoB,IAAA,CAAK,GAAA,CAAI,KAAK,IAAA,CAAK,GAAA,CAAI,SAAS,YAAY;AACpE,IAAA,MAAM,QAAQ,MAAM,IAAA,CAAK,MAAM,WAAA,CAAY,IAAA,CAAK,IAAI,OAAO,CAAA;AAC3D,IAAA,MAAM,UAAA,GAAa,iBAAA,CAAkB,WAAA,CAAY,KAAA,EAAO;AAAA,MACtD,GAAA,EAAK,KAAK,GAAA,CAAI,GAAA;AAAA,MACd,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,KAAA,IAAS;AAAA,KAC1B,CAAA;AACD,IAAA,UAAA,CAAW,cAAA,CAAe,KAAK,QAAQ,CAAA;AACvC,IAAA,UAAA,CAAW,mBAAA,CAAoB,EAAE,IAAA,EAAM,IAAA,CAAK,eAAe,CAAA;AAC3D,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,aAAA,CAAc,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,UAAA,CAAW,OAAA,EAAQ,CAAE,KAAA,CAAM,KAAA,CAAM,MAAM,CAAC,CAAA;AACzF,IAAA,sBAAA,CAAuB,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,IAAI,OAAO,CAAA;AAAA,EACvD,CAAC,CAAA;AACH","file":"inject-session-E5UDFVLI.js","sourcesContent":["/**\n * M51 (agent-builder) — inject a SYNTHETIC user+assistant pair into a session's persisted transcript\n * WITHOUT running an LLM turn. This is the Codex review-exit mechanism (`exit_success.xml` appended to\n * the parent thread history so follow-ups like \"fix finding 2\" work): the pair chains onto the DAG\n * leaf (M50 pattern) and the in-memory cache is invalidated so the NEXT send re-hydrates with it.\n */\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport { SessionTranscript } from \"../persistence/session-transcript.js\";\nimport { enqueueSessionWrite, invalidateSessionCache } from \"./agent-session.js\";\nimport type { CompactLocation } from \"./compact-session.js\";\n\nexport async function injectSessionTurn(opts: {\n store: SessionStore;\n loc: CompactLocation;\n sessionId: string;\n userText: string;\n assistantText: string;\n}): Promise<void> {\n await enqueueSessionWrite(opts.loc.cwd, opts.loc.agentId, async () => {\n const prior = await opts.store.readRecords(opts.loc.agentId);\n const transcript = SessionTranscript.fromRecords(prior, {\n cwd: opts.loc.cwd,\n sessionId: opts.sessionId,\n model: opts.loc.model ?? \"unknown\",\n });\n transcript.appendUserTurn(opts.userText);\n transcript.appendAssistantTurn({ text: opts.assistantText });\n await opts.store.appendRecords(opts.loc.agentId, transcript.records().slice(prior.length));\n invalidateSessionCache(opts.loc.cwd, opts.loc.agentId);\n });\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkFR6COQXZ_cjs = require('./chunk-FR6COQXZ.cjs');
3
+ var chunkN7ZIPW2N_cjs = require('./chunk-N7ZIPW2N.cjs');
4
4
  var chunkYJNYDJ2T_cjs = require('./chunk-YJNYDJ2T.cjs');
5
5
  require('./chunk-ZAM7BJXJ.cjs');
6
6
  var chunkZWACQM5U_cjs = require('./chunk-ZWACQM5U.cjs');
@@ -10,7 +10,7 @@ require('./chunk-6LHQPOMI.cjs');
10
10
 
11
11
  // src/internal/session/inject-session.ts
12
12
  async function injectSessionTurn(opts) {
13
- await chunkFR6COQXZ_cjs.enqueueSessionWrite(opts.loc.cwd, opts.loc.agentId, async () => {
13
+ await chunkN7ZIPW2N_cjs.enqueueSessionWrite(opts.loc.cwd, opts.loc.agentId, async () => {
14
14
  const prior = await opts.store.readRecords(opts.loc.agentId);
15
15
  const transcript = chunkZWACQM5U_cjs.SessionTranscript.fromRecords(prior, {
16
16
  cwd: opts.loc.cwd,
@@ -25,5 +25,5 @@ async function injectSessionTurn(opts) {
25
25
  }
26
26
 
27
27
  exports.injectSessionTurn = injectSessionTurn;
28
- //# sourceMappingURL=inject-session-OMLFL2V3.cjs.map
29
- //# sourceMappingURL=inject-session-OMLFL2V3.cjs.map
28
+ //# sourceMappingURL=inject-session-W4UKACYJ.cjs.map
29
+ //# sourceMappingURL=inject-session-W4UKACYJ.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/session/inject-session.ts"],"names":["enqueueSessionWrite","SessionTranscript","invalidateSessionCache"],"mappings":";;;;;;;;;;;AAWA,eAAsB,kBAAkB,IAAA,EAMtB;AAChB,EAAA,MAAMA,sCAAoB,IAAA,CAAK,GAAA,CAAI,KAAK,IAAA,CAAK,GAAA,CAAI,SAAS,YAAY;AACpE,IAAA,MAAM,QAAQ,MAAM,IAAA,CAAK,MAAM,WAAA,CAAY,IAAA,CAAK,IAAI,OAAO,CAAA;AAC3D,IAAA,MAAM,UAAA,GAAaC,mCAAA,CAAkB,WAAA,CAAY,KAAA,EAAO;AAAA,MACtD,GAAA,EAAK,KAAK,GAAA,CAAI,GAAA;AAAA,MACd,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,KAAA,IAAS;AAAA,KAC1B,CAAA;AACD,IAAA,UAAA,CAAW,cAAA,CAAe,KAAK,QAAQ,CAAA;AACvC,IAAA,UAAA,CAAW,mBAAA,CAAoB,EAAE,IAAA,EAAM,IAAA,CAAK,eAAe,CAAA;AAC3D,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,aAAA,CAAc,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,UAAA,CAAW,OAAA,EAAQ,CAAE,KAAA,CAAM,KAAA,CAAM,MAAM,CAAC,CAAA;AACzF,IAAAC,wCAAA,CAAuB,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,IAAI,OAAO,CAAA;AAAA,EACvD,CAAC,CAAA;AACH","file":"inject-session-OMLFL2V3.cjs","sourcesContent":["/**\n * M51 (agent-builder) — inject a SYNTHETIC user+assistant pair into a session's persisted transcript\n * WITHOUT running an LLM turn. This is the Codex review-exit mechanism (`exit_success.xml` appended to\n * the parent thread history so follow-ups like \"fix finding 2\" work): the pair chains onto the DAG\n * leaf (M50 pattern) and the in-memory cache is invalidated so the NEXT send re-hydrates with it.\n */\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport { SessionTranscript } from \"../persistence/session-transcript.js\";\nimport { enqueueSessionWrite, invalidateSessionCache } from \"./agent-session.js\";\nimport type { CompactLocation } from \"./compact-session.js\";\n\nexport async function injectSessionTurn(opts: {\n store: SessionStore;\n loc: CompactLocation;\n sessionId: string;\n userText: string;\n assistantText: string;\n}): Promise<void> {\n await enqueueSessionWrite(opts.loc.cwd, opts.loc.agentId, async () => {\n const prior = await opts.store.readRecords(opts.loc.agentId);\n const transcript = SessionTranscript.fromRecords(prior, {\n cwd: opts.loc.cwd,\n sessionId: opts.sessionId,\n model: opts.loc.model ?? \"unknown\",\n });\n transcript.appendUserTurn(opts.userText);\n transcript.appendAssistantTurn({ text: opts.assistantText });\n await opts.store.appendRecords(opts.loc.agentId, transcript.records().slice(prior.length));\n invalidateSessionCache(opts.loc.cwd, opts.loc.agentId);\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/session/inject-session.ts"],"names":["enqueueSessionWrite","SessionTranscript","invalidateSessionCache"],"mappings":";;;;;;;;;;;AAWA,eAAsB,kBAAkB,IAAA,EAMtB;AAChB,EAAA,MAAMA,sCAAoB,IAAA,CAAK,GAAA,CAAI,KAAK,IAAA,CAAK,GAAA,CAAI,SAAS,YAAY;AACpE,IAAA,MAAM,QAAQ,MAAM,IAAA,CAAK,MAAM,WAAA,CAAY,IAAA,CAAK,IAAI,OAAO,CAAA;AAC3D,IAAA,MAAM,UAAA,GAAaC,mCAAA,CAAkB,WAAA,CAAY,KAAA,EAAO;AAAA,MACtD,GAAA,EAAK,KAAK,GAAA,CAAI,GAAA;AAAA,MACd,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,KAAA,IAAS;AAAA,KAC1B,CAAA;AACD,IAAA,UAAA,CAAW,cAAA,CAAe,KAAK,QAAQ,CAAA;AACvC,IAAA,UAAA,CAAW,mBAAA,CAAoB,EAAE,IAAA,EAAM,IAAA,CAAK,eAAe,CAAA;AAC3D,IAAA,MAAM,IAAA,CAAK,KAAA,CAAM,aAAA,CAAc,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,UAAA,CAAW,OAAA,EAAQ,CAAE,KAAA,CAAM,KAAA,CAAM,MAAM,CAAC,CAAA;AACzF,IAAAC,wCAAA,CAAuB,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,IAAI,OAAO,CAAA;AAAA,EACvD,CAAC,CAAA;AACH","file":"inject-session-W4UKACYJ.cjs","sourcesContent":["/**\n * M51 (agent-builder) — inject a SYNTHETIC user+assistant pair into a session's persisted transcript\n * WITHOUT running an LLM turn. This is the Codex review-exit mechanism (`exit_success.xml` appended to\n * the parent thread history so follow-ups like \"fix finding 2\" work): the pair chains onto the DAG\n * leaf (M50 pattern) and the in-memory cache is invalidated so the NEXT send re-hydrates with it.\n */\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport { SessionTranscript } from \"../persistence/session-transcript.js\";\nimport { enqueueSessionWrite, invalidateSessionCache } from \"./agent-session.js\";\nimport type { CompactLocation } from \"./compact-session.js\";\n\nexport async function injectSessionTurn(opts: {\n store: SessionStore;\n loc: CompactLocation;\n sessionId: string;\n userText: string;\n assistantText: string;\n}): Promise<void> {\n await enqueueSessionWrite(opts.loc.cwd, opts.loc.agentId, async () => {\n const prior = await opts.store.readRecords(opts.loc.agentId);\n const transcript = SessionTranscript.fromRecords(prior, {\n cwd: opts.loc.cwd,\n sessionId: opts.sessionId,\n model: opts.loc.model ?? \"unknown\",\n });\n transcript.appendUserTurn(opts.userText);\n transcript.appendAssistantTurn({ text: opts.assistantText });\n await opts.store.appendRecords(opts.loc.agentId, transcript.records().slice(prior.length));\n invalidateSessionCache(opts.loc.cwd, opts.loc.agentId);\n });\n}\n"]}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * normalizeUsage — convert provider-shaped raw `usage` object to
3
+ * canonical `TokenUsage`. Ports Hermes Agent's `normalize_usage`
4
+ * (reference/peer-agent/agent/usage_pricing.py:672-742).
5
+ *
6
+ * Handles 3 API shapes:
7
+ * - Anthropic Messages: 4 explicit buckets (input/output/cache_read/cache_creation).
8
+ * - OpenAI Chat Completions: prompt_tokens INCLUDES cache; subtract cached_tokens.
9
+ * - OpenAI Responses (Codex): input_tokens INCLUDES cache; same subtraction.
10
+ *
11
+ * Edge cases:
12
+ * - a peer#10266 — OpenAI-compat proxies (OpenRouter, a peer vendor AI Gateway,
13
+ * a peer) routing Claude expose Anthropic-style top-level fields
14
+ * (cache_read_input_tokens / cache_creation_input_tokens). Both
15
+ * locations are checked with top-level fallback.
16
+ * - Null/undefined fields → 0 via `int()` coerce.
17
+ * - String token counts → parsed via int.
18
+ * - Negative values → clamped to 0 (defensive against proxy bugs).
19
+ *
20
+ * @internal
21
+ */
22
+ export type ApiMode = "anthropic_messages" | "openai_chat_completions" | "openai_responses";
23
+ /**
24
+ * Guess which usage shape a provider reports, from its name alone.
25
+ *
26
+ * Matching is case-insensitive and exact — `"anthropic"`, `"claude"` and `"bedrock_anthropic"`
27
+ * give `"anthropic_messages"`; `"openai-codex"` and `"codex"` give `"openai_responses"`. Every
28
+ * other name, including ones this SDK has never seen, falls through to
29
+ * `"openai_chat_completions"`. There is no unknown result, so a wrong guess is silent: it is
30
+ * read as a Chat Completions payload, whose fields are absent, and the tokens come back as 0.
31
+ *
32
+ * The default is right for the OpenAI-compatible majority (openai, openrouter, deepseek, and the
33
+ * compat endpoints of google, ollama and lmstudio). When it is not, pass `apiMode` to
34
+ * `normalizeUsage` explicitly instead of relying on the name.
35
+ */
36
+ export declare function inferApiMode(provider: string): ApiMode;
37
+ /**
38
+ * Convert a provider's raw `usage` object into the SDK's canonical `TokenUsage`.
39
+ *
40
+ * Never throws and never reports failure. `null`, `undefined` and any non-object argument return
41
+ * all-zero usage, which is indistinguishable from a real response that used no tokens — so this
42
+ * is not the place to detect a malformed payload.
43
+ *
44
+ * `opts.apiMode` selects the reader; when omitted it is derived from `opts.provider` via
45
+ * `inferApiMode`, whose fallback is Chat Completions. Pass it explicitly for any provider whose
46
+ * name does not identify its wire shape.
47
+ *
48
+ * The shapes differ in one way that matters: Anthropic reports cache tokens in buckets separate
49
+ * from `input_tokens`, while both OpenAI shapes report a prompt total that already includes
50
+ * them. For the OpenAI readers the cache buckets are subtracted, so `inputTokens` is always the
51
+ * uncached portion and `inputTokens + cacheReadTokens + cacheWriteTokens` reconstructs the
52
+ * provider's prompt total. The subtraction is floored at 0, so a payload whose cache counts
53
+ * exceed its prompt total yields 0 rather than a negative.
54
+ *
55
+ * Every field is coerced: numbers are truncated toward zero, numeric strings are parsed, negative
56
+ * and non-finite values become 0, and anything else becomes 0.
57
+ *
58
+ * `totalTokens` is computed here as input + output + both cache buckets; a `total_tokens` the
59
+ * provider sent is ignored. Reasoning tokens are reported separately but are NOT added again —
60
+ * providers already count them inside output. `cacheReadTokens`, `cacheWriteTokens` and
61
+ * `reasoningTokens` are omitted from the result when they are 0, so absent means zero, not
62
+ * unknown.
63
+ *
64
+ * Chat Completions also accepts Anthropic-style top-level `cache_read_input_tokens` /
65
+ * `cache_creation_input_tokens`, which OpenAI-compatible proxies emit when they route Claude.
66
+ * The nested `prompt_tokens_details` values win; the top-level fields are consulted only when
67
+ * those are 0 or missing.
68
+ */
69
+ export declare function normalizeUsage(rawUsage: unknown, opts: {
70
+ provider: string;
71
+ apiMode?: ApiMode;
72
+ }): TokenUsage;
73
+ /** The prompt of a Chat Completions `usage` object, split the way {@link splitChatCompletionsPrompt} reports it. */
74
+ export interface ChatCompletionsPromptSplit {
75
+ /** `prompt_tokens` minus both cache buckets, floored at 0; absent when `prompt_tokens` is. */
76
+ readonly inputTokens?: number;
77
+ /** Absent when the payload carries neither cache-read field. */
78
+ readonly cacheReadTokens?: number;
79
+ /** Absent when the payload carries neither cache-write field. */
80
+ readonly cacheWriteTokens?: number;
81
+ }
@@ -20,6 +20,8 @@ export interface AnthropicResponseLike {
20
20
  usage?: {
21
21
  input_tokens?: number;
22
22
  output_tokens?: number;
23
+ cache_read_input_tokens?: number;
24
+ cache_creation_input_tokens?: number;
23
25
  };
24
26
  }
25
27
  export declare function extractAnthropicText(response: AnthropicResponseLike): string;
@@ -62,6 +64,8 @@ export declare function handleAnthropicResponse(args: {
62
64
  stopReason: LlmStopReason;
63
65
  inputTokens?: number;
64
66
  outputTokens?: number;
67
+ cacheReadTokens?: number;
68
+ cacheWriteTokens?: number;
65
69
  }>;
66
70
  export declare function consumeAnthropicResponse(response: Response): Promise<{
67
71
  text: string;
@@ -69,6 +73,8 @@ export declare function consumeAnthropicResponse(response: Response): Promise<{
69
73
  stopReason: LlmStopReason;
70
74
  inputTokens?: number;
71
75
  outputTokens?: number;
76
+ cacheReadTokens?: number;
77
+ cacheWriteTokens?: number;
72
78
  }>;
73
79
  export declare function parseHttpErrorBody(response: Response): Promise<unknown>;
74
80
  export interface AnthropicTextBlock {
@@ -70,6 +70,7 @@ interface OpenAIDeltaChunk {
70
70
  */
71
71
  prompt_tokens_details?: {
72
72
  cached_tokens?: number;
73
+ cache_write_tokens?: number;
73
74
  };
74
75
  completion_tokens_details?: {
75
76
  reasoning_tokens?: number;
@@ -106,11 +107,8 @@ declare class OpenAIStreamAccumulator {
106
107
  private readonly allowedToolNames?;
107
108
  private text;
108
109
  private stopReason;
109
- private inputTokens?;
110
- private outputTokens?;
111
- private cacheReadTokens?;
112
- private cacheWriteTokens?;
113
- private reasoningTokens?;
110
+ /** The stream's `usage` object, a later chunk replacing an earlier one per top-level field. */
111
+ private usage;
114
112
  private readonly toolCalls;
115
113
  /** R7: present only when recovery is enabled AND the request declares tools — holds suspected
116
114
  * leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
@@ -0,0 +1 @@
1
+ export {};
@@ -83,14 +83,15 @@ export interface LlmMessage {
83
83
  * annotation in the Anthropic wire body, which lets Anthropic bill
84
84
  * subsequent same-content requests at the cache-read rate (1-3x discount).
85
85
  *
86
- * For providers that don't support prompt caching (OpenAI, OpenRouter, etc),
87
- * the array is joined into a single string at the wire boundary so the
88
- * upstream contract stays unchanged. Back-compat: `LlmRequest.system` still
86
+ * OpenRouter receives the blocks as `content` parts of the `system` message,
87
+ * with `cache_control` on each cacheable part. Every other chat-completions
88
+ * provider (OpenAI and the compatible ones) gets the array joined into a
89
+ * single string at the wire boundary. Back-compat: `LlmRequest.system` still
89
90
  * accepts a plain `string` (pre-T3.5 callers compile unchanged).
90
91
  */
91
92
  export interface LlmSystemBlock {
92
93
  text: string;
93
- /** When `true`, ask the provider to cache this block (Anthropic only). */
94
+ /** When `true`, ask the provider to cache this block (Anthropic, and OpenRouter content parts). */
94
95
  cacheable?: boolean;
95
96
  }
96
97
  /**
@@ -204,11 +205,15 @@ export interface LlmFinish {
204
205
  stopReason: LlmStopReason;
205
206
  text: string;
206
207
  toolCalls: LlmToolCallPart[];
208
+ /**
209
+ * Uncached input tokens, the way Anthropic reports them. The chat-completions client subtracts
210
+ * the cache buckets from the provider's cache-inclusive `prompt_tokens` to match.
211
+ */
207
212
  inputTokens?: number;
208
213
  outputTokens?: number;
209
214
  /** Cache read tokens (Anthropic prompt caching / OpenAI cached). ADR D376. */
210
215
  cacheReadTokens?: number;
211
- /** Cache creation tokens (Anthropic only). ADR D376. */
216
+ /** Cache creation tokens (Anthropic, and OpenRouter's `cache_write_tokens`). ADR D376. */
212
217
  cacheWriteTokens?: number;
213
218
  /** Reasoning tokens (OpenAI o-series). ADR D376. */
214
219
  reasoningTokens?: number;
package/dist/models.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunk72CPLZPC_cjs = require('./chunk-72CPLZPC.cjs');
4
+ var chunkNWS2CS3Y_cjs = require('./chunk-NWS2CS3Y.cjs');
4
5
  var chunk3EE6LVWT_cjs = require('./chunk-3EE6LVWT.cjs');
5
6
  var chunkHU666T3Q_cjs = require('./chunk-HU666T3Q.cjs');
6
7
  require('./chunk-NVXXWAU7.cjs');
@@ -15,55 +16,6 @@ var fs = require('fs');
15
16
  var os = require('os');
16
17
  var path = require('path');
17
18
 
18
- // src/internal/llm/model-capabilities.ts
19
- var CONSERVATIVE_DEFAULTS = {
20
- supportsVision: false,
21
- supportsStructuredOutput: false,
22
- supportsToolUse: false,
23
- supportsCacheControl: false,
24
- maxContextTokens: 4096,
25
- maxOutputTokens: 4096
26
- };
27
- var ROUTING_PREFIXES = ["openrouter/", "vertex/", "bedrock/"];
28
- function capsFromCatalog(m) {
29
- return {
30
- supportsVision: m.modalities?.input?.includes("image") ?? m.attachment ?? false,
31
- supportsStructuredOutput: m.structured_output ?? false,
32
- supportsToolUse: m.tool_call ?? false,
33
- supportsCacheControl: m.cache_control ?? false,
34
- maxContextTokens: m.limit?.context ?? CONSERVATIVE_DEFAULTS.maxContextTokens,
35
- maxOutputTokens: m.limit?.output ?? CONSERVATIVE_DEFAULTS.maxOutputTokens
36
- };
37
- }
38
- function resolveModelCapabilities(modelId) {
39
- const bare = stripVariantSuffix(stripRoutingPrefix(modelId));
40
- const fromIndex = chunkHU666T3Q_cjs.getCatalogModelInfo(bare);
41
- if (fromIndex !== void 0) return capsFromCatalog(fromIndex);
42
- const withVendor = inferVendorPrefix(bare);
43
- if (withVendor !== bare) {
44
- const vendored = chunkHU666T3Q_cjs.getCatalogModelInfo(withVendor);
45
- if (vendored !== void 0) return capsFromCatalog(vendored);
46
- }
47
- return CONSERVATIVE_DEFAULTS;
48
- }
49
- function stripRoutingPrefix(modelId) {
50
- for (const prefix of ROUTING_PREFIXES) {
51
- if (modelId.startsWith(prefix)) return modelId.slice(prefix.length);
52
- }
53
- return modelId;
54
- }
55
- function stripVariantSuffix(modelId) {
56
- const i = modelId.indexOf(":");
57
- return i >= 0 ? modelId.slice(0, i) : modelId;
58
- }
59
- function inferVendorPrefix(bare) {
60
- if (bare.startsWith("claude")) return `anthropic/${bare}`;
61
- if (bare.startsWith("gpt-") || bare.startsWith("o1") || bare.startsWith("o3"))
62
- return `openai/${bare}`;
63
- if (bare.startsWith("gemini")) return `google/${bare}`;
64
- return bare;
65
- }
66
-
67
19
  // src/internal/llm/model-option.ts
68
20
  var ACRONYMS = /* @__PURE__ */ new Set(["gpt", "ai", "hd", "ui", "api", "sdk", "llm", "xl"]);
69
21
  function prettyToken(token) {
@@ -283,6 +235,10 @@ function getModelInfo(modelId) {
283
235
  return chunkHU666T3Q_cjs.getCatalogModelInfo(modelId);
284
236
  }
285
237
 
238
+ Object.defineProperty(exports, "resolveModelCapabilities", {
239
+ enumerable: true,
240
+ get: function () { return chunkNWS2CS3Y_cjs.resolveModelCapabilities; }
241
+ });
286
242
  Object.defineProperty(exports, "parseModelId", {
287
243
  enumerable: true,
288
244
  get: function () { return chunk3EE6LVWT_cjs.parseModelId; }
@@ -290,7 +246,6 @@ Object.defineProperty(exports, "parseModelId", {
290
246
  exports.getModelInfo = getModelInfo;
291
247
  exports.humanizeModelName = humanizeModelName;
292
248
  exports.refreshModelCatalog = refreshModelCatalog;
293
- exports.resolveModelCapabilities = resolveModelCapabilities;
294
249
  exports.toModelOption = toModelOption;
295
250
  //# sourceMappingURL=models.cjs.map
296
251
  //# sourceMappingURL=models.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/llm/model-capabilities.ts","../src/internal/llm/model-option.ts","../src/internal/providers/catalog-source-models-dev.ts"],"names":["getCatalogModelInfo","parseModelId","join","homedir","createHash","path","mkdirSync","dirname","randomBytes","writeFileSync","renameSync","unlinkSync","loadProviderCatalog","getProviderProfile","catalogModelSchema","patchModelInfo","diag","readFileSync","statSync","Retry","registerBuiltins","boundFetch"],"mappings":"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","file":"models.cjs","sourcesContent":["/**\n * T3.10c — Model capability registry (DR3 #17).\n *\n * Typed per-model flags that let the SDK gate features at the boundary\n * (before hitting the provider) instead of letting opaque 400s surface.\n *\n * Resolution algorithm:\n * 1. Strip routing prefixes (openrouter/, vertex/, bedrock/) AND the\n * OpenRouter `:variant` suffix (:free/:nitro/…) to find the bare vendor id.\n * 2. Exact match in the `EXACT` catalog → return entry.\n * 3. Vendor inference (e.g., `claude-*` → `anthropic/claude-*`) → return entry.\n * 4. No match → conservative defaults (all false, minimum tokens).\n *\n * Module is internal, but `ModelCapabilities` + `resolveModelCapabilities` are\n * re-exported publicly via the `@theokit/sdk/models` subpath (see their @public tags).\n */\n\n/**\n * Per-model capability shape. Consumers use this to gate features at\n * the SDK boundary (before request construction, not after 400).\n *\n * @public\n */\nexport interface ModelCapabilities {\n supportsVision: boolean;\n supportsStructuredOutput: boolean;\n supportsToolUse: boolean;\n supportsCacheControl: boolean;\n maxContextTokens: number;\n maxOutputTokens: number;\n}\n\nconst CONSERVATIVE_DEFAULTS: ModelCapabilities = {\n supportsVision: false,\n supportsStructuredOutput: false,\n supportsToolUse: false,\n supportsCacheControl: false,\n maxContextTokens: 4096,\n maxOutputTokens: 4096,\n};\n\n/** Routing prefixes to strip to find the underlying vendor model. */\nconst ROUTING_PREFIXES = [\"openrouter/\", \"vertex/\", \"bedrock/\"] as const;\n\nimport { getCatalogModelInfo } from \"../providers/catalog-loader.js\";\nimport type { CatalogModel } from \"../providers/catalog-schema.js\";\n\n/** Map a catalog model block (models.dev shape) to the SDK's ModelCapabilities (M44 — catalog-backed). */\nfunction capsFromCatalog(m: CatalogModel): ModelCapabilities {\n return {\n supportsVision: m.modalities?.input?.includes(\"image\") ?? m.attachment ?? false,\n supportsStructuredOutput: m.structured_output ?? false,\n supportsToolUse: m.tool_call ?? false,\n supportsCacheControl: (m as { cache_control?: boolean }).cache_control ?? false,\n maxContextTokens: m.limit?.context ?? CONSERVATIVE_DEFAULTS.maxContextTokens,\n maxOutputTokens: m.limit?.output ?? CONSERVATIVE_DEFAULTS.maxOutputTokens,\n };\n}\n\n/**\n * Resolve per-model capability flags (vision/structured-output/tool-use/cache +\n * `maxContextTokens`/`maxOutputTokens`) for a model id. Pure, sync, offline (a\n * static catalog — no network). Strips routing prefixes (`openrouter/`/`vertex/`/\n * `bedrock/`) and OpenRouter `:variant` suffixes before lookup; unknown models get\n * conservative defaults. Public via `@theokit/sdk/models`.\n *\n * @public\n */\nexport function resolveModelCapabilities(modelId: string): ModelCapabilities {\n const bare = stripVariantSuffix(stripRoutingPrefix(modelId));\n // M44 — the catalog model-info index replaced the hand-curated EXACT map (single source of truth; the\n // vendored provider-catalog.json carries the migrated data — parity-tested against the old map snapshot).\n const fromIndex = getCatalogModelInfo(bare);\n if (fromIndex !== undefined) return capsFromCatalog(fromIndex);\n // Routing-prefixed models (vertex/claude-3-5-sonnet) strip to bare\n // model name without vendor. Try inferring the vendor from the name.\n const withVendor = inferVendorPrefix(bare);\n if (withVendor !== bare) {\n const vendored = getCatalogModelInfo(withVendor);\n if (vendored !== undefined) return capsFromCatalog(vendored);\n }\n return CONSERVATIVE_DEFAULTS;\n}\n\nfunction stripRoutingPrefix(modelId: string): string {\n for (const prefix of ROUTING_PREFIXES) {\n if (modelId.startsWith(prefix)) return modelId.slice(prefix.length);\n }\n return modelId;\n}\n\n/**\n * Strip an OpenRouter variant suffix (`:free`/`:nitro`/`:floor`/`:beta`/…) so the\n * catalog lookup hits the underlying model. Model slugs contain no `:` except the\n * variant separator, so cutting at the first `:` is safe.\n */\nfunction stripVariantSuffix(modelId: string): string {\n const i = modelId.indexOf(\":\");\n return i >= 0 ? modelId.slice(0, i) : modelId;\n}\n\n/** Infer vendor prefix from bare model name for routing-prefixed lookups. */\nfunction inferVendorPrefix(bare: string): string {\n if (bare.startsWith(\"claude\")) return `anthropic/${bare}`;\n if (bare.startsWith(\"gpt-\") || bare.startsWith(\"o1\") || bare.startsWith(\"o3\"))\n return `openai/${bare}`;\n if (bare.startsWith(\"gemini\")) return `google/${bare}`;\n return bare;\n}\n","import { parseModelId } from \"./model-identifier.js\";\n\n/**\n * A UI-friendly model option — the shape a `<select>`/dropdown consumes.\n *\n * Public via `@theokit/sdk/models`.\n *\n * @public\n */\nexport interface ModelOption {\n /** The original model id (what you pass back to the SDK). */\n value: string;\n /** Best-effort human label (see {@link humanizeModelName}). */\n label: string;\n /** Provider from the slug prefix, or `undefined` when none. */\n provider: string | undefined;\n}\n\n/** Tokens rendered upper-case rather than title-case. */\nconst ACRONYMS = new Set([\"gpt\", \"ai\", \"hd\", \"ui\", \"api\", \"sdk\", \"llm\", \"xl\"]);\n\nfunction prettyToken(token: string): string {\n if (ACRONYMS.has(token.toLowerCase())) return token.toUpperCase();\n return token.charAt(0).toUpperCase() + token.slice(1);\n}\n\n/**\n * Turn a model id into a best-effort human label: strip the routing/vendor\n * prefix to the core model segment, split on `-`/`_`/`.`/whitespace, title-case\n * each token (known acronyms upper-cased), and append an OpenRouter `:variant`\n * in parentheses. Deterministic, pure, dependency-free.\n *\n * Best-effort, NOT vendor-canonical: `\"anthropic/claude-3-5-sonnet\"` →\n * `\"Claude 3 5 Sonnet\"`. A UI wanting exact marketing names overrides per id.\n *\n * Public via `@theokit/sdk/models`.\n *\n * @public\n */\nexport function humanizeModelName(modelId: string): string {\n const { name } = parseModelId(modelId);\n if (name.length === 0) return \"\";\n const colon = name.indexOf(\":\");\n // Strip a trailing slash so a typo'd `gpt-4o/` keeps its name (not lost to an\n // empty last segment).\n //\n // Done by index rather than with `/\\/+$/`, and the difference is not stylistic: that pattern\n // consumes slashes to the end of the string at every start position and then backtracks\n // looking for an end-anchor that is not there (CodeQL js/polynomial-redos #6). This function\n // is `@public` and takes a caller-supplied model id. Measured on a run of slashes followed by\n // one other character: 25_000 took 496 ms, 100_000 took 31_305 ms — half a minute of pinned\n // CPU to render a label. Walking back from the end is linear and cannot backtrack.\n const base = trimTrailingSlashes(colon >= 0 ? name.slice(0, colon) : name);\n const variant = colon >= 0 ? name.slice(colon + 1) : \"\";\n const lastSlash = base.lastIndexOf(\"/\");\n const core = lastSlash >= 0 ? base.slice(lastSlash + 1) : base;\n const label = core\n .split(/[-_.\\s]+/)\n .filter((t) => t.length > 0)\n .map(prettyToken)\n .join(\" \");\n if (label.length === 0) return variant; // no base label (e.g. \":free\") → bare variant\n return variant.length > 0 ? `${label} (${variant})` : label;\n}\n\n/**\n * Build a {@link ModelOption} (`{ value, label, provider }`) for a model id —\n * a dropdown-ready entry composing {@link humanizeModelName} + `parseModelId`.\n *\n * Public via `@theokit/sdk/models`.\n *\n * @public\n */\nexport function toModelOption(modelId: string): ModelOption {\n return {\n value: modelId,\n label: humanizeModelName(modelId),\n provider: parseModelId(modelId).provider,\n };\n}\n\nconst SLASH_CODE = 47; // \"/\"\n\n/** Removes trailing `/` characters in one linear pass. See the call site for why not a regex. */\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === SLASH_CODE) end--;\n return end === value.length ? value : value.slice(0, end);\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport { Retry } from \"../../retry.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { boundFetch } from \"../runtime-fetch.js\";\nimport { registerBuiltins } from \"./builtin/index.js\";\nimport { getCatalogModelInfo, loadProviderCatalog, patchModelInfo } from \"./catalog-loader.js\";\nimport { catalogModelSchema } from \"./catalog-schema.js\";\nimport { getProviderProfile } from \"./registry.js\";\n\n/**\n * M44 — the OPTIONAL models.dev catalog source. Explicit opt-in ONLY (`refreshModelCatalog()`): the SDK\n * NEVER fetches at startup or per-request — the vendored catalog + any existing disk cache are the offline\n * base, and every failure mode fails CLOSED back to them (ROADMAP constraint: no runtime hard network dep).\n *\n * The models.dev consumption mechanism:\n * disk cache with mtime TTL, atomic tempfile+rename write, corrupt-cache delete-and-fall-through, kill-switch\n * env. Library adaptations: NO background refresh loop (a consumer composes refresh with the SDK's cron);\n * TTL 1h (not 5min — no background refresher to keep it warm); no cross-process flock v1 (atomic rename +\n * idempotent upstream content — at most one wasted fetch).\n *\n * @internal (the public surface is `refreshModelCatalog` re-exported via `@theokit/sdk/models`)\n */\n\nconst DEFAULT_URL = \"https://models.dev/api.json\";\n\n/**\n * Ceiling on a fetched catalog, before it is parsed or written to the cache.\n *\n * The response body was read with `res.text()` and persisted with no bound, so a host serving a\n * multi-gigabyte document would have been materialised in memory and then written to disk. The\n * default URL is trusted, but `THEOKIT_MODELS_URL` lets an operator point this anywhere, and a\n * limit is the difference between \"we only fetch from a host we trust\" and \"we cannot be hurt by\n * one we do not\".\n *\n * 32 MiB is roughly 40x the real catalog (~800 KiB in 2026-08), so it bounds the failure without\n * being a number anyone has to tune when models.dev grows.\n */\nconst MAX_CATALOG_BYTES = 32 * 1024 * 1024;\nconst TTL_MS = 60 * 60 * 1000; // 1 hour\nconst FETCH_TIMEOUT_MS = 10_000;\n\n/**\n * Arguments to `refreshModelCatalog`, the SDK's only network trigger for model metadata.\n *\n * `url` wins over the `THEOKIT_MODELS_URL` environment variable, which in turn wins over\n * `https://models.dev/api.json`. A non-default URL gets its own cache file, named by a hash of\n * the URL, so switching sources never reads another source's cache.\n *\n * `force` skips only the freshness check — it does not skip the kill switch. When\n * `THEOKIT_DISABLE_MODELS_FETCH` is set to anything other than an empty string, `\"0\"` or\n * `\"false\"`, the call returns `source: \"skipped\"` with no fetch and no cache read, whatever\n * `force` says.\n *\n * Without `force`, a cache file whose mtime is under an hour old is served directly and no\n * request is made.\n *\n * `deps` exists so tests can drive the fetch and the clock; production leaves it unset.\n */\nexport interface RefreshModelCatalogOptions {\n /** Override the source URL (`THEOKIT_MODELS_URL` env also honored). */\n url?: string;\n /** Bypass the TTL freshness gate. */\n force?: boolean;\n /** Injected for tests. */\n deps?: { fetch?: typeof fetch; now?: () => number };\n}\n\n/**\n * What `refreshModelCatalog` did. It never throws on a failed refresh, so this is the only place\n * the outcome is reported.\n *\n * `source` is `\"network\"` only when a request succeeded, its body parsed as JSON, and the models\n * were patched in. Every failure path — kill switch aside — returns `\"cache\"`: an HTTP error\n * after retries, a timeout, an unparseable body, or a patch that threw. `\"cache\"` therefore means\n * \"serving what was already there\", which may be a fresh cache, a stale one, or nothing but the\n * vendored catalog. Read `models` to tell those apart. `\"skipped\"` means the kill switch was set.\n *\n * `models` counts the models patched into the index during this call, not the catalog's size. It\n * counts each model once even when the provider has aliases and the model is written under several\n * index keys, and it excludes models whose payload failed schema validation and models under\n * providers the SDK does not know. It is 0 whenever there was no cache to fall back on, and 0 for\n * `\"skipped\"`.\n *\n * A cache write that fails is logged and otherwise ignored: the result can still say `\"network\"`\n * with the data live in memory and nothing on disk.\n */\nexport interface RefreshModelCatalogResult {\n /** Where the data came from: a fresh network fetch, the still-fresh disk cache, or skipped entirely. */\n source: \"network\" | \"cache\" | \"skipped\";\n /** How many models were patched into the index. */\n models: number;\n}\n\n/** The cache file for a source URL (custom URLs get a hash-suffixed name). */\nexport function cachePathFor(url: string): string {\n // M44 L8 fix — honor the SDK home override so tests and multi-home setups never touch the real ~/.theokit.\n const base = process.env.THEOKIT_HOME?.trim() || join(homedir(), \".theokit\");\n const dir = join(base, \"cache\", \"models-dev\");\n if (url === DEFAULT_URL) return join(dir, \"api.json\");\n const hash = createHash(\"sha256\").update(url).digest(\"hex\").slice(0, 12);\n return join(dir, `api-${hash}.json`);\n}\n\n/** Atomic write: temp file + rename (crash mid-write never leaves a partial cache). */\nfunction writeCacheAtomic(path: string, body: string): void {\n mkdirSync(dirname(path), { recursive: true });\n const tmp = `${path}.tmp-${randomBytes(6).toString(\"hex\")}`;\n try {\n writeFileSync(tmp, body);\n renameSync(tmp, path);\n } catch (err) {\n try {\n unlinkSync(tmp);\n } catch {\n // best effort\n }\n throw err;\n }\n}\n\n/**\n * M44 H3 fix — models.dev provider ids ≠ theokit ids (google↔google-gemini, zai↔zhipu, togetherai↔together,\n * fireworks-ai↔fireworks, amazon-bedrock↔bedrock, google-vertex↔vertex). The RUNTIME mapping mirrors the\n * maintenance script's table; targets resolve against the CATALOG entries (id + aliases — the same namespace\n * the vendored index uses) first, then the registry.\n */\nconst MODELS_DEV_ID_MAP: Record<string, string> = {\n google: \"google-gemini\",\n zai: \"zhipu\",\n togetherai: \"together\",\n \"fireworks-ai\": \"fireworks\",\n \"amazon-bedrock\": \"bedrock\",\n \"google-vertex\": \"vertex\",\n};\n\nlet _catalogTargets: Map<string, { keys: string[] }> | undefined;\n/** external-id → the index keys (entry id + aliases) its models patch under. Lazy; catalog-first. */\nfunction catalogTargets(): Map<string, { keys: string[] }> {\n if (_catalogTargets !== undefined) return _catalogTargets;\n _catalogTargets = new Map();\n try {\n for (const entry of Object.values(loadProviderCatalog())) {\n const keys = [entry.id, ...(entry.aliases ?? [])];\n for (const k of keys) {\n if (!_catalogTargets.has(k)) _catalogTargets.set(k, { keys });\n }\n }\n } catch {\n // catalog unavailable — registry fallback below still works\n }\n return _catalogTargets;\n}\n\nfunction resolvePatchKeys(externalId: string): string[] | undefined {\n const mapped = MODELS_DEV_ID_MAP[externalId] ?? externalId;\n const fromCatalog = catalogTargets().get(mapped);\n if (fromCatalog !== undefined) return fromCatalog.keys;\n const profile = getProviderProfile(mapped);\n if (profile !== undefined) return [profile.name, ...(profile.aliases ?? [])];\n return undefined;\n}\n\n/** Parse + patch the index from an api.json payload. Unknown providers are skipped with WARN (once each). */\n// PRE-EXISTING debt, exposed when M75 fixed the Biome config that used to abort before\n// sweeping these files (a nested root under refactor/). It is not new code and was not touched\n// by M75; refactoring SDK internals without review would trade a visible problem for a diff\n// risky. Tracked in usetheodev/theokit-sdk#151.\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: validates and merges an UNTRUSTED upstream payload (models.dev api.json) shape by shape, dropping a single bad model rather than the file. Each branch is a distinct malformed input; merging them loses which one was seen.\nfunction patchIndexFromApiJson(raw: unknown): number {\n if (typeof raw !== \"object\" || raw === null) return 0;\n let patched = 0;\n const skipped: string[] = [];\n for (const [providerId, provider] of Object.entries(raw as Record<string, unknown>)) {\n const models = (provider as { models?: Record<string, unknown> })?.models;\n if (models === undefined || typeof models !== \"object\") continue;\n // Enrich ONLY providers the SDK knows — models.dev's npm/api fields cannot be mapped to a theokit\n // apiMode/authType safely, so unknown providers are data we cannot route.\n const keys = resolvePatchKeys(providerId);\n if (keys === undefined) {\n skipped.push(providerId);\n continue;\n }\n for (const [modelId, rawModel] of Object.entries(models)) {\n const parsed = catalogModelSchema.safeParse(rawModel);\n if (!parsed.success) continue; // malformed model — drop silently (live data, additive drift expected)\n for (const key of keys) patchModelInfo(`${key}/${modelId}`, parsed.data);\n patched++;\n }\n }\n if (skipped.length > 0) {\n diag(\n `[theokit-sdk] WARN: models-dev refresh skipped ${skipped.length} unknown provider(s) (e.g. ${skipped.slice(0, 3).join(\", \")})\\n`,\n );\n }\n return patched;\n}\n\n/**\n * Load an existing disk cache into the index (called by refresh when fresh, or opportunistically by a\n * consumer at startup — NEVER fetches). Corrupt cache → delete + fall through to vendored data.\n */\nexport function loadCacheIntoIndex(url: string = DEFAULT_URL): number {\n const path = cachePathFor(url);\n let body: string;\n try {\n body = readFileSync(path, \"utf-8\");\n } catch {\n return 0; // no cache — vendored data only\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n // corrupt cache: delete and fall through to the vendored catalog.\n // M44 L9 fix — only a PARSE failure is cache corruption; a patch error must never delete a valid cache.\n try {\n unlinkSync(path);\n } catch {\n // best effort\n }\n diag(`[theokit-sdk] WARN: corrupt models-dev cache deleted (${path})\\n`);\n return 0;\n }\n try {\n return patchIndexFromApiJson(parsed);\n } catch (err) {\n diag(`[theokit-sdk] WARN: models-dev cache patch failed (${(err as Error).message})\\n`);\n return 0;\n }\n}\n\n/**\n * The kill switch, read from the environment.\n *\n * Truthy-only by design (M44 L11): a presence check would treat `THEOKIT_DISABLE_MODELS_FETCH=0`\n * and `=false` as \"disabled\", which is the opposite of what anyone writing them means.\n */\nfunction fetchDisabledByEnv(): boolean {\n const kill = process.env.THEOKIT_DISABLE_MODELS_FETCH;\n return kill !== undefined && kill !== \"\" && kill !== \"0\" && kill.toLowerCase() !== \"false\";\n}\n\n/**\n * The TTL gate: a cache file younger than {@link TTL_MS} serves without touching the network.\n *\n * Returns `undefined` when there is no usable cache — including when `statSync` throws, which is\n * how \"no cache file\" arrives. That is the ONE failure this step owns, and its fallback is simply\n * to let the caller proceed to the fetch.\n */\nfunction servedFromFreshCache(\n url: string,\n path: string,\n now: () => number,\n): RefreshModelCatalogResult | undefined {\n try {\n if (now() - statSync(path).mtimeMs >= TTL_MS) return undefined;\n } catch {\n return undefined; // no cache — proceed to fetch\n }\n return { source: \"cache\", models: loadCacheIntoIndex(url) };\n}\n\n/**\n * Fetch the catalog, bound its size, and prove it parses before anyone persists it.\n *\n * Returns `undefined` after logging when ANY of that fails — timeout, non-2xx, oversize, unparseable\n * body. The fallback this step owns is \"serve what we already have\", and the caller applies it.\n * Splitting the step from its fallback was the objection recorded in the suppression this replaces;\n * the fallback is not separated, it is named — `undefined` means \"this step could not produce a\n * body\", and there is exactly one thing to do about that.\n */\nasync function fetchCatalogBody(url: string, fetchImpl: typeof fetch): Promise<string | undefined> {\n try {\n const res = await Retry.run(\n async () => {\n const r = await fetchImpl(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n return r;\n },\n // 2 transient retries with backoff; every error here is worth one more try —\n // the whole call is already fail-closed at the caller.\n { retries: 2, isRetryable: () => true, initialDelayMs: 200 },\n );\n const declaredLength = Number(res.headers.get(\"content-length\") ?? Number.NaN);\n if (Number.isFinite(declaredLength) && declaredLength > MAX_CATALOG_BYTES) {\n throw new Error(`catalog too large: ${declaredLength} bytes declared`);\n }\n const body = await res.text();\n // Checked again after reading: `content-length` is the server's claim, and a server that\n // omits it or lies about it is exactly the one worth bounding. Both checks are cheap and\n // neither alone is sufficient.\n if (body.length > MAX_CATALOG_BYTES) {\n throw new Error(`catalog too large: ${body.length} bytes received`);\n }\n JSON.parse(body); // validate before persisting — never cache garbage\n return body;\n } catch (err) {\n diag(\n `[theokit-sdk] WARN: models-dev refresh failed (${(err as Error).message}) — serving existing data\\n`,\n );\n return undefined;\n }\n}\n\n/**\n * Persist the fetched body and patch it into the index.\n *\n * Two independent failures with two different consequences, which is why they are two try blocks\n * and not one: a cache write that fails costs the NEXT process a fetch and nothing more, so the run\n * continues; a patch that fails means this body never reached the index, so the index is whatever it\n * was and the result says `cache`.\n */\nfunction persistAndPatch(path: string, body: string): RefreshModelCatalogResult {\n try {\n writeCacheAtomic(path, body);\n } catch (err) {\n diag(`[theokit-sdk] WARN: models-dev cache write failed (${(err as Error).message})\\n`);\n }\n try {\n return { source: \"network\", models: patchIndexFromApiJson(JSON.parse(body)) };\n } catch (err) {\n diag(\n `[theokit-sdk] WARN: models-dev patch failed (${(err as Error).message}) — serving existing data\\n`,\n );\n return { source: \"cache\", models: 0 };\n }\n}\n\n/**\n * Explicitly refresh the model catalog from models.dev (the ONLY network trigger in the subsystem).\n * Fail-closed: any failure keeps serving the current index (cache or vendored). Kill-switch:\n * `THEOKIT_DISABLE_MODELS_FETCH`.\n *\n * The sequence carried a cognitive-complexity suppression whose reason said splitting it would\n * \"separate a step from the fallback it owns\". The four helpers above each KEEP their fallback: the\n * TTL gate owns \"no cache\", the fetch owns every network and validation failure, the persist owns\n * the write and the patch. What is left here is the order they run in, which is what this function\n * was always for.\n *\n * Still true, and NOT fixed by the split: three of the paths return `{ source: \"cache\" }`, so a\n * caller cannot tell a timeout from an unparseable body from a patch failure. Distinguishing them\n * means adding a field to the PUBLIC `RefreshModelCatalogResult`, which is a release decision rather\n * than a refactor. The `diag` line already names the cause for an operator.\n */\nexport async function refreshModelCatalog(\n opts: RefreshModelCatalogOptions = {},\n): Promise<RefreshModelCatalogResult> {\n // M44 M4 fix — self-initialize provider registration so the `@theokit/sdk/models` subpath works\n // standalone (without the consumer having built an Agent first).\n registerBuiltins();\n if (fetchDisabledByEnv()) return { source: \"skipped\", models: 0 };\n\n const url = opts.url ?? process.env.THEOKIT_MODELS_URL ?? DEFAULT_URL;\n const path = cachePathFor(url);\n const now = opts.deps?.now ?? (() => Date.now());\n\n if (opts.force !== true) {\n const cached = servedFromFreshCache(url, path, now);\n if (cached !== undefined) return cached;\n }\n\n const body = await fetchCatalogBody(url, boundFetch(opts.deps?.fetch));\n // fail-closed: serve whatever we already have (stale cache if present, else vendored)\n if (body === undefined) return { source: \"cache\", models: loadCacheIntoIndex(url) };\n\n return persistAndPatch(path, body);\n}\n\n/** The enriched per-model view (public via `@theokit/sdk/models`): index lookup by (possibly prefixed) id. */\nexport function getModelInfo(modelId: string): ReturnType<typeof getCatalogModelInfo> {\n return getCatalogModelInfo(modelId);\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/llm/model-option.ts","../src/internal/providers/catalog-source-models-dev.ts"],"names":["parseModelId","join","homedir","createHash","path","mkdirSync","dirname","randomBytes","writeFileSync","renameSync","unlinkSync","loadProviderCatalog","getProviderProfile","catalogModelSchema","patchModelInfo","diag","readFileSync","statSync","Retry","registerBuiltins","boundFetch","getCatalogModelInfo"],"mappings":"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","file":"models.cjs","sourcesContent":["import { parseModelId } from \"./model-identifier.js\";\n\n/**\n * A UI-friendly model option — the shape a `<select>`/dropdown consumes.\n *\n * Public via `@theokit/sdk/models`.\n *\n * @public\n */\nexport interface ModelOption {\n /** The original model id (what you pass back to the SDK). */\n value: string;\n /** Best-effort human label (see {@link humanizeModelName}). */\n label: string;\n /** Provider from the slug prefix, or `undefined` when none. */\n provider: string | undefined;\n}\n\n/** Tokens rendered upper-case rather than title-case. */\nconst ACRONYMS = new Set([\"gpt\", \"ai\", \"hd\", \"ui\", \"api\", \"sdk\", \"llm\", \"xl\"]);\n\nfunction prettyToken(token: string): string {\n if (ACRONYMS.has(token.toLowerCase())) return token.toUpperCase();\n return token.charAt(0).toUpperCase() + token.slice(1);\n}\n\n/**\n * Turn a model id into a best-effort human label: strip the routing/vendor\n * prefix to the core model segment, split on `-`/`_`/`.`/whitespace, title-case\n * each token (known acronyms upper-cased), and append an OpenRouter `:variant`\n * in parentheses. Deterministic, pure, dependency-free.\n *\n * Best-effort, NOT vendor-canonical: `\"anthropic/claude-3-5-sonnet\"` →\n * `\"Claude 3 5 Sonnet\"`. A UI wanting exact marketing names overrides per id.\n *\n * Public via `@theokit/sdk/models`.\n *\n * @public\n */\nexport function humanizeModelName(modelId: string): string {\n const { name } = parseModelId(modelId);\n if (name.length === 0) return \"\";\n const colon = name.indexOf(\":\");\n // Strip a trailing slash so a typo'd `gpt-4o/` keeps its name (not lost to an\n // empty last segment).\n //\n // Done by index rather than with `/\\/+$/`, and the difference is not stylistic: that pattern\n // consumes slashes to the end of the string at every start position and then backtracks\n // looking for an end-anchor that is not there (CodeQL js/polynomial-redos #6). This function\n // is `@public` and takes a caller-supplied model id. Measured on a run of slashes followed by\n // one other character: 25_000 took 496 ms, 100_000 took 31_305 ms — half a minute of pinned\n // CPU to render a label. Walking back from the end is linear and cannot backtrack.\n const base = trimTrailingSlashes(colon >= 0 ? name.slice(0, colon) : name);\n const variant = colon >= 0 ? name.slice(colon + 1) : \"\";\n const lastSlash = base.lastIndexOf(\"/\");\n const core = lastSlash >= 0 ? base.slice(lastSlash + 1) : base;\n const label = core\n .split(/[-_.\\s]+/)\n .filter((t) => t.length > 0)\n .map(prettyToken)\n .join(\" \");\n if (label.length === 0) return variant; // no base label (e.g. \":free\") → bare variant\n return variant.length > 0 ? `${label} (${variant})` : label;\n}\n\n/**\n * Build a {@link ModelOption} (`{ value, label, provider }`) for a model id —\n * a dropdown-ready entry composing {@link humanizeModelName} + `parseModelId`.\n *\n * Public via `@theokit/sdk/models`.\n *\n * @public\n */\nexport function toModelOption(modelId: string): ModelOption {\n return {\n value: modelId,\n label: humanizeModelName(modelId),\n provider: parseModelId(modelId).provider,\n };\n}\n\nconst SLASH_CODE = 47; // \"/\"\n\n/** Removes trailing `/` characters in one linear pass. See the call site for why not a regex. */\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === SLASH_CODE) end--;\n return end === value.length ? value : value.slice(0, end);\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport { Retry } from \"../../retry.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { boundFetch } from \"../runtime-fetch.js\";\nimport { registerBuiltins } from \"./builtin/index.js\";\nimport { getCatalogModelInfo, loadProviderCatalog, patchModelInfo } from \"./catalog-loader.js\";\nimport { catalogModelSchema } from \"./catalog-schema.js\";\nimport { getProviderProfile } from \"./registry.js\";\n\n/**\n * M44 — the OPTIONAL models.dev catalog source. Explicit opt-in ONLY (`refreshModelCatalog()`): the SDK\n * NEVER fetches at startup or per-request — the vendored catalog + any existing disk cache are the offline\n * base, and every failure mode fails CLOSED back to them (ROADMAP constraint: no runtime hard network dep).\n *\n * The models.dev consumption mechanism:\n * disk cache with mtime TTL, atomic tempfile+rename write, corrupt-cache delete-and-fall-through, kill-switch\n * env. Library adaptations: NO background refresh loop (a consumer composes refresh with the SDK's cron);\n * TTL 1h (not 5min — no background refresher to keep it warm); no cross-process flock v1 (atomic rename +\n * idempotent upstream content — at most one wasted fetch).\n *\n * @internal (the public surface is `refreshModelCatalog` re-exported via `@theokit/sdk/models`)\n */\n\nconst DEFAULT_URL = \"https://models.dev/api.json\";\n\n/**\n * Ceiling on a fetched catalog, before it is parsed or written to the cache.\n *\n * The response body was read with `res.text()` and persisted with no bound, so a host serving a\n * multi-gigabyte document would have been materialised in memory and then written to disk. The\n * default URL is trusted, but `THEOKIT_MODELS_URL` lets an operator point this anywhere, and a\n * limit is the difference between \"we only fetch from a host we trust\" and \"we cannot be hurt by\n * one we do not\".\n *\n * 32 MiB is roughly 40x the real catalog (~800 KiB in 2026-08), so it bounds the failure without\n * being a number anyone has to tune when models.dev grows.\n */\nconst MAX_CATALOG_BYTES = 32 * 1024 * 1024;\nconst TTL_MS = 60 * 60 * 1000; // 1 hour\nconst FETCH_TIMEOUT_MS = 10_000;\n\n/**\n * Arguments to `refreshModelCatalog`, the SDK's only network trigger for model metadata.\n *\n * `url` wins over the `THEOKIT_MODELS_URL` environment variable, which in turn wins over\n * `https://models.dev/api.json`. A non-default URL gets its own cache file, named by a hash of\n * the URL, so switching sources never reads another source's cache.\n *\n * `force` skips only the freshness check — it does not skip the kill switch. When\n * `THEOKIT_DISABLE_MODELS_FETCH` is set to anything other than an empty string, `\"0\"` or\n * `\"false\"`, the call returns `source: \"skipped\"` with no fetch and no cache read, whatever\n * `force` says.\n *\n * Without `force`, a cache file whose mtime is under an hour old is served directly and no\n * request is made.\n *\n * `deps` exists so tests can drive the fetch and the clock; production leaves it unset.\n */\nexport interface RefreshModelCatalogOptions {\n /** Override the source URL (`THEOKIT_MODELS_URL` env also honored). */\n url?: string;\n /** Bypass the TTL freshness gate. */\n force?: boolean;\n /** Injected for tests. */\n deps?: { fetch?: typeof fetch; now?: () => number };\n}\n\n/**\n * What `refreshModelCatalog` did. It never throws on a failed refresh, so this is the only place\n * the outcome is reported.\n *\n * `source` is `\"network\"` only when a request succeeded, its body parsed as JSON, and the models\n * were patched in. Every failure path — kill switch aside — returns `\"cache\"`: an HTTP error\n * after retries, a timeout, an unparseable body, or a patch that threw. `\"cache\"` therefore means\n * \"serving what was already there\", which may be a fresh cache, a stale one, or nothing but the\n * vendored catalog. Read `models` to tell those apart. `\"skipped\"` means the kill switch was set.\n *\n * `models` counts the models patched into the index during this call, not the catalog's size. It\n * counts each model once even when the provider has aliases and the model is written under several\n * index keys, and it excludes models whose payload failed schema validation and models under\n * providers the SDK does not know. It is 0 whenever there was no cache to fall back on, and 0 for\n * `\"skipped\"`.\n *\n * A cache write that fails is logged and otherwise ignored: the result can still say `\"network\"`\n * with the data live in memory and nothing on disk.\n */\nexport interface RefreshModelCatalogResult {\n /** Where the data came from: a fresh network fetch, the still-fresh disk cache, or skipped entirely. */\n source: \"network\" | \"cache\" | \"skipped\";\n /** How many models were patched into the index. */\n models: number;\n}\n\n/** The cache file for a source URL (custom URLs get a hash-suffixed name). */\nexport function cachePathFor(url: string): string {\n // M44 L8 fix — honor the SDK home override so tests and multi-home setups never touch the real ~/.theokit.\n const base = process.env.THEOKIT_HOME?.trim() || join(homedir(), \".theokit\");\n const dir = join(base, \"cache\", \"models-dev\");\n if (url === DEFAULT_URL) return join(dir, \"api.json\");\n const hash = createHash(\"sha256\").update(url).digest(\"hex\").slice(0, 12);\n return join(dir, `api-${hash}.json`);\n}\n\n/** Atomic write: temp file + rename (crash mid-write never leaves a partial cache). */\nfunction writeCacheAtomic(path: string, body: string): void {\n mkdirSync(dirname(path), { recursive: true });\n const tmp = `${path}.tmp-${randomBytes(6).toString(\"hex\")}`;\n try {\n writeFileSync(tmp, body);\n renameSync(tmp, path);\n } catch (err) {\n try {\n unlinkSync(tmp);\n } catch {\n // best effort\n }\n throw err;\n }\n}\n\n/**\n * M44 H3 fix — models.dev provider ids ≠ theokit ids (google↔google-gemini, zai↔zhipu, togetherai↔together,\n * fireworks-ai↔fireworks, amazon-bedrock↔bedrock, google-vertex↔vertex). The RUNTIME mapping mirrors the\n * maintenance script's table; targets resolve against the CATALOG entries (id + aliases — the same namespace\n * the vendored index uses) first, then the registry.\n */\nconst MODELS_DEV_ID_MAP: Record<string, string> = {\n google: \"google-gemini\",\n zai: \"zhipu\",\n togetherai: \"together\",\n \"fireworks-ai\": \"fireworks\",\n \"amazon-bedrock\": \"bedrock\",\n \"google-vertex\": \"vertex\",\n};\n\nlet _catalogTargets: Map<string, { keys: string[] }> | undefined;\n/** external-id → the index keys (entry id + aliases) its models patch under. Lazy; catalog-first. */\nfunction catalogTargets(): Map<string, { keys: string[] }> {\n if (_catalogTargets !== undefined) return _catalogTargets;\n _catalogTargets = new Map();\n try {\n for (const entry of Object.values(loadProviderCatalog())) {\n const keys = [entry.id, ...(entry.aliases ?? [])];\n for (const k of keys) {\n if (!_catalogTargets.has(k)) _catalogTargets.set(k, { keys });\n }\n }\n } catch {\n // catalog unavailable — registry fallback below still works\n }\n return _catalogTargets;\n}\n\nfunction resolvePatchKeys(externalId: string): string[] | undefined {\n const mapped = MODELS_DEV_ID_MAP[externalId] ?? externalId;\n const fromCatalog = catalogTargets().get(mapped);\n if (fromCatalog !== undefined) return fromCatalog.keys;\n const profile = getProviderProfile(mapped);\n if (profile !== undefined) return [profile.name, ...(profile.aliases ?? [])];\n return undefined;\n}\n\n/** Parse + patch the index from an api.json payload. Unknown providers are skipped with WARN (once each). */\n// PRE-EXISTING debt, exposed when M75 fixed the Biome config that used to abort before\n// sweeping these files (a nested root under refactor/). It is not new code and was not touched\n// by M75; refactoring SDK internals without review would trade a visible problem for a diff\n// risky. Tracked in usetheodev/theokit-sdk#151.\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: validates and merges an UNTRUSTED upstream payload (models.dev api.json) shape by shape, dropping a single bad model rather than the file. Each branch is a distinct malformed input; merging them loses which one was seen.\nfunction patchIndexFromApiJson(raw: unknown): number {\n if (typeof raw !== \"object\" || raw === null) return 0;\n let patched = 0;\n const skipped: string[] = [];\n for (const [providerId, provider] of Object.entries(raw as Record<string, unknown>)) {\n const models = (provider as { models?: Record<string, unknown> })?.models;\n if (models === undefined || typeof models !== \"object\") continue;\n // Enrich ONLY providers the SDK knows — models.dev's npm/api fields cannot be mapped to a theokit\n // apiMode/authType safely, so unknown providers are data we cannot route.\n const keys = resolvePatchKeys(providerId);\n if (keys === undefined) {\n skipped.push(providerId);\n continue;\n }\n for (const [modelId, rawModel] of Object.entries(models)) {\n const parsed = catalogModelSchema.safeParse(rawModel);\n if (!parsed.success) continue; // malformed model — drop silently (live data, additive drift expected)\n for (const key of keys) patchModelInfo(`${key}/${modelId}`, parsed.data);\n patched++;\n }\n }\n if (skipped.length > 0) {\n diag(\n `[theokit-sdk] WARN: models-dev refresh skipped ${skipped.length} unknown provider(s) (e.g. ${skipped.slice(0, 3).join(\", \")})\\n`,\n );\n }\n return patched;\n}\n\n/**\n * Load an existing disk cache into the index (called by refresh when fresh, or opportunistically by a\n * consumer at startup — NEVER fetches). Corrupt cache → delete + fall through to vendored data.\n */\nexport function loadCacheIntoIndex(url: string = DEFAULT_URL): number {\n const path = cachePathFor(url);\n let body: string;\n try {\n body = readFileSync(path, \"utf-8\");\n } catch {\n return 0; // no cache — vendored data only\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n // corrupt cache: delete and fall through to the vendored catalog.\n // M44 L9 fix — only a PARSE failure is cache corruption; a patch error must never delete a valid cache.\n try {\n unlinkSync(path);\n } catch {\n // best effort\n }\n diag(`[theokit-sdk] WARN: corrupt models-dev cache deleted (${path})\\n`);\n return 0;\n }\n try {\n return patchIndexFromApiJson(parsed);\n } catch (err) {\n diag(`[theokit-sdk] WARN: models-dev cache patch failed (${(err as Error).message})\\n`);\n return 0;\n }\n}\n\n/**\n * The kill switch, read from the environment.\n *\n * Truthy-only by design (M44 L11): a presence check would treat `THEOKIT_DISABLE_MODELS_FETCH=0`\n * and `=false` as \"disabled\", which is the opposite of what anyone writing them means.\n */\nfunction fetchDisabledByEnv(): boolean {\n const kill = process.env.THEOKIT_DISABLE_MODELS_FETCH;\n return kill !== undefined && kill !== \"\" && kill !== \"0\" && kill.toLowerCase() !== \"false\";\n}\n\n/**\n * The TTL gate: a cache file younger than {@link TTL_MS} serves without touching the network.\n *\n * Returns `undefined` when there is no usable cache — including when `statSync` throws, which is\n * how \"no cache file\" arrives. That is the ONE failure this step owns, and its fallback is simply\n * to let the caller proceed to the fetch.\n */\nfunction servedFromFreshCache(\n url: string,\n path: string,\n now: () => number,\n): RefreshModelCatalogResult | undefined {\n try {\n if (now() - statSync(path).mtimeMs >= TTL_MS) return undefined;\n } catch {\n return undefined; // no cache — proceed to fetch\n }\n return { source: \"cache\", models: loadCacheIntoIndex(url) };\n}\n\n/**\n * Fetch the catalog, bound its size, and prove it parses before anyone persists it.\n *\n * Returns `undefined` after logging when ANY of that fails — timeout, non-2xx, oversize, unparseable\n * body. The fallback this step owns is \"serve what we already have\", and the caller applies it.\n * Splitting the step from its fallback was the objection recorded in the suppression this replaces;\n * the fallback is not separated, it is named — `undefined` means \"this step could not produce a\n * body\", and there is exactly one thing to do about that.\n */\nasync function fetchCatalogBody(url: string, fetchImpl: typeof fetch): Promise<string | undefined> {\n try {\n const res = await Retry.run(\n async () => {\n const r = await fetchImpl(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n return r;\n },\n // 2 transient retries with backoff; every error here is worth one more try —\n // the whole call is already fail-closed at the caller.\n { retries: 2, isRetryable: () => true, initialDelayMs: 200 },\n );\n const declaredLength = Number(res.headers.get(\"content-length\") ?? Number.NaN);\n if (Number.isFinite(declaredLength) && declaredLength > MAX_CATALOG_BYTES) {\n throw new Error(`catalog too large: ${declaredLength} bytes declared`);\n }\n const body = await res.text();\n // Checked again after reading: `content-length` is the server's claim, and a server that\n // omits it or lies about it is exactly the one worth bounding. Both checks are cheap and\n // neither alone is sufficient.\n if (body.length > MAX_CATALOG_BYTES) {\n throw new Error(`catalog too large: ${body.length} bytes received`);\n }\n JSON.parse(body); // validate before persisting — never cache garbage\n return body;\n } catch (err) {\n diag(\n `[theokit-sdk] WARN: models-dev refresh failed (${(err as Error).message}) — serving existing data\\n`,\n );\n return undefined;\n }\n}\n\n/**\n * Persist the fetched body and patch it into the index.\n *\n * Two independent failures with two different consequences, which is why they are two try blocks\n * and not one: a cache write that fails costs the NEXT process a fetch and nothing more, so the run\n * continues; a patch that fails means this body never reached the index, so the index is whatever it\n * was and the result says `cache`.\n */\nfunction persistAndPatch(path: string, body: string): RefreshModelCatalogResult {\n try {\n writeCacheAtomic(path, body);\n } catch (err) {\n diag(`[theokit-sdk] WARN: models-dev cache write failed (${(err as Error).message})\\n`);\n }\n try {\n return { source: \"network\", models: patchIndexFromApiJson(JSON.parse(body)) };\n } catch (err) {\n diag(\n `[theokit-sdk] WARN: models-dev patch failed (${(err as Error).message}) — serving existing data\\n`,\n );\n return { source: \"cache\", models: 0 };\n }\n}\n\n/**\n * Explicitly refresh the model catalog from models.dev (the ONLY network trigger in the subsystem).\n * Fail-closed: any failure keeps serving the current index (cache or vendored). Kill-switch:\n * `THEOKIT_DISABLE_MODELS_FETCH`.\n *\n * The sequence carried a cognitive-complexity suppression whose reason said splitting it would\n * \"separate a step from the fallback it owns\". The four helpers above each KEEP their fallback: the\n * TTL gate owns \"no cache\", the fetch owns every network and validation failure, the persist owns\n * the write and the patch. What is left here is the order they run in, which is what this function\n * was always for.\n *\n * Still true, and NOT fixed by the split: three of the paths return `{ source: \"cache\" }`, so a\n * caller cannot tell a timeout from an unparseable body from a patch failure. Distinguishing them\n * means adding a field to the PUBLIC `RefreshModelCatalogResult`, which is a release decision rather\n * than a refactor. The `diag` line already names the cause for an operator.\n */\nexport async function refreshModelCatalog(\n opts: RefreshModelCatalogOptions = {},\n): Promise<RefreshModelCatalogResult> {\n // M44 M4 fix — self-initialize provider registration so the `@theokit/sdk/models` subpath works\n // standalone (without the consumer having built an Agent first).\n registerBuiltins();\n if (fetchDisabledByEnv()) return { source: \"skipped\", models: 0 };\n\n const url = opts.url ?? process.env.THEOKIT_MODELS_URL ?? DEFAULT_URL;\n const path = cachePathFor(url);\n const now = opts.deps?.now ?? (() => Date.now());\n\n if (opts.force !== true) {\n const cached = servedFromFreshCache(url, path, now);\n if (cached !== undefined) return cached;\n }\n\n const body = await fetchCatalogBody(url, boundFetch(opts.deps?.fetch));\n // fail-closed: serve whatever we already have (stale cache if present, else vendored)\n if (body === undefined) return { source: \"cache\", models: loadCacheIntoIndex(url) };\n\n return persistAndPatch(path, body);\n}\n\n/** The enriched per-model view (public via `@theokit/sdk/models`): index lookup by (possibly prefixed) id. */\nexport function getModelInfo(modelId: string): ReturnType<typeof getCatalogModelInfo> {\n return getCatalogModelInfo(modelId);\n}\n"]}
package/dist/models.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Retry } from './chunk-7F7XE6CM.js';
2
+ export { resolveModelCapabilities } from './chunk-LZJXEPEQ.js';
2
3
  import { parseModelId } from './chunk-R7WIIPUR.js';
3
4
  export { parseModelId } from './chunk-R7WIIPUR.js';
4
- import { getCatalogModelInfo, registerBuiltins, catalogModelSchema, patchModelInfo, getProviderProfile, loadProviderCatalog } from './chunk-SVCPJRUB.js';
5
+ import { registerBuiltins, getCatalogModelInfo, catalogModelSchema, patchModelInfo, getProviderProfile, loadProviderCatalog } from './chunk-SVCPJRUB.js';
5
6
  import './chunk-AGACYRJ2.js';
6
7
  import './chunk-TX56N7FR.js';
7
8
  import './chunk-5Q4RTLMP.js';
@@ -14,55 +15,6 @@ import { statSync, readFileSync, unlinkSync, mkdirSync, writeFileSync, renameSyn
14
15
  import { homedir } from 'os';
15
16
  import { join, dirname } from 'path';
16
17
 
17
- // src/internal/llm/model-capabilities.ts
18
- var CONSERVATIVE_DEFAULTS = {
19
- supportsVision: false,
20
- supportsStructuredOutput: false,
21
- supportsToolUse: false,
22
- supportsCacheControl: false,
23
- maxContextTokens: 4096,
24
- maxOutputTokens: 4096
25
- };
26
- var ROUTING_PREFIXES = ["openrouter/", "vertex/", "bedrock/"];
27
- function capsFromCatalog(m) {
28
- return {
29
- supportsVision: m.modalities?.input?.includes("image") ?? m.attachment ?? false,
30
- supportsStructuredOutput: m.structured_output ?? false,
31
- supportsToolUse: m.tool_call ?? false,
32
- supportsCacheControl: m.cache_control ?? false,
33
- maxContextTokens: m.limit?.context ?? CONSERVATIVE_DEFAULTS.maxContextTokens,
34
- maxOutputTokens: m.limit?.output ?? CONSERVATIVE_DEFAULTS.maxOutputTokens
35
- };
36
- }
37
- function resolveModelCapabilities(modelId) {
38
- const bare = stripVariantSuffix(stripRoutingPrefix(modelId));
39
- const fromIndex = getCatalogModelInfo(bare);
40
- if (fromIndex !== void 0) return capsFromCatalog(fromIndex);
41
- const withVendor = inferVendorPrefix(bare);
42
- if (withVendor !== bare) {
43
- const vendored = getCatalogModelInfo(withVendor);
44
- if (vendored !== void 0) return capsFromCatalog(vendored);
45
- }
46
- return CONSERVATIVE_DEFAULTS;
47
- }
48
- function stripRoutingPrefix(modelId) {
49
- for (const prefix of ROUTING_PREFIXES) {
50
- if (modelId.startsWith(prefix)) return modelId.slice(prefix.length);
51
- }
52
- return modelId;
53
- }
54
- function stripVariantSuffix(modelId) {
55
- const i = modelId.indexOf(":");
56
- return i >= 0 ? modelId.slice(0, i) : modelId;
57
- }
58
- function inferVendorPrefix(bare) {
59
- if (bare.startsWith("claude")) return `anthropic/${bare}`;
60
- if (bare.startsWith("gpt-") || bare.startsWith("o1") || bare.startsWith("o3"))
61
- return `openai/${bare}`;
62
- if (bare.startsWith("gemini")) return `google/${bare}`;
63
- return bare;
64
- }
65
-
66
18
  // src/internal/llm/model-option.ts
67
19
  var ACRONYMS = /* @__PURE__ */ new Set(["gpt", "ai", "hd", "ui", "api", "sdk", "llm", "xl"]);
68
20
  function prettyToken(token) {
@@ -282,6 +234,6 @@ function getModelInfo(modelId) {
282
234
  return getCatalogModelInfo(modelId);
283
235
  }
284
236
 
285
- export { getModelInfo, humanizeModelName, refreshModelCatalog, resolveModelCapabilities, toModelOption };
237
+ export { getModelInfo, humanizeModelName, refreshModelCatalog, toModelOption };
286
238
  //# sourceMappingURL=models.js.map
287
239
  //# sourceMappingURL=models.js.map