@namzu/openrouter 1.0.1 → 1.0.3

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,59 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 3fd2524: Normalize request-start and mid-stream failures across all seven provider
8
+ drivers with the new public `ProviderRequestError` taxonomy. Errors expose
9
+ `kind` (`throttle`, `network`, `auth`, `context_overflow`, `bad_request`, or
10
+ `server`), `providerId`, and optional `status` / `retryAfterMs`, with
11
+ `isProviderRequestError` available for structural narrowing across package
12
+ copies.
13
+
14
+ Provider error messages and metadata deliberately omit vendor response bodies,
15
+ URLs, messages, and causes because upstream errors can echo credentials. HTTP
16
+ dialect-mismatch diagnostics now keep only the endpoint origin and status.
17
+ Caller-owned aborts remain unchanged instead of being reclassified.
18
+
19
+ The runtime preserves the classified error through streaming and publishes its
20
+ safe metadata as `Run.lastProviderError` and
21
+ `run_failed.providerError`. Bedrock stream-exception events and provider
22
+ iterator/SSE failures no longer appear as clean end-of-stream.
23
+
24
+ `retryAfterMs` is metadata only; this change does not add retries or alter vendor
25
+ SDK retry settings. Provider packages now require `@namzu/sdk >=1.3.0`, the
26
+ first SDK release containing these runtime helpers and types.
27
+
28
+ Ollama now maps `done_reason: "length"` truthfully so runtime continuation can
29
+ run. LM Studio treats content-free `contextLengthReached` as context overflow,
30
+ while preserving `"length"` after partial content, and creates its WebSocket
31
+ client lazily on first use.
32
+
33
+ ## 1.0.2
34
+
35
+ ### Patch Changes
36
+
37
+ - f1f000c: Declare honest driver capabilities on each provider instance.
38
+
39
+ Every shipped driver now exposes `readonly capabilities` (and
40
+ re-exports its `*_CAPABILITIES` constant from the client module)
41
+ describing what the DRIVER does — not what the vendor API could do —
42
+ so the SDK's capability negotiation can warn instead of silently
43
+ degrading:
44
+
45
+ - `@namzu/ollama`: `supportsTools: false`, `supportsVision: false`
46
+ (the driver never sends tool schemas and drops image attachments).
47
+ - `@namzu/lmstudio`: `supportsTools` corrected `true` → `false` and
48
+ `supportsFunctionCalling` `true` → `false` — the driver folds tool
49
+ messages into user text and never sends tool schemas;
50
+ `supportsVision: false`.
51
+ - `@namzu/anthropic`: full (`supportsVision: true` — image attachments
52
+ already mapped).
53
+ - `@namzu/bedrock`, `@namzu/openrouter`, `@namzu/http`: tools pass
54
+ through (`supportsTools: true`) but `supportsVision: false` until
55
+ their message translation maps attachments.
56
+
3
57
  ## 1.0.1
4
58
 
5
59
  ### Patch Changes
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Provider error taxonomy — OpenRouter driver.
3
+ *
4
+ * Every provider failure must reach the caller as a classified
5
+ * `ProviderRequestError` (kind + status + retryAfterMs + providerId), not
6
+ * as an opaque vendor string. The message must be built from the status
7
+ * line and the classified kind ONLY — never from the response body, which
8
+ * is where a credential echoed back by the upstream would leak into a log.
9
+ *
10
+ * Transport seam: the driver calls the global `fetch` directly, so the
11
+ * existing `vi.stubGlobal('fetch', …)` harness from `http.test.ts` applies
12
+ * verbatim.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=error-taxonomy.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-taxonomy.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/error-taxonomy.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Provider error taxonomy — OpenRouter driver.
3
+ *
4
+ * Every provider failure must reach the caller as a classified
5
+ * `ProviderRequestError` (kind + status + retryAfterMs + providerId), not
6
+ * as an opaque vendor string. The message must be built from the status
7
+ * line and the classified kind ONLY — never from the response body, which
8
+ * is where a credential echoed back by the upstream would leak into a log.
9
+ *
10
+ * Transport seam: the driver calls the global `fetch` directly, so the
11
+ * existing `vi.stubGlobal('fetch', …)` harness from `http.test.ts` applies
12
+ * verbatim.
13
+ */
14
+ import { afterEach, describe, expect, it, vi } from 'vitest';
15
+ import { OpenRouterProvider } from '../client.js';
16
+ /**
17
+ * An obviously fake, non-functional token. Present ONLY to prove the
18
+ * driver does not interpolate a response body into a thrown message.
19
+ */
20
+ const FAKE_CREDENTIAL = 'sk-ant-FAKE-DO-NOT-USE-0000';
21
+ afterEach(() => {
22
+ vi.unstubAllGlobals();
23
+ });
24
+ function respond(status, body, headers = {}) {
25
+ return new Response(body, {
26
+ status,
27
+ headers: { 'Content-Type': 'application/json', ...headers },
28
+ });
29
+ }
30
+ function sseResponse(frames) {
31
+ return new Response(`${frames.join('\n\n')}\n\n`, {
32
+ status: 200,
33
+ headers: { 'Content-Type': 'text/event-stream' },
34
+ });
35
+ }
36
+ async function captureChatStreamError(provider, signal) {
37
+ try {
38
+ for await (const _chunk of provider.chatStream({
39
+ model: 'anthropic/claude-sonnet-4-5',
40
+ messages: [{ role: 'user', content: 'hi' }],
41
+ signal,
42
+ })) {
43
+ // drain
44
+ }
45
+ }
46
+ catch (err) {
47
+ return err;
48
+ }
49
+ throw new Error('expected chatStream to throw');
50
+ }
51
+ function makeProvider() {
52
+ return new OpenRouterProvider({
53
+ apiKey: 'test-key',
54
+ baseUrl: 'https://example.test/api/v1',
55
+ });
56
+ }
57
+ describe('@namzu/openrouter — provider error taxonomy', () => {
58
+ it('(a) 429 with `retry-after: 2` throws a throttle-classified error carrying retryAfterMs', async () => {
59
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(respond(429, JSON.stringify({ error: { message: 'rate limit exceeded' } }), {
60
+ 'retry-after': '2',
61
+ })));
62
+ const err = await captureChatStreamError(makeProvider());
63
+ expect(err).toMatchObject({
64
+ name: 'ProviderRequestError',
65
+ kind: 'throttle',
66
+ status: 429,
67
+ retryAfterMs: 2000,
68
+ providerId: 'openrouter',
69
+ });
70
+ });
71
+ it("(c) 400 whose body says the prompt is too long classifies as 'context_overflow'", async () => {
72
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(respond(400, JSON.stringify({
73
+ error: {
74
+ type: 'invalid_request_error',
75
+ message: 'prompt is too long: 305231 tokens > 200000 maximum',
76
+ },
77
+ }))));
78
+ const err = await captureChatStreamError(makeProvider());
79
+ expect(err).toMatchObject({
80
+ name: 'ProviderRequestError',
81
+ kind: 'context_overflow',
82
+ status: 400,
83
+ providerId: 'openrouter',
84
+ });
85
+ });
86
+ it("(c) 400 that is not an overflow classifies as 'bad_request'", async () => {
87
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(respond(400, JSON.stringify({
88
+ error: {
89
+ type: 'invalid_request_error',
90
+ message: "unknown field 'temperture'",
91
+ },
92
+ }))));
93
+ const err = await captureChatStreamError(makeProvider());
94
+ expect(err).toMatchObject({
95
+ name: 'ProviderRequestError',
96
+ kind: 'bad_request',
97
+ status: 400,
98
+ providerId: 'openrouter',
99
+ });
100
+ });
101
+ it("(a/auth) 401 classifies as 'auth'", async () => {
102
+ vi.stubGlobal('fetch', vi
103
+ .fn()
104
+ .mockResolvedValue(respond(401, JSON.stringify({ error: { message: 'No auth credentials found' } }))));
105
+ const err = await captureChatStreamError(makeProvider());
106
+ expect(err).toMatchObject({
107
+ name: 'ProviderRequestError',
108
+ kind: 'auth',
109
+ status: 401,
110
+ providerId: 'openrouter',
111
+ });
112
+ });
113
+ it('(d) SECURITY: a credential echoed back in a 401 body never reaches the thrown message', async () => {
114
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(respond(401, JSON.stringify({
115
+ error: { message: `invalid api key: ${FAKE_CREDENTIAL}` },
116
+ }))));
117
+ const err = await captureChatStreamError(makeProvider());
118
+ expect(err).toBeInstanceOf(Error);
119
+ const message = err.message;
120
+ expect(message).not.toContain(FAKE_CREDENTIAL);
121
+ expect(JSON.stringify(err)).not.toContain(FAKE_CREDENTIAL);
122
+ expect('cause' in err).toBe(false);
123
+ });
124
+ it('(d) SECURITY: a credential echoed back in a 400 body never reaches the thrown message', async () => {
125
+ vi.stubGlobal('fetch', vi
126
+ .fn()
127
+ .mockResolvedValue(respond(400, `bad request for key ${FAKE_CREDENTIAL}: model not permitted`)));
128
+ const err = await captureChatStreamError(makeProvider());
129
+ expect(err.message).not.toContain(FAKE_CREDENTIAL);
130
+ });
131
+ it("classifies a fetch rejection as 'network' without retaining its cause", async () => {
132
+ const vendor = new Error(`socket failed for ${FAKE_CREDENTIAL}`);
133
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(vendor));
134
+ const err = await captureChatStreamError(makeProvider());
135
+ expect(err).toMatchObject({
136
+ name: 'ProviderRequestError',
137
+ kind: 'network',
138
+ providerId: 'openrouter',
139
+ });
140
+ expect(err.message).not.toContain(FAKE_CREDENTIAL);
141
+ expect('cause' in err).toBe(false);
142
+ });
143
+ it('preserves the caller abort reason when fetch replaces the error object', async () => {
144
+ const controller = new AbortController();
145
+ const reason = new Error('user stopped');
146
+ const fetchAbort = Object.assign(new Error('request aborted'), {
147
+ name: 'AbortError',
148
+ });
149
+ controller.abort(reason);
150
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(fetchAbort));
151
+ const err = await captureChatStreamError(makeProvider(), controller.signal);
152
+ expect(fetchAbort).not.toBe(reason);
153
+ expect(err).toBe(reason);
154
+ });
155
+ it('classifies and sanitizes an SSE error after the 200 response', async () => {
156
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(sseResponse([
157
+ `data: ${JSON.stringify({
158
+ error: {
159
+ type: 'overloaded_error',
160
+ message: `echoed ${FAKE_CREDENTIAL}`,
161
+ },
162
+ })}`,
163
+ ])));
164
+ const err = await captureChatStreamError(makeProvider());
165
+ expect(err).toMatchObject({
166
+ name: 'ProviderRequestError',
167
+ kind: 'server',
168
+ providerId: 'openrouter',
169
+ });
170
+ expect(err.message).not.toContain(FAKE_CREDENTIAL);
171
+ expect(JSON.stringify(err)).not.toContain(FAKE_CREDENTIAL);
172
+ expect('cause' in err).toBe(false);
173
+ });
174
+ it('classifies malformed SSE without retaining the credential-bearing frame', async () => {
175
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(sseResponse([`data: ${FAKE_CREDENTIAL} not-json`])));
176
+ const err = await captureChatStreamError(makeProvider());
177
+ expect(err).toMatchObject({
178
+ name: 'ProviderRequestError',
179
+ kind: 'server',
180
+ providerId: 'openrouter',
181
+ });
182
+ expect(err.message).not.toContain(FAKE_CREDENTIAL);
183
+ expect(JSON.stringify(err)).not.toContain(FAKE_CREDENTIAL);
184
+ expect('cause' in err).toBe(false);
185
+ });
186
+ });
187
+ //# sourceMappingURL=error-taxonomy.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-taxonomy.test.js","sourceRoot":"","sources":["../../src/__tests__/error-taxonomy.test.ts"],"names":[],"mappings":"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"}
@@ -38,8 +38,13 @@ describe('@namzu/openrouter', () => {
38
38
  supportsTools: true,
39
39
  supportsStreaming: true,
40
40
  supportsFunctionCalling: true,
41
+ supportsVision: false,
41
42
  });
42
43
  });
44
+ it('is exposed on the provider instance for runtime negotiation', () => {
45
+ const provider = new OpenRouterProvider({ apiKey: 'test-key' });
46
+ expect(provider.capabilities).toEqual(OPENROUTER_CAPABILITIES);
47
+ });
43
48
  });
44
49
  describe('ProviderRegistry.create({ type: "openrouter", ... })', () => {
45
50
  it('narrows the config type via module augmentation and instantiates OpenRouterProvider', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"openrouter.test.js","sourceRoot":"","sources":["../../src/__tests__/openrouter.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openrouter.test.js","sourceRoot":"","sources":["../../src/__tests__/openrouter.test.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -1,8 +1,16 @@
1
- import type { ChatCompletionParams, LLMProvider, ModelInfo, StreamChunk } from '@namzu/sdk';
1
+ import type { ChatCompletionParams, LLMProvider, ModelInfo, ProviderCapabilities, StreamChunk } from '@namzu/sdk';
2
2
  import type { OpenRouterConfig } from './types.js';
3
+ /**
4
+ * What this DRIVER does, not what OpenRouter could do: tools pass
5
+ * through to the request body, but user-message image `attachments`
6
+ * are not mapped into content parts — `supportsVision` stays false
7
+ * until the message translation handles them.
8
+ */
9
+ export declare const OPENROUTER_CAPABILITIES: ProviderCapabilities;
3
10
  export declare class OpenRouterProvider implements LLMProvider {
4
11
  readonly id = "openrouter";
5
12
  readonly name = "OpenRouter";
13
+ readonly capabilities: ProviderCapabilities;
6
14
  private config;
7
15
  private baseUrl;
8
16
  constructor(config: OpenRouterConfig);
@@ -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,WAAW,EAGX,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAwClD,qBAAa,kBAAmB,YAAW,WAAW;IACrD,QAAQ,CAAC,EAAE,gBAAe;IAC1B,QAAQ,CAAC,IAAI,gBAAe;IAE5B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAQ;gBAEX,MAAM,EAAE,gBAAgB;IAQpC,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,gBAAgB;IAqCjB,UAAU,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CAAC,WAAW,CAAC;IAgGrE,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IA+BlC,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;AAQnB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAwClD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,oBAKrC,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;IAclB,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,gBAAgB;IAqCjB,UAAU,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CAAC,WAAW,CAAC;IAkIrE,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IA+BlC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAWrC"}
package/dist/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ProviderRequestError, isCallerAbortError, isProviderRequestError, providerHttpError, providerVendorError, } from '@namzu/sdk';
1
2
  const OPENROUTER_BASE_URL = process.env.OPENROUTER_BASE_URL ?? 'https://openrouter.ai/api/v1';
2
3
  function parseUsage(raw) {
3
4
  if (!raw) {
@@ -22,9 +23,22 @@ function formatToolChoice(tc) {
22
23
  return tc;
23
24
  return tc;
24
25
  }
26
+ /**
27
+ * What this DRIVER does, not what OpenRouter could do: tools pass
28
+ * through to the request body, but user-message image `attachments`
29
+ * are not mapped into content parts — `supportsVision` stays false
30
+ * until the message translation handles them.
31
+ */
32
+ export const OPENROUTER_CAPABILITIES = {
33
+ supportsTools: true,
34
+ supportsStreaming: true,
35
+ supportsFunctionCalling: true,
36
+ supportsVision: false,
37
+ };
25
38
  export class OpenRouterProvider {
26
39
  id = 'openrouter';
27
40
  name = 'OpenRouter';
41
+ capabilities = OPENROUTER_CAPABILITIES;
28
42
  config;
29
43
  baseUrl;
30
44
  constructor(config) {
@@ -116,18 +130,41 @@ export class OpenRouterProvider {
116
130
  // response body stream. When no caller signal is present this is the exact
117
131
  // prior `AbortSignal.timeout(...)` expression (byte-identical).
118
132
  const signal = params.signal ? AbortSignal.any([timeout, params.signal]) : timeout;
119
- const response = await fetch(`${this.baseUrl}/chat/completions`, {
120
- method: 'POST',
121
- headers: this.getHeaders(),
122
- body: JSON.stringify(body),
123
- signal,
124
- });
133
+ let response;
134
+ try {
135
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
136
+ method: 'POST',
137
+ headers: this.getHeaders(),
138
+ body: JSON.stringify(body),
139
+ signal,
140
+ });
141
+ }
142
+ catch (err) {
143
+ if (isCallerAbortError(err, params.signal))
144
+ throw params.signal?.reason ?? err;
145
+ throw providerVendorError({ providerId: 'openrouter', error: err });
146
+ }
125
147
  if (!response.ok) {
126
- const errorBody = await response.text();
127
- throw new Error(`OpenRouter API error (${response.status}): ${errorBody}`);
148
+ // The body is read to CLASSIFY (a 400 saying "prompt is too long" is a
149
+ // context overflow, any other 400 is a bad request) and then dropped. It
150
+ // used to be interpolated straight into the message, which is how a
151
+ // credential the upstream echoed back reached every log that recorded
152
+ // the failure — proven with a planted fake token.
153
+ const errorBody = await response.text().catch(() => '');
154
+ throw providerHttpError({
155
+ providerId: 'openrouter',
156
+ status: response.status,
157
+ body: errorBody,
158
+ retryAfter: response.headers.get('retry-after'),
159
+ });
128
160
  }
129
161
  if (!response.body) {
130
- throw new Error('OpenRouter returned no stream body');
162
+ throw new ProviderRequestError({
163
+ kind: 'server',
164
+ providerId: 'openrouter',
165
+ status: response.status,
166
+ detail: 'the response contained no stream body',
167
+ });
131
168
  }
132
169
  const reader = response.body.getReader();
133
170
  const decoder = new TextDecoder();
@@ -150,6 +187,12 @@ export class OpenRouterProvider {
150
187
  return;
151
188
  try {
152
189
  const parsed = JSON.parse(data);
190
+ if (parsed.error !== undefined) {
191
+ throw providerVendorError({
192
+ providerId: 'openrouter',
193
+ error: new Error(data),
194
+ });
195
+ }
153
196
  const choice = parsed.choices[0];
154
197
  if (!choice)
155
198
  continue;
@@ -169,17 +212,26 @@ export class OpenRouterProvider {
169
212
  };
170
213
  }
171
214
  catch (parseErr) {
172
- yield {
173
- id: '',
174
- delta: { content: undefined },
175
- finishReason: undefined,
176
- usage: undefined,
177
- error: `Stream parse error: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`,
178
- };
215
+ if (isProviderRequestError(parseErr))
216
+ throw parseErr;
217
+ // JSON SyntaxError messages include a source snippet, and
218
+ // mapping failures may include vendor values. Drop both.
219
+ throw new ProviderRequestError({
220
+ kind: 'server',
221
+ providerId: 'openrouter',
222
+ detail: 'the provider stream returned malformed data',
223
+ });
179
224
  }
180
225
  }
181
226
  }
182
227
  }
228
+ catch (err) {
229
+ if (isCallerAbortError(err, params.signal))
230
+ throw params.signal?.reason ?? err;
231
+ if (isProviderRequestError(err))
232
+ throw err;
233
+ throw providerVendorError({ providerId: 'openrouter', error: err });
234
+ }
183
235
  finally {
184
236
  reader.releaseLock();
185
237
  }
@@ -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/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
- import { type ProviderCapabilities, type RegisterOptions } from '@namzu/sdk';
1
+ import { type RegisterOptions } from '@namzu/sdk';
2
2
  import type { OpenRouterProviderConfig } from './types.js';
3
3
  declare module '@namzu/sdk' {
4
4
  interface ProviderConfigRegistry {
5
5
  openrouter: OpenRouterProviderConfig;
6
6
  }
7
7
  }
8
- export declare const OPENROUTER_CAPABILITIES: ProviderCapabilities;
9
8
  /**
10
9
  * Register `OpenRouterProvider` under the `'openrouter'` type in `@namzu/sdk`'s
11
10
  * `ProviderRegistry`. Call once at app startup before
@@ -15,6 +14,6 @@ export declare const OPENROUTER_CAPABILITIES: ProviderCapabilities;
15
14
  * Pass `{ replace: true }` to override.
16
15
  */
17
16
  export declare function registerOpenRouter(options?: RegisterOptions): void;
18
- export { OpenRouterProvider } from './client.js';
17
+ export { OPENROUTER_CAPABILITIES, OpenRouterProvider } from './client.js';
19
18
  export type { OpenRouterConfig, OpenRouterProviderConfig } from './types.js';
20
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAoB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAE9F,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAK1D,OAAO,QAAQ,YAAY,CAAC;IAC3B,UAAU,sBAAsB;QAC/B,UAAU,EAAE,wBAAwB,CAAA;KACpC;CACD;AAED,eAAO,MAAM,uBAAuB,EAAE,oBAIrC,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAElE;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,YAAY,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAEnE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAK1D,OAAO,QAAQ,YAAY,CAAC;IAC3B,UAAU,sBAAsB;QAC/B,UAAU,EAAE,wBAAwB,CAAA;KACpC;CACD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAElE;AAED,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACzE,YAAY,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,10 +1,5 @@
1
1
  import { ProviderRegistry } from '@namzu/sdk';
2
- import { OpenRouterProvider } from './client.js';
3
- export const OPENROUTER_CAPABILITIES = {
4
- supportsTools: true,
5
- supportsStreaming: true,
6
- supportsFunctionCalling: true,
7
- };
2
+ import { OPENROUTER_CAPABILITIES, OpenRouterProvider } from './client.js';
8
3
  /**
9
4
  * Register `OpenRouterProvider` under the `'openrouter'` type in `@namzu/sdk`'s
10
5
  * `ProviderRegistry`. Call once at app startup before
@@ -16,5 +11,5 @@ export const OPENROUTER_CAPABILITIES = {
16
11
  export function registerOpenRouter(options) {
17
12
  ProviderRegistry.register('openrouter', OpenRouterProvider, OPENROUTER_CAPABILITIES, options);
18
13
  }
19
- export { OpenRouterProvider } from './client.js';
14
+ export { OPENROUTER_CAPABILITIES, OpenRouterProvider } from './client.js';
20
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,gBAAgB,EAAwB,MAAM,YAAY,CAAA;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAYhD,MAAM,CAAC,MAAM,uBAAuB,GAAyB;IAC5D,aAAa,EAAE,IAAI;IACnB,iBAAiB,EAAE,IAAI;IACvB,uBAAuB,EAAE,IAAI;CAC7B,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAyB;IAC3D,gBAAgB,CAAC,QAAQ,CAAC,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,OAAO,CAAC,CAAA;AAC9F,CAAC;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAwB,MAAM,YAAY,CAAA;AACnE,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAYzE;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAyB;IAC3D,gBAAgB,CAAC,QAAQ,CAAC,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,OAAO,CAAC,CAAA;AAC9F,CAAC;AAED,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namzu/openrouter",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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
  "type": "module",
@@ -35,14 +35,14 @@
35
35
  },
36
36
  "dependencies": {},
37
37
  "peerDependencies": {
38
- "@namzu/sdk": ">=1.0.0"
38
+ "@namzu/sdk": ">=1.3.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@biomejs/biome": "^1.9.4",
42
42
  "@types/node": "^22.19.17",
43
43
  "typescript": "^5.5.0",
44
44
  "vitest": "^2.0.0",
45
- "@namzu/sdk": "^1.1.0"
45
+ "@namzu/sdk": "^1.4.0"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsc --build",
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Provider error taxonomy — OpenRouter driver.
3
+ *
4
+ * Every provider failure must reach the caller as a classified
5
+ * `ProviderRequestError` (kind + status + retryAfterMs + providerId), not
6
+ * as an opaque vendor string. The message must be built from the status
7
+ * line and the classified kind ONLY — never from the response body, which
8
+ * is where a credential echoed back by the upstream would leak into a log.
9
+ *
10
+ * Transport seam: the driver calls the global `fetch` directly, so the
11
+ * existing `vi.stubGlobal('fetch', …)` harness from `http.test.ts` applies
12
+ * verbatim.
13
+ */
14
+
15
+ import { afterEach, describe, expect, it, vi } from 'vitest'
16
+ import { OpenRouterProvider } from '../client.js'
17
+
18
+ /**
19
+ * An obviously fake, non-functional token. Present ONLY to prove the
20
+ * driver does not interpolate a response body into a thrown message.
21
+ */
22
+ const FAKE_CREDENTIAL = 'sk-ant-FAKE-DO-NOT-USE-0000'
23
+
24
+ afterEach(() => {
25
+ vi.unstubAllGlobals()
26
+ })
27
+
28
+ function respond(status: number, body: string, headers: Record<string, string> = {}): Response {
29
+ return new Response(body, {
30
+ status,
31
+ headers: { 'Content-Type': 'application/json', ...headers },
32
+ })
33
+ }
34
+
35
+ function sseResponse(frames: string[]): Response {
36
+ return new Response(`${frames.join('\n\n')}\n\n`, {
37
+ status: 200,
38
+ headers: { 'Content-Type': 'text/event-stream' },
39
+ })
40
+ }
41
+
42
+ async function captureChatStreamError(
43
+ provider: OpenRouterProvider,
44
+ signal?: AbortSignal,
45
+ ): Promise<unknown> {
46
+ try {
47
+ for await (const _chunk of provider.chatStream({
48
+ model: 'anthropic/claude-sonnet-4-5',
49
+ messages: [{ role: 'user', content: 'hi' }],
50
+ signal,
51
+ })) {
52
+ // drain
53
+ }
54
+ } catch (err) {
55
+ return err
56
+ }
57
+ throw new Error('expected chatStream to throw')
58
+ }
59
+
60
+ function makeProvider(): OpenRouterProvider {
61
+ return new OpenRouterProvider({
62
+ apiKey: 'test-key',
63
+ baseUrl: 'https://example.test/api/v1',
64
+ })
65
+ }
66
+
67
+ describe('@namzu/openrouter — provider error taxonomy', () => {
68
+ it('(a) 429 with `retry-after: 2` throws a throttle-classified error carrying retryAfterMs', async () => {
69
+ vi.stubGlobal(
70
+ 'fetch',
71
+ vi.fn().mockResolvedValue(
72
+ respond(429, JSON.stringify({ error: { message: 'rate limit exceeded' } }), {
73
+ 'retry-after': '2',
74
+ }),
75
+ ),
76
+ )
77
+
78
+ const err = await captureChatStreamError(makeProvider())
79
+
80
+ expect(err).toMatchObject({
81
+ name: 'ProviderRequestError',
82
+ kind: 'throttle',
83
+ status: 429,
84
+ retryAfterMs: 2000,
85
+ providerId: 'openrouter',
86
+ })
87
+ })
88
+
89
+ it("(c) 400 whose body says the prompt is too long classifies as 'context_overflow'", async () => {
90
+ vi.stubGlobal(
91
+ 'fetch',
92
+ vi.fn().mockResolvedValue(
93
+ respond(
94
+ 400,
95
+ JSON.stringify({
96
+ error: {
97
+ type: 'invalid_request_error',
98
+ message: 'prompt is too long: 305231 tokens > 200000 maximum',
99
+ },
100
+ }),
101
+ ),
102
+ ),
103
+ )
104
+
105
+ const err = await captureChatStreamError(makeProvider())
106
+
107
+ expect(err).toMatchObject({
108
+ name: 'ProviderRequestError',
109
+ kind: 'context_overflow',
110
+ status: 400,
111
+ providerId: 'openrouter',
112
+ })
113
+ })
114
+
115
+ it("(c) 400 that is not an overflow classifies as 'bad_request'", async () => {
116
+ vi.stubGlobal(
117
+ 'fetch',
118
+ vi.fn().mockResolvedValue(
119
+ respond(
120
+ 400,
121
+ JSON.stringify({
122
+ error: {
123
+ type: 'invalid_request_error',
124
+ message: "unknown field 'temperture'",
125
+ },
126
+ }),
127
+ ),
128
+ ),
129
+ )
130
+
131
+ const err = await captureChatStreamError(makeProvider())
132
+
133
+ expect(err).toMatchObject({
134
+ name: 'ProviderRequestError',
135
+ kind: 'bad_request',
136
+ status: 400,
137
+ providerId: 'openrouter',
138
+ })
139
+ })
140
+
141
+ it("(a/auth) 401 classifies as 'auth'", async () => {
142
+ vi.stubGlobal(
143
+ 'fetch',
144
+ vi
145
+ .fn()
146
+ .mockResolvedValue(
147
+ respond(401, JSON.stringify({ error: { message: 'No auth credentials found' } })),
148
+ ),
149
+ )
150
+
151
+ const err = await captureChatStreamError(makeProvider())
152
+
153
+ expect(err).toMatchObject({
154
+ name: 'ProviderRequestError',
155
+ kind: 'auth',
156
+ status: 401,
157
+ providerId: 'openrouter',
158
+ })
159
+ })
160
+
161
+ it('(d) SECURITY: a credential echoed back in a 401 body never reaches the thrown message', async () => {
162
+ vi.stubGlobal(
163
+ 'fetch',
164
+ vi.fn().mockResolvedValue(
165
+ respond(
166
+ 401,
167
+ JSON.stringify({
168
+ error: { message: `invalid api key: ${FAKE_CREDENTIAL}` },
169
+ }),
170
+ ),
171
+ ),
172
+ )
173
+
174
+ const err = await captureChatStreamError(makeProvider())
175
+
176
+ expect(err).toBeInstanceOf(Error)
177
+ const message = (err as Error).message
178
+ expect(message).not.toContain(FAKE_CREDENTIAL)
179
+ expect(JSON.stringify(err)).not.toContain(FAKE_CREDENTIAL)
180
+ expect('cause' in (err as object)).toBe(false)
181
+ })
182
+
183
+ it('(d) SECURITY: a credential echoed back in a 400 body never reaches the thrown message', async () => {
184
+ vi.stubGlobal(
185
+ 'fetch',
186
+ vi
187
+ .fn()
188
+ .mockResolvedValue(
189
+ respond(400, `bad request for key ${FAKE_CREDENTIAL}: model not permitted`),
190
+ ),
191
+ )
192
+
193
+ const err = await captureChatStreamError(makeProvider())
194
+
195
+ expect((err as Error).message).not.toContain(FAKE_CREDENTIAL)
196
+ })
197
+
198
+ it("classifies a fetch rejection as 'network' without retaining its cause", async () => {
199
+ const vendor = new Error(`socket failed for ${FAKE_CREDENTIAL}`)
200
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(vendor))
201
+
202
+ const err = await captureChatStreamError(makeProvider())
203
+
204
+ expect(err).toMatchObject({
205
+ name: 'ProviderRequestError',
206
+ kind: 'network',
207
+ providerId: 'openrouter',
208
+ })
209
+ expect((err as Error).message).not.toContain(FAKE_CREDENTIAL)
210
+ expect('cause' in (err as object)).toBe(false)
211
+ })
212
+
213
+ it('preserves the caller abort reason when fetch replaces the error object', async () => {
214
+ const controller = new AbortController()
215
+ const reason = new Error('user stopped')
216
+ const fetchAbort = Object.assign(new Error('request aborted'), {
217
+ name: 'AbortError',
218
+ })
219
+ controller.abort(reason)
220
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(fetchAbort))
221
+
222
+ const err = await captureChatStreamError(makeProvider(), controller.signal)
223
+
224
+ expect(fetchAbort).not.toBe(reason)
225
+ expect(err).toBe(reason)
226
+ })
227
+
228
+ it('classifies and sanitizes an SSE error after the 200 response', async () => {
229
+ vi.stubGlobal(
230
+ 'fetch',
231
+ vi.fn().mockResolvedValue(
232
+ sseResponse([
233
+ `data: ${JSON.stringify({
234
+ error: {
235
+ type: 'overloaded_error',
236
+ message: `echoed ${FAKE_CREDENTIAL}`,
237
+ },
238
+ })}`,
239
+ ]),
240
+ ),
241
+ )
242
+
243
+ const err = await captureChatStreamError(makeProvider())
244
+
245
+ expect(err).toMatchObject({
246
+ name: 'ProviderRequestError',
247
+ kind: 'server',
248
+ providerId: 'openrouter',
249
+ })
250
+ expect((err as Error).message).not.toContain(FAKE_CREDENTIAL)
251
+ expect(JSON.stringify(err)).not.toContain(FAKE_CREDENTIAL)
252
+ expect('cause' in (err as object)).toBe(false)
253
+ })
254
+
255
+ it('classifies malformed SSE without retaining the credential-bearing frame', async () => {
256
+ vi.stubGlobal(
257
+ 'fetch',
258
+ vi.fn().mockResolvedValue(sseResponse([`data: ${FAKE_CREDENTIAL} not-json`])),
259
+ )
260
+
261
+ const err = await captureChatStreamError(makeProvider())
262
+
263
+ expect(err).toMatchObject({
264
+ name: 'ProviderRequestError',
265
+ kind: 'server',
266
+ providerId: 'openrouter',
267
+ })
268
+ expect((err as Error).message).not.toContain(FAKE_CREDENTIAL)
269
+ expect(JSON.stringify(err)).not.toContain(FAKE_CREDENTIAL)
270
+ expect('cause' in (err as object)).toBe(false)
271
+ })
272
+ })
@@ -44,8 +44,14 @@ describe('@namzu/openrouter', () => {
44
44
  supportsTools: true,
45
45
  supportsStreaming: true,
46
46
  supportsFunctionCalling: true,
47
+ supportsVision: false,
47
48
  })
48
49
  })
50
+
51
+ it('is exposed on the provider instance for runtime negotiation', () => {
52
+ const provider = new OpenRouterProvider({ apiKey: 'test-key' })
53
+ expect(provider.capabilities).toEqual(OPENROUTER_CAPABILITIES)
54
+ })
49
55
  })
50
56
 
51
57
  describe('ProviderRegistry.create({ type: "openrouter", ... })', () => {
package/src/client.ts CHANGED
@@ -2,10 +2,18 @@ import type {
2
2
  ChatCompletionParams,
3
3
  LLMProvider,
4
4
  ModelInfo,
5
+ ProviderCapabilities,
5
6
  StreamChunk,
6
7
  TokenUsage,
7
8
  ToolChoice,
8
9
  } from '@namzu/sdk'
10
+ import {
11
+ ProviderRequestError,
12
+ isCallerAbortError,
13
+ isProviderRequestError,
14
+ providerHttpError,
15
+ providerVendorError,
16
+ } from '@namzu/sdk'
9
17
  import type { OpenRouterConfig } from './types.js'
10
18
 
11
19
  const OPENROUTER_BASE_URL = process.env.OPENROUTER_BASE_URL ?? 'https://openrouter.ai/api/v1'
@@ -46,9 +54,23 @@ function formatToolChoice(tc: ToolChoice): unknown {
46
54
  return tc
47
55
  }
48
56
 
57
+ /**
58
+ * What this DRIVER does, not what OpenRouter could do: tools pass
59
+ * through to the request body, but user-message image `attachments`
60
+ * are not mapped into content parts — `supportsVision` stays false
61
+ * until the message translation handles them.
62
+ */
63
+ export const OPENROUTER_CAPABILITIES: ProviderCapabilities = {
64
+ supportsTools: true,
65
+ supportsStreaming: true,
66
+ supportsFunctionCalling: true,
67
+ supportsVision: false,
68
+ }
69
+
49
70
  export class OpenRouterProvider implements LLMProvider {
50
71
  readonly id = 'openrouter'
51
72
  readonly name = 'OpenRouter'
73
+ readonly capabilities = OPENROUTER_CAPABILITIES
52
74
 
53
75
  private config: OpenRouterConfig
54
76
  private baseUrl: string
@@ -145,20 +167,41 @@ export class OpenRouterProvider implements LLMProvider {
145
167
  // prior `AbortSignal.timeout(...)` expression (byte-identical).
146
168
  const signal = params.signal ? AbortSignal.any([timeout, params.signal]) : timeout
147
169
 
148
- const response = await fetch(`${this.baseUrl}/chat/completions`, {
149
- method: 'POST',
150
- headers: this.getHeaders(),
151
- body: JSON.stringify(body),
152
- signal,
153
- })
170
+ let response: Response
171
+ try {
172
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
173
+ method: 'POST',
174
+ headers: this.getHeaders(),
175
+ body: JSON.stringify(body),
176
+ signal,
177
+ })
178
+ } catch (err) {
179
+ if (isCallerAbortError(err, params.signal)) throw params.signal?.reason ?? err
180
+ throw providerVendorError({ providerId: 'openrouter', error: err })
181
+ }
154
182
 
155
183
  if (!response.ok) {
156
- const errorBody = await response.text()
157
- throw new Error(`OpenRouter API error (${response.status}): ${errorBody}`)
184
+ // The body is read to CLASSIFY (a 400 saying "prompt is too long" is a
185
+ // context overflow, any other 400 is a bad request) and then dropped. It
186
+ // used to be interpolated straight into the message, which is how a
187
+ // credential the upstream echoed back reached every log that recorded
188
+ // the failure — proven with a planted fake token.
189
+ const errorBody = await response.text().catch(() => '')
190
+ throw providerHttpError({
191
+ providerId: 'openrouter',
192
+ status: response.status,
193
+ body: errorBody,
194
+ retryAfter: response.headers.get('retry-after'),
195
+ })
158
196
  }
159
197
 
160
198
  if (!response.body) {
161
- throw new Error('OpenRouter returned no stream body')
199
+ throw new ProviderRequestError({
200
+ kind: 'server',
201
+ providerId: 'openrouter',
202
+ status: response.status,
203
+ detail: 'the response contained no stream body',
204
+ })
162
205
  }
163
206
 
164
207
  const reader = response.body.getReader()
@@ -184,6 +227,7 @@ export class OpenRouterProvider implements LLMProvider {
184
227
  try {
185
228
  const parsed = JSON.parse(data) as {
186
229
  id: string
230
+ error?: unknown
187
231
  choices: Array<{
188
232
  delta: {
189
233
  content?: string
@@ -199,6 +243,13 @@ export class OpenRouterProvider implements LLMProvider {
199
243
  usage?: RawUsage
200
244
  }
201
245
 
246
+ if (parsed.error !== undefined) {
247
+ throw providerVendorError({
248
+ providerId: 'openrouter',
249
+ error: new Error(data),
250
+ })
251
+ }
252
+
202
253
  const choice = parsed.choices[0]
203
254
  if (!choice) continue
204
255
 
@@ -217,16 +268,21 @@ export class OpenRouterProvider implements LLMProvider {
217
268
  usage: parsed.usage ? parseUsage(parsed.usage) : undefined,
218
269
  }
219
270
  } catch (parseErr) {
220
- yield {
221
- id: '',
222
- delta: { content: undefined },
223
- finishReason: undefined,
224
- usage: undefined,
225
- error: `Stream parse error: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`,
226
- }
271
+ if (isProviderRequestError(parseErr)) throw parseErr
272
+ // JSON SyntaxError messages include a source snippet, and
273
+ // mapping failures may include vendor values. Drop both.
274
+ throw new ProviderRequestError({
275
+ kind: 'server',
276
+ providerId: 'openrouter',
277
+ detail: 'the provider stream returned malformed data',
278
+ })
227
279
  }
228
280
  }
229
281
  }
282
+ } catch (err) {
283
+ if (isCallerAbortError(err, params.signal)) throw params.signal?.reason ?? err
284
+ if (isProviderRequestError(err)) throw err
285
+ throw providerVendorError({ providerId: 'openrouter', error: err })
230
286
  } finally {
231
287
  reader.releaseLock()
232
288
  }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type ProviderCapabilities, ProviderRegistry, type RegisterOptions } from '@namzu/sdk'
2
- import { OpenRouterProvider } from './client.js'
1
+ import { ProviderRegistry, type RegisterOptions } from '@namzu/sdk'
2
+ import { OPENROUTER_CAPABILITIES, OpenRouterProvider } from './client.js'
3
3
  import type { OpenRouterProviderConfig } from './types.js'
4
4
 
5
5
  // Module augmentation: register openrouter's config type in the sdk's registry
@@ -11,12 +11,6 @@ declare module '@namzu/sdk' {
11
11
  }
12
12
  }
13
13
 
14
- export const OPENROUTER_CAPABILITIES: ProviderCapabilities = {
15
- supportsTools: true,
16
- supportsStreaming: true,
17
- supportsFunctionCalling: true,
18
- }
19
-
20
14
  /**
21
15
  * Register `OpenRouterProvider` under the `'openrouter'` type in `@namzu/sdk`'s
22
16
  * `ProviderRegistry`. Call once at app startup before
@@ -29,5 +23,5 @@ export function registerOpenRouter(options?: RegisterOptions): void {
29
23
  ProviderRegistry.register('openrouter', OpenRouterProvider, OPENROUTER_CAPABILITIES, options)
30
24
  }
31
25
 
32
- export { OpenRouterProvider } from './client.js'
26
+ export { OPENROUTER_CAPABILITIES, OpenRouterProvider } from './client.js'
33
27
  export type { OpenRouterConfig, OpenRouterProviderConfig } from './types.js'