@velum-labs/routekit-gateway 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +28 -0
  3. package/dist/acp-agent.d.ts +38 -0
  4. package/dist/acp-agent.js +142 -0
  5. package/dist/acp-registry.d.ts +36 -0
  6. package/dist/acp-registry.js +85 -0
  7. package/dist/adapters/anthropic.d.ts +131 -0
  8. package/dist/adapters/anthropic.js +1195 -0
  9. package/dist/adapters/chat.d.ts +14 -0
  10. package/dist/adapters/chat.js +34 -0
  11. package/dist/adapters/cursor.d.ts +34 -0
  12. package/dist/adapters/cursor.js +305 -0
  13. package/dist/adapters/dropped.d.ts +10 -0
  14. package/dist/adapters/dropped.js +24 -0
  15. package/dist/adapters/openai-chat-wire.d.ts +93 -0
  16. package/dist/adapters/openai-chat-wire.js +143 -0
  17. package/dist/adapters/responses-stream.d.ts +7 -0
  18. package/dist/adapters/responses-stream.js +597 -0
  19. package/dist/adapters/responses.d.ts +174 -0
  20. package/dist/adapters/responses.js +778 -0
  21. package/dist/adapters/server-tool-loop.d.ts +94 -0
  22. package/dist/adapters/server-tool-loop.js +477 -0
  23. package/dist/adapters/upstream-error.d.ts +14 -0
  24. package/dist/adapters/upstream-error.js +25 -0
  25. package/dist/adapters/validate.d.ts +27 -0
  26. package/dist/adapters/validate.js +180 -0
  27. package/dist/adapters/web-search.d.ts +46 -0
  28. package/dist/adapters/web-search.js +151 -0
  29. package/dist/auth.d.ts +10 -0
  30. package/dist/auth.js +28 -0
  31. package/dist/backend.d.ts +151 -0
  32. package/dist/backend.js +143 -0
  33. package/dist/capacity-pool.d.ts +31 -0
  34. package/dist/capacity-pool.js +99 -0
  35. package/dist/cost.d.ts +49 -0
  36. package/dist/cost.js +112 -0
  37. package/dist/endpoint-health.d.ts +54 -0
  38. package/dist/endpoint-health.js +123 -0
  39. package/dist/index.d.ts +40 -0
  40. package/dist/index.js +23 -0
  41. package/dist/provenance.d.ts +31 -0
  42. package/dist/provenance.js +191 -0
  43. package/dist/provider-backends.d.ts +40 -0
  44. package/dist/provider-backends.js +1050 -0
  45. package/dist/provider-source.d.ts +40 -0
  46. package/dist/provider-source.js +293 -0
  47. package/dist/router.d.ts +168 -0
  48. package/dist/router.js +474 -0
  49. package/dist/server.d.ts +67 -0
  50. package/dist/server.js +930 -0
  51. package/dist/sse/chat-assembler.d.ts +45 -0
  52. package/dist/sse/chat-assembler.js +190 -0
  53. package/dist/sse/parse.d.ts +50 -0
  54. package/dist/sse/parse.js +149 -0
  55. package/dist/sse-wire.d.ts +10 -0
  56. package/dist/sse-wire.js +31 -0
  57. package/dist/switching-proxy.d.ts +15 -0
  58. package/dist/switching-proxy.js +232 -0
  59. package/dist/test/acp-agent.test.d.ts +1 -0
  60. package/dist/test/acp-agent.test.js +66 -0
  61. package/dist/test/acp-registry.test.d.ts +1 -0
  62. package/dist/test/acp-registry.test.js +70 -0
  63. package/dist/test/anthropic.test.d.ts +1 -0
  64. package/dist/test/anthropic.test.js +793 -0
  65. package/dist/test/auth.test.d.ts +1 -0
  66. package/dist/test/auth.test.js +25 -0
  67. package/dist/test/boundary.test.d.ts +1 -0
  68. package/dist/test/boundary.test.js +32 -0
  69. package/dist/test/chat.test.d.ts +1 -0
  70. package/dist/test/chat.test.js +418 -0
  71. package/dist/test/cost.test.d.ts +1 -0
  72. package/dist/test/cost.test.js +60 -0
  73. package/dist/test/cursor.test.d.ts +1 -0
  74. package/dist/test/cursor.test.js +100 -0
  75. package/dist/test/drain.test.d.ts +1 -0
  76. package/dist/test/drain.test.js +116 -0
  77. package/dist/test/dropped.test.d.ts +1 -0
  78. package/dist/test/dropped.test.js +80 -0
  79. package/dist/test/endpoint-health.test.d.ts +1 -0
  80. package/dist/test/endpoint-health.test.js +73 -0
  81. package/dist/test/provenance.test.d.ts +1 -0
  82. package/dist/test/provenance.test.js +176 -0
  83. package/dist/test/provider-backends.test.d.ts +1 -0
  84. package/dist/test/provider-backends.test.js +699 -0
  85. package/dist/test/responses.test.d.ts +1 -0
  86. package/dist/test/responses.test.js +813 -0
  87. package/dist/test/routed-backend.test.d.ts +1 -0
  88. package/dist/test/routed-backend.test.js +39 -0
  89. package/dist/test/router.test.d.ts +1 -0
  90. package/dist/test/router.test.js +297 -0
  91. package/dist/test/server-resilience.test.d.ts +1 -0
  92. package/dist/test/server-resilience.test.js +169 -0
  93. package/dist/test/sse-codec.test.d.ts +1 -0
  94. package/dist/test/sse-codec.test.js +186 -0
  95. package/dist/test/web-search-loop.test.d.ts +1 -0
  96. package/dist/test/web-search-loop.test.js +469 -0
  97. package/dist/test/wire-validation.test.d.ts +1 -0
  98. package/dist/test/wire-validation.test.js +140 -0
  99. package/package.json +48 -0
@@ -0,0 +1,40 @@
1
+ import { type ProviderDiscoveryResponseShape } from "@velum-labs/routekit-registry";
2
+ import type { ModelReasoningCapabilities } from "@velum-labs/routekit-contracts";
3
+ import type { BackendRequestOptions } from "./backend.js";
4
+ export declare const API_PROVIDER_IDS: readonly ["openai", "anthropic", "google", "openrouter", "cliproxy"];
5
+ export declare const SUBSCRIPTION_PROVIDER_IDS: readonly ["codex", "claude-code"];
6
+ export declare const PROVIDER_IDS: readonly ["openai", "anthropic", "google", "openrouter", "cliproxy", "codex", "claude-code"];
7
+ export type ApiProviderId = (typeof API_PROVIDER_IDS)[number];
8
+ export type SubscriptionProviderId = (typeof SUBSCRIPTION_PROVIDER_IDS)[number];
9
+ export type ProviderId = (typeof PROVIDER_IDS)[number];
10
+ export type DiscoveredModel = {
11
+ id: string;
12
+ capabilities?: Readonly<Record<string, string>>;
13
+ reasoning?: ModelReasoningCapabilities;
14
+ };
15
+ export type ProviderSource = {
16
+ readonly sourceId: ProviderId;
17
+ discoverModels(signal?: AbortSignal): Promise<readonly DiscoveredModel[]>;
18
+ chat(body: unknown, signal?: AbortSignal, options?: BackendRequestOptions): Promise<Response>;
19
+ embeddings(body: unknown, signal?: AbortSignal, options?: BackendRequestOptions): Promise<Response>;
20
+ capabilities?(model: string): Readonly<Record<string, string>>;
21
+ reasoningCapabilities?(model: string): ModelReasoningCapabilities | undefined;
22
+ close?(): Promise<void> | void;
23
+ };
24
+ export type ProviderSourceTransport = (url: string, init: RequestInit) => Promise<Response>;
25
+ export declare function parseReasoningCapabilities(entry: unknown, provider?: ProviderId, refreshedAt?: string): ModelReasoningCapabilities | undefined;
26
+ export declare function parseDiscoveredModels(shape: ProviderDiscoveryResponseShape, payload: unknown, provider?: ProviderId): DiscoveredModel[];
27
+ export type ApiProviderSourceOptions = {
28
+ provider: ApiProviderId;
29
+ env?: Readonly<Record<string, string | undefined>>;
30
+ transport?: ProviderSourceTransport;
31
+ };
32
+ export declare class ApiProviderSource implements ProviderSource {
33
+ #private;
34
+ readonly sourceId: ApiProviderId;
35
+ constructor(options: ApiProviderSourceOptions);
36
+ discoverModels(signal?: AbortSignal): Promise<readonly DiscoveredModel[]>;
37
+ chat(body: unknown, signal?: AbortSignal, options?: BackendRequestOptions): Promise<Response>;
38
+ embeddings(body: unknown, signal?: AbortSignal, options?: BackendRequestOptions): Promise<Response>;
39
+ close(): Promise<void> | void;
40
+ }
@@ -0,0 +1,293 @@
1
+ import { PROVIDERS } from "@velum-labs/routekit-registry";
2
+ import { OpenAiBackend } from "./backend.js";
3
+ import { AnthropicBackend, CodexResponsesBackend, GoogleGenAiBackend } from "./provider-backends.js";
4
+ export const API_PROVIDER_IDS = [
5
+ "openai",
6
+ "anthropic",
7
+ "google",
8
+ "openrouter",
9
+ "cliproxy"
10
+ ];
11
+ export const SUBSCRIPTION_PROVIDER_IDS = ["codex", "claude-code"];
12
+ export const PROVIDER_IDS = [...API_PROVIDER_IDS, ...SUBSCRIPTION_PROVIDER_IDS];
13
+ function isRecord(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ }
16
+ function effortOptions(value) {
17
+ if (!Array.isArray(value))
18
+ return [];
19
+ const seen = new Set();
20
+ return value.flatMap((candidate) => {
21
+ const record = isRecord(candidate) ? candidate : undefined;
22
+ const id = typeof candidate === "string"
23
+ ? candidate
24
+ : typeof record?.effort === "string"
25
+ ? record.effort
26
+ : typeof record?.id === "string"
27
+ ? record.id
28
+ : undefined;
29
+ if (id === undefined || id.length === 0 || seen.has(id))
30
+ return [];
31
+ seen.add(id);
32
+ return [
33
+ {
34
+ id,
35
+ ...(typeof record?.label === "string" ? { label: record.label } : {}),
36
+ ...(typeof record?.description === "string"
37
+ ? { description: record.description }
38
+ : {}),
39
+ ...(Array.isArray(record?.aliases)
40
+ ? {
41
+ aliases: record.aliases.filter((alias) => typeof alias === "string" && alias.length > 0)
42
+ }
43
+ : {})
44
+ }
45
+ ];
46
+ });
47
+ }
48
+ function reasoningWireShape(provider) {
49
+ switch (provider) {
50
+ case "codex":
51
+ return "openai-responses";
52
+ case "anthropic":
53
+ case "claude-code":
54
+ return "anthropic";
55
+ case "google":
56
+ return "google";
57
+ case "openrouter":
58
+ return "openrouter";
59
+ case "openai":
60
+ case "cliproxy":
61
+ return "openai-chat";
62
+ case undefined:
63
+ return undefined;
64
+ }
65
+ }
66
+ export function parseReasoningCapabilities(entry, provider, refreshedAt = new Date().toISOString()) {
67
+ if (!isRecord(entry))
68
+ return undefined;
69
+ const capabilities = isRecord(entry.capabilities) ? entry.capabilities : undefined;
70
+ const nested = (isRecord(entry.reasoning) ? entry.reasoning : undefined) ??
71
+ (isRecord(capabilities?.reasoning) ? capabilities.reasoning : undefined);
72
+ const efforts = effortOptions(entry.supported_reasoning_levels ??
73
+ entry.supported_reasoning_efforts ??
74
+ nested?.efforts ??
75
+ nested?.supported_efforts);
76
+ const supportedParameters = Array.isArray(entry.supported_parameters)
77
+ ? entry.supported_parameters.filter((parameter) => typeof parameter === "string")
78
+ : [];
79
+ const explicitStatus = nested?.status ??
80
+ capabilities?.reasoning_controls ??
81
+ entry.reasoning_controls;
82
+ const supported = efforts.length > 0 ||
83
+ supportedParameters.includes("reasoning") ||
84
+ supportedParameters.includes("reasoning_effort") ||
85
+ explicitStatus === "supported";
86
+ const unsupported = explicitStatus === "unsupported" || nested?.supported === false;
87
+ if (!supported && !unsupported && nested === undefined)
88
+ return undefined;
89
+ const defaultEffort = typeof entry.default_reasoning_level === "string"
90
+ ? entry.default_reasoning_level
91
+ : typeof nested?.default_effort === "string"
92
+ ? nested.default_effort
93
+ : typeof nested?.defaultEffort === "string"
94
+ ? nested.defaultEffort
95
+ : undefined;
96
+ const budgetSource = isRecord(nested?.budget) ? nested.budget : undefined;
97
+ const budget = budgetSource === undefined
98
+ ? undefined
99
+ : {
100
+ ...(typeof budgetSource.min_tokens === "number"
101
+ ? { minTokens: budgetSource.min_tokens }
102
+ : typeof budgetSource.minTokens === "number"
103
+ ? { minTokens: budgetSource.minTokens }
104
+ : {}),
105
+ ...(typeof budgetSource.max_tokens === "number"
106
+ ? { maxTokens: budgetSource.max_tokens }
107
+ : typeof budgetSource.maxTokens === "number"
108
+ ? { maxTokens: budgetSource.maxTokens }
109
+ : {}),
110
+ ...(typeof budgetSource.default_tokens === "number"
111
+ ? { defaultTokens: budgetSource.default_tokens }
112
+ : typeof budgetSource.defaultTokens === "number"
113
+ ? { defaultTokens: budgetSource.defaultTokens }
114
+ : {})
115
+ };
116
+ return {
117
+ status: unsupported ? "unsupported" : supported ? "supported" : "unknown",
118
+ ...(efforts.length > 0 ? { efforts } : {}),
119
+ ...(defaultEffort !== undefined ? { defaultEffort } : {}),
120
+ ...(budget !== undefined ? { budget } : {}),
121
+ ...(typeof nested?.adaptive === "boolean" ? { adaptive: nested.adaptive } : {}),
122
+ ...(reasoningWireShape(provider) !== undefined
123
+ ? { wireShape: reasoningWireShape(provider) }
124
+ : {}),
125
+ provenance: "provider",
126
+ refreshedAt
127
+ };
128
+ }
129
+ function modelId(value, key) {
130
+ if (!isRecord(value) || typeof value[key] !== "string")
131
+ return undefined;
132
+ const id = value[key].trim();
133
+ if (id.length === 0)
134
+ return undefined;
135
+ return key === "name" && id.startsWith("models/") ? id.slice("models/".length) : id;
136
+ }
137
+ export function parseDiscoveredModels(shape, payload, provider) {
138
+ if (!isRecord(payload))
139
+ throw new Error("model discovery returned a non-object payload");
140
+ let entries;
141
+ switch (shape) {
142
+ case "openai":
143
+ case "anthropic":
144
+ entries = Array.isArray(payload.data) ? payload.data : [];
145
+ break;
146
+ case "google":
147
+ case "codex":
148
+ entries = Array.isArray(payload.models) ? payload.models : [];
149
+ break;
150
+ default: {
151
+ const unreachable = shape;
152
+ throw new Error(`unsupported discovery response shape: ${String(unreachable)}`);
153
+ }
154
+ }
155
+ const key = shape === "google" ? "name" : shape === "codex" ? "slug" : "id";
156
+ const seen = new Set();
157
+ const models = [];
158
+ for (const entry of entries) {
159
+ const id = modelId(entry, key);
160
+ if (id === undefined || seen.has(id))
161
+ continue;
162
+ seen.add(id);
163
+ const capabilities = isRecord(entry) && isRecord(entry.capabilities)
164
+ ? Object.fromEntries(Object.entries(entry.capabilities).flatMap(([name, value]) => typeof value === "string" ? [[name, value]] : []))
165
+ : undefined;
166
+ const reasoning = parseReasoningCapabilities(entry, provider);
167
+ models.push({
168
+ id,
169
+ ...(capabilities !== undefined && Object.keys(capabilities).length > 0
170
+ ? { capabilities }
171
+ : {}),
172
+ ...(reasoning !== undefined ? { reasoning } : {})
173
+ });
174
+ }
175
+ if (models.length === 0) {
176
+ throw new Error(`model discovery returned no usable ${shape} models`);
177
+ }
178
+ return models;
179
+ }
180
+ function authHeaders(style, credential) {
181
+ switch (style) {
182
+ case "bearer":
183
+ return { authorization: `Bearer ${credential}` };
184
+ case "x-api-key":
185
+ return { "x-api-key": credential };
186
+ case "x-goog-api-key":
187
+ return { "x-goog-api-key": credential };
188
+ default: {
189
+ const unreachable = style;
190
+ throw new Error(`unsupported provider auth style: ${String(unreachable)}`);
191
+ }
192
+ }
193
+ }
194
+ function providerUrl(baseUrl, path) {
195
+ const url = new URL(baseUrl);
196
+ const baseSegments = url.pathname.split("/").filter(Boolean);
197
+ const pathSegments = path.split("/").filter(Boolean);
198
+ let overlap = Math.min(baseSegments.length, pathSegments.length);
199
+ while (overlap > 0 &&
200
+ !baseSegments
201
+ .slice(baseSegments.length - overlap)
202
+ .every((segment, index) => segment === pathSegments[index])) {
203
+ overlap -= 1;
204
+ }
205
+ url.pathname = `/${[
206
+ ...baseSegments,
207
+ ...pathSegments.slice(overlap)
208
+ ].join("/")}`;
209
+ url.search = "";
210
+ url.hash = "";
211
+ return url.toString().replace(/\/$/, "");
212
+ }
213
+ function providerBackend(protocol, baseUrl, apiKey, headers) {
214
+ const options = { baseUrl, apiKey, headers };
215
+ switch (protocol) {
216
+ case "openai":
217
+ return new OpenAiBackend(options);
218
+ case "anthropic":
219
+ return new AnthropicBackend(options);
220
+ case "google":
221
+ return new GoogleGenAiBackend(options);
222
+ case "codex":
223
+ return new CodexResponsesBackend(options);
224
+ default: {
225
+ const unreachable = protocol;
226
+ throw new Error(`unsupported provider wire protocol: ${String(unreachable)}`);
227
+ }
228
+ }
229
+ }
230
+ function providerMetadata(provider) {
231
+ const info = PROVIDERS[provider];
232
+ if (info?.baseUrl === undefined || info.discovery === undefined || info.wire === undefined) {
233
+ throw new Error(`provider "${provider}" has incomplete registry metadata`);
234
+ }
235
+ return info;
236
+ }
237
+ function providerCredential(provider, info, env) {
238
+ const keyEnv = info.keyEnv;
239
+ if (keyEnv === undefined) {
240
+ throw new Error(`provider "${provider}" has no registry-defined credential environment`);
241
+ }
242
+ const value = env[keyEnv];
243
+ if (value === undefined || value.length === 0) {
244
+ throw new Error(`provider "${provider}" is missing credential environment variable ${keyEnv}`);
245
+ }
246
+ return value;
247
+ }
248
+ export class ApiProviderSource {
249
+ sourceId;
250
+ #info;
251
+ #baseUrl;
252
+ #credential;
253
+ #backend;
254
+ #transport;
255
+ constructor(options) {
256
+ this.sourceId = options.provider;
257
+ this.#info = providerMetadata(options.provider);
258
+ const env = options.env ?? process.env;
259
+ this.#credential = providerCredential(options.provider, this.#info, env);
260
+ this.#baseUrl =
261
+ (this.#info.baseUrlEnv === undefined ? undefined : env[this.#info.baseUrlEnv]) ??
262
+ this.#info.baseUrl;
263
+ const wire = this.#info.wire;
264
+ const headers = this.#info.attributionHeaders ?? {};
265
+ this.#backend = providerBackend(wire.protocol, providerUrl(this.#baseUrl, wire.basePath), this.#credential, headers);
266
+ this.#transport =
267
+ options.transport ?? (async (url, init) => await fetch(url, init));
268
+ }
269
+ async discoverModels(signal) {
270
+ const discovery = this.#info.discovery;
271
+ const response = await this.#transport(providerUrl(this.#baseUrl, discovery.path), {
272
+ headers: {
273
+ accept: "application/json",
274
+ ...authHeaders(discovery.auth, this.#credential),
275
+ ...(discovery.extraHeaders ?? {})
276
+ },
277
+ ...(signal !== undefined ? { signal } : {})
278
+ });
279
+ if (!response.ok) {
280
+ throw new Error(`model discovery returned HTTP ${response.status}`);
281
+ }
282
+ return parseDiscoveredModels(discovery.responseShape, await response.json(), this.sourceId);
283
+ }
284
+ chat(body, signal, options) {
285
+ return this.#backend.chat(body, signal, options);
286
+ }
287
+ embeddings(body, signal, options) {
288
+ return this.#backend.embeddings(body, signal, options);
289
+ }
290
+ close() {
291
+ return this.#backend.close?.();
292
+ }
293
+ }
@@ -0,0 +1,168 @@
1
+ import { z } from "zod";
2
+ import type { ModelReasoningCapabilities } from "@velum-labs/routekit-contracts";
3
+ import type { Backend, BackendModelRoute, BackendRequestOptions } from "./backend.js";
4
+ import { SUBSCRIPTION_PROVIDER_IDS } from "./provider-source.js";
5
+ import type { ApiProviderId, ProviderId, ProviderSource } from "./provider-source.js";
6
+ export declare class UnknownModelError extends Error {
7
+ readonly model: string;
8
+ constructor(model: string);
9
+ }
10
+ export declare class NoModelAvailableError extends Error {
11
+ constructor();
12
+ }
13
+ declare const providerPolicySchema: z.ZodObject<{
14
+ strategy: z.ZodDefault<z.ZodEnum<{
15
+ sticky: "sticky";
16
+ round_robin: "round_robin";
17
+ capacity_weighted: "capacity_weighted";
18
+ }>>;
19
+ switchThreshold: z.ZodDefault<z.ZodNumber>;
20
+ probeIntervalMs: z.ZodOptional<z.ZodNumber>;
21
+ fallbackCooldownSeconds: z.ZodOptional<z.ZodNumber>;
22
+ }, z.core.$strict>;
23
+ export declare const routerConfigSchema: z.ZodObject<{
24
+ providers: z.ZodObject<{
25
+ openai: z.ZodOptional<z.ZodObject<{
26
+ strategy: z.ZodDefault<z.ZodEnum<{
27
+ sticky: "sticky";
28
+ round_robin: "round_robin";
29
+ capacity_weighted: "capacity_weighted";
30
+ }>>;
31
+ switchThreshold: z.ZodDefault<z.ZodNumber>;
32
+ probeIntervalMs: z.ZodOptional<z.ZodNumber>;
33
+ fallbackCooldownSeconds: z.ZodOptional<z.ZodNumber>;
34
+ }, z.core.$strict>>;
35
+ anthropic: z.ZodOptional<z.ZodObject<{
36
+ strategy: z.ZodDefault<z.ZodEnum<{
37
+ sticky: "sticky";
38
+ round_robin: "round_robin";
39
+ capacity_weighted: "capacity_weighted";
40
+ }>>;
41
+ switchThreshold: z.ZodDefault<z.ZodNumber>;
42
+ probeIntervalMs: z.ZodOptional<z.ZodNumber>;
43
+ fallbackCooldownSeconds: z.ZodOptional<z.ZodNumber>;
44
+ }, z.core.$strict>>;
45
+ google: z.ZodOptional<z.ZodObject<{
46
+ strategy: z.ZodDefault<z.ZodEnum<{
47
+ sticky: "sticky";
48
+ round_robin: "round_robin";
49
+ capacity_weighted: "capacity_weighted";
50
+ }>>;
51
+ switchThreshold: z.ZodDefault<z.ZodNumber>;
52
+ probeIntervalMs: z.ZodOptional<z.ZodNumber>;
53
+ fallbackCooldownSeconds: z.ZodOptional<z.ZodNumber>;
54
+ }, z.core.$strict>>;
55
+ openrouter: z.ZodOptional<z.ZodObject<{
56
+ strategy: z.ZodDefault<z.ZodEnum<{
57
+ sticky: "sticky";
58
+ round_robin: "round_robin";
59
+ capacity_weighted: "capacity_weighted";
60
+ }>>;
61
+ switchThreshold: z.ZodDefault<z.ZodNumber>;
62
+ probeIntervalMs: z.ZodOptional<z.ZodNumber>;
63
+ fallbackCooldownSeconds: z.ZodOptional<z.ZodNumber>;
64
+ }, z.core.$strict>>;
65
+ cliproxy: z.ZodOptional<z.ZodObject<{
66
+ strategy: z.ZodDefault<z.ZodEnum<{
67
+ sticky: "sticky";
68
+ round_robin: "round_robin";
69
+ capacity_weighted: "capacity_weighted";
70
+ }>>;
71
+ switchThreshold: z.ZodDefault<z.ZodNumber>;
72
+ probeIntervalMs: z.ZodOptional<z.ZodNumber>;
73
+ fallbackCooldownSeconds: z.ZodOptional<z.ZodNumber>;
74
+ }, z.core.$strict>>;
75
+ codex: z.ZodOptional<z.ZodObject<{
76
+ strategy: z.ZodDefault<z.ZodEnum<{
77
+ sticky: "sticky";
78
+ round_robin: "round_robin";
79
+ capacity_weighted: "capacity_weighted";
80
+ }>>;
81
+ switchThreshold: z.ZodDefault<z.ZodNumber>;
82
+ probeIntervalMs: z.ZodOptional<z.ZodNumber>;
83
+ fallbackCooldownSeconds: z.ZodOptional<z.ZodNumber>;
84
+ }, z.core.$strict>>;
85
+ "claude-code": z.ZodOptional<z.ZodObject<{
86
+ strategy: z.ZodDefault<z.ZodEnum<{
87
+ sticky: "sticky";
88
+ round_robin: "round_robin";
89
+ capacity_weighted: "capacity_weighted";
90
+ }>>;
91
+ switchThreshold: z.ZodDefault<z.ZodNumber>;
92
+ probeIntervalMs: z.ZodOptional<z.ZodNumber>;
93
+ fallbackCooldownSeconds: z.ZodOptional<z.ZodNumber>;
94
+ }, z.core.$strict>>;
95
+ }, z.core.$strict>;
96
+ defaultModel: z.ZodOptional<z.ZodString>;
97
+ reasoningCapabilities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
98
+ status: z.ZodDefault<z.ZodEnum<{
99
+ unknown: "unknown";
100
+ unsupported: "unsupported";
101
+ supported: "supported";
102
+ }>>;
103
+ efforts: z.ZodOptional<z.ZodArray<z.ZodObject<{
104
+ id: z.ZodString;
105
+ label: z.ZodOptional<z.ZodString>;
106
+ description: z.ZodOptional<z.ZodString>;
107
+ aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
108
+ }, z.core.$strict>>>;
109
+ defaultEffort: z.ZodOptional<z.ZodString>;
110
+ budget: z.ZodOptional<z.ZodObject<{
111
+ minTokens: z.ZodOptional<z.ZodNumber>;
112
+ maxTokens: z.ZodOptional<z.ZodNumber>;
113
+ defaultTokens: z.ZodOptional<z.ZodNumber>;
114
+ }, z.core.$strict>>;
115
+ adaptive: z.ZodOptional<z.ZodBoolean>;
116
+ wireShape: z.ZodOptional<z.ZodString>;
117
+ }, z.core.$strict>>>;
118
+ }, z.core.$strict>;
119
+ export type ProviderPolicy = z.infer<typeof providerPolicySchema>;
120
+ export type RouterConfig = z.infer<typeof routerConfigSchema>;
121
+ export declare function normalizeRouterConfigAliases(value: unknown): unknown;
122
+ export declare function splitNamespacedModel(model: string): {
123
+ provider: ProviderId;
124
+ model: string;
125
+ };
126
+ export declare function parseRouterConfig(value: unknown): RouterConfig;
127
+ export type CatalogModelInfo = {
128
+ id: string;
129
+ provider: ProviderId;
130
+ nativeModel: string;
131
+ accountClass: "api-key" | "subscription" | "proxy";
132
+ billingMode: "metered-api" | "subscription" | "upstream-managed";
133
+ default: boolean;
134
+ capabilities: Readonly<Record<string, string>>;
135
+ reasoning: ModelReasoningCapabilities | null;
136
+ };
137
+ export type CatalogBackendOptions = {
138
+ config: RouterConfig | unknown;
139
+ env?: Readonly<Record<string, string | undefined>>;
140
+ sources?: Partial<Record<ProviderId, ProviderSource>>;
141
+ createApiSource?: (provider: ApiProviderId, env: Readonly<Record<string, string | undefined>>) => ProviderSource;
142
+ signal?: AbortSignal;
143
+ };
144
+ export declare class CatalogBackend implements Backend {
145
+ #private;
146
+ readonly defaultModel: string | undefined;
147
+ private constructor();
148
+ static create(options: CatalogBackendOptions): Promise<CatalogBackend>;
149
+ listModelIds(): readonly string[];
150
+ modelInfo(model: string): CatalogModelInfo | undefined;
151
+ providerStatuses(signal?: AbortSignal): Promise<Array<{
152
+ provider: string;
153
+ ok: boolean;
154
+ models: string[];
155
+ error?: string;
156
+ }>>;
157
+ servesModel(model: string): boolean;
158
+ resolveModel(requested: string | undefined): string | undefined;
159
+ resolveModelRoute(requested: string | undefined, nativeProvider?: string): BackendModelRoute | undefined;
160
+ capabilities(model: string): Readonly<Record<string, string>>;
161
+ reasoningCapabilities(model: string): ModelReasoningCapabilities | undefined;
162
+ chat(body: unknown, signal?: AbortSignal, options?: BackendRequestOptions): Promise<Response>;
163
+ models(): Promise<Response>;
164
+ embeddings(body: unknown, signal?: AbortSignal, options?: BackendRequestOptions): Promise<Response>;
165
+ close(): Promise<void>;
166
+ }
167
+ export declare function isSubscriptionProvider(provider: ProviderId): provider is (typeof SUBSCRIPTION_PROVIDER_IDS)[number];
168
+ export {};