@agentionai/agents 1.10.3 → 1.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.
|
@@ -228,6 +228,29 @@ export interface OpenRouterSpecificConfig {
|
|
|
228
228
|
* Also groups the requests in OpenRouter's observability views.
|
|
229
229
|
*/
|
|
230
230
|
sessionId?: string;
|
|
231
|
+
/**
|
|
232
|
+
* Mark the system prompt and the last tool definition with an Anthropic
|
|
233
|
+
* `cache_control: { type: "ephemeral" }` breakpoint, so the stable prefix of
|
|
234
|
+
* a request — system prompt plus tool schemas — is billed once and read
|
|
235
|
+
* from cache on later requests in the same conversation instead of paid in
|
|
236
|
+
* full every time.
|
|
237
|
+
*
|
|
238
|
+
* Worth it for an agent whose system prompt and tools are reused across
|
|
239
|
+
* several calls in a session. Not worth it for a one-shot or few-call
|
|
240
|
+
* agent: a cache write costs more than a plain input token, and there are
|
|
241
|
+
* too few reads after it to recoup that. Pair with {@link sessionId} — a
|
|
242
|
+
* cache breakpoint with no sticky routing key can land on a different
|
|
243
|
+
* upstream instance on the very next request, with nothing to hit.
|
|
244
|
+
*
|
|
245
|
+
* Written in Anthropic's `cache_control` shape, but not Anthropic-only:
|
|
246
|
+
* OpenRouter translates it for other providers it fronts (e.g. into
|
|
247
|
+
* OpenAI's `prompt_cache_breakpoint`), rather than passing it through
|
|
248
|
+
* as-is or dropping it. Some of those providers (OpenAI, DeepSeek,
|
|
249
|
+
* Gemini 2.5) already cache automatically with no marker needed, so the
|
|
250
|
+
* translated breakpoint is redundant there rather than load-bearing.
|
|
251
|
+
* `ttl` on `cache_control` does not survive translation to OpenAI.
|
|
252
|
+
*/
|
|
253
|
+
promptCaching?: boolean;
|
|
231
254
|
/** Stable per-end-user identifier used for abuse isolation. Never forwarded raw. */
|
|
232
255
|
user?: string;
|
|
233
256
|
/** Processing tier; `"fast"` is an accepted alias for `"priority"`. */
|
|
@@ -41,6 +41,12 @@ export type TokenUsage = {
|
|
|
41
41
|
* for instance, folds thinking tokens into `output_tokens`.
|
|
42
42
|
*/
|
|
43
43
|
reasoning_tokens?: number;
|
|
44
|
+
/**
|
|
45
|
+
* USD billed for this usage, straight from the provider's own accounting —
|
|
46
|
+
* not derived from a local price table. Undefined where the provider
|
|
47
|
+
* doesn't report it (most do not; OpenRouter does, per response).
|
|
48
|
+
*/
|
|
49
|
+
cost_usd?: number;
|
|
44
50
|
/**
|
|
45
51
|
* Milliseconds from sending the request to the first token of the response —
|
|
46
52
|
* prompt upload plus prompt processing.
|
package/dist/agents/BaseAgent.js
CHANGED
|
@@ -197,6 +197,7 @@ class BaseAgent extends events_1.default {
|
|
|
197
197
|
output_tokens: previous.output_tokens + timed.output_tokens,
|
|
198
198
|
total_tokens: previous.total_tokens + timed.total_tokens,
|
|
199
199
|
reasoning_tokens: sumOptional(previous.reasoning_tokens, timed.reasoning_tokens),
|
|
200
|
+
cost_usd: sumOptional(previous.cost_usd, timed.cost_usd),
|
|
200
201
|
timeToFirstTokenMs: sumOptional(previous.timeToFirstTokenMs, timed.timeToFirstTokenMs),
|
|
201
202
|
generationMs: sumOptional(previous.generationMs, timed.generationMs),
|
|
202
203
|
totalMs: sumOptional(previous.totalMs, timed.totalMs),
|
|
@@ -392,9 +392,24 @@ class OpenRouterAgent extends BaseAgent_1.BaseAgent {
|
|
|
392
392
|
}
|
|
393
393
|
/** The `ChatRequest` body, identical for the streaming and buffered paths. */
|
|
394
394
|
buildRequest(stream) {
|
|
395
|
-
const
|
|
395
|
+
const cachePrompt = this.config.promptCaching === true;
|
|
396
|
+
const messages = transformers_1.openRouterTransformer.toProvider(this.history.getEntries(), {
|
|
397
|
+
cacheSystemPrompt: cachePrompt,
|
|
398
|
+
});
|
|
396
399
|
const allTools = this.getAllToolDefinitions();
|
|
397
|
-
|
|
400
|
+
// The breakpoint goes on the *last* tool: Anthropic (and OpenRouter's
|
|
401
|
+
// translation of this marker for other providers, see
|
|
402
|
+
// OpenRouterSpecificConfig.promptCaching) caches everything up through a
|
|
403
|
+
// marked block, so one marker at the end of the array covers the whole
|
|
404
|
+
// tool list in one cached segment. Marking every tool would just spend
|
|
405
|
+
// more cache-write budget for the same coverage.
|
|
406
|
+
const cacheableTools = cachePrompt && allTools.length > 0
|
|
407
|
+
? [
|
|
408
|
+
...allTools.slice(0, -1),
|
|
409
|
+
{ ...allTools[allTools.length - 1], cacheControl: { type: "ephemeral" } },
|
|
410
|
+
]
|
|
411
|
+
: allTools;
|
|
412
|
+
const tools = cacheableTools.length > 0 ? cacheableTools : undefined;
|
|
398
413
|
return {
|
|
399
414
|
model: this.config.model,
|
|
400
415
|
messages,
|
|
@@ -656,6 +671,7 @@ class OpenRouterAgent extends BaseAgent_1.BaseAgent {
|
|
|
656
671
|
output_tokens: usage?.completionTokens ?? 0,
|
|
657
672
|
total_tokens: usage?.totalTokens ?? 0,
|
|
658
673
|
reasoning_tokens: usage?.completionTokensDetails?.reasoningTokens ?? undefined,
|
|
674
|
+
cost_usd: typeof usage?.cost === "number" ? usage.cost : undefined,
|
|
659
675
|
};
|
|
660
676
|
}
|
|
661
677
|
completeViz(textContent) {
|
|
@@ -163,24 +163,20 @@ export declare const chatCompletionsTransformer: {
|
|
|
163
163
|
*/
|
|
164
164
|
toolResultEntry(tool_call_id: string, output: string): HistoryEntry;
|
|
165
165
|
};
|
|
166
|
-
/**
|
|
167
|
-
* Convert normalized entries to/from the message format `@openrouter/sdk`
|
|
168
|
-
* accepts.
|
|
169
|
-
*
|
|
170
|
-
* The wire format is OpenAI Chat Completions, but the SDK's TypeScript surface
|
|
171
|
-
* is camelCase (`toolCalls`, `toolCallId`, `reasoningDetails`) and it
|
|
172
|
-
* zod-serializes to snake_case on the way out — so this cannot reuse
|
|
173
|
-
* {@link chatCompletionsTransformer}, whose output is already snake_case.
|
|
174
|
-
*
|
|
175
|
-
* Beyond the casing it also carries `reasoning_details` through the round trip,
|
|
176
|
-
* which the OpenAI-compatible path has no equivalent for.
|
|
177
|
-
*/
|
|
178
166
|
export declare const openRouterTransformer: {
|
|
179
167
|
/**
|
|
180
168
|
* Convert normalized entries to OpenRouter message format.
|
|
181
169
|
* Tool results become role:"tool" messages; tool calls ride on the assistant message.
|
|
170
|
+
*
|
|
171
|
+
* `cacheSystemPrompt` marks the system message *and* the latest eligible
|
|
172
|
+
* message with a cache breakpoint each — see
|
|
173
|
+
* {@link markLatestCacheBreakpoint} for why it's both ends, not just the
|
|
174
|
+
* front, and {@link OpenRouterSpecificConfig.promptCaching} for why this is
|
|
175
|
+
* opt-in rather than automatic.
|
|
182
176
|
*/
|
|
183
|
-
toProvider(entries: HistoryEntry[]
|
|
177
|
+
toProvider(entries: HistoryEntry[], options?: {
|
|
178
|
+
cacheSystemPrompt?: boolean;
|
|
179
|
+
}): OpenRouterMessage[];
|
|
184
180
|
/**
|
|
185
181
|
* Convert an OpenRouter assistant message to a normalized HistoryEntry.
|
|
186
182
|
*/
|
|
@@ -201,6 +197,7 @@ type OpenRouterToolCallParam = {
|
|
|
201
197
|
type OpenRouterContentPart = {
|
|
202
198
|
type: "text";
|
|
203
199
|
text: string;
|
|
200
|
+
cacheControl?: OpenRouterCacheControl;
|
|
204
201
|
} | {
|
|
205
202
|
type: "image_url";
|
|
206
203
|
imageUrl: {
|
|
@@ -208,15 +205,23 @@ type OpenRouterContentPart = {
|
|
|
208
205
|
detail?: "auto" | "low" | "high";
|
|
209
206
|
};
|
|
210
207
|
};
|
|
208
|
+
/**
|
|
209
|
+
* A cache breakpoint on one content block. `cacheControl` (camelCase, per
|
|
210
|
+
* this file's header) rather than the wire's `cache_control` — the SDK
|
|
211
|
+
* zod-serializes the rename on the way out.
|
|
212
|
+
*/
|
|
213
|
+
export type OpenRouterCacheControl = {
|
|
214
|
+
type: "ephemeral";
|
|
215
|
+
};
|
|
211
216
|
export type OpenRouterMessage = {
|
|
212
217
|
role: "system";
|
|
213
|
-
content: string;
|
|
218
|
+
content: string | OpenRouterContentPart[];
|
|
214
219
|
} | {
|
|
215
220
|
role: "user";
|
|
216
221
|
content: string | OpenRouterContentPart[];
|
|
217
222
|
} | {
|
|
218
223
|
role: "assistant";
|
|
219
|
-
content: string | null;
|
|
224
|
+
content: string | null | OpenRouterContentPart[];
|
|
220
225
|
toolCalls?: OpenRouterToolCallParam[];
|
|
221
226
|
/** Plain reasoning text replayed from a previous turn. */
|
|
222
227
|
reasoning?: string;
|
|
@@ -228,7 +233,7 @@ export type OpenRouterMessage = {
|
|
|
228
233
|
} | {
|
|
229
234
|
role: "tool";
|
|
230
235
|
toolCallId: string;
|
|
231
|
-
content: string;
|
|
236
|
+
content: string | OpenRouterContentPart[];
|
|
232
237
|
};
|
|
233
238
|
type OpenRouterResponseMessage = {
|
|
234
239
|
role: string;
|
|
@@ -782,12 +782,49 @@ exports.chatCompletionsTransformer = {
|
|
|
782
782
|
* Beyond the casing it also carries `reasoning_details` through the round trip,
|
|
783
783
|
* which the OpenAI-compatible path has no equivalent for.
|
|
784
784
|
*/
|
|
785
|
+
/**
|
|
786
|
+
* Marks the *last* message carrying plain string content with an Anthropic
|
|
787
|
+
* cache breakpoint, mutating it in place.
|
|
788
|
+
*
|
|
789
|
+
* The system-prompt breakpoint alone only caches the fixed part of a request
|
|
790
|
+
* — an agentic loop's growing tool-call history is not fixed, and without a
|
|
791
|
+
* second breakpoint it is resent as fresh, full-price input on every turn.
|
|
792
|
+
* Marking the tail instead: turn N's breakpoint lands on its newest message,
|
|
793
|
+
* so turn N+1's *identical, longer prefix up to that point* is a cache hit,
|
|
794
|
+
* and only the new content past it needs to be freshly priced (and gets its
|
|
795
|
+
* own breakpoint in turn). Anthropic allows up to 4 breakpoints total; this
|
|
796
|
+
* is the second, after the one on the system message — see
|
|
797
|
+
* {@link OpenRouterSpecificConfig.promptCaching}.
|
|
798
|
+
*
|
|
799
|
+
* Skips a message whose content isn't a non-empty string — `null` (an
|
|
800
|
+
* assistant turn that's purely a tool call, nothing to attach a text block
|
|
801
|
+
* to) or an already-structured array (images). Landing one message later
|
|
802
|
+
* than ideal in that case is a smaller loss than the alternative of writing
|
|
803
|
+
* this for every content shape up front.
|
|
804
|
+
*/
|
|
805
|
+
function markLatestCacheBreakpoint(messages) {
|
|
806
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
807
|
+
const message = messages[i];
|
|
808
|
+
if (typeof message.content === "string" && message.content.length > 0) {
|
|
809
|
+
message.content = [
|
|
810
|
+
{ type: "text", text: message.content, cacheControl: { type: "ephemeral" } },
|
|
811
|
+
];
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
785
816
|
exports.openRouterTransformer = {
|
|
786
817
|
/**
|
|
787
818
|
* Convert normalized entries to OpenRouter message format.
|
|
788
819
|
* Tool results become role:"tool" messages; tool calls ride on the assistant message.
|
|
820
|
+
*
|
|
821
|
+
* `cacheSystemPrompt` marks the system message *and* the latest eligible
|
|
822
|
+
* message with a cache breakpoint each — see
|
|
823
|
+
* {@link markLatestCacheBreakpoint} for why it's both ends, not just the
|
|
824
|
+
* front, and {@link OpenRouterSpecificConfig.promptCaching} for why this is
|
|
825
|
+
* opt-in rather than automatic.
|
|
789
826
|
*/
|
|
790
|
-
toProvider(entries) {
|
|
827
|
+
toProvider(entries, options) {
|
|
791
828
|
const messages = [];
|
|
792
829
|
for (const entry of entries) {
|
|
793
830
|
const textBlocks = entry.content.filter(types_1.isTextContent);
|
|
@@ -798,9 +835,12 @@ exports.openRouterTransformer = {
|
|
|
798
835
|
const imageBase64Blocks = entry.content.filter(types_1.isImageBase64Content);
|
|
799
836
|
const hasImages = imageUrlBlocks.length > 0 || imageBase64Blocks.length > 0;
|
|
800
837
|
if (entry.role === "system") {
|
|
838
|
+
const text = textBlocks.map((c) => c.text).join("\n");
|
|
801
839
|
messages.push({
|
|
802
840
|
role: "system",
|
|
803
|
-
content:
|
|
841
|
+
content: options?.cacheSystemPrompt
|
|
842
|
+
? [{ type: "text", text, cacheControl: { type: "ephemeral" } }]
|
|
843
|
+
: text,
|
|
804
844
|
});
|
|
805
845
|
continue;
|
|
806
846
|
}
|
|
@@ -879,6 +919,8 @@ exports.openRouterTransformer = {
|
|
|
879
919
|
});
|
|
880
920
|
}
|
|
881
921
|
}
|
|
922
|
+
if (options?.cacheSystemPrompt)
|
|
923
|
+
markLatestCacheBreakpoint(messages);
|
|
882
924
|
return messages;
|
|
883
925
|
},
|
|
884
926
|
/**
|