offrouter-core 0.2.1 → 0.2.2

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.
Files changed (139) hide show
  1. package/dist/audit.d.ts +137 -0
  2. package/dist/audit.d.ts.map +1 -0
  3. package/dist/audit.js +320 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/auth/credential-chain.d.ts +84 -0
  6. package/dist/auth/credential-chain.d.ts.map +1 -0
  7. package/dist/auth/credential-chain.js +150 -0
  8. package/dist/auth/credential-chain.js.map +1 -0
  9. package/dist/auth/index.d.ts +10 -0
  10. package/dist/auth/index.d.ts.map +1 -0
  11. package/dist/auth/index.js +7 -0
  12. package/dist/auth/index.js.map +1 -0
  13. package/dist/auth/keychain.d.ts +60 -0
  14. package/dist/auth/keychain.d.ts.map +1 -0
  15. package/dist/auth/keychain.js +104 -0
  16. package/dist/auth/keychain.js.map +1 -0
  17. package/dist/auth/oauth-pkce.d.ts +138 -0
  18. package/dist/auth/oauth-pkce.d.ts.map +1 -0
  19. package/dist/auth/oauth-pkce.js +375 -0
  20. package/dist/auth/oauth-pkce.js.map +1 -0
  21. package/dist/auth/oauth-server.d.ts +36 -0
  22. package/dist/auth/oauth-server.d.ts.map +1 -0
  23. package/dist/auth/oauth-server.js +210 -0
  24. package/dist/auth/oauth-server.js.map +1 -0
  25. package/dist/config.d.ts +76 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +355 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/delegation.d.ts +123 -0
  30. package/dist/delegation.d.ts.map +1 -0
  31. package/dist/delegation.js +455 -0
  32. package/dist/delegation.js.map +1 -0
  33. package/dist/index.d.ts +41 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +26 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/policy.d.ts +45 -0
  38. package/dist/policy.d.ts.map +1 -0
  39. package/dist/policy.js +318 -0
  40. package/dist/policy.js.map +1 -0
  41. package/{src/protocol.ts → dist/protocol.d.ts} +2 -2
  42. package/dist/protocol.d.ts.map +1 -0
  43. package/dist/protocol.js +6 -0
  44. package/dist/protocol.js.map +1 -0
  45. package/dist/providers/adapter.d.ts +137 -0
  46. package/dist/providers/adapter.d.ts.map +1 -0
  47. package/dist/providers/adapter.js +163 -0
  48. package/dist/providers/adapter.js.map +1 -0
  49. package/dist/providers/catalog-data.d.ts +128 -0
  50. package/dist/providers/catalog-data.d.ts.map +1 -0
  51. package/dist/providers/catalog-data.js +397 -0
  52. package/dist/providers/catalog-data.js.map +1 -0
  53. package/dist/providers/catalog.d.ts +63 -0
  54. package/dist/providers/catalog.d.ts.map +1 -0
  55. package/dist/providers/catalog.js +394 -0
  56. package/dist/providers/catalog.js.map +1 -0
  57. package/dist/providers/fake.d.ts +46 -0
  58. package/dist/providers/fake.d.ts.map +1 -0
  59. package/dist/providers/fake.js +234 -0
  60. package/dist/providers/fake.js.map +1 -0
  61. package/dist/providers/openai-compatible.d.ts +65 -0
  62. package/dist/providers/openai-compatible.d.ts.map +1 -0
  63. package/dist/providers/openai-compatible.js +434 -0
  64. package/dist/providers/openai-compatible.js.map +1 -0
  65. package/dist/proxy/responses-mapper.d.ts +366 -0
  66. package/dist/proxy/responses-mapper.d.ts.map +1 -0
  67. package/dist/proxy/responses-mapper.js +517 -0
  68. package/dist/proxy/responses-mapper.js.map +1 -0
  69. package/dist/proxy/responses-server.d.ts +29 -0
  70. package/dist/proxy/responses-server.d.ts.map +1 -0
  71. package/dist/proxy/responses-server.js +360 -0
  72. package/dist/proxy/responses-server.js.map +1 -0
  73. package/dist/router.d.ts +18 -0
  74. package/dist/router.d.ts.map +1 -0
  75. package/dist/router.js +296 -0
  76. package/dist/router.js.map +1 -0
  77. package/dist/schemas.d.ts +560 -0
  78. package/dist/schemas.d.ts.map +1 -0
  79. package/{src/schemas.ts → dist/schemas.js} +83 -103
  80. package/dist/schemas.js.map +1 -0
  81. package/dist/secrets.d.ts +112 -0
  82. package/dist/secrets.d.ts.map +1 -0
  83. package/dist/secrets.js +326 -0
  84. package/dist/secrets.js.map +1 -0
  85. package/dist/types.d.ts +117 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +6 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/usage-file.d.ts +59 -0
  90. package/dist/usage-file.d.ts.map +1 -0
  91. package/dist/usage-file.js +316 -0
  92. package/dist/usage-file.js.map +1 -0
  93. package/dist/usage.d.ts +235 -0
  94. package/dist/usage.d.ts.map +1 -0
  95. package/dist/usage.js +517 -0
  96. package/dist/usage.js.map +1 -0
  97. package/package.json +7 -2
  98. package/src/audit.test.ts +0 -302
  99. package/src/audit.ts +0 -553
  100. package/src/auth/credential-chain.test.ts +0 -326
  101. package/src/auth/credential-chain.ts +0 -235
  102. package/src/auth/index.ts +0 -45
  103. package/src/auth/keychain.test.ts +0 -265
  104. package/src/auth/keychain.ts +0 -168
  105. package/src/auth/oauth-pkce.test.ts +0 -329
  106. package/src/auth/oauth-pkce.ts +0 -571
  107. package/src/auth/oauth-server.test.ts +0 -83
  108. package/src/auth/oauth-server.ts +0 -296
  109. package/src/config.test.ts +0 -479
  110. package/src/config.ts +0 -505
  111. package/src/delegation.test.ts +0 -368
  112. package/src/delegation.ts +0 -605
  113. package/src/index.ts +0 -280
  114. package/src/policy.test.ts +0 -634
  115. package/src/policy.ts +0 -420
  116. package/src/providers/adapter.test.ts +0 -293
  117. package/src/providers/adapter.ts +0 -328
  118. package/src/providers/catalog-data.test.ts +0 -258
  119. package/src/providers/catalog-data.ts +0 -498
  120. package/src/providers/catalog.test.ts +0 -84
  121. package/src/providers/catalog.ts +0 -483
  122. package/src/providers/fake.ts +0 -312
  123. package/src/providers/openai-compatible.test.ts +0 -366
  124. package/src/providers/openai-compatible.ts +0 -554
  125. package/src/proxy/responses-mapper.test.ts +0 -290
  126. package/src/proxy/responses-mapper.ts +0 -736
  127. package/src/proxy/responses-server.test.ts +0 -322
  128. package/src/proxy/responses-server.ts +0 -469
  129. package/src/router.test.ts +0 -699
  130. package/src/router.ts +0 -352
  131. package/src/schemas.test.ts +0 -291
  132. package/src/secrets.test.ts +0 -271
  133. package/src/secrets.ts +0 -461
  134. package/src/types.ts +0 -173
  135. package/src/usage-file.test.ts +0 -243
  136. package/src/usage-file.ts +0 -435
  137. package/src/usage.test.ts +0 -335
  138. package/src/usage.ts +0 -859
  139. package/tsconfig.json +0 -9
@@ -1,498 +0,0 @@
1
- /**
2
- * Static built-in provider + model family metadata for offline routing.
3
- *
4
- * Metadata only: no network calls, no credential reads, no secrets. API-key
5
- * presence is checked elsewhere by the adapter (checkAuth). Pricing hints are
6
- * approximate public values in USD per 1M tokens; re-verify against vendor
7
- * pricing before any billing-sensitive use. Unknown fields are left undefined;
8
- * nothing here is invented.
9
- *
10
- * Subscription-first invariant: this catalog never auto-promotes an api-key
11
- * candidate over a subscription candidate for the same logical family. The
12
- * supportsSubscriptionRouting flag only documents which families expose an
13
- * official subscription surface (official login, token helper, OAuth, or MCP
14
- * login); subscription routing itself is handled by the harness adapters, not
15
- * by these OpenAI-compatible provider entries.
16
- */
17
-
18
- import { z } from "zod";
19
- import type { AuthScope, AuthTier } from "../types.js";
20
- import type { LogicalModelFamily } from "./catalog.js";
21
- import type { OpenAiCompatibleFlavor } from "./openai-compatible.js";
22
-
23
- /** Public pricing hint in USD per 1M tokens. Undefined when undocumented. */
24
- export interface PricingHint {
25
- /** USD per 1M input tokens, when publicly documented. */
26
- inputPer1M?: number;
27
- /** USD per 1M output tokens, when publicly documented. */
28
- outputPer1M?: number;
29
- }
30
-
31
- /**
32
- * Known model family metadata. Optional fields are unknown, never invented.
33
- * Local runtime models omit token/capability fields because they are
34
- * discovered from the running server.
35
- */
36
- export interface BuiltinModelFamily {
37
- modelId: string;
38
- displayName?: string;
39
- /** Logical family used to group candidates across auth tiers. */
40
- family: LogicalModelFamily;
41
- contextWindowTokens?: number;
42
- maxOutputTokens?: number;
43
- supportsTools?: boolean;
44
- supportsStreaming?: boolean;
45
- supportsJson?: boolean;
46
- supportsImages?: boolean;
47
- /** Public pricing hint; undefined when undocumented. */
48
- pricing?: PricingHint;
49
- }
50
-
51
- /**
52
- * Static provider entry describing how to build its OpenAI-compatible adapter
53
- * and which model families it exposes. No secrets.
54
- */
55
- export interface BuiltinProviderEntry {
56
- providerId: string;
57
- displayName: string;
58
- /** Default base URL for the OpenAI-compatible adapter. */
59
- defaultBaseUrl: string;
60
- /** Adapter flavor used to construct the OpenAI-compatible client. */
61
- flavor: OpenAiCompatibleFlavor;
62
- /** Default auth tier for candidates built from this provider. */
63
- defaultAuthTier: AuthTier;
64
- /** Default auth scope for candidates built from this provider. */
65
- defaultAuthScope: AuthScope;
66
- /**
67
- * True only when an official subscription surface exists (official login,
68
- * token helper, OAuth, or MCP login). Informational only; never promotes a
69
- * tier. False for pure api-key and local providers.
70
- */
71
- supportsSubscriptionRouting: boolean;
72
- /** ASCII-only note on the official subscription surface, if any. */
73
- subscriptionSurfaceNote?: string;
74
- knownModels: readonly BuiltinModelFamily[];
75
- }
76
-
77
- /**
78
- * Per-provider catalog config mirroring the existing config providers map.
79
- * Users enable/disable providers and override base URLs in TOML; this is not a
80
- * new config file format.
81
- */
82
- export interface ProviderCatalogConfig {
83
- /** Whether the provider is enabled. Defaults to true. */
84
- enabled?: boolean;
85
- /** Override the built-in default base URL. */
86
- baseUrl?: string;
87
- }
88
-
89
- /** Map of providerId to provider catalog config (mirrors config providers map). */
90
- export type BuiltinCatalogConfig = Record<string, ProviderCatalogConfig>;
91
-
92
- // ---------------------------------------------------------------------------
93
- // Runtime validation (Zod). Mirrors the config.ts pattern: a shape object plus
94
- // a strict object schema so unknown keys are rejected. The TS interfaces above
95
- // remain the source of truth for the catalog-data public types.
96
- // ---------------------------------------------------------------------------
97
-
98
- const ProviderCatalogConfigShape = {
99
- enabled: z.boolean().optional(),
100
- baseUrl: z.string().optional(),
101
- } satisfies z.ZodRawShape;
102
-
103
- export const ProviderCatalogConfigSchema = z
104
- .object(ProviderCatalogConfigShape)
105
- .strict();
106
-
107
- /** Record of providerId -> ProviderCatalogConfig. */
108
- export const BuiltinCatalogConfigSchema = z.record(
109
- z.string().min(1),
110
- ProviderCatalogConfigSchema,
111
- );
112
-
113
- /**
114
- * Parse and validate a BuiltinCatalogConfig, throwing on invalid input.
115
- * Lets callers validate provider catalog overrides at runtime.
116
- */
117
- export function parseBuiltinCatalogConfig(
118
- input: unknown,
119
- ): BuiltinCatalogConfig {
120
- return BuiltinCatalogConfigSchema.parse(input);
121
- }
122
-
123
- // TODO(catalog-config): wire base_url + enable/disable overrides declared in
124
- // config.toml `[providers]` through BuiltinCatalogConfigSchema. config.ts only
125
- // validates `enabled` today; integrating `baseUrl` there would change the
126
- // config contract, so it is tracked as a follow-up (code-review finding 4).
127
-
128
- /**
129
- * Shared note for local runtime providers: model list and capabilities are
130
- * discovered from the running server, not from this static metadata.
131
- */
132
- const LOCAL_RUNTIME_SUBSCRIPTION_NOTE =
133
- "Local runtime; model list and capabilities are discovered from the running server.";
134
-
135
- /**
136
- * Built-in providers. Subscription-backed first-party login happens through the
137
- * harness adapters (claude/codex/gemini/grok), not these api endpoints, so each
138
- * api/local entry reports supportsSubscriptionRouting=false with a note.
139
- */
140
- export const BUILTIN_PROVIDERS: readonly BuiltinProviderEntry[] = [
141
- {
142
- providerId: "openai",
143
- displayName: "OpenAI API",
144
- defaultBaseUrl: "https://api.openai.com/v1",
145
- flavor: "openai",
146
- defaultAuthTier: "api-key",
147
- defaultAuthScope: "first-party",
148
- supportsSubscriptionRouting: false,
149
- subscriptionSurfaceNote:
150
- "Subscription routing for first-party OpenAI models is handled by the codex harness adapter, not this api endpoint.",
151
- knownModels: [
152
- {
153
- modelId: "gpt-4.1",
154
- displayName: "GPT-4.1",
155
- family: "frontier-coder",
156
- contextWindowTokens: 1_047_576,
157
- maxOutputTokens: 32_768,
158
- supportsTools: true,
159
- supportsStreaming: true,
160
- supportsJson: true,
161
- supportsImages: true,
162
- pricing: { inputPer1M: 2.0, outputPer1M: 8.0 },
163
- },
164
- {
165
- modelId: "gpt-4.1-mini",
166
- displayName: "GPT-4.1 mini",
167
- family: "fast-coder",
168
- contextWindowTokens: 1_047_576,
169
- maxOutputTokens: 32_768,
170
- supportsTools: true,
171
- supportsStreaming: true,
172
- supportsJson: true,
173
- supportsImages: true,
174
- pricing: { inputPer1M: 0.4, outputPer1M: 1.6 },
175
- },
176
- {
177
- modelId: "o3",
178
- displayName: "o3",
179
- family: "frontier-coder",
180
- contextWindowTokens: 200_000,
181
- maxOutputTokens: 100_000,
182
- supportsTools: true,
183
- supportsStreaming: true,
184
- supportsJson: true,
185
- supportsImages: true,
186
- pricing: { inputPer1M: 2.0, outputPer1M: 8.0 },
187
- },
188
- ],
189
- },
190
- {
191
- providerId: "anthropic",
192
- displayName: "Anthropic API (OpenAI-compatible)",
193
- defaultBaseUrl: "https://api.anthropic.com/v1/openai",
194
- flavor: "openai",
195
- defaultAuthTier: "api-key",
196
- defaultAuthScope: "first-party",
197
- supportsSubscriptionRouting: false,
198
- subscriptionSurfaceNote:
199
- "Subscription routing for first-party Anthropic models is handled by the claude harness adapter, not this api endpoint.",
200
- knownModels: [
201
- {
202
- modelId: "claude-sonnet-4",
203
- displayName: "Claude Sonnet 4",
204
- family: "frontier-coder",
205
- contextWindowTokens: 200_000,
206
- maxOutputTokens: 32_000,
207
- supportsTools: true,
208
- supportsStreaming: true,
209
- supportsJson: true,
210
- supportsImages: true,
211
- pricing: { inputPer1M: 3.0, outputPer1M: 15.0 },
212
- },
213
- {
214
- modelId: "claude-opus-4",
215
- displayName: "Claude Opus 4",
216
- family: "frontier-coder",
217
- contextWindowTokens: 200_000,
218
- maxOutputTokens: 32_000,
219
- supportsTools: true,
220
- supportsStreaming: true,
221
- supportsJson: true,
222
- supportsImages: true,
223
- pricing: { inputPer1M: 15.0, outputPer1M: 75.0 },
224
- },
225
- {
226
- modelId: "claude-haiku-4",
227
- displayName: "Claude Haiku 4",
228
- family: "fast-coder",
229
- contextWindowTokens: 200_000,
230
- maxOutputTokens: 16_000,
231
- supportsTools: true,
232
- supportsStreaming: true,
233
- supportsJson: true,
234
- supportsImages: true,
235
- },
236
- ],
237
- },
238
- {
239
- providerId: "google",
240
- displayName: "Google Gemini API (OpenAI-compatible)",
241
- defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
242
- flavor: "openai",
243
- defaultAuthTier: "api-key",
244
- defaultAuthScope: "first-party",
245
- supportsSubscriptionRouting: false,
246
- subscriptionSurfaceNote:
247
- "Subscription routing for first-party Google models is handled by the gemini harness adapter, not this api endpoint.",
248
- knownModels: [
249
- {
250
- modelId: "gemini-2.5-pro",
251
- displayName: "Gemini 2.5 Pro",
252
- family: "frontier-coder",
253
- contextWindowTokens: 1_048_576,
254
- maxOutputTokens: 65_536,
255
- supportsTools: true,
256
- supportsStreaming: true,
257
- supportsJson: true,
258
- supportsImages: true,
259
- pricing: { inputPer1M: 1.25, outputPer1M: 10.0 },
260
- },
261
- {
262
- modelId: "gemini-2.5-flash",
263
- displayName: "Gemini 2.5 Flash",
264
- family: "fast-coder",
265
- contextWindowTokens: 1_048_576,
266
- maxOutputTokens: 65_536,
267
- supportsTools: true,
268
- supportsStreaming: true,
269
- supportsJson: true,
270
- supportsImages: true,
271
- pricing: { inputPer1M: 0.3, outputPer1M: 2.5 },
272
- },
273
- ],
274
- },
275
- {
276
- providerId: "xai",
277
- displayName: "xAI Grok API",
278
- defaultBaseUrl: "https://api.x.ai/v1",
279
- flavor: "xai",
280
- defaultAuthTier: "api-key",
281
- defaultAuthScope: "first-party",
282
- supportsSubscriptionRouting: false,
283
- subscriptionSurfaceNote:
284
- "Subscription routing for first-party xAI models is handled by the grok harness adapter, not this api endpoint.",
285
- knownModels: [
286
- {
287
- modelId: "grok-4",
288
- displayName: "Grok 4",
289
- family: "frontier-coder",
290
- contextWindowTokens: 256_000,
291
- maxOutputTokens: 32_768,
292
- supportsTools: true,
293
- supportsStreaming: true,
294
- supportsJson: true,
295
- supportsImages: true,
296
- pricing: { inputPer1M: 3.0, outputPer1M: 15.0 },
297
- },
298
- {
299
- modelId: "grok-code-fast-1",
300
- displayName: "Grok Code Fast",
301
- family: "fast-coder",
302
- contextWindowTokens: 256_000,
303
- maxOutputTokens: 16_384,
304
- supportsTools: true,
305
- supportsStreaming: true,
306
- supportsJson: true,
307
- supportsImages: false,
308
- },
309
- ],
310
- },
311
- {
312
- providerId: "openrouter",
313
- displayName: "OpenRouter",
314
- defaultBaseUrl: "https://openrouter.ai/api/v1",
315
- flavor: "openrouter",
316
- defaultAuthTier: "api-key",
317
- defaultAuthScope: "third-party",
318
- supportsSubscriptionRouting: false,
319
- knownModels: [
320
- {
321
- modelId: "anthropic/claude-sonnet-4",
322
- family: "frontier-coder",
323
- supportsTools: true,
324
- supportsStreaming: true,
325
- supportsJson: true,
326
- supportsImages: true,
327
- },
328
- {
329
- modelId: "google/gemini-2.5-flash",
330
- family: "fast-coder",
331
- supportsTools: true,
332
- supportsStreaming: true,
333
- supportsJson: true,
334
- supportsImages: true,
335
- },
336
- ],
337
- },
338
- {
339
- providerId: "zai",
340
- displayName: "Z.ai GLM API",
341
- defaultBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
342
- flavor: "openai",
343
- defaultAuthTier: "api-key",
344
- defaultAuthScope: "first-party",
345
- supportsSubscriptionRouting: false,
346
- knownModels: [
347
- {
348
- modelId: "glm-4.6",
349
- displayName: "GLM-4.6",
350
- family: "fast-coder",
351
- contextWindowTokens: 200_000,
352
- maxOutputTokens: 16_000,
353
- supportsTools: true,
354
- supportsStreaming: true,
355
- supportsJson: true,
356
- supportsImages: false,
357
- },
358
- {
359
- modelId: "glm-4.5",
360
- displayName: "GLM-4.5",
361
- family: "fast-coder",
362
- supportsTools: true,
363
- supportsStreaming: true,
364
- supportsJson: true,
365
- supportsImages: false,
366
- },
367
- ],
368
- },
369
- {
370
- providerId: "moonshot",
371
- displayName: "Moonshot Kimi API",
372
- defaultBaseUrl: "https://api.moonshot.cn/v1",
373
- flavor: "openai",
374
- defaultAuthTier: "api-key",
375
- defaultAuthScope: "first-party",
376
- supportsSubscriptionRouting: false,
377
- knownModels: [
378
- {
379
- modelId: "kimi-k2",
380
- displayName: "Kimi K2",
381
- family: "fast-coder",
382
- contextWindowTokens: 200_000,
383
- maxOutputTokens: 8_192,
384
- supportsTools: true,
385
- supportsStreaming: true,
386
- supportsJson: true,
387
- supportsImages: false,
388
- },
389
- ],
390
- },
391
- {
392
- providerId: "ollama",
393
- displayName: "Ollama (local)",
394
- defaultBaseUrl: "http://localhost:11434/v1",
395
- flavor: "ollama",
396
- defaultAuthTier: "local",
397
- defaultAuthScope: "first-party",
398
- supportsSubscriptionRouting: false,
399
- subscriptionSurfaceNote: LOCAL_RUNTIME_SUBSCRIPTION_NOTE,
400
- knownModels: [
401
- {
402
- modelId: "qwen2.5-coder:32b",
403
- family: "local-coder",
404
- supportsTools: true,
405
- supportsStreaming: true,
406
- supportsJson: true,
407
- supportsImages: false,
408
- },
409
- {
410
- modelId: "llava:latest",
411
- family: "vision-helper",
412
- supportsTools: false,
413
- supportsStreaming: true,
414
- supportsJson: false,
415
- supportsImages: true,
416
- },
417
- ],
418
- },
419
- {
420
- providerId: "lm-studio",
421
- displayName: "LM Studio (local)",
422
- defaultBaseUrl: "http://localhost:1234/v1",
423
- flavor: "openai",
424
- defaultAuthTier: "local",
425
- defaultAuthScope: "first-party",
426
- supportsSubscriptionRouting: false,
427
- subscriptionSurfaceNote: LOCAL_RUNTIME_SUBSCRIPTION_NOTE,
428
- knownModels: [
429
- {
430
- modelId: "local-coder",
431
- family: "local-coder",
432
- supportsTools: true,
433
- supportsStreaming: true,
434
- supportsJson: true,
435
- supportsImages: false,
436
- },
437
- ],
438
- },
439
- {
440
- providerId: "custom",
441
- displayName: "Custom OpenAI-compatible endpoint",
442
- defaultBaseUrl: "",
443
- flavor: "openai",
444
- defaultAuthTier: "local",
445
- defaultAuthScope: "unknown",
446
- supportsSubscriptionRouting: false,
447
- subscriptionSurfaceNote:
448
- "User-defined OpenAI-compatible gateway. base URL and model ids must be supplied via provider config.",
449
- knownModels: [
450
- {
451
- modelId: "custom-model",
452
- family: "local-coder",
453
- supportsTools: true,
454
- supportsStreaming: true,
455
- supportsJson: true,
456
- supportsImages: false,
457
- },
458
- ],
459
- },
460
- ];
461
-
462
- /** Provider ids covered by the built-in catalog. */
463
- export const BUILTIN_PROVIDER_IDS: readonly string[] = BUILTIN_PROVIDERS.map(
464
- (provider) => provider.providerId,
465
- );
466
-
467
- /** Look up a built-in provider by id. */
468
- export function getBuiltinProvider(
469
- providerId: string,
470
- ): BuiltinProviderEntry | undefined {
471
- return BUILTIN_PROVIDERS.find((p) => p.providerId === providerId);
472
- }
473
-
474
- /**
475
- * Resolve built-in providers applying enable/disable and base URL overrides.
476
- * Returns copies so callers cannot mutate the static source. Disabled providers
477
- * are excluded. This never adds or removes a subscription tier; it only filters
478
- * api-key/local provider metadata, preserving the subscription-first invariant.
479
- */
480
- export function selectBuiltinProviders(
481
- config: BuiltinCatalogConfig = {},
482
- ): BuiltinProviderEntry[] {
483
- return BUILTIN_PROVIDERS.filter((provider) => {
484
- const override = config[provider.providerId];
485
- return override?.enabled ?? true;
486
- }).map((provider) => {
487
- const override = config[provider.providerId];
488
- const overrideUrl = override?.baseUrl?.trim();
489
- return {
490
- ...provider,
491
- defaultBaseUrl:
492
- overrideUrl && overrideUrl.length > 0
493
- ? overrideUrl
494
- : provider.defaultBaseUrl,
495
- knownModels: provider.knownModels.map((model) => ({ ...model })),
496
- };
497
- });
498
- }
@@ -1,84 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { AuthTier } from "../types.js";
3
- import {
4
- MULTI_TIER_FAMILIES,
5
- STATIC_CATALOG,
6
- catalogToCandidates,
7
- familyAuthTiers,
8
- loadStaticCatalog,
9
- type LogicalModelFamily,
10
- } from "./catalog.js";
11
-
12
- describe("static provider catalog", () => {
13
- it("loads a non-empty static catalog offline", () => {
14
- const entries = loadStaticCatalog();
15
- expect(entries.length).toBeGreaterThan(0);
16
- expect(entries).toEqual(
17
- expect.arrayContaining(STATIC_CATALOG.map((e) => ({ ...e }))),
18
- );
19
- });
20
-
21
- it("returns copies so callers cannot mutate the static source", () => {
22
- const [first] = loadStaticCatalog();
23
- first.modelId = "mutated";
24
- expect(STATIC_CATALOG[0]?.modelId).not.toBe("mutated");
25
- });
26
-
27
- it("exposes subscription, local, and api-key candidates for key families", () => {
28
- const required: AuthTier[] = ["subscription", "local", "api-key"];
29
- for (const family of MULTI_TIER_FAMILIES) {
30
- const tiers = familyAuthTiers(family);
31
- for (const tier of required) {
32
- expect(tiers.has(tier), `${family} missing tier ${tier}`).toBe(true);
33
- }
34
- }
35
- });
36
-
37
- it("catalog candidates carry auth scope and subscription status for sub tiers", () => {
38
- const candidates = catalogToCandidates();
39
- expect(candidates.every((c) => c.authScope)).toBe(true);
40
-
41
- const subs = candidates.filter((c) => c.authTier === "subscription");
42
- expect(subs.length).toBeGreaterThan(0);
43
- for (const s of subs) {
44
- expect(s.subscriptionStatus).toBeDefined();
45
- expect(["first-party", "third-party", "unknown"]).toContain(s.authScope);
46
- }
47
- });
48
-
49
- it("filters by family and auth tier", () => {
50
- const family: LogicalModelFamily = "frontier-coder";
51
- const onlySub = loadStaticCatalog({
52
- families: [family],
53
- authTiers: ["subscription"],
54
- });
55
- expect(onlySub.length).toBeGreaterThan(0);
56
- expect(
57
- onlySub.every(
58
- (e) => e.family === family && e.authTier === "subscription",
59
- ),
60
- ).toBe(true);
61
- });
62
-
63
- it("can exclude unhealthy entries", () => {
64
- const healthy = loadStaticCatalog({ includeUnhealthy: false });
65
- expect(healthy.every((e) => e.health === "healthy")).toBe(true);
66
- expect(STATIC_CATALOG.some((e) => e.health !== "healthy")).toBe(true);
67
- });
68
-
69
- it("maps catalog entries into ProviderCandidate rows usable by routing", () => {
70
- const candidates = catalogToCandidates(
71
- loadStaticCatalog({ families: ["fast-coder"], includeUnhealthy: false }),
72
- );
73
- expect(candidates.length).toBeGreaterThanOrEqual(3);
74
- const tiers = new Set(candidates.map((c) => c.authTier));
75
- expect(tiers.has("subscription")).toBe(true);
76
- expect(tiers.has("local")).toBe(true);
77
- expect(tiers.has("api-key")).toBe(true);
78
- for (const c of candidates) {
79
- expect(c.providerId).toBeTruthy();
80
- expect(c.modelId).toBeTruthy();
81
- expect(typeof c.supportsTools).toBe("boolean");
82
- }
83
- });
84
- });