@jaypie/llm 1.3.14 → 1.3.15
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/cjs/index.cjs +192 -17
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +20 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/operate/adapters/BedrockAdapter.d.ts +6 -0
- package/dist/cjs/operate/adapters/OpenAiAdapter.d.ts +5 -0
- package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +11 -0
- package/dist/cjs/operate/types.d.ts +6 -1
- package/dist/cjs/providers/anthropic/types.d.ts +14 -1
- package/dist/cjs/types/LlmProvider.interface.d.ts +19 -0
- package/dist/cjs/util/cacheControl.d.ts +21 -0
- package/dist/cjs/util/index.d.ts +1 -0
- package/dist/esm/index.d.ts +20 -1
- package/dist/esm/index.js +192 -17
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operate/adapters/BedrockAdapter.d.ts +6 -0
- package/dist/esm/operate/adapters/OpenAiAdapter.d.ts +5 -0
- package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +11 -0
- package/dist/esm/operate/types.d.ts +6 -1
- package/dist/esm/providers/anthropic/types.d.ts +14 -1
- package/dist/esm/types/LlmProvider.interface.d.ts +19 -0
- package/dist/esm/util/cacheControl.d.ts +21 -0
- package/dist/esm/util/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -498,6 +498,12 @@ function sumUsageByProviderModel(usage) {
|
|
|
498
498
|
totals[key].output += item.output;
|
|
499
499
|
totals[key].reasoning += item.reasoning;
|
|
500
500
|
totals[key].total += item.total;
|
|
501
|
+
if (item.cacheRead !== undefined) {
|
|
502
|
+
totals[key].cacheRead = (totals[key].cacheRead ?? 0) + item.cacheRead;
|
|
503
|
+
}
|
|
504
|
+
if (item.cacheWrite !== undefined) {
|
|
505
|
+
totals[key].cacheWrite = (totals[key].cacheWrite ?? 0) + item.cacheWrite;
|
|
506
|
+
}
|
|
501
507
|
}
|
|
502
508
|
return totals;
|
|
503
509
|
}
|
|
@@ -523,6 +529,7 @@ function buildExchangeEnvelope({ duration, initialHistoryLength, input, options,
|
|
|
523
529
|
return {
|
|
524
530
|
ids: extractResponseIds(response.responses),
|
|
525
531
|
request: {
|
|
532
|
+
cache: options.cache,
|
|
526
533
|
data: options.data,
|
|
527
534
|
effort: options.effort,
|
|
528
535
|
explain: options.explain,
|
|
@@ -560,6 +567,38 @@ function buildExchangeEnvelope({ duration, initialHistoryLength, input, options,
|
|
|
560
567
|
};
|
|
561
568
|
}
|
|
562
569
|
|
|
570
|
+
const CACHE_TTL_DEFAULT = "5m";
|
|
571
|
+
/**
|
|
572
|
+
* Normalize the caller-facing `cache` option into a concrete decision.
|
|
573
|
+
* - `undefined` / `true` → enabled at the default TTL
|
|
574
|
+
* - `false` / `0` → disabled
|
|
575
|
+
* - `"5m"` / `"1h"` → enabled at that TTL
|
|
576
|
+
*/
|
|
577
|
+
function resolveCache(cache) {
|
|
578
|
+
if (cache === false || cache === 0) {
|
|
579
|
+
return { enabled: false, ttl: CACHE_TTL_DEFAULT };
|
|
580
|
+
}
|
|
581
|
+
if (cache === "5m" || cache === "1h") {
|
|
582
|
+
return { enabled: true, ttl: cache };
|
|
583
|
+
}
|
|
584
|
+
// undefined or true
|
|
585
|
+
return { enabled: true, ttl: CACHE_TTL_DEFAULT };
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Deterministic short key for providers with automatic, prefix-based caching
|
|
589
|
+
* (e.g. OpenAI `prompt_cache_key`). Derived from the stable prefix so the same
|
|
590
|
+
* system prompt + tools + model always routes to the same cache. Dependency-
|
|
591
|
+
* free FNV-1a; not cryptographic.
|
|
592
|
+
*/
|
|
593
|
+
function promptCacheKey(seed) {
|
|
594
|
+
let hash = 0x811c9dc5;
|
|
595
|
+
for (let i = 0; i < seed.length; i += 1) {
|
|
596
|
+
hash ^= seed.charCodeAt(i);
|
|
597
|
+
hash = Math.imul(hash, 0x01000193);
|
|
598
|
+
}
|
|
599
|
+
return `jaypie-${(hash >>> 0).toString(16)}`;
|
|
600
|
+
}
|
|
601
|
+
|
|
563
602
|
/**
|
|
564
603
|
* Type guard to check if an item is a dedicated reasoning item (OpenAI)
|
|
565
604
|
*/
|
|
@@ -1411,6 +1450,14 @@ function tallyOperate({ toolCallNames = [], turns, usage = [], }) {
|
|
|
1411
1450
|
usageByModel[key].output += item.output;
|
|
1412
1451
|
usageByModel[key].reasoning += item.reasoning;
|
|
1413
1452
|
usageByModel[key].total += item.total;
|
|
1453
|
+
if (item.cacheRead !== undefined) {
|
|
1454
|
+
usageByModel[key].cacheRead =
|
|
1455
|
+
(usageByModel[key].cacheRead ?? 0) + item.cacheRead;
|
|
1456
|
+
}
|
|
1457
|
+
if (item.cacheWrite !== undefined) {
|
|
1458
|
+
usageByModel[key].cacheWrite =
|
|
1459
|
+
(usageByModel[key].cacheWrite ?? 0) + item.cacheWrite;
|
|
1460
|
+
}
|
|
1414
1461
|
}
|
|
1415
1462
|
llm.usage = usageByModel;
|
|
1416
1463
|
}
|
|
@@ -1517,27 +1564,26 @@ const MODULE$1 = {
|
|
|
1517
1564
|
//
|
|
1518
1565
|
// Helpers
|
|
1519
1566
|
//
|
|
1520
|
-
//
|
|
1521
|
-
//
|
|
1522
|
-
//
|
|
1523
|
-
//
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
: createRequire(import.meta.url);
|
|
1567
|
+
// Native dynamic import that neither rollup nor tsc rewrites to require(), so
|
|
1568
|
+
// a CJS-bundled build still loads @jaypie/dynamodb's ESM entry and shares the
|
|
1569
|
+
// host's initialized module instance. A require()-based resolution would load
|
|
1570
|
+
// the CJS build, whose module-level client state is separate from the ESM
|
|
1571
|
+
// instance an ESM host initializes (dual-package hazard, issue #429).
|
|
1572
|
+
const dynamicImport = new Function("s", "return import(s)");
|
|
1527
1573
|
let resolved$1 = false;
|
|
1528
1574
|
let cachedSdk$2 = null;
|
|
1529
1575
|
/**
|
|
1530
1576
|
* Lazily resolve @jaypie/dynamodb's storeExchange. Returns null (and never
|
|
1531
1577
|
* throws) when the peer is absent. Cached after the first attempt.
|
|
1532
1578
|
*/
|
|
1533
|
-
function resolveExchangeStore() {
|
|
1579
|
+
async function resolveExchangeStore() {
|
|
1534
1580
|
if (resolved$1) {
|
|
1535
1581
|
return cachedSdk$2;
|
|
1536
1582
|
}
|
|
1537
1583
|
resolved$1 = true;
|
|
1538
1584
|
try {
|
|
1539
|
-
const dynamodb =
|
|
1540
|
-
const sdk = dynamodb?.default ?? dynamodb;
|
|
1585
|
+
const dynamodb = await dynamicImport(MODULE$1.JAYPIE_DYNAMODB);
|
|
1586
|
+
const sdk = (dynamodb?.default ?? dynamodb);
|
|
1541
1587
|
if (sdk && typeof sdk.storeExchange === "function") {
|
|
1542
1588
|
cachedSdk$2 = sdk;
|
|
1543
1589
|
}
|
|
@@ -1560,7 +1606,7 @@ async function persistExchange(envelope) {
|
|
|
1560
1606
|
if (!isExchangeStoreEnabled()) {
|
|
1561
1607
|
return;
|
|
1562
1608
|
}
|
|
1563
|
-
const sdk = resolveExchangeStore();
|
|
1609
|
+
const sdk = (await resolveExchangeStore());
|
|
1564
1610
|
if (!sdk) {
|
|
1565
1611
|
return;
|
|
1566
1612
|
}
|
|
@@ -2348,8 +2394,19 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
2348
2394
|
PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT,
|
|
2349
2395
|
stream: false,
|
|
2350
2396
|
};
|
|
2397
|
+
const cache = resolveCache(request.cache);
|
|
2398
|
+
const cacheControl = cache.enabled
|
|
2399
|
+
? {
|
|
2400
|
+
type: "ephemeral",
|
|
2401
|
+
...(cache.ttl === "1h" ? { ttl: "1h" } : {}),
|
|
2402
|
+
}
|
|
2403
|
+
: undefined;
|
|
2351
2404
|
if (request.system) {
|
|
2352
|
-
|
|
2405
|
+
// A cache breakpoint on the system block caches tools+system together
|
|
2406
|
+
// (render order is tools -> system -> messages).
|
|
2407
|
+
anthropicRequest.system = cacheControl
|
|
2408
|
+
? [{ type: "text", text: request.system, cache_control: cacheControl }]
|
|
2409
|
+
: request.system;
|
|
2353
2410
|
}
|
|
2354
2411
|
const useFallbackStructuredOutput = Boolean(request.format) &&
|
|
2355
2412
|
!this.supportsStructuredOutput(anthropicRequest.model);
|
|
@@ -2366,7 +2423,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
2366
2423
|
});
|
|
2367
2424
|
}
|
|
2368
2425
|
if (allTools.length > 0) {
|
|
2369
|
-
anthropicRequest.tools = allTools.map((tool) => ({
|
|
2426
|
+
anthropicRequest.tools = allTools.map((tool, index) => ({
|
|
2370
2427
|
name: tool.name,
|
|
2371
2428
|
description: tool.description,
|
|
2372
2429
|
input_schema: {
|
|
@@ -2374,6 +2431,11 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
2374
2431
|
type: "object",
|
|
2375
2432
|
},
|
|
2376
2433
|
type: "custom",
|
|
2434
|
+
// Breakpoint on the last tool caches the tool list (which renders
|
|
2435
|
+
// before system) when there is no system prompt to anchor it.
|
|
2436
|
+
...(cacheControl && index === allTools.length - 1
|
|
2437
|
+
? { cache_control: cacheControl }
|
|
2438
|
+
: {}),
|
|
2377
2439
|
}));
|
|
2378
2440
|
anthropicRequest.tool_choice = useFallbackStructuredOutput
|
|
2379
2441
|
? { type: "any" }
|
|
@@ -2676,6 +2738,12 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
2676
2738
|
output: usage.output_tokens,
|
|
2677
2739
|
reasoning: usage.thinking_tokens || 0,
|
|
2678
2740
|
total: usage.input_tokens + usage.output_tokens,
|
|
2741
|
+
...(usage.cache_read_input_tokens !== undefined
|
|
2742
|
+
? { cacheRead: usage.cache_read_input_tokens }
|
|
2743
|
+
: {}),
|
|
2744
|
+
...(usage.cache_creation_input_tokens !== undefined
|
|
2745
|
+
? { cacheWrite: usage.cache_creation_input_tokens }
|
|
2746
|
+
: {}),
|
|
2679
2747
|
provider: this.name,
|
|
2680
2748
|
model,
|
|
2681
2749
|
};
|
|
@@ -2928,6 +2996,14 @@ function isTemperatureDeprecationError$3(error) {
|
|
|
2928
2996
|
const msg = error?.message ?? "";
|
|
2929
2997
|
return /temperature.*deprecated|deprecated.*temperature/i.test(msg);
|
|
2930
2998
|
}
|
|
2999
|
+
function isCachePointUnsupportedError(error) {
|
|
3000
|
+
const name = error?.constructor?.name ?? "";
|
|
3001
|
+
const msg = error?.message ?? "";
|
|
3002
|
+
// A model that cannot cache rejects the cachePoint block with a
|
|
3003
|
+
// ValidationException naming caching / cachePoint.
|
|
3004
|
+
return (/ValidationException|invalid|not support/i.test(name + " " + msg) &&
|
|
3005
|
+
/cachePoint|cache_point|prompt caching|caching/i.test(msg));
|
|
3006
|
+
}
|
|
2931
3007
|
function extractJson(text) {
|
|
2932
3008
|
// Try direct parse first
|
|
2933
3009
|
try {
|
|
@@ -2963,6 +3039,13 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2963
3039
|
this.defaultModel = PROVIDER.BEDROCK.DEFAULT;
|
|
2964
3040
|
this._modelsFallbackToStructuredOutputTool = new Set();
|
|
2965
3041
|
this._modelsWithoutTemperature = new Set();
|
|
3042
|
+
this._modelsWithoutCachePoint = new Set();
|
|
3043
|
+
}
|
|
3044
|
+
rememberModelRejectsCachePoint(model) {
|
|
3045
|
+
this._modelsWithoutCachePoint.add(model);
|
|
3046
|
+
}
|
|
3047
|
+
supportsCachePoint(model) {
|
|
3048
|
+
return !this._modelsWithoutCachePoint.has(model);
|
|
2966
3049
|
}
|
|
2967
3050
|
rememberModelRejectsOutputConfig(model) {
|
|
2968
3051
|
this._modelsFallbackToStructuredOutputTool.add(model);
|
|
@@ -3099,11 +3182,51 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
3099
3182
|
};
|
|
3100
3183
|
}
|
|
3101
3184
|
}
|
|
3185
|
+
// Prompt caching via Converse cachePoint blocks (5-min default TTL; the
|
|
3186
|
+
// `ttl` option does not apply). Gated per model — cachePoint 400s on
|
|
3187
|
+
// unsupported models, which executeRequest catches and denylists.
|
|
3188
|
+
if (resolveCache(request.cache).enabled && this.supportsCachePoint(model)) {
|
|
3189
|
+
const cachePoint = { cachePoint: { type: "default" } };
|
|
3190
|
+
if (bedrockRequest.system && bedrockRequest.system.length > 0) {
|
|
3191
|
+
bedrockRequest.system = [
|
|
3192
|
+
...bedrockRequest.system,
|
|
3193
|
+
cachePoint,
|
|
3194
|
+
];
|
|
3195
|
+
}
|
|
3196
|
+
if (bedrockRequest.toolConfig?.tools?.length) {
|
|
3197
|
+
bedrockRequest.toolConfig = {
|
|
3198
|
+
...bedrockRequest.toolConfig,
|
|
3199
|
+
tools: [
|
|
3200
|
+
...bedrockRequest.toolConfig.tools,
|
|
3201
|
+
cachePoint,
|
|
3202
|
+
],
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3102
3206
|
if (request.providerOptions) {
|
|
3103
3207
|
Object.assign(bedrockRequest, request.providerOptions);
|
|
3104
3208
|
}
|
|
3105
3209
|
return bedrockRequest;
|
|
3106
3210
|
}
|
|
3211
|
+
/** Remove any cachePoint blocks so the request can be retried unsupported. */
|
|
3212
|
+
stripCachePoints(request) {
|
|
3213
|
+
const stripped = { ...request };
|
|
3214
|
+
if (stripped.system) {
|
|
3215
|
+
stripped.system = stripped.system.filter((block) => !("cachePoint" in block));
|
|
3216
|
+
}
|
|
3217
|
+
if (stripped.toolConfig?.tools) {
|
|
3218
|
+
stripped.toolConfig = {
|
|
3219
|
+
...stripped.toolConfig,
|
|
3220
|
+
tools: stripped.toolConfig.tools.filter((tool) => !("cachePoint" in tool)),
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
return stripped;
|
|
3224
|
+
}
|
|
3225
|
+
requestHasCachePoints(request) {
|
|
3226
|
+
const inSystem = (request.system ?? []).some((block) => "cachePoint" in block);
|
|
3227
|
+
const inTools = (request.toolConfig?.tools ?? []).some((tool) => "cachePoint" in tool);
|
|
3228
|
+
return inSystem || inTools;
|
|
3229
|
+
}
|
|
3107
3230
|
formatTools(toolkit) {
|
|
3108
3231
|
return toolkit.tools.map((tool) => ({
|
|
3109
3232
|
name: tool.name,
|
|
@@ -3158,6 +3281,15 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
3158
3281
|
const fallbackRequest = this.toFallbackStructuredOutputRequest(bedrockRequest);
|
|
3159
3282
|
return (await bedrockClient.send(new ConverseCommand(fallbackRequest), signal ? { abortSignal: signal } : undefined));
|
|
3160
3283
|
}
|
|
3284
|
+
if (this.requestHasCachePoints(bedrockRequest) &&
|
|
3285
|
+
isCachePointUnsupportedError(error)) {
|
|
3286
|
+
this.rememberModelRejectsCachePoint(bedrockRequest.modelId || this.defaultModel);
|
|
3287
|
+
const retryRequest = this.stripCachePoints(bedrockRequest);
|
|
3288
|
+
const response = (await bedrockClient.send(new ConverseCommand(retryRequest), signal ? { abortSignal: signal } : undefined));
|
|
3289
|
+
if (wantsStructuredOutput)
|
|
3290
|
+
response.__jaypieStructuredOutput = true;
|
|
3291
|
+
return response;
|
|
3292
|
+
}
|
|
3161
3293
|
throw error;
|
|
3162
3294
|
}
|
|
3163
3295
|
}
|
|
@@ -3301,6 +3433,12 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
3301
3433
|
reasoning: 0,
|
|
3302
3434
|
total: usage?.totalTokens ??
|
|
3303
3435
|
(usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0),
|
|
3436
|
+
...(usage?.cacheReadInputTokens !== undefined
|
|
3437
|
+
? { cacheRead: usage.cacheReadInputTokens }
|
|
3438
|
+
: {}),
|
|
3439
|
+
...(usage?.cacheWriteInputTokens !== undefined
|
|
3440
|
+
? { cacheWrite: usage.cacheWriteInputTokens }
|
|
3441
|
+
: {}),
|
|
3304
3442
|
provider: this.name,
|
|
3305
3443
|
model,
|
|
3306
3444
|
};
|
|
@@ -5472,6 +5610,13 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
5472
5610
|
supportsReasoningEffort(model) {
|
|
5473
5611
|
return isReasoningModel(model);
|
|
5474
5612
|
}
|
|
5613
|
+
/**
|
|
5614
|
+
* Whether to emit `prompt_cache_key` (OpenAI Responses API). Overridable by
|
|
5615
|
+
* OpenAI-compatible subclasses whose backend rejects the field.
|
|
5616
|
+
*/
|
|
5617
|
+
supportsPromptCacheKey() {
|
|
5618
|
+
return true;
|
|
5619
|
+
}
|
|
5475
5620
|
/** Translate a normalized effort to this provider's `reasoning.effort` value. */
|
|
5476
5621
|
mapReasoningEffort(effort, model) {
|
|
5477
5622
|
return toOpenAiEffort(effort, { model });
|
|
@@ -5511,6 +5656,18 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
5511
5656
|
summary: "auto",
|
|
5512
5657
|
};
|
|
5513
5658
|
}
|
|
5659
|
+
// OpenAI prompt caching is automatic (prefix-based). Setting a stable
|
|
5660
|
+
// prompt_cache_key routes repeat traffic to the same cache across
|
|
5661
|
+
// instances. Keyed on the stable prefix only (system + instructions +
|
|
5662
|
+
// tools + model), never the volatile user turn.
|
|
5663
|
+
if (this.supportsPromptCacheKey() && resolveCache(request.cache).enabled) {
|
|
5664
|
+
openaiRequest.prompt_cache_key = promptCacheKey(JSON.stringify([
|
|
5665
|
+
model,
|
|
5666
|
+
request.system ?? "",
|
|
5667
|
+
request.instructions ?? "",
|
|
5668
|
+
request.tools ?? [],
|
|
5669
|
+
]));
|
|
5670
|
+
}
|
|
5514
5671
|
if (request.providerOptions) {
|
|
5515
5672
|
Object.assign(openaiRequest, request.providerOptions);
|
|
5516
5673
|
}
|
|
@@ -5769,11 +5926,13 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
5769
5926
|
model,
|
|
5770
5927
|
};
|
|
5771
5928
|
}
|
|
5929
|
+
const cachedTokens = openaiResponse.usage.input_tokens_details?.cached_tokens;
|
|
5772
5930
|
return {
|
|
5773
5931
|
input: openaiResponse.usage.input_tokens || 0,
|
|
5774
5932
|
output: openaiResponse.usage.output_tokens || 0,
|
|
5775
5933
|
reasoning: openaiResponse.usage.output_tokens_details?.reasoning_tokens || 0,
|
|
5776
5934
|
total: openaiResponse.usage.total_tokens || 0,
|
|
5935
|
+
...(cachedTokens !== undefined ? { cacheRead: cachedTokens } : {}),
|
|
5777
5936
|
provider: this.name,
|
|
5778
5937
|
model,
|
|
5779
5938
|
};
|
|
@@ -6165,7 +6324,13 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
6165
6324
|
//
|
|
6166
6325
|
buildRequest(request) {
|
|
6167
6326
|
// Convert messages to OpenRouter format (OpenAI-compatible)
|
|
6168
|
-
const
|
|
6327
|
+
const cache = resolveCache(request.cache);
|
|
6328
|
+
const messages = this.convertMessagesToOpenRouter(request.messages, request.system, cache.enabled
|
|
6329
|
+
? {
|
|
6330
|
+
type: "ephemeral",
|
|
6331
|
+
...(cache.ttl === "1h" ? { ttl: "1h" } : {}),
|
|
6332
|
+
}
|
|
6333
|
+
: undefined);
|
|
6169
6334
|
// Append instructions to last message if provided
|
|
6170
6335
|
if (request.instructions && messages.length > 0) {
|
|
6171
6336
|
const lastMsg = messages[messages.length - 1];
|
|
@@ -6529,11 +6694,15 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
6529
6694
|
}
|
|
6530
6695
|
// SDK returns camelCase, but support snake_case as fallback
|
|
6531
6696
|
const usage = openRouterResponse.usage;
|
|
6697
|
+
const cachedTokens = usage.promptTokensDetails?.cachedTokens ??
|
|
6698
|
+
usage.promptTokensDetails?.cached_tokens ??
|
|
6699
|
+
usage.prompt_tokens_details?.cached_tokens;
|
|
6532
6700
|
return {
|
|
6533
6701
|
input: usage.promptTokens || usage.prompt_tokens || 0,
|
|
6534
6702
|
output: usage.completionTokens || usage.completion_tokens || 0,
|
|
6535
6703
|
reasoning: usage.completionTokensDetails?.reasoningTokens || 0,
|
|
6536
6704
|
total: usage.totalTokens || usage.total_tokens || 0,
|
|
6705
|
+
...(cachedTokens !== undefined ? { cacheRead: cachedTokens } : {}),
|
|
6537
6706
|
provider: this.name,
|
|
6538
6707
|
model,
|
|
6539
6708
|
};
|
|
@@ -6734,13 +6903,17 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
6734
6903
|
const choice = response.choices[0];
|
|
6735
6904
|
return choice?.message?.content ?? undefined;
|
|
6736
6905
|
}
|
|
6737
|
-
convertMessagesToOpenRouter(messages, system) {
|
|
6906
|
+
convertMessagesToOpenRouter(messages, system, cacheControl) {
|
|
6738
6907
|
const openRouterMessages = [];
|
|
6739
|
-
// Add system message if provided
|
|
6908
|
+
// Add system message if provided. A cache_control breakpoint on the system
|
|
6909
|
+
// content is forwarded by OpenRouter to Anthropic/Gemini backends (and
|
|
6910
|
+
// ignored by others), caching the stable system prefix.
|
|
6740
6911
|
if (system) {
|
|
6741
6912
|
openRouterMessages.push({
|
|
6742
6913
|
role: "system",
|
|
6743
|
-
content:
|
|
6914
|
+
content: cacheControl
|
|
6915
|
+
? [{ type: "text", text: system, cache_control: cacheControl }]
|
|
6916
|
+
: system,
|
|
6744
6917
|
});
|
|
6745
6918
|
}
|
|
6746
6919
|
for (const msg of messages) {
|
|
@@ -8452,6 +8625,7 @@ class OperateLoop {
|
|
|
8452
8625
|
}
|
|
8453
8626
|
buildInitialRequest(state, options) {
|
|
8454
8627
|
return {
|
|
8628
|
+
cache: options.cache,
|
|
8455
8629
|
effort: options.effort,
|
|
8456
8630
|
format: state.formattedFormat,
|
|
8457
8631
|
instructions: options.instructions,
|
|
@@ -9085,6 +9259,7 @@ class StreamLoop {
|
|
|
9085
9259
|
}
|
|
9086
9260
|
buildInitialRequest(state, options) {
|
|
9087
9261
|
return {
|
|
9262
|
+
cache: options.cache,
|
|
9088
9263
|
effort: options.effort,
|
|
9089
9264
|
format: state.formattedFormat,
|
|
9090
9265
|
instructions: options.instructions,
|