@namzu/openrouter 2.4.1 → 3.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 3641102: Prompt caching now places explicit breakpoints instead of sending a top-level
8
+ `cache_control: { type: 'auto' }`, a value OpenRouter does not define (its
9
+ top-level field takes `{ type: 'ephemeral' }` and puts the breakpoint on the
10
+ last cacheable block — with request-only context at the tail, the part the next
11
+ request replaces).
12
+
13
+ For `anthropic/`, `google/gemini` and `qwen/` models, the models OpenRouter
14
+ documents as needing explicit breakpoints, a cache request marks the last
15
+ static system message and the last non-system message before request-only
16
+ context, each inside a content part (a string becomes one text part). A system
17
+ message directly before the context is the step's guidance, which changes from
18
+ step to step, so it is passed over rather than marked. Other models, which
19
+ OpenRouter documents as caching automatically, are sent no marker and keep
20
+ their plain string content.
21
+
22
+ ## 3.0.0
23
+
24
+ ### Major Changes
25
+
26
+ - e83dfe5: `listModels()` omits `inputPrice` and `outputPrice` for a model this driver has no rate for, instead of reporting the rate as `0`.
27
+
28
+ The returned `ModelInfo[]` is the published signature that moved: both fields are optional on it now, so read them as `number | undefined`. Their runtime values changed too, and that half is not caught by a type — a key that used to be present is now absent, and code that defaulted it with `?? 0` will keep doing exactly what this release stopped doing for you.
29
+
30
+ What each driver does now:
31
+
32
+ - **`@namzu/anthropic`** — the live listing from `models.list` carries no rates, so it omits both. The bundled offline catalogue still carries its real published prices, unchanged.
33
+ - **`@namzu/openai`** — `client.models.list` publishes no rates, so both are omitted. `codex` likewise.
34
+ - **`@namzu/deepseek`** — the account listing and the bundled known-model list are unrated here, so both are omitted.
35
+ - **`@namzu/google`** — the two-row price table still prices `gemini-2.5-flash` and `gemini-2.5-pro`; every other model the API returns is now unpriced rather than free.
36
+ - **`@namzu/openrouter`** — a model whose listing carries no `pricing` block is unpriced. A model the vendor prices at `"0"` is still free and still reports `0`, which is the distinction this release is about.
37
+
38
+ An absent rate means unknown, and a consumer that shows one should say so rather than `$0.00`. `@namzu/sdk`'s `ModelInfo.inputPrice` carries the reasoning.
39
+
40
+ ### Minor Changes
41
+
42
+ - e5411cc: Send and surface reasoning.
43
+
44
+ The driver now carries the SDK's thinking controls onto OpenRouter's unified `reasoning`
45
+ object — `thinking: { type: 'enabled', budgetTokens }` becomes `{ enabled: true, max_tokens }`,
46
+ `adaptive` becomes `{ enabled: true }`, `disabled` becomes `{ enabled: false }`, and `effort`
47
+ becomes `{ effort }` — and streams the model's thinking back on the reasoning channel
48
+ instead of dropping it. `effort: 'max'` and `effort: 'ultra'` are refused rather than sent:
49
+ this wire cannot carry those levels, and an effort that arrives as a different depth is
50
+ indistinguishable from one that was honoured.
51
+
52
+ What a host observes: a caller that set `thinking` or `effort` used to get
53
+ `OpenRouterProvider does not implement thinking`; the request is now made. Reasoning blocks
54
+ arrive on the assistant message for every model that returns them (verified against
55
+ `nvidia/nemotron-3.5-lightning:free`, which returns both `reasoning` and `reasoning_details`),
56
+ and `usage.reasoningTokens` reports the thinking share of the completion tokens the vendor
57
+ already billed. The text is emitted once, not twice: OpenRouter sends it in both fields, and
58
+ the indexed `reasoning_details` form is the one used.
59
+
3
60
  ## 2.4.1
4
61
 
5
62
  ### Patch Changes
package/README.md CHANGED
@@ -62,9 +62,38 @@ for await (const chunk of provider.chatStream({
62
62
  ```
63
63
 
64
64
  `chatStream` is the only model entry point; a non-streaming call is that
65
- stream collected. In practice the kernel's run loop calls it and hands you
65
+ stream collected. In practice the kernel's turn loop calls it and hands you
66
66
  events.
67
67
 
68
+ ## Reasoning
69
+
70
+ The driver carries the SDK's thinking controls and returns what the model thinks:
71
+
72
+ ```ts
73
+ import { registerOpenRouter, OpenRouterProvider } from '@namzu/openrouter'
74
+
75
+ registerOpenRouter()
76
+ const provider = new OpenRouterProvider({ apiKey: 'sk-example' })
77
+
78
+ for await (const chunk of provider.chatStream({
79
+ model: 'nvidia/nemotron-3.5-lightning:free',
80
+ messages: [{ role: 'user', content: 'What is 17 times 3?' }],
81
+ thinking: { type: 'enabled', budgetTokens: 4_096 },
82
+ })) {
83
+ if (chunk.delta.reasoning?.text) process.stderr.write(chunk.delta.reasoning.text)
84
+ if (chunk.delta.content) process.stdout.write(chunk.delta.content)
85
+ }
86
+ ```
87
+
88
+ `thinking: { type: 'enabled', budgetTokens }` is sent as `reasoning: { enabled: true,
89
+ max_tokens }`, `adaptive` as `{ enabled: true }`, `disabled` as `{ enabled: false }`, and
90
+ `effort` as `{ effort }`. `effort: 'max'` and `'ultra'` are refused rather than sent — this
91
+ wire cannot carry those levels. Reasoning arrives on the kernel's reasoning channel with an
92
+ index per block and a `done` marker when output starts, and `usage.reasoningTokens` reports
93
+ the thinking share of the completion tokens OpenRouter already bills as output. OpenRouter
94
+ sends each fragment twice (a flat `reasoning` string and the indexed `reasoning_details`);
95
+ the driver emits it once.
96
+
68
97
  ## Documentation
69
98
 
70
99
  - [Namzu docs](https://github.com/cogitave/namzu/tree/main/docs)
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,oBAAoB,EACpB,WAAW,EACX,SAAS,EACT,oBAAoB,EACpB,WAAW,EAGX,MAAM,YAAY,CAAA;AAUnB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAwClD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,oBAUrC,CAAA;AAED,qBAAa,kBAAmB,YAAW,WAAW;IACrD,QAAQ,CAAC,EAAE,gBAAe;IAC1B,QAAQ,CAAC,IAAI,gBAAe;IAC5B,QAAQ,CAAC,YAAY,uBAA0B;IAE/C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAQ;gBAEX,MAAM,EAAE,gBAAgB;IAQpC,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,gBAAgB;IAqCjB,UAAU,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CAAC,WAAW,CAAC;IAmI3E;;;;;;;;;;;;;OAaG;IACG,eAAe,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB1D;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAyB5F,uEAAuE;IACvE,OAAO,CAAC,cAAc,CAAC,CAAqB;IAEtC,UAAU,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAkCtD,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAWrC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,oBAAoB,EACpB,WAAW,EACX,SAAS,EACT,oBAAoB,EACpB,WAAW,EAGX,MAAM,YAAY,CAAA;AASnB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAyQlD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,oBAUrC,CAAA;AAED,qBAAa,kBAAmB,YAAW,WAAW;IACrD,QAAQ,CAAC,EAAE,gBAAe;IAC1B,QAAQ,CAAC,IAAI,gBAAe;IAC5B,QAAQ,CAAC,YAAY,uBAA0B;IAE/C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAQ;gBAEX,MAAM,EAAE,gBAAgB;IAQpC,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,gBAAgB;IA4CjB,UAAU,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CAAC,WAAW,CAAC;IA4K3E;;;;;;;;;;;;;OAaG;IACG,eAAe,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB1D;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAyB5F,uEAAuE;IACvE,OAAO,CAAC,cAAc,CAAC,CAAqB;IAEtC,UAAU,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAsCtD,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAWrC"}
package/dist/client.js CHANGED
@@ -1,6 +1,126 @@
1
- import { ProviderRequestError, assertThinkingUnsupported, isCallerAbortError, isProviderRequestError, providerHttpError, providerVendorError, } from '@namzu/sdk';
1
+ import { ProviderRequestError, isCallerAbortError, isProviderRequestError, providerHttpError, providerVendorError, } from '@namzu/sdk';
2
2
  import { attributionHeaders } from '@namzu/sdk';
3
+ /**
4
+ * Models whose upstream caches only where the request places an explicit
5
+ * `cache_control` breakpoint. OpenRouter's prompt-caching page
6
+ * (openrouter.ai/docs/features/prompt-caching) lists Anthropic Claude, Google
7
+ * Gemini ("only the last breakpoint" is used) and Alibaba Qwen ("requires
8
+ * explicit `cache_control`"); OpenAI, DeepSeek, Grok, Moonshot, Groq and Z.AI
9
+ * cache automatically and are sent no marker, so their messages keep the
10
+ * plain string shape they have always had.
11
+ */
12
+ function takesExplicitBreakpoints(model) {
13
+ const id = model.replace(/^~/, '').toLowerCase();
14
+ return id.startsWith('anthropic/') || id.startsWith('google/gemini') || id.startsWith('qwen/');
15
+ }
16
+ /**
17
+ * Request-only context the runtime appends after the conversation: a
18
+ * runtime-context user message of kind `step-context`. It changes from one
19
+ * request to the next, so a breakpoint after it caches a prefix no later
20
+ * request repeats. The same test as the Anthropic driver's.
21
+ */
22
+ function isRequestOnlyContext(msg) {
23
+ return (msg.role === 'user' &&
24
+ msg.source?.type === 'runtime-context' &&
25
+ msg.source.kind === 'step-context');
26
+ }
27
+ const EPHEMERAL = { type: 'ephemeral' };
28
+ /**
29
+ * Put a breakpoint on a formatted message's last content part, turning a
30
+ * string into a one-part array to carry it. Answers false when the message
31
+ * has no content to carry one — an assistant turn holding only tool calls.
32
+ *
33
+ * Always INSIDE a content part: a top-level `cache_control` on a `tool`
34
+ * message is not accepted by OpenRouter, while one inside its content part
35
+ * is (NousResearch/hermes-agent#57845, verified against
36
+ * `anthropic/claude-haiku-4.5`).
37
+ */
38
+ function markLastPart(message) {
39
+ const content = message.content;
40
+ if (typeof content === 'string') {
41
+ if (content.length === 0)
42
+ return false;
43
+ message.content = [{ type: 'text', text: content, cache_control: EPHEMERAL }];
44
+ return true;
45
+ }
46
+ if (Array.isArray(content) && content.length > 0) {
47
+ const last = content[content.length - 1];
48
+ if (last === null || typeof last !== 'object')
49
+ return false;
50
+ // A copy: the array is the caller's own message content.
51
+ message.content = [...content.slice(0, -1), { ...last, cache_control: EPHEMERAL }];
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ /**
57
+ * Explicit breakpoints for the runtime's cache request: one after the last
58
+ * static system message, one on the last non-system message before
59
+ * request-only context. Two of the four Anthropic allows.
60
+ *
61
+ * This replaces a top-level `cache_control: { type: 'auto' }`. OpenRouter
62
+ * defines the top-level field with `{ type: 'ephemeral' }` only, and applies
63
+ * it "to the last cacheable block" — which, with request-only context at the
64
+ * tail, is the context itself, so the cached prefix would never be read
65
+ * again.
66
+ *
67
+ * System messages are passed over when walking back: the runtime places a
68
+ * step's preamble (host `step.system`, step skills, the policy notice,
69
+ * `turn` contributions) as a system message directly before the context,
70
+ * and its text changes from step to step, so a marker there would end the
71
+ * prefix on bytes the next request does not repeat.
72
+ */
73
+ function applyCacheBreakpoints(source, formatted) {
74
+ let lastStatic = -1;
75
+ let historyEnd = source.length;
76
+ for (const [i, msg] of source.entries()) {
77
+ if (msg.role === 'system' && msg.cacheHint === 'cache')
78
+ lastStatic = i;
79
+ if (historyEnd === source.length && isRequestOnlyContext(msg))
80
+ historyEnd = i;
81
+ }
82
+ const staticMessage = lastStatic >= 0 ? formatted[lastStatic] : undefined;
83
+ if (staticMessage)
84
+ markLastPart(staticMessage);
85
+ for (let i = historyEnd - 1; i > lastStatic; i--) {
86
+ if (source[i]?.role === 'system')
87
+ continue;
88
+ const message = formatted[i];
89
+ if (message && markLastPart(message))
90
+ return;
91
+ }
92
+ }
3
93
  const OPENROUTER_BASE_URL = process.env.OPENROUTER_BASE_URL ?? 'https://openrouter.ai/api/v1';
94
+ /**
95
+ * A rate OpenRouter published for a model, or nothing.
96
+ *
97
+ * This listing is the one place both answers arrive on the same field and
98
+ * only one of them used to survive. OpenRouter states each rate as a decimal
99
+ * string of USD per token and states `"0"` for the models it serves at no
100
+ * charge — so `?? '0'` did not default a missing price, it asserted one: every
101
+ * model whose pricing block was absent was reported as free, which is a claim
102
+ * about a bill. Absence now stays absent, a published `"0"` is still a real
103
+ * zero, and a value that does not parse is treated as absence rather than
104
+ * `NaN` — which would reach a consumer as a total nobody can read.
105
+ *
106
+ * See `ModelInfo.inputPrice`.
107
+ */
108
+ function pricePerMillion(raw) {
109
+ if (raw === undefined)
110
+ return undefined;
111
+ const parsed = Number.parseFloat(raw);
112
+ return Number.isFinite(parsed) ? parsed * 1_000_000 : undefined;
113
+ }
114
+ /**
115
+ * The reasoning breakdown, when the vendor reports one.
116
+ *
117
+ * `completion_tokens` already includes these — OpenRouter bills thinking as
118
+ * output — so this is a SUBSET, never an addition. It is reported rather than
119
+ * dropped because it is the only measure of how much of a turn was spent
120
+ * thinking, which is what a reader of `/cost` is looking at when a reasoning
121
+ * model looks expensive. Absent stays absent: a vendor that does not report
122
+ * the split has not reported zero.
123
+ */
4
124
  function parseUsage(raw) {
5
125
  if (!raw) {
6
126
  return {
@@ -11,12 +131,87 @@ function parseUsage(raw) {
11
131
  cacheWriteTokens: 0,
12
132
  };
13
133
  }
134
+ const reasoningTokens = raw.completion_tokens_details?.reasoning_tokens;
14
135
  return {
15
136
  promptTokens: raw.prompt_tokens,
16
137
  completionTokens: raw.completion_tokens,
17
138
  totalTokens: raw.total_tokens,
18
139
  cachedTokens: raw.prompt_tokens_details?.cached_tokens ?? raw.cache_read_input_tokens ?? 0,
19
140
  cacheWriteTokens: raw.cache_creation_input_tokens ?? 0,
141
+ ...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
142
+ };
143
+ }
144
+ /**
145
+ * Map one frame's reasoning onto the kernel's channel.
146
+ *
147
+ * The wire sends the same text twice: a flat `delta.reasoning` string AND a
148
+ * `delta.reasoning_details` array. Emitting both would double every block, so
149
+ * the array wins whenever it is present — it is the one that carries the index
150
+ * and the signature — and the flat string is the fallback for a vendor that
151
+ * sends only that.
152
+ *
153
+ * An empty array means the frame carried no reasoning at all; it is not a
154
+ * statement that the flat field should be trusted.
155
+ */
156
+ function parseReasoning(details, flat) {
157
+ if (details && details.length > 0) {
158
+ const mapped = [];
159
+ for (const [position, detail] of details.entries()) {
160
+ const index = typeof detail.index === 'number' ? detail.index : position;
161
+ if (detail.type === 'reasoning.encrypted') {
162
+ if (detail.data)
163
+ mapped.push({ index, type: 'redacted_thinking', encrypted: detail.data });
164
+ continue;
165
+ }
166
+ if (typeof detail.text !== 'string' || detail.text.length === 0)
167
+ continue;
168
+ mapped.push({
169
+ index,
170
+ type: 'thinking',
171
+ text: detail.text,
172
+ ...(detail.signature ? { signature: detail.signature } : {}),
173
+ });
174
+ }
175
+ return mapped;
176
+ }
177
+ return typeof flat === 'string' && flat.length > 0
178
+ ? [{ index: 0, type: 'thinking', text: flat }]
179
+ : [];
180
+ }
181
+ /**
182
+ * The effort levels this wire can carry.
183
+ *
184
+ * OpenRouter's unified `reasoning.effort` publishes exactly these. The SDK's
185
+ * `max` and `ultra` are OpenAI-model-specific names with no equivalent here,
186
+ * and a driver must not send one and hope: an effort that arrives as a
187
+ * different depth is indistinguishable from one that was honoured.
188
+ */
189
+ const OPENROUTER_REASONING_EFFORT = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'];
190
+ /**
191
+ * Map the SDK's thinking controls onto OpenRouter's unified `reasoning` object.
192
+ *
193
+ * `undefined` means "say nothing", which leaves the vendor's own default in
194
+ * place — the only correct answer for a driver that was asked for nothing.
195
+ */
196
+ function formatReasoning(thinking, effort) {
197
+ if (effort !== undefined) {
198
+ const carried = OPENROUTER_REASONING_EFFORT.includes(effort);
199
+ if (!carried) {
200
+ throw new Error(`OpenRouterProvider cannot carry effort "${effort}". Its reasoning.effort accepts ${OPENROUTER_REASONING_EFFORT.join(', ')}. Sending a different level would return a perfectly ordinary completion that no caller could tell apart from the one that was asked for.`);
201
+ }
202
+ return { effort };
203
+ }
204
+ if (!thinking || thinking.type === undefined)
205
+ return undefined;
206
+ if (thinking.type === 'disabled')
207
+ return { enabled: false };
208
+ // 'adaptive' has no separate spelling on this wire: the model decides its
209
+ // own depth, which is what `enabled: true` without a budget expresses.
210
+ // 'enabled' fixes the depth with budgetTokens, so it carries the cap.
211
+ const budgetTokens = thinking.type === 'enabled' ? thinking.budgetTokens : undefined;
212
+ return {
213
+ enabled: true,
214
+ ...(budgetTokens !== undefined ? { max_tokens: budgetTokens } : {}),
20
215
  };
21
216
  }
22
217
  function formatToolChoice(tc) {
@@ -125,8 +320,14 @@ export class OpenRouterProvider {
125
320
  body.repetition_penalty = params.repetitionPenalty;
126
321
  if (params.stop)
127
322
  body.stop = params.stop;
128
- if (params.cacheControl) {
129
- body.cache_control = params.cacheControl;
323
+ if (params.cacheControl && takesExplicitBreakpoints(params.model)) {
324
+ applyCacheBreakpoints(params.messages, body.messages);
325
+ }
326
+ // Refused, not dropped, when this wire cannot carry the level asked
327
+ // for — see `formatReasoning`.
328
+ const reasoning = formatReasoning(params.thinking, params.effort);
329
+ if (reasoning !== undefined) {
330
+ body.reasoning = reasoning;
130
331
  }
131
332
  if (params.responseFormat) {
132
333
  body.response_format = params.responseFormat;
@@ -134,7 +335,6 @@ export class OpenRouterProvider {
134
335
  return body;
135
336
  }
136
337
  async *chatStream(params) {
137
- assertThinkingUnsupported('OpenRouterProvider', params);
138
338
  const body = this.buildRequestBody(params, true);
139
339
  const timeout = AbortSignal.timeout(this.config.timeout ?? 120_000);
140
340
  // Compose the caller abort with the request timeout so a Stop cancels the
@@ -180,6 +380,9 @@ export class OpenRouterProvider {
180
380
  const reader = response.body.getReader();
181
381
  const decoder = new TextDecoder();
182
382
  let buffer = '';
383
+ // Reasoning blocks this stream has opened and not yet closed, in the
384
+ // order the wire reported them.
385
+ const openReasoning = new Set();
183
386
  try {
184
387
  while (true) {
185
388
  params.signal?.throwIfAborted();
@@ -207,20 +410,52 @@ export class OpenRouterProvider {
207
410
  const choice = parsed.choices[0];
208
411
  if (!choice)
209
412
  continue;
210
- yield {
211
- id: parsed.id,
212
- delta: {
213
- content: choice.delta.content,
214
- toolCalls: choice.delta.tool_calls?.map((tc) => ({
215
- index: tc.index,
216
- id: tc.id,
217
- type: tc.type,
218
- function: tc.function,
219
- })),
220
- },
221
- finishReason: choice.finish_reason,
222
- usage: parsed.usage ? parseUsage(parsed.usage) : undefined,
223
- };
413
+ const fragments = parseReasoning(choice.delta.reasoning_details, choice.delta.reasoning);
414
+ const content = choice.delta.content;
415
+ const toolCalls = choice.delta.tool_calls?.map((tc) => ({
416
+ index: tc.index,
417
+ id: tc.id,
418
+ type: tc.type,
419
+ function: tc.function,
420
+ }));
421
+ // A block closes when output starts, which is the only boundary
422
+ // this wire gives: without it a consumer's reasoning pane never
423
+ // receives `done` and stays open for the life of the run. An
424
+ // empty `content` is NOT output — every reasoning frame on this
425
+ // wire carries `"content":""`, so treating it as the boundary
426
+ // would close each block on the frame that opened it.
427
+ if ((typeof content === 'string' && content.length > 0) || toolCalls?.length) {
428
+ for (const index of openReasoning) {
429
+ yield { id: parsed.id, delta: { reasoning: { index, done: true } } };
430
+ }
431
+ openReasoning.clear();
432
+ }
433
+ const [firstFragment, ...restFragments] = fragments;
434
+ if (firstFragment)
435
+ openReasoning.add(firstFragment.index);
436
+ if (firstFragment ||
437
+ content !== undefined ||
438
+ toolCalls !== undefined ||
439
+ choice.finish_reason !== undefined ||
440
+ parsed.usage !== undefined) {
441
+ yield {
442
+ id: parsed.id,
443
+ delta: {
444
+ ...(firstFragment ? { reasoning: firstFragment } : {}),
445
+ content,
446
+ toolCalls,
447
+ },
448
+ finishReason: choice.finish_reason,
449
+ usage: parsed.usage ? parseUsage(parsed.usage) : undefined,
450
+ };
451
+ }
452
+ // The delta shape carries one reasoning object per chunk, so a
453
+ // frame reporting several blocks rides in sequence rather than
454
+ // being collapsed into one — order is what replay echoes back.
455
+ for (const fragment of restFragments) {
456
+ openReasoning.add(fragment.index);
457
+ yield { id: parsed.id, delta: { reasoning: fragment } };
458
+ }
224
459
  }
225
460
  catch (parseErr) {
226
461
  if (isProviderRequestError(parseErr))
@@ -332,16 +567,20 @@ export class OpenRouterProvider {
332
567
  }
333
568
  const data = (await response.json());
334
569
  signal?.throwIfAborted();
335
- return data.data.map((m) => ({
336
- id: m.id,
337
- name: m.name,
338
- contextWindow: m.context_length,
339
- maxOutputTokens: m.top_provider?.max_completion_tokens ?? 4096,
340
- inputPrice: Number.parseFloat(m.pricing?.prompt ?? '0') * 1_000_000,
341
- outputPrice: Number.parseFloat(m.pricing?.completion ?? '0') * 1_000_000,
342
- supportsToolUse: true,
343
- supportsStreaming: true,
344
- }));
570
+ return data.data.map((m) => {
571
+ const inputPrice = pricePerMillion(m.pricing?.prompt);
572
+ const outputPrice = pricePerMillion(m.pricing?.completion);
573
+ return {
574
+ id: m.id,
575
+ name: m.name,
576
+ contextWindow: m.context_length,
577
+ maxOutputTokens: m.top_provider?.max_completion_tokens ?? 4096,
578
+ ...(inputPrice !== undefined ? { inputPrice } : {}),
579
+ ...(outputPrice !== undefined ? { outputPrice } : {}),
580
+ supportsToolUse: true,
581
+ supportsStreaming: true,
582
+ };
583
+ });
345
584
  }
346
585
  async healthCheck() {
347
586
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namzu/openrouter",
3
- "version": "2.4.1",
3
+ "version": "3.0.1",
4
4
  "description": "OpenRouter LLM provider for @namzu/sdk. Access to 200+ models (Claude, GPT-4, Llama, Mixtral) via OpenRouter's unified API.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "engines": {
@@ -55,7 +55,7 @@
55
55
  "@types/node": "^22.19.17",
56
56
  "typescript": "^5.5.0",
57
57
  "vitest": "^4.1.11",
58
- "@namzu/sdk": "^42.0.2"
58
+ "@namzu/sdk": "^44.0.0"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "tsc --build",
package/src/client.ts CHANGED
@@ -9,7 +9,6 @@ import type {
9
9
  } from '@namzu/sdk'
10
10
  import {
11
11
  ProviderRequestError,
12
- assertThinkingUnsupported,
13
12
  isCallerAbortError,
14
13
  isProviderRequestError,
15
14
  providerHttpError,
@@ -18,8 +17,121 @@ import {
18
17
  import { attributionHeaders } from '@namzu/sdk'
19
18
  import type { OpenRouterConfig } from './types.js'
20
19
 
20
+ /**
21
+ * Models whose upstream caches only where the request places an explicit
22
+ * `cache_control` breakpoint. OpenRouter's prompt-caching page
23
+ * (openrouter.ai/docs/features/prompt-caching) lists Anthropic Claude, Google
24
+ * Gemini ("only the last breakpoint" is used) and Alibaba Qwen ("requires
25
+ * explicit `cache_control`"); OpenAI, DeepSeek, Grok, Moonshot, Groq and Z.AI
26
+ * cache automatically and are sent no marker, so their messages keep the
27
+ * plain string shape they have always had.
28
+ */
29
+ function takesExplicitBreakpoints(model: string): boolean {
30
+ const id = model.replace(/^~/, '').toLowerCase()
31
+ return id.startsWith('anthropic/') || id.startsWith('google/gemini') || id.startsWith('qwen/')
32
+ }
33
+
34
+ /**
35
+ * Request-only context the runtime appends after the conversation: a
36
+ * runtime-context user message of kind `step-context`. It changes from one
37
+ * request to the next, so a breakpoint after it caches a prefix no later
38
+ * request repeats. The same test as the Anthropic driver's.
39
+ */
40
+ function isRequestOnlyContext(msg: ChatCompletionParams['messages'][number]): boolean {
41
+ return (
42
+ msg.role === 'user' &&
43
+ msg.source?.type === 'runtime-context' &&
44
+ msg.source.kind === 'step-context'
45
+ )
46
+ }
47
+
48
+ const EPHEMERAL = { type: 'ephemeral' } as const
49
+
50
+ /**
51
+ * Put a breakpoint on a formatted message's last content part, turning a
52
+ * string into a one-part array to carry it. Answers false when the message
53
+ * has no content to carry one — an assistant turn holding only tool calls.
54
+ *
55
+ * Always INSIDE a content part: a top-level `cache_control` on a `tool`
56
+ * message is not accepted by OpenRouter, while one inside its content part
57
+ * is (NousResearch/hermes-agent#57845, verified against
58
+ * `anthropic/claude-haiku-4.5`).
59
+ */
60
+ function markLastPart(message: { content?: unknown }): boolean {
61
+ const content = message.content
62
+ if (typeof content === 'string') {
63
+ if (content.length === 0) return false
64
+ message.content = [{ type: 'text', text: content, cache_control: EPHEMERAL }]
65
+ return true
66
+ }
67
+ if (Array.isArray(content) && content.length > 0) {
68
+ const last = content[content.length - 1]
69
+ if (last === null || typeof last !== 'object') return false
70
+ // A copy: the array is the caller's own message content.
71
+ message.content = [...content.slice(0, -1), { ...last, cache_control: EPHEMERAL }]
72
+ return true
73
+ }
74
+ return false
75
+ }
76
+
77
+ /**
78
+ * Explicit breakpoints for the runtime's cache request: one after the last
79
+ * static system message, one on the last non-system message before
80
+ * request-only context. Two of the four Anthropic allows.
81
+ *
82
+ * This replaces a top-level `cache_control: { type: 'auto' }`. OpenRouter
83
+ * defines the top-level field with `{ type: 'ephemeral' }` only, and applies
84
+ * it "to the last cacheable block" — which, with request-only context at the
85
+ * tail, is the context itself, so the cached prefix would never be read
86
+ * again.
87
+ *
88
+ * System messages are passed over when walking back: the runtime places a
89
+ * step's preamble (host `step.system`, step skills, the policy notice,
90
+ * `turn` contributions) as a system message directly before the context,
91
+ * and its text changes from step to step, so a marker there would end the
92
+ * prefix on bytes the next request does not repeat.
93
+ */
94
+ function applyCacheBreakpoints(
95
+ source: ChatCompletionParams['messages'],
96
+ formatted: { content?: unknown }[],
97
+ ): void {
98
+ let lastStatic = -1
99
+ let historyEnd = source.length
100
+ for (const [i, msg] of source.entries()) {
101
+ if (msg.role === 'system' && msg.cacheHint === 'cache') lastStatic = i
102
+ if (historyEnd === source.length && isRequestOnlyContext(msg)) historyEnd = i
103
+ }
104
+ const staticMessage = lastStatic >= 0 ? formatted[lastStatic] : undefined
105
+ if (staticMessage) markLastPart(staticMessage)
106
+ for (let i = historyEnd - 1; i > lastStatic; i--) {
107
+ if (source[i]?.role === 'system') continue
108
+ const message = formatted[i]
109
+ if (message && markLastPart(message)) return
110
+ }
111
+ }
112
+
21
113
  const OPENROUTER_BASE_URL = process.env.OPENROUTER_BASE_URL ?? 'https://openrouter.ai/api/v1'
22
114
 
115
+ /**
116
+ * A rate OpenRouter published for a model, or nothing.
117
+ *
118
+ * This listing is the one place both answers arrive on the same field and
119
+ * only one of them used to survive. OpenRouter states each rate as a decimal
120
+ * string of USD per token and states `"0"` for the models it serves at no
121
+ * charge — so `?? '0'` did not default a missing price, it asserted one: every
122
+ * model whose pricing block was absent was reported as free, which is a claim
123
+ * about a bill. Absence now stays absent, a published `"0"` is still a real
124
+ * zero, and a value that does not parse is treated as absence rather than
125
+ * `NaN` — which would reach a consumer as a total nobody can read.
126
+ *
127
+ * See `ModelInfo.inputPrice`.
128
+ */
129
+ function pricePerMillion(raw: string | undefined): number | undefined {
130
+ if (raw === undefined) return undefined
131
+ const parsed = Number.parseFloat(raw)
132
+ return Number.isFinite(parsed) ? parsed * 1_000_000 : undefined
133
+ }
134
+
23
135
  interface RawUsage {
24
136
  prompt_tokens: number
25
137
  completion_tokens: number
@@ -27,11 +139,24 @@ interface RawUsage {
27
139
  prompt_tokens_details?: {
28
140
  cached_tokens?: number
29
141
  }
142
+ completion_tokens_details?: {
143
+ reasoning_tokens?: number
144
+ }
30
145
  cache_discount?: number
31
146
  cache_read_input_tokens?: number
32
147
  cache_creation_input_tokens?: number
33
148
  }
34
149
 
150
+ /**
151
+ * The reasoning breakdown, when the vendor reports one.
152
+ *
153
+ * `completion_tokens` already includes these — OpenRouter bills thinking as
154
+ * output — so this is a SUBSET, never an addition. It is reported rather than
155
+ * dropped because it is the only measure of how much of a turn was spent
156
+ * thinking, which is what a reader of `/cost` is looking at when a reasoning
157
+ * model looks expensive. Absent stays absent: a vendor that does not report
158
+ * the split has not reported zero.
159
+ */
35
160
  function parseUsage(raw?: RawUsage): TokenUsage {
36
161
  if (!raw) {
37
162
  return {
@@ -42,12 +167,111 @@ function parseUsage(raw?: RawUsage): TokenUsage {
42
167
  cacheWriteTokens: 0,
43
168
  }
44
169
  }
170
+ const reasoningTokens = raw.completion_tokens_details?.reasoning_tokens
45
171
  return {
46
172
  promptTokens: raw.prompt_tokens,
47
173
  completionTokens: raw.completion_tokens,
48
174
  totalTokens: raw.total_tokens,
49
175
  cachedTokens: raw.prompt_tokens_details?.cached_tokens ?? raw.cache_read_input_tokens ?? 0,
50
176
  cacheWriteTokens: raw.cache_creation_input_tokens ?? 0,
177
+ ...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
178
+ }
179
+ }
180
+
181
+ /**
182
+ * One entry of OpenRouter's unified `reasoning_details`.
183
+ *
184
+ * `reasoning.text` and `reasoning.summary` carry visible text; the encrypted
185
+ * form carries an opaque payload this driver must pass through unchanged, or
186
+ * a replaying caller loses the block it paid for.
187
+ */
188
+ interface RawReasoningDetail {
189
+ type?: string
190
+ text?: string
191
+ signature?: string
192
+ data?: string
193
+ index?: number
194
+ }
195
+
196
+ type ReasoningDelta = NonNullable<StreamChunk['delta']['reasoning']>
197
+
198
+ /**
199
+ * Map one frame's reasoning onto the kernel's channel.
200
+ *
201
+ * The wire sends the same text twice: a flat `delta.reasoning` string AND a
202
+ * `delta.reasoning_details` array. Emitting both would double every block, so
203
+ * the array wins whenever it is present — it is the one that carries the index
204
+ * and the signature — and the flat string is the fallback for a vendor that
205
+ * sends only that.
206
+ *
207
+ * An empty array means the frame carried no reasoning at all; it is not a
208
+ * statement that the flat field should be trusted.
209
+ */
210
+ function parseReasoning(
211
+ details: readonly RawReasoningDetail[] | undefined,
212
+ flat: string | null | undefined,
213
+ ): ReasoningDelta[] {
214
+ if (details && details.length > 0) {
215
+ const mapped: ReasoningDelta[] = []
216
+ for (const [position, detail] of details.entries()) {
217
+ const index = typeof detail.index === 'number' ? detail.index : position
218
+ if (detail.type === 'reasoning.encrypted') {
219
+ if (detail.data) mapped.push({ index, type: 'redacted_thinking', encrypted: detail.data })
220
+ continue
221
+ }
222
+ if (typeof detail.text !== 'string' || detail.text.length === 0) continue
223
+ mapped.push({
224
+ index,
225
+ type: 'thinking',
226
+ text: detail.text,
227
+ ...(detail.signature ? { signature: detail.signature } : {}),
228
+ })
229
+ }
230
+ return mapped
231
+ }
232
+ return typeof flat === 'string' && flat.length > 0
233
+ ? [{ index: 0, type: 'thinking', text: flat }]
234
+ : []
235
+ }
236
+
237
+ /**
238
+ * The effort levels this wire can carry.
239
+ *
240
+ * OpenRouter's unified `reasoning.effort` publishes exactly these. The SDK's
241
+ * `max` and `ultra` are OpenAI-model-specific names with no equivalent here,
242
+ * and a driver must not send one and hope: an effort that arrives as a
243
+ * different depth is indistinguishable from one that was honoured.
244
+ */
245
+ const OPENROUTER_REASONING_EFFORT = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'] as const
246
+
247
+ /**
248
+ * Map the SDK's thinking controls onto OpenRouter's unified `reasoning` object.
249
+ *
250
+ * `undefined` means "say nothing", which leaves the vendor's own default in
251
+ * place — the only correct answer for a driver that was asked for nothing.
252
+ */
253
+ function formatReasoning(
254
+ thinking: ChatCompletionParams['thinking'],
255
+ effort: ChatCompletionParams['effort'],
256
+ ): Record<string, unknown> | undefined {
257
+ if (effort !== undefined) {
258
+ const carried = (OPENROUTER_REASONING_EFFORT as readonly string[]).includes(effort)
259
+ if (!carried) {
260
+ throw new Error(
261
+ `OpenRouterProvider cannot carry effort "${effort}". Its reasoning.effort accepts ${OPENROUTER_REASONING_EFFORT.join(', ')}. Sending a different level would return a perfectly ordinary completion that no caller could tell apart from the one that was asked for.`,
262
+ )
263
+ }
264
+ return { effort }
265
+ }
266
+ if (!thinking || thinking.type === undefined) return undefined
267
+ if (thinking.type === 'disabled') return { enabled: false }
268
+ // 'adaptive' has no separate spelling on this wire: the model decides its
269
+ // own depth, which is what `enabled: true` without a budget expresses.
270
+ // 'enabled' fixes the depth with budgetTokens, so it carries the cap.
271
+ const budgetTokens = thinking.type === 'enabled' ? thinking.budgetTokens : undefined
272
+ return {
273
+ enabled: true,
274
+ ...(budgetTokens !== undefined ? { max_tokens: budgetTokens } : {}),
51
275
  }
52
276
  }
53
277
 
@@ -158,8 +382,15 @@ export class OpenRouterProvider implements LLMProvider {
158
382
  if (params.repetitionPenalty !== undefined) body.repetition_penalty = params.repetitionPenalty
159
383
  if (params.stop) body.stop = params.stop
160
384
 
161
- if (params.cacheControl) {
162
- body.cache_control = params.cacheControl
385
+ if (params.cacheControl && takesExplicitBreakpoints(params.model)) {
386
+ applyCacheBreakpoints(params.messages, body.messages as { content?: unknown }[])
387
+ }
388
+
389
+ // Refused, not dropped, when this wire cannot carry the level asked
390
+ // for — see `formatReasoning`.
391
+ const reasoning = formatReasoning(params.thinking, params.effort)
392
+ if (reasoning !== undefined) {
393
+ body.reasoning = reasoning
163
394
  }
164
395
 
165
396
  if (params.responseFormat) {
@@ -170,7 +401,6 @@ export class OpenRouterProvider implements LLMProvider {
170
401
  }
171
402
 
172
403
  async *chatStream(params: ChatCompletionParams): AsyncIterable<StreamChunk> {
173
- assertThinkingUnsupported('OpenRouterProvider', params)
174
404
  const body = this.buildRequestBody(params, true)
175
405
 
176
406
  const timeout = AbortSignal.timeout(this.config.timeout ?? 120_000)
@@ -219,6 +449,9 @@ export class OpenRouterProvider implements LLMProvider {
219
449
  const reader = response.body.getReader()
220
450
  const decoder = new TextDecoder()
221
451
  let buffer = ''
452
+ // Reasoning blocks this stream has opened and not yet closed, in the
453
+ // order the wire reported them.
454
+ const openReasoning = new Set<number>()
222
455
 
223
456
  try {
224
457
  while (true) {
@@ -243,6 +476,8 @@ export class OpenRouterProvider implements LLMProvider {
243
476
  choices: Array<{
244
477
  delta: {
245
478
  content?: string
479
+ reasoning?: string | null
480
+ reasoning_details?: RawReasoningDetail[]
246
481
  tool_calls?: Array<{
247
482
  index: number
248
483
  id?: string
@@ -265,19 +500,56 @@ export class OpenRouterProvider implements LLMProvider {
265
500
  const choice = parsed.choices[0]
266
501
  if (!choice) continue
267
502
 
268
- yield {
269
- id: parsed.id,
270
- delta: {
271
- content: choice.delta.content,
272
- toolCalls: choice.delta.tool_calls?.map((tc) => ({
273
- index: tc.index,
274
- id: tc.id,
275
- type: tc.type as 'function' | undefined,
276
- function: tc.function,
277
- })),
278
- },
279
- finishReason: choice.finish_reason as StreamChunk['finishReason'],
280
- usage: parsed.usage ? parseUsage(parsed.usage) : undefined,
503
+ const fragments = parseReasoning(choice.delta.reasoning_details, choice.delta.reasoning)
504
+ const content = choice.delta.content
505
+ const toolCalls = choice.delta.tool_calls?.map((tc) => ({
506
+ index: tc.index,
507
+ id: tc.id,
508
+ type: tc.type as 'function' | undefined,
509
+ function: tc.function,
510
+ }))
511
+
512
+ // A block closes when output starts, which is the only boundary
513
+ // this wire gives: without it a consumer's reasoning pane never
514
+ // receives `done` and stays open for the life of the run. An
515
+ // empty `content` is NOT output — every reasoning frame on this
516
+ // wire carries `"content":""`, so treating it as the boundary
517
+ // would close each block on the frame that opened it.
518
+ if ((typeof content === 'string' && content.length > 0) || toolCalls?.length) {
519
+ for (const index of openReasoning) {
520
+ yield { id: parsed.id, delta: { reasoning: { index, done: true } } }
521
+ }
522
+ openReasoning.clear()
523
+ }
524
+
525
+ const [firstFragment, ...restFragments] = fragments
526
+ if (firstFragment) openReasoning.add(firstFragment.index)
527
+
528
+ if (
529
+ firstFragment ||
530
+ content !== undefined ||
531
+ toolCalls !== undefined ||
532
+ choice.finish_reason !== undefined ||
533
+ parsed.usage !== undefined
534
+ ) {
535
+ yield {
536
+ id: parsed.id,
537
+ delta: {
538
+ ...(firstFragment ? { reasoning: firstFragment } : {}),
539
+ content,
540
+ toolCalls,
541
+ },
542
+ finishReason: choice.finish_reason as StreamChunk['finishReason'],
543
+ usage: parsed.usage ? parseUsage(parsed.usage) : undefined,
544
+ }
545
+ }
546
+
547
+ // The delta shape carries one reasoning object per chunk, so a
548
+ // frame reporting several blocks rides in sequence rather than
549
+ // being collapsed into one — order is what replay echoes back.
550
+ for (const fragment of restFragments) {
551
+ openReasoning.add(fragment.index)
552
+ yield { id: parsed.id, delta: { reasoning: fragment } }
281
553
  }
282
554
  } catch (parseErr) {
283
555
  if (isProviderRequestError(parseErr)) throw parseErr
@@ -403,16 +675,20 @@ export class OpenRouterProvider implements LLMProvider {
403
675
  }
404
676
  signal?.throwIfAborted()
405
677
 
406
- return data.data.map((m) => ({
407
- id: m.id,
408
- name: m.name,
409
- contextWindow: m.context_length,
410
- maxOutputTokens: m.top_provider?.max_completion_tokens ?? 4096,
411
- inputPrice: Number.parseFloat(m.pricing?.prompt ?? '0') * 1_000_000,
412
- outputPrice: Number.parseFloat(m.pricing?.completion ?? '0') * 1_000_000,
413
- supportsToolUse: true,
414
- supportsStreaming: true,
415
- }))
678
+ return data.data.map((m) => {
679
+ const inputPrice = pricePerMillion(m.pricing?.prompt)
680
+ const outputPrice = pricePerMillion(m.pricing?.completion)
681
+ return {
682
+ id: m.id,
683
+ name: m.name,
684
+ contextWindow: m.context_length,
685
+ maxOutputTokens: m.top_provider?.max_completion_tokens ?? 4096,
686
+ ...(inputPrice !== undefined ? { inputPrice } : {}),
687
+ ...(outputPrice !== undefined ? { outputPrice } : {}),
688
+ supportsToolUse: true,
689
+ supportsStreaming: true,
690
+ }
691
+ })
416
692
  }
417
693
 
418
694
  async healthCheck(): Promise<boolean> {