@hasna/switcher 0.1.1 → 0.1.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.
package/dist/sdk.d.ts CHANGED
@@ -57,12 +57,12 @@ export declare class SwitcherClient {
57
57
  credentialEnv?: string;
58
58
  credentialAliases: string[];
59
59
  protocols: {
60
- protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
60
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
61
61
  baseUrl?: string;
62
- authStyle: "bearer" | "x-api-key";
62
+ authStyle: "bearer" | "x-api-key" | "api-key";
63
63
  catalogBaseUrl?: string;
64
- catalogFormat: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
65
- catalogAuthStyle?: "bearer" | "x-api-key" | "none";
64
+ catalogFormat: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none";
65
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none";
66
66
  modelsPath: string;
67
67
  notes: string[];
68
68
  }[];
@@ -73,12 +73,12 @@ export declare class SwitcherClient {
73
73
  id: string;
74
74
  name: string;
75
75
  baseUrl: string;
76
- protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
76
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
77
77
  credentialEnv?: string;
78
- authStyle: "bearer" | "x-api-key";
78
+ authStyle: "bearer" | "x-api-key" | "api-key";
79
79
  catalogBaseUrl?: string;
80
- catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
81
- catalogAuthStyle?: "bearer" | "x-api-key" | "none";
80
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none";
81
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none";
82
82
  catalogCredentialEnv?: string;
83
83
  catalogAccountId?: string;
84
84
  modelsPath: string;
@@ -92,6 +92,7 @@ export declare class SwitcherClient {
92
92
  inputModalities?: string[];
93
93
  outputModalities?: string[];
94
94
  supportedParameters?: string[];
95
+ supportedGenerationMethods?: string[];
95
96
  }[];
96
97
  version: number;
97
98
  updatedAt: string;
@@ -100,12 +101,12 @@ export declare class SwitcherClient {
100
101
  id: string;
101
102
  name: string;
102
103
  baseUrl: string;
103
- protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
104
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
104
105
  credentialEnv?: string;
105
- authStyle: "bearer" | "x-api-key";
106
+ authStyle: "bearer" | "x-api-key" | "api-key";
106
107
  catalogBaseUrl?: string;
107
- catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
108
- catalogAuthStyle?: "bearer" | "x-api-key" | "none";
108
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none";
109
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none";
109
110
  catalogCredentialEnv?: string;
110
111
  catalogAccountId?: string;
111
112
  modelsPath: string;
@@ -119,6 +120,7 @@ export declare class SwitcherClient {
119
120
  inputModalities?: string[];
120
121
  outputModalities?: string[];
121
122
  supportedParameters?: string[];
123
+ supportedGenerationMethods?: string[];
122
124
  }[];
123
125
  version: number;
124
126
  updatedAt: string;
@@ -127,12 +129,12 @@ export declare class SwitcherClient {
127
129
  id: string;
128
130
  name: string;
129
131
  baseUrl: string;
130
- protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
132
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
131
133
  credentialEnv?: string;
132
- authStyle: "bearer" | "x-api-key";
134
+ authStyle: "bearer" | "x-api-key" | "api-key";
133
135
  catalogBaseUrl?: string;
134
- catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
135
- catalogAuthStyle?: "bearer" | "x-api-key" | "none";
136
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none";
137
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none";
136
138
  catalogCredentialEnv?: string;
137
139
  catalogAccountId?: string;
138
140
  modelsPath: string;
@@ -146,6 +148,7 @@ export declare class SwitcherClient {
146
148
  inputModalities?: string[];
147
149
  outputModalities?: string[];
148
150
  supportedParameters?: string[];
151
+ supportedGenerationMethods?: string[];
149
152
  }[];
150
153
  version: number;
151
154
  updatedAt: string;
@@ -154,12 +157,12 @@ export declare class SwitcherClient {
154
157
  id: string;
155
158
  name: string;
156
159
  baseUrl: string;
157
- protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
160
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
158
161
  credentialEnv?: string;
159
- authStyle: "bearer" | "x-api-key";
162
+ authStyle: "bearer" | "x-api-key" | "api-key";
160
163
  catalogBaseUrl?: string;
161
- catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
162
- catalogAuthStyle?: "bearer" | "x-api-key" | "none";
164
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none";
165
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none";
163
166
  catalogCredentialEnv?: string;
164
167
  catalogAccountId?: string;
165
168
  modelsPath: string;
@@ -173,6 +176,7 @@ export declare class SwitcherClient {
173
176
  inputModalities?: string[];
174
177
  outputModalities?: string[];
175
178
  supportedParameters?: string[];
179
+ supportedGenerationMethods?: string[];
176
180
  }[];
177
181
  version: number;
178
182
  updatedAt: string;
@@ -191,6 +195,7 @@ export declare class SwitcherClient {
191
195
  inputModalities?: string[];
192
196
  outputModalities?: string[];
193
197
  supportedParameters?: string[];
198
+ supportedGenerationMethods?: string[];
194
199
  }[];
195
200
  refreshedAt: string;
196
201
  source: "remote" | "manual";
@@ -206,6 +211,7 @@ export declare class SwitcherClient {
206
211
  inputModalities?: string[];
207
212
  outputModalities?: string[];
208
213
  supportedParameters?: string[];
214
+ supportedGenerationMethods?: string[];
209
215
  codingEligible: boolean;
210
216
  }[];
211
217
  total: number;
@@ -218,7 +224,7 @@ export declare class SwitcherClient {
218
224
  id: string;
219
225
  name: string;
220
226
  providerId: string;
221
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
227
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
222
228
  model: string;
223
229
  version: number;
224
230
  updatedAt: string;
@@ -227,7 +233,7 @@ export declare class SwitcherClient {
227
233
  id: string;
228
234
  name: string;
229
235
  providerId: string;
230
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
236
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
231
237
  model: string;
232
238
  version: number;
233
239
  updatedAt: string;
@@ -236,7 +242,7 @@ export declare class SwitcherClient {
236
242
  id: string;
237
243
  name: string;
238
244
  providerId: string;
239
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
245
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
240
246
  model: string;
241
247
  version: number;
242
248
  updatedAt: string;
@@ -245,7 +251,7 @@ export declare class SwitcherClient {
245
251
  id: string;
246
252
  name: string;
247
253
  providerId: string;
248
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
254
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
249
255
  model: string;
250
256
  version: number;
251
257
  updatedAt: string;
@@ -258,12 +264,12 @@ export declare class SwitcherClient {
258
264
  id: string;
259
265
  name: string;
260
266
  baseUrl: string;
261
- protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
267
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
262
268
  credentialEnv?: string;
263
- authStyle: "bearer" | "x-api-key";
269
+ authStyle: "bearer" | "x-api-key" | "api-key";
264
270
  catalogBaseUrl?: string;
265
- catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
266
- catalogAuthStyle?: "bearer" | "x-api-key" | "none";
271
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none";
272
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none";
267
273
  catalogCredentialEnv?: string;
268
274
  catalogAccountId?: string;
269
275
  modelsPath: string;
@@ -277,6 +283,7 @@ export declare class SwitcherClient {
277
283
  inputModalities?: string[];
278
284
  outputModalities?: string[];
279
285
  supportedParameters?: string[];
286
+ supportedGenerationMethods?: string[];
280
287
  }[];
281
288
  version: number;
282
289
  updatedAt: string;
@@ -285,7 +292,7 @@ export declare class SwitcherClient {
285
292
  id: string;
286
293
  name: string;
287
294
  providerId: string;
288
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
295
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
289
296
  model: string;
290
297
  version: number;
291
298
  updatedAt: string;
@@ -301,6 +308,7 @@ export declare class SwitcherClient {
301
308
  inputModalities?: string[];
302
309
  outputModalities?: string[];
303
310
  supportedParameters?: string[];
311
+ supportedGenerationMethods?: string[];
304
312
  }[];
305
313
  refreshedAt: string;
306
314
  source: "remote" | "manual";
@@ -310,7 +318,7 @@ export declare class SwitcherClient {
310
318
  }>;
311
319
  listRuns(options?: {}): Promise<Page<{
312
320
  profileId: string;
313
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
321
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
314
322
  model: string;
315
323
  planToken: string;
316
324
  version: number;
@@ -326,7 +334,7 @@ export declare class SwitcherClient {
326
334
  }>>;
327
335
  getRun(id: string): Promise<{
328
336
  profileId: string;
329
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
337
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
330
338
  model: string;
331
339
  planToken: string;
332
340
  version: number;
@@ -342,7 +350,7 @@ export declare class SwitcherClient {
342
350
  }>;
343
351
  createRun(input: components["schemas"]["RunInput"], idempotencyKey?: string): Promise<{
344
352
  profileId: string;
345
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
353
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
346
354
  model: string;
347
355
  planToken: string;
348
356
  version: number;
@@ -361,7 +369,7 @@ export declare class SwitcherClient {
361
369
  exitCode: number;
362
370
  }, idempotencyKey?: string): Promise<{
363
371
  profileId: string;
364
- harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
372
+ harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
365
373
  model: string;
366
374
  planToken: string;
367
375
  version: number;
package/dist/sdk.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/domain.ts
2
2
  import { z } from "zod";
3
- var harnessSchema = z.enum(["claude", "codex", "grok", "opencode2", "pi"]);
4
- var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat"]);
3
+ var harnessSchema = z.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
4
+ var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
5
5
  var idSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
6
6
  var label = z.string().min(1).max(200);
7
7
  var envRef = z.string().regex(/^SWITCHER_PROVIDER_[A-Z0-9_]+$/);
@@ -33,7 +33,8 @@ var modelSchema = z.object({
33
33
  maxOutputTokens: z.number().int().positive().optional(),
34
34
  inputModalities: z.array(z.string().max(50)).max(20).optional(),
35
35
  outputModalities: z.array(z.string().max(50)).max(20).optional(),
36
- supportedParameters: z.array(z.string().max(100)).max(100).optional()
36
+ supportedParameters: z.array(z.string().max(100)).max(100).optional(),
37
+ supportedGenerationMethods: z.array(z.string().min(1).max(100)).max(100).optional()
37
38
  }).strict();
38
39
  var providerInputSchema = z.object({
39
40
  id: idSchema,
@@ -41,10 +42,10 @@ var providerInputSchema = z.object({
41
42
  baseUrl: urlSchema,
42
43
  protocol: protocolSchema,
43
44
  credentialEnv: envRef.optional(),
44
- authStyle: z.enum(["bearer", "x-api-key"]).default("bearer"),
45
+ authStyle: z.enum(["bearer", "x-api-key", "api-key"]).default("bearer"),
45
46
  catalogBaseUrl: urlSchema.optional(),
46
- catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]).optional(),
47
- catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
47
+ catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]).optional(),
48
+ catalogAuthStyle: z.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
48
49
  catalogCredentialEnv: envRef.optional(),
49
50
  catalogAccountId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
50
51
  modelsPath: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
@@ -58,10 +59,10 @@ var providerPresetSchema = z.object({
58
59
  protocols: z.array(z.object({
59
60
  protocol: protocolSchema,
60
61
  baseUrl: urlSchema.optional(),
61
- authStyle: z.enum(["bearer", "x-api-key"]),
62
+ authStyle: z.enum(["bearer", "x-api-key", "api-key"]),
62
63
  catalogBaseUrl: urlSchema.optional(),
63
- catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]),
64
- catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
64
+ catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]),
65
+ catalogAuthStyle: z.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
65
66
  modelsPath: z.string(),
66
67
  notes: z.array(z.string())
67
68
  }).strict()).min(1),
@@ -102,7 +103,15 @@ function parse(schema, value) {
102
103
  return result.data;
103
104
  }
104
105
  function compatible(harness, protocol) {
105
- return harness === "claude" ? protocol === "anthropic-messages" : harness === "codex" ? protocol === "openai-responses" : true;
106
+ if (harness === "claude")
107
+ return protocol === "anthropic-messages";
108
+ if (harness === "codex")
109
+ return protocol === "openai-responses";
110
+ if (harness === "gemini")
111
+ return protocol === "gemini-generate-content";
112
+ if (protocol === "gemini-generate-content")
113
+ return false;
114
+ return true;
106
115
  }
107
116
 
108
117
  // src/http.ts
@@ -168,7 +177,18 @@ var providerPresets = [
168
177
  ], ["https://api-docs.deepseek.com/guides/anthropic_api", "https://api-docs.deepseek.com/api/list-models"], "DEEPSEEK_API_KEY"),
169
178
  preset("openrouter", "OpenRouter", ["openai-chat", "openai-responses", "anthropic-messages"].map((protocol) => route(protocol, "https://openrouter.ai/api/v1", { catalogAuthStyle: "none" })), ["https://openrouter.ai/docs/api/api-reference/models/list-all-models-and-their-properties", "https://openrouter.ai/docs/guides/overview"], "OPENROUTER_API_KEY"),
170
179
  preset("anthropic", "Anthropic", [route("anthropic-messages", "https://api.anthropic.com/v1", { authStyle: "x-api-key" })], ["https://platform.claude.com/docs/en/api/overview", "https://platform.claude.com/docs/en/api/models/list"], "ANTHROPIC_API_KEY"),
180
+ preset("gemini", "Google Gemini", [route("gemini-generate-content", "https://generativelanguage.googleapis.com/v1beta", {
181
+ authStyle: "x-api-key",
182
+ catalogBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
183
+ catalogFormat: "gemini",
184
+ catalogAuthStyle: "x-api-key",
185
+ notes: ["Gemini CLI uses the native generateContent wire with x-goog-api-key authentication; model IDs are returned as models/{id}."]
186
+ }), route("openai-chat", "https://generativelanguage.googleapis.com/v1beta/openai")], ["https://ai.google.dev/api", "https://ai.google.dev/api/models", "https://github.com/google-gemini/gemini-cli", "https://ai.google.dev/gemini-api/docs/openai"], "GEMINI_API_KEY"),
171
187
  preset("openai", "OpenAI", [route("openai-responses", "https://api.openai.com/v1"), route("openai-chat", "https://api.openai.com/v1")], ["https://platform.openai.com/docs/api-reference/introduction", "https://platform.openai.com/docs/api-reference/models/list"], "OPENAI_API_KEY"),
188
+ preset("azure-openai", "Azure OpenAI (v1)", [
189
+ route("openai-responses", undefined, { authStyle: "api-key", catalogFormat: "none", notes: ["Pass the Azure OpenAI v1 resource endpoint ending in /openai/v1. The request model is your deployment name. Azure's model-definition list is not a deployment catalog, so configure manual deployment models or an explicit deployment catalog parser; Switcher does not synthesize deployment paths or api-version query parameters."] }),
190
+ route("openai-chat", undefined, { authStyle: "api-key", catalogFormat: "none", notes: ["Pass the Azure OpenAI v1 resource endpoint ending in /openai/v1. Chat Completions is POST /chat/completions and accepts the literal api-key header. The request model is your deployment name; configure manual deployment models or an explicit deployment catalog parser because GET /models does not establish deployment names."] })
191
+ ], ["https://learn.microsoft.com/en-us/rest/api/aifoundry/azureopenai/models", "https://learn.microsoft.com/en-us/rest/api/microsoft-foundry/azureopenai/chat", "https://learn.microsoft.com/en-us/rest/api/aifoundry/azureopenai/responses"], "AZURE_OPENAI_API_KEY"),
172
192
  preset("xai", "xAI", ["openai-chat", "openai-responses", "anthropic-messages"].map((protocol) => route(protocol, "https://api.x.ai/v1")), ["https://api.x.ai/docs/", "https://docs.x.ai/developers/model-capabilities/text/generate-text"], "XAI_API_KEY"),
173
193
  preset("ollama", "Ollama", ["openai-chat", "openai-responses"].map((protocol) => route(protocol, "http://127.0.0.1:11434/v1", {
174
194
  catalogBaseUrl: "http://127.0.0.1:11434",
@@ -229,6 +249,8 @@ function providerFromPreset(presetId, options = {}) {
229
249
  const baseUrl = options.baseUrl ?? selected.baseUrl;
230
250
  if (!baseUrl)
231
251
  throw new Fault(400, "endpoint_required", "This preset requires an explicit --url for its inference endpoint.");
252
+ if (presetId === "azure-openai" && !/\/openai\/v1$/.test(endpoint(baseUrl)))
253
+ throw new Fault(400, "invalid_url", "Azure OpenAI v1 requires an explicit endpoint ending in /openai/v1; deployment and api-version URLs are unsupported.");
232
254
  if (options.baseUrl && selected.baseUrl && new URL(endpoint(options.baseUrl)).origin !== new URL(selected.baseUrl).origin && preset2.credentialEnv && !options.credentialEnv)
233
255
  throw new Fault(422, "credential_authority", "An endpoint on another origin requires an explicit --credential-env reference.");
234
256
  const suffix = selected.protocol === "anthropic-messages" ? "messages" : selected.protocol === "openai-responses" ? "responses" : "chat";
@@ -265,6 +287,26 @@ class SwitcherError extends Error {
265
287
  this.requestId = requestId;
266
288
  }
267
289
  }
290
+ function apiError(status, data, apiKey) {
291
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
292
+ const error = object(data) && object(data.error) ? data.error : {};
293
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
294
+ const base64 = Buffer.from(apiKey, "utf8").toString("base64");
295
+ const literal = [apiKey, JSON.stringify(apiKey).slice(1, -1), base64, base64.replace(/=+$/, ""), Buffer.from(apiKey, "utf8").toString("base64url")];
296
+ const encoded = [encodeURIComponent(apiKey), new URLSearchParams({ key: apiKey }).toString().slice(4)];
297
+ const patterns = [...new Set(literal)].sort((a, b) => b.length - a.length).map(escape);
298
+ for (const value of encoded) {
299
+ patterns.unshift(escape(value).replace(/%[0-9A-F]{2}/g, (part) => part.replace(/[A-F]/g, (letter) => `[${letter}${letter.toLowerCase()}]`)));
300
+ }
301
+ const reflected = new RegExp(patterns.join("|"), "g");
302
+ const redact = (value) => value.replace(reflected, "[REDACTED]");
303
+ const identifier = (value, pattern) => typeof value === "string" && pattern.test(value) && redact(value) === value ? value : undefined;
304
+ const code = identifier(error.code, /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/) ?? "api_error";
305
+ const requestId = identifier(error.requestId, /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
306
+ const fallback = `Switcher API returned HTTP ${status}.`;
307
+ const message = typeof error.message === "string" && error.message.length <= 4096 ? redact(error.message).replace(/[\x00-\x1f\x7f-\x9f]/g, " ").trim().slice(0, 2048) || fallback : fallback;
308
+ return new SwitcherError(status, code, message, requestId);
309
+ }
268
310
 
269
311
  class SwitcherClient {
270
312
  options;
@@ -301,7 +343,7 @@ class SwitcherClient {
301
343
  throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
302
344
  }
303
345
  if (!response.ok)
304
- throw new SwitcherError(response.status, data?.error?.code ?? "api_error", data?.error?.message ?? `Switcher API returned HTTP ${response.status}.`, data?.error?.requestId);
346
+ throw apiError(response.status, data, apiKey);
305
347
  return data;
306
348
  }
307
349
  query(options = {}) {