@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.
package/dist/index.cjs CHANGED
@@ -101,6 +101,9 @@ function assertValidRequest(req) {
101
101
  "PromptRequest.messages must not contain role: 'system'. Pass the system prompt via PromptRequest.system instead."
102
102
  );
103
103
  }
104
+ if (req.cacheKey !== void 0 && (typeof req.cacheKey !== "string" || req.cacheKey.length === 0)) {
105
+ throw new Error("PromptRequest.cacheKey must be a non-empty string");
106
+ }
104
107
  }
105
108
 
106
109
  // src/adapters/openai/_streaming.ts
@@ -125,6 +128,9 @@ async function* streamFromOpenAI(client, req, options) {
125
128
  body.tool_choice = "auto";
126
129
  }
127
130
  if (req.temperature !== void 0) body.temperature = req.temperature;
131
+ if (req.cacheKey && options.includeCacheKey !== false) {
132
+ body.prompt_cache_key = req.cacheKey;
133
+ }
128
134
  const effectiveMaxTokens = req.maxTokens ?? options.defaultMaxTokens;
129
135
  if (effectiveMaxTokens !== void 0) {
130
136
  const param = options.tokensParam ?? (needsMaxCompletionTokens(req.model) ? "max_completion_tokens" : "max_tokens");
@@ -178,10 +184,12 @@ async function* streamFromOpenAI(client, req, options) {
178
184
  throw new Error(`OpenAI-compatible stream returned an error: ${message}`);
179
185
  }
180
186
  if (chunk.usage) {
187
+ const cachedTokens = chunk.usage.prompt_tokens_details?.cached_tokens;
181
188
  usage = {
182
- ...chunk.usage.prompt_tokens !== void 0 ? { inputTokens: chunk.usage.prompt_tokens } : {},
189
+ ...chunk.usage.prompt_tokens !== void 0 ? { inputTokens: chunk.usage.prompt_tokens - (cachedTokens ?? 0) } : {},
183
190
  ...chunk.usage.completion_tokens !== void 0 ? { outputTokens: chunk.usage.completion_tokens } : {},
184
- ...chunk.usage.total_tokens !== void 0 ? { totalTokens: chunk.usage.total_tokens } : {}
191
+ ...chunk.usage.total_tokens !== void 0 ? { totalTokens: chunk.usage.total_tokens } : {},
192
+ ...cachedTokens !== void 0 ? { cacheReadTokens: cachedTokens } : {}
185
193
  };
186
194
  }
187
195
  const choice = chunk.choices?.[0];
@@ -560,7 +568,8 @@ var OpenAIAdapter = class {
560
568
  stream(req) {
561
569
  return streamFromOpenAI(this.ensureClient(), req, {
562
570
  knownModels: this.knownModels(),
563
- vendorMetadataExtractor: OPENAI_METADATA_EXTRACTOR
571
+ vendorMetadataExtractor: OPENAI_METADATA_EXTRACTOR,
572
+ includeCacheKey: this.forwardCacheKey()
564
573
  });
565
574
  }
566
575
  appendAssistantToolCall(history, toolCalls) {
@@ -581,6 +590,20 @@ var OpenAIAdapter = class {
581
590
  knownModels() {
582
591
  return openAIManifest.knownModels;
583
592
  }
593
+ /**
594
+ * Whether to forward `req.cacheKey` as OpenAI's `prompt_cache_key` request
595
+ * param. Default true — verified accepted by OpenAI, Azure OpenAI,
596
+ * Vertex-OpenAI, and Baseten. OpenAI-compatible vendors whose param
597
+ * handling is strict or unverified (DeepSeek, Moonshot, Zai, xAI — and
598
+ * Groq via its own stream override) override to false: a strict backend
599
+ * rejects the whole request on an unknown body param, which is far worse
600
+ * than forgoing a cache-routing hint. Flip a vendor to true only after
601
+ * verifying its endpoint (including any proxy in front of it, e.g.
602
+ * Bedrock Mantle) tolerates the param.
603
+ */
604
+ forwardCacheKey() {
605
+ return true;
606
+ }
584
607
  async testCredential() {
585
608
  await this.ensureClient().models.list();
586
609
  }
@@ -693,6 +716,11 @@ var DeepSeekAdapter = class extends OpenAIAdapter {
693
716
  knownModels() {
694
717
  return deepseekManifest.knownModels;
695
718
  }
719
+ // DeepSeek caches automatically server-side; prompt_cache_key is
720
+ // unverified against its param handling — do not forward.
721
+ forwardCacheKey() {
722
+ return false;
723
+ }
696
724
  };
697
725
  var deepseekFactory = {
698
726
  vendor: deepseekManifest.vendor,
@@ -916,6 +944,12 @@ var MoonshotAdapter = class extends OpenAIAdapter {
916
944
  knownModels() {
917
945
  return moonshotManifest.knownModels;
918
946
  }
947
+ // Moonshot's param handling is strict (e.g. it rejects unknown template
948
+ // kwargs) and prompt_cache_key is unverified against both the native
949
+ // endpoint and Bedrock Mantle deployments — do not forward.
950
+ forwardCacheKey() {
951
+ return false;
952
+ }
919
953
  };
920
954
  var moonshotFactory = {
921
955
  vendor: moonshotManifest.vendor,
@@ -989,6 +1023,11 @@ var ZaiAdapter = class extends OpenAIAdapter {
989
1023
  knownModels() {
990
1024
  return zaiManifest.knownModels;
991
1025
  }
1026
+ // prompt_cache_key is unverified against Zai's OpenAI-compat endpoint —
1027
+ // do not forward.
1028
+ forwardCacheKey() {
1029
+ return false;
1030
+ }
992
1031
  };
993
1032
  var zaiFactory = {
994
1033
  vendor: zaiManifest.vendor,
@@ -1084,6 +1123,11 @@ var XaiAdapter = class extends OpenAIAdapter {
1084
1123
  knownModels() {
1085
1124
  return xaiManifest.knownModels;
1086
1125
  }
1126
+ // xAI caches automatically server-side; prompt_cache_key is unverified
1127
+ // against its param handling — do not forward.
1128
+ forwardCacheKey() {
1129
+ return false;
1130
+ }
1087
1131
  };
1088
1132
  var xaiFactory = {
1089
1133
  vendor: xaiManifest.vendor,
@@ -1234,6 +1278,16 @@ var AnthropicAdapter = class {
1234
1278
  if (req.system) body.system = req.system;
1235
1279
  if (tools) body.tools = tools;
1236
1280
  if (req.temperature !== void 0) body.temperature = req.temperature;
1281
+ if (req.cacheKey) {
1282
+ if (req.system) {
1283
+ body.system = [{ type: "text", text: req.system, cache_control: { type: "ephemeral" } }];
1284
+ }
1285
+ if (tools) {
1286
+ const lastTool = tools[tools.length - 1];
1287
+ lastTool.cache_control = { type: "ephemeral" };
1288
+ }
1289
+ markLastMessageForCaching(messages);
1290
+ }
1237
1291
  let stream;
1238
1292
  let responseHeaders;
1239
1293
  try {
@@ -1261,6 +1315,8 @@ var AnthropicAdapter = class {
1261
1315
  let stopReason;
1262
1316
  let inputTokens;
1263
1317
  let outputTokens;
1318
+ let cacheReadTokens;
1319
+ let cacheWriteTokens;
1264
1320
  try {
1265
1321
  for await (const event of stream) {
1266
1322
  if (req.signal?.aborted) {
@@ -1270,6 +1326,8 @@ var AnthropicAdapter = class {
1270
1326
  switch (event.type) {
1271
1327
  case "message_start":
1272
1328
  inputTokens = event.message?.usage?.input_tokens;
1329
+ cacheReadTokens = event.message?.usage?.cache_read_input_tokens;
1330
+ cacheWriteTokens = event.message?.usage?.cache_creation_input_tokens;
1273
1331
  break;
1274
1332
  case "content_block_start": {
1275
1333
  const block = event.content_block;
@@ -1351,7 +1409,11 @@ var AnthropicAdapter = class {
1351
1409
  const usage = inputTokens !== void 0 || outputTokens !== void 0 ? {
1352
1410
  ...inputTokens !== void 0 ? { inputTokens } : {},
1353
1411
  ...outputTokens !== void 0 ? { outputTokens } : {},
1354
- ...inputTokens !== void 0 && outputTokens !== void 0 ? { totalTokens: inputTokens + outputTokens } : {}
1412
+ ...inputTokens !== void 0 && outputTokens !== void 0 ? {
1413
+ totalTokens: inputTokens + outputTokens + (cacheReadTokens ?? 0) + (cacheWriteTokens ?? 0)
1414
+ } : {},
1415
+ ...cacheReadTokens !== void 0 ? { cacheReadTokens } : {},
1416
+ ...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
1355
1417
  } : void 0;
1356
1418
  const endEvent = {
1357
1419
  type: "end",
@@ -1450,6 +1512,23 @@ function buildWireMessages2(req) {
1450
1512
  }
1451
1513
  return out;
1452
1514
  }
1515
+ function markLastMessageForCaching(messages) {
1516
+ if (messages.length === 0) return;
1517
+ const last = messages[messages.length - 1];
1518
+ const cacheControl = { cache_control: { type: "ephemeral" } };
1519
+ if (typeof last.content === "string" && last.content.length > 0) {
1520
+ messages[messages.length - 1] = {
1521
+ ...last,
1522
+ content: [{ type: "text", text: last.content, ...cacheControl }]
1523
+ };
1524
+ } else if (Array.isArray(last.content) && last.content.length > 0) {
1525
+ const blocks = last.content;
1526
+ messages[messages.length - 1] = {
1527
+ ...last,
1528
+ content: [...blocks.slice(0, -1), { ...blocks[blocks.length - 1], ...cacheControl }]
1529
+ };
1530
+ }
1531
+ }
1453
1532
  function formatTools2(tools) {
1454
1533
  return tools.map((t) => ({
1455
1534
  name: t.name,
@@ -1512,6 +1591,11 @@ async function* streamFromGemini(client, req) {
1512
1591
  if (req.temperature !== void 0) config.temperature = req.temperature;
1513
1592
  if (req.maxTokens !== void 0) config.maxOutputTokens = req.maxTokens;
1514
1593
  if (req.signal) config.abortSignal = req.signal;
1594
+ if (req.providerParams) {
1595
+ for (const [key, value] of Object.entries(req.providerParams)) {
1596
+ if (!(key in config)) config[key] = value;
1597
+ }
1598
+ }
1515
1599
  let stream;
1516
1600
  try {
1517
1601
  stream = await client.models.generateContentStream({
@@ -1532,6 +1616,7 @@ async function* streamFromGemini(client, req) {
1532
1616
  let inputTokens;
1533
1617
  let outputTokens;
1534
1618
  let totalTokens;
1619
+ let cacheReadTokens;
1535
1620
  try {
1536
1621
  for await (const chunk of stream) {
1537
1622
  if (req.signal?.aborted) {
@@ -1540,7 +1625,12 @@ async function* streamFromGemini(client, req) {
1540
1625
  }
1541
1626
  const usage2 = chunk.usageMetadata;
1542
1627
  if (usage2) {
1543
- if (usage2.promptTokenCount !== void 0) inputTokens = usage2.promptTokenCount;
1628
+ if (usage2.cachedContentTokenCount !== void 0) {
1629
+ cacheReadTokens = usage2.cachedContentTokenCount;
1630
+ }
1631
+ if (usage2.promptTokenCount !== void 0) {
1632
+ inputTokens = usage2.promptTokenCount - (usage2.cachedContentTokenCount ?? 0);
1633
+ }
1544
1634
  if (usage2.candidatesTokenCount !== void 0) outputTokens = usage2.candidatesTokenCount;
1545
1635
  if (usage2.totalTokenCount !== void 0) totalTokens = usage2.totalTokenCount;
1546
1636
  }
@@ -1594,7 +1684,8 @@ async function* streamFromGemini(client, req) {
1594
1684
  const usage = inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 ? {
1595
1685
  ...inputTokens !== void 0 ? { inputTokens } : {},
1596
1686
  ...outputTokens !== void 0 ? { outputTokens } : {},
1597
- ...totalTokens !== void 0 ? { totalTokens } : {}
1687
+ ...totalTokens !== void 0 ? { totalTokens } : {},
1688
+ ...cacheReadTokens !== void 0 ? { cacheReadTokens } : {}
1598
1689
  } : void 0;
1599
1690
  const endEvent = {
1600
1691
  type: "end",
@@ -1736,6 +1827,39 @@ function isAbortError3(err) {
1736
1827
 
1737
1828
  // src/adapters/google/manifest.ts
1738
1829
  var GOOGLE_KNOWN_MODELS = [
1830
+ {
1831
+ id: "gemini-3.6-flash",
1832
+ displayName: "Gemini 3.6 Flash",
1833
+ capabilities: {
1834
+ streaming: true,
1835
+ tools: true,
1836
+ vision: true,
1837
+ systemPrompt: true,
1838
+ maxContextTokens: 1e6
1839
+ }
1840
+ },
1841
+ {
1842
+ id: "gemini-3.5-flash",
1843
+ displayName: "Gemini 3.5 Flash",
1844
+ capabilities: {
1845
+ streaming: true,
1846
+ tools: true,
1847
+ vision: true,
1848
+ systemPrompt: true,
1849
+ maxContextTokens: 1e6
1850
+ }
1851
+ },
1852
+ {
1853
+ id: "gemini-3.5-flash-lite",
1854
+ displayName: "Gemini 3.5 Flash-Lite",
1855
+ capabilities: {
1856
+ streaming: true,
1857
+ tools: true,
1858
+ vision: true,
1859
+ systemPrompt: true,
1860
+ maxContextTokens: 1e6
1861
+ }
1862
+ },
1739
1863
  {
1740
1864
  id: "gemini-2.5-pro",
1741
1865
  displayName: "Gemini 2.5 Pro",
@@ -1879,6 +2003,39 @@ var import_genai2 = require("@google/genai");
1879
2003
 
1880
2004
  // src/adapters/vertex-gemini/manifest.ts
1881
2005
  var VERTEX_GEMINI_KNOWN_MODELS = [
2006
+ {
2007
+ id: "gemini-3.6-flash",
2008
+ displayName: "Gemini 3.6 Flash",
2009
+ capabilities: {
2010
+ streaming: true,
2011
+ tools: true,
2012
+ vision: true,
2013
+ systemPrompt: true,
2014
+ maxContextTokens: 1e6
2015
+ }
2016
+ },
2017
+ {
2018
+ id: "gemini-3.5-flash",
2019
+ displayName: "Gemini 3.5 Flash",
2020
+ capabilities: {
2021
+ streaming: true,
2022
+ tools: true,
2023
+ vision: true,
2024
+ systemPrompt: true,
2025
+ maxContextTokens: 1e6
2026
+ }
2027
+ },
2028
+ {
2029
+ id: "gemini-3.5-flash-lite",
2030
+ displayName: "Gemini 3.5 Flash-Lite",
2031
+ capabilities: {
2032
+ streaming: true,
2033
+ tools: true,
2034
+ vision: true,
2035
+ systemPrompt: true,
2036
+ maxContextTokens: 1e6
2037
+ }
2038
+ },
1882
2039
  {
1883
2040
  id: "gemini-2.5-pro",
1884
2041
  displayName: "Gemini 2.5 Pro",
@@ -2562,6 +2719,15 @@ var BedrockAdapter = class {
2562
2719
  if (Object.keys(inferenceConfig).length > 0) {
2563
2720
  commandInput.inferenceConfig = inferenceConfig;
2564
2721
  }
2722
+ if (req.cacheKey) {
2723
+ if (commandInput.system) {
2724
+ commandInput.system.push({ cachePoint: { type: "default" } });
2725
+ }
2726
+ if (tools) {
2727
+ tools.tools.push({ cachePoint: { type: "default" } });
2728
+ }
2729
+ markLastMessageForCaching2(messages);
2730
+ }
2565
2731
  const command = new import_client_bedrock_runtime.ConverseStreamCommand(
2566
2732
  commandInput
2567
2733
  );
@@ -2585,6 +2751,8 @@ var BedrockAdapter = class {
2585
2751
  let inputTokens;
2586
2752
  let outputTokens;
2587
2753
  let totalTokens;
2754
+ let cacheReadTokens;
2755
+ let cacheWriteTokens;
2588
2756
  try {
2589
2757
  for await (const event of response.stream) {
2590
2758
  if (req.signal?.aborted) {
@@ -2637,6 +2805,8 @@ var BedrockAdapter = class {
2637
2805
  inputTokens = event.metadata.usage.inputTokens;
2638
2806
  outputTokens = event.metadata.usage.outputTokens;
2639
2807
  totalTokens = event.metadata.usage.totalTokens;
2808
+ cacheReadTokens = event.metadata.usage.cacheReadInputTokens;
2809
+ cacheWriteTokens = event.metadata.usage.cacheWriteInputTokens;
2640
2810
  }
2641
2811
  }
2642
2812
  }
@@ -2663,7 +2833,9 @@ var BedrockAdapter = class {
2663
2833
  const usage = inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 ? {
2664
2834
  ...inputTokens !== void 0 ? { inputTokens } : {},
2665
2835
  ...outputTokens !== void 0 ? { outputTokens } : {},
2666
- ...totalTokens !== void 0 ? { totalTokens } : {}
2836
+ ...totalTokens !== void 0 ? { totalTokens } : {},
2837
+ ...cacheReadTokens !== void 0 ? { cacheReadTokens } : {},
2838
+ ...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
2667
2839
  } : void 0;
2668
2840
  const endEvent = {
2669
2841
  type: "end",
@@ -2771,6 +2943,16 @@ function buildWireMessages3(req) {
2771
2943
  }
2772
2944
  return out;
2773
2945
  }
2946
+ function markLastMessageForCaching2(messages) {
2947
+ if (messages.length === 0) return;
2948
+ const last = messages[messages.length - 1];
2949
+ if (Array.isArray(last.content) && last.content.length > 0) {
2950
+ messages[messages.length - 1] = {
2951
+ ...last,
2952
+ content: [...last.content, { cachePoint: { type: "default" } }]
2953
+ };
2954
+ }
2955
+ }
2774
2956
  function formatTools4(tools) {
2775
2957
  return tools.map((t) => ({
2776
2958
  toolSpec: {
@@ -3126,7 +3308,10 @@ var GroqAdapter = class extends OpenAIAdapter {
3126
3308
  stream(req) {
3127
3309
  return streamFromOpenAI(this.ensureClient(), req, {
3128
3310
  knownModels: groqManifest.knownModels,
3129
- vendorMetadataExtractor: GROQ_METADATA_EXTRACTOR
3311
+ vendorMetadataExtractor: GROQ_METADATA_EXTRACTOR,
3312
+ // Groq caches automatically server-side and rejects/ignores undocumented
3313
+ // OpenAI params — do not forward prompt_cache_key.
3314
+ includeCacheKey: false
3130
3315
  });
3131
3316
  }
3132
3317
  };
@@ -3247,7 +3432,10 @@ var HuggingfaceAdapter = class extends OpenAIAdapter {
3247
3432
  stream(req) {
3248
3433
  return streamFromOpenAI(this.ensureClient(), req, {
3249
3434
  knownModels: huggingfaceManifest.knownModels,
3250
- vendorMetadataExtractor: HUGGINGFACE_METADATA_EXTRACTOR
3435
+ vendorMetadataExtractor: HUGGINGFACE_METADATA_EXTRACTOR,
3436
+ // The HF router fronts many third-party inference providers;
3437
+ // prompt_cache_key is unverified against them — do not forward.
3438
+ includeCacheKey: false
3251
3439
  });
3252
3440
  }
3253
3441
  };