@jambonz/llm 0.5.7 → 0.6.1

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.
@@ -88,6 +88,9 @@ function assertValidRequest(req) {
88
88
  "PromptRequest.messages must not contain role: 'system'. Pass the system prompt via PromptRequest.system instead."
89
89
  );
90
90
  }
91
+ if (req.cacheKey !== void 0 && (typeof req.cacheKey !== "string" || req.cacheKey.length === 0)) {
92
+ throw new Error("PromptRequest.cacheKey must be a non-empty string");
93
+ }
91
94
  }
92
95
 
93
96
  // src/adapters/openai/_streaming.ts
@@ -112,6 +115,9 @@ async function* streamFromOpenAI(client, req, options) {
112
115
  body.tool_choice = "auto";
113
116
  }
114
117
  if (req.temperature !== void 0) body.temperature = req.temperature;
118
+ if (req.cacheKey && options.includeCacheKey !== false) {
119
+ body.prompt_cache_key = req.cacheKey;
120
+ }
115
121
  const effectiveMaxTokens = req.maxTokens ?? options.defaultMaxTokens;
116
122
  if (effectiveMaxTokens !== void 0) {
117
123
  const param = options.tokensParam ?? (needsMaxCompletionTokens(req.model) ? "max_completion_tokens" : "max_tokens");
@@ -165,10 +171,12 @@ async function* streamFromOpenAI(client, req, options) {
165
171
  throw new Error(`OpenAI-compatible stream returned an error: ${message}`);
166
172
  }
167
173
  if (chunk.usage) {
174
+ const cachedTokens = chunk.usage.prompt_tokens_details?.cached_tokens;
168
175
  usage = {
169
- ...chunk.usage.prompt_tokens !== void 0 ? { inputTokens: chunk.usage.prompt_tokens } : {},
176
+ ...chunk.usage.prompt_tokens !== void 0 ? { inputTokens: chunk.usage.prompt_tokens - (cachedTokens ?? 0) } : {},
170
177
  ...chunk.usage.completion_tokens !== void 0 ? { outputTokens: chunk.usage.completion_tokens } : {},
171
- ...chunk.usage.total_tokens !== void 0 ? { totalTokens: chunk.usage.total_tokens } : {}
178
+ ...chunk.usage.total_tokens !== void 0 ? { totalTokens: chunk.usage.total_tokens } : {},
179
+ ...cachedTokens !== void 0 ? { cacheReadTokens: cachedTokens } : {}
172
180
  };
173
181
  }
174
182
  const choice = chunk.choices?.[0];
@@ -547,7 +555,8 @@ var OpenAIAdapter = class {
547
555
  stream(req) {
548
556
  return streamFromOpenAI(this.ensureClient(), req, {
549
557
  knownModels: this.knownModels(),
550
- vendorMetadataExtractor: OPENAI_METADATA_EXTRACTOR
558
+ vendorMetadataExtractor: OPENAI_METADATA_EXTRACTOR,
559
+ includeCacheKey: this.forwardCacheKey()
551
560
  });
552
561
  }
553
562
  appendAssistantToolCall(history, toolCalls) {
@@ -568,6 +577,20 @@ var OpenAIAdapter = class {
568
577
  knownModels() {
569
578
  return openAIManifest.knownModels;
570
579
  }
580
+ /**
581
+ * Whether to forward `req.cacheKey` as OpenAI's `prompt_cache_key` request
582
+ * param. Default true — verified accepted by OpenAI, Azure OpenAI,
583
+ * Vertex-OpenAI, and Baseten. OpenAI-compatible vendors whose param
584
+ * handling is strict or unverified (DeepSeek, Moonshot, Zai, xAI — and
585
+ * Groq via its own stream override) override to false: a strict backend
586
+ * rejects the whole request on an unknown body param, which is far worse
587
+ * than forgoing a cache-routing hint. Flip a vendor to true only after
588
+ * verifying its endpoint (including any proxy in front of it, e.g.
589
+ * Bedrock Mantle) tolerates the param.
590
+ */
591
+ forwardCacheKey() {
592
+ return true;
593
+ }
571
594
  async testCredential() {
572
595
  await this.ensureClient().models.list();
573
596
  }
@@ -680,6 +703,11 @@ var DeepSeekAdapter = class extends OpenAIAdapter {
680
703
  knownModels() {
681
704
  return deepseekManifest.knownModels;
682
705
  }
706
+ // DeepSeek caches automatically server-side; prompt_cache_key is
707
+ // unverified against its param handling — do not forward.
708
+ forwardCacheKey() {
709
+ return false;
710
+ }
683
711
  };
684
712
  var deepseekFactory = {
685
713
  vendor: deepseekManifest.vendor,
@@ -903,6 +931,12 @@ var MoonshotAdapter = class extends OpenAIAdapter {
903
931
  knownModels() {
904
932
  return moonshotManifest.knownModels;
905
933
  }
934
+ // Moonshot's param handling is strict (e.g. it rejects unknown template
935
+ // kwargs) and prompt_cache_key is unverified against both the native
936
+ // endpoint and Bedrock Mantle deployments — do not forward.
937
+ forwardCacheKey() {
938
+ return false;
939
+ }
906
940
  };
907
941
  var moonshotFactory = {
908
942
  vendor: moonshotManifest.vendor,
@@ -976,6 +1010,11 @@ var ZaiAdapter = class extends OpenAIAdapter {
976
1010
  knownModels() {
977
1011
  return zaiManifest.knownModels;
978
1012
  }
1013
+ // prompt_cache_key is unverified against Zai's OpenAI-compat endpoint —
1014
+ // do not forward.
1015
+ forwardCacheKey() {
1016
+ return false;
1017
+ }
979
1018
  };
980
1019
  var zaiFactory = {
981
1020
  vendor: zaiManifest.vendor,
@@ -1071,6 +1110,11 @@ var XaiAdapter = class extends OpenAIAdapter {
1071
1110
  knownModels() {
1072
1111
  return xaiManifest.knownModels;
1073
1112
  }
1113
+ // xAI caches automatically server-side; prompt_cache_key is unverified
1114
+ // against its param handling — do not forward.
1115
+ forwardCacheKey() {
1116
+ return false;
1117
+ }
1074
1118
  };
1075
1119
  var xaiFactory = {
1076
1120
  vendor: xaiManifest.vendor,
@@ -1221,6 +1265,16 @@ var AnthropicAdapter = class {
1221
1265
  if (req.system) body.system = req.system;
1222
1266
  if (tools) body.tools = tools;
1223
1267
  if (req.temperature !== void 0) body.temperature = req.temperature;
1268
+ if (req.cacheKey) {
1269
+ if (req.system) {
1270
+ body.system = [{ type: "text", text: req.system, cache_control: { type: "ephemeral" } }];
1271
+ }
1272
+ if (tools) {
1273
+ const lastTool = tools[tools.length - 1];
1274
+ lastTool.cache_control = { type: "ephemeral" };
1275
+ }
1276
+ markLastMessageForCaching(messages);
1277
+ }
1224
1278
  let stream;
1225
1279
  let responseHeaders;
1226
1280
  try {
@@ -1248,6 +1302,8 @@ var AnthropicAdapter = class {
1248
1302
  let stopReason;
1249
1303
  let inputTokens;
1250
1304
  let outputTokens;
1305
+ let cacheReadTokens;
1306
+ let cacheWriteTokens;
1251
1307
  try {
1252
1308
  for await (const event of stream) {
1253
1309
  if (req.signal?.aborted) {
@@ -1257,6 +1313,8 @@ var AnthropicAdapter = class {
1257
1313
  switch (event.type) {
1258
1314
  case "message_start":
1259
1315
  inputTokens = event.message?.usage?.input_tokens;
1316
+ cacheReadTokens = event.message?.usage?.cache_read_input_tokens;
1317
+ cacheWriteTokens = event.message?.usage?.cache_creation_input_tokens;
1260
1318
  break;
1261
1319
  case "content_block_start": {
1262
1320
  const block = event.content_block;
@@ -1338,7 +1396,11 @@ var AnthropicAdapter = class {
1338
1396
  const usage = inputTokens !== void 0 || outputTokens !== void 0 ? {
1339
1397
  ...inputTokens !== void 0 ? { inputTokens } : {},
1340
1398
  ...outputTokens !== void 0 ? { outputTokens } : {},
1341
- ...inputTokens !== void 0 && outputTokens !== void 0 ? { totalTokens: inputTokens + outputTokens } : {}
1399
+ ...inputTokens !== void 0 && outputTokens !== void 0 ? {
1400
+ totalTokens: inputTokens + outputTokens + (cacheReadTokens ?? 0) + (cacheWriteTokens ?? 0)
1401
+ } : {},
1402
+ ...cacheReadTokens !== void 0 ? { cacheReadTokens } : {},
1403
+ ...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
1342
1404
  } : void 0;
1343
1405
  const endEvent = {
1344
1406
  type: "end",
@@ -1437,6 +1499,23 @@ function buildWireMessages2(req) {
1437
1499
  }
1438
1500
  return out;
1439
1501
  }
1502
+ function markLastMessageForCaching(messages) {
1503
+ if (messages.length === 0) return;
1504
+ const last = messages[messages.length - 1];
1505
+ const cacheControl = { cache_control: { type: "ephemeral" } };
1506
+ if (typeof last.content === "string" && last.content.length > 0) {
1507
+ messages[messages.length - 1] = {
1508
+ ...last,
1509
+ content: [{ type: "text", text: last.content, ...cacheControl }]
1510
+ };
1511
+ } else if (Array.isArray(last.content) && last.content.length > 0) {
1512
+ const blocks = last.content;
1513
+ messages[messages.length - 1] = {
1514
+ ...last,
1515
+ content: [...blocks.slice(0, -1), { ...blocks[blocks.length - 1], ...cacheControl }]
1516
+ };
1517
+ }
1518
+ }
1440
1519
  function formatTools2(tools) {
1441
1520
  return tools.map((t) => ({
1442
1521
  name: t.name,
@@ -1499,6 +1578,11 @@ async function* streamFromGemini(client, req) {
1499
1578
  if (req.temperature !== void 0) config.temperature = req.temperature;
1500
1579
  if (req.maxTokens !== void 0) config.maxOutputTokens = req.maxTokens;
1501
1580
  if (req.signal) config.abortSignal = req.signal;
1581
+ if (req.providerParams) {
1582
+ for (const [key, value] of Object.entries(req.providerParams)) {
1583
+ if (!(key in config)) config[key] = value;
1584
+ }
1585
+ }
1502
1586
  let stream;
1503
1587
  try {
1504
1588
  stream = await client.models.generateContentStream({
@@ -1519,6 +1603,7 @@ async function* streamFromGemini(client, req) {
1519
1603
  let inputTokens;
1520
1604
  let outputTokens;
1521
1605
  let totalTokens;
1606
+ let cacheReadTokens;
1522
1607
  try {
1523
1608
  for await (const chunk of stream) {
1524
1609
  if (req.signal?.aborted) {
@@ -1527,7 +1612,12 @@ async function* streamFromGemini(client, req) {
1527
1612
  }
1528
1613
  const usage2 = chunk.usageMetadata;
1529
1614
  if (usage2) {
1530
- if (usage2.promptTokenCount !== void 0) inputTokens = usage2.promptTokenCount;
1615
+ if (usage2.cachedContentTokenCount !== void 0) {
1616
+ cacheReadTokens = usage2.cachedContentTokenCount;
1617
+ }
1618
+ if (usage2.promptTokenCount !== void 0) {
1619
+ inputTokens = usage2.promptTokenCount - (usage2.cachedContentTokenCount ?? 0);
1620
+ }
1531
1621
  if (usage2.candidatesTokenCount !== void 0) outputTokens = usage2.candidatesTokenCount;
1532
1622
  if (usage2.totalTokenCount !== void 0) totalTokens = usage2.totalTokenCount;
1533
1623
  }
@@ -1581,7 +1671,8 @@ async function* streamFromGemini(client, req) {
1581
1671
  const usage = inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 ? {
1582
1672
  ...inputTokens !== void 0 ? { inputTokens } : {},
1583
1673
  ...outputTokens !== void 0 ? { outputTokens } : {},
1584
- ...totalTokens !== void 0 ? { totalTokens } : {}
1674
+ ...totalTokens !== void 0 ? { totalTokens } : {},
1675
+ ...cacheReadTokens !== void 0 ? { cacheReadTokens } : {}
1585
1676
  } : void 0;
1586
1677
  const endEvent = {
1587
1678
  type: "end",
@@ -1723,6 +1814,39 @@ function isAbortError3(err) {
1723
1814
 
1724
1815
  // src/adapters/google/manifest.ts
1725
1816
  var GOOGLE_KNOWN_MODELS = [
1817
+ {
1818
+ id: "gemini-3.6-flash",
1819
+ displayName: "Gemini 3.6 Flash",
1820
+ capabilities: {
1821
+ streaming: true,
1822
+ tools: true,
1823
+ vision: true,
1824
+ systemPrompt: true,
1825
+ maxContextTokens: 1e6
1826
+ }
1827
+ },
1828
+ {
1829
+ id: "gemini-3.5-flash",
1830
+ displayName: "Gemini 3.5 Flash",
1831
+ capabilities: {
1832
+ streaming: true,
1833
+ tools: true,
1834
+ vision: true,
1835
+ systemPrompt: true,
1836
+ maxContextTokens: 1e6
1837
+ }
1838
+ },
1839
+ {
1840
+ id: "gemini-3.5-flash-lite",
1841
+ displayName: "Gemini 3.5 Flash-Lite",
1842
+ capabilities: {
1843
+ streaming: true,
1844
+ tools: true,
1845
+ vision: true,
1846
+ systemPrompt: true,
1847
+ maxContextTokens: 1e6
1848
+ }
1849
+ },
1726
1850
  {
1727
1851
  id: "gemini-2.5-pro",
1728
1852
  displayName: "Gemini 2.5 Pro",
@@ -1866,6 +1990,39 @@ var import_genai2 = require("@google/genai");
1866
1990
 
1867
1991
  // src/adapters/vertex-gemini/manifest.ts
1868
1992
  var VERTEX_GEMINI_KNOWN_MODELS = [
1993
+ {
1994
+ id: "gemini-3.6-flash",
1995
+ displayName: "Gemini 3.6 Flash",
1996
+ capabilities: {
1997
+ streaming: true,
1998
+ tools: true,
1999
+ vision: true,
2000
+ systemPrompt: true,
2001
+ maxContextTokens: 1e6
2002
+ }
2003
+ },
2004
+ {
2005
+ id: "gemini-3.5-flash",
2006
+ displayName: "Gemini 3.5 Flash",
2007
+ capabilities: {
2008
+ streaming: true,
2009
+ tools: true,
2010
+ vision: true,
2011
+ systemPrompt: true,
2012
+ maxContextTokens: 1e6
2013
+ }
2014
+ },
2015
+ {
2016
+ id: "gemini-3.5-flash-lite",
2017
+ displayName: "Gemini 3.5 Flash-Lite",
2018
+ capabilities: {
2019
+ streaming: true,
2020
+ tools: true,
2021
+ vision: true,
2022
+ systemPrompt: true,
2023
+ maxContextTokens: 1e6
2024
+ }
2025
+ },
1869
2026
  {
1870
2027
  id: "gemini-2.5-pro",
1871
2028
  displayName: "Gemini 2.5 Pro",
@@ -2549,6 +2706,15 @@ var BedrockAdapter = class {
2549
2706
  if (Object.keys(inferenceConfig).length > 0) {
2550
2707
  commandInput.inferenceConfig = inferenceConfig;
2551
2708
  }
2709
+ if (req.cacheKey) {
2710
+ if (commandInput.system) {
2711
+ commandInput.system.push({ cachePoint: { type: "default" } });
2712
+ }
2713
+ if (tools) {
2714
+ tools.tools.push({ cachePoint: { type: "default" } });
2715
+ }
2716
+ markLastMessageForCaching2(messages);
2717
+ }
2552
2718
  const command = new import_client_bedrock_runtime.ConverseStreamCommand(
2553
2719
  commandInput
2554
2720
  );
@@ -2572,6 +2738,8 @@ var BedrockAdapter = class {
2572
2738
  let inputTokens;
2573
2739
  let outputTokens;
2574
2740
  let totalTokens;
2741
+ let cacheReadTokens;
2742
+ let cacheWriteTokens;
2575
2743
  try {
2576
2744
  for await (const event of response.stream) {
2577
2745
  if (req.signal?.aborted) {
@@ -2624,6 +2792,8 @@ var BedrockAdapter = class {
2624
2792
  inputTokens = event.metadata.usage.inputTokens;
2625
2793
  outputTokens = event.metadata.usage.outputTokens;
2626
2794
  totalTokens = event.metadata.usage.totalTokens;
2795
+ cacheReadTokens = event.metadata.usage.cacheReadInputTokens;
2796
+ cacheWriteTokens = event.metadata.usage.cacheWriteInputTokens;
2627
2797
  }
2628
2798
  }
2629
2799
  }
@@ -2650,7 +2820,9 @@ var BedrockAdapter = class {
2650
2820
  const usage = inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 ? {
2651
2821
  ...inputTokens !== void 0 ? { inputTokens } : {},
2652
2822
  ...outputTokens !== void 0 ? { outputTokens } : {},
2653
- ...totalTokens !== void 0 ? { totalTokens } : {}
2823
+ ...totalTokens !== void 0 ? { totalTokens } : {},
2824
+ ...cacheReadTokens !== void 0 ? { cacheReadTokens } : {},
2825
+ ...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
2654
2826
  } : void 0;
2655
2827
  const endEvent = {
2656
2828
  type: "end",
@@ -2758,6 +2930,16 @@ function buildWireMessages3(req) {
2758
2930
  }
2759
2931
  return out;
2760
2932
  }
2933
+ function markLastMessageForCaching2(messages) {
2934
+ if (messages.length === 0) return;
2935
+ const last = messages[messages.length - 1];
2936
+ if (Array.isArray(last.content) && last.content.length > 0) {
2937
+ messages[messages.length - 1] = {
2938
+ ...last,
2939
+ content: [...last.content, { cachePoint: { type: "default" } }]
2940
+ };
2941
+ }
2942
+ }
2761
2943
  function formatTools4(tools) {
2762
2944
  return tools.map((t) => ({
2763
2945
  toolSpec: {
@@ -3113,7 +3295,10 @@ var GroqAdapter = class extends OpenAIAdapter {
3113
3295
  stream(req) {
3114
3296
  return streamFromOpenAI(this.ensureClient(), req, {
3115
3297
  knownModels: groqManifest.knownModels,
3116
- vendorMetadataExtractor: GROQ_METADATA_EXTRACTOR
3298
+ vendorMetadataExtractor: GROQ_METADATA_EXTRACTOR,
3299
+ // Groq caches automatically server-side and rejects/ignores undocumented
3300
+ // OpenAI params — do not forward prompt_cache_key.
3301
+ includeCacheKey: false
3117
3302
  });
3118
3303
  }
3119
3304
  };
@@ -3234,7 +3419,10 @@ var HuggingfaceAdapter = class extends OpenAIAdapter {
3234
3419
  stream(req) {
3235
3420
  return streamFromOpenAI(this.ensureClient(), req, {
3236
3421
  knownModels: huggingfaceManifest.knownModels,
3237
- vendorMetadataExtractor: HUGGINGFACE_METADATA_EXTRACTOR
3422
+ vendorMetadataExtractor: HUGGINGFACE_METADATA_EXTRACTOR,
3423
+ // The HF router fronts many third-party inference providers;
3424
+ // prompt_cache_key is unverified against them — do not forward.
3425
+ includeCacheKey: false
3238
3426
  });
3239
3427
  }
3240
3428
  };