@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.
@@ -0,0 +1,19 @@
1
+ import type { HarnessLaunchInput, PreparedLaunch } from "./harness-types";
2
+ /** Hermes' native runtime names for the three Switcher wire contracts. */
3
+ export declare const hermesApiMode: {
4
+ readonly "anthropic-messages": "anthropic_messages";
5
+ readonly "openai-responses": "codex_responses";
6
+ readonly "openai-chat": "chat_completions";
7
+ };
8
+ /**
9
+ * Keep Hermes' picker and inference route on the exact Switcher catalog.
10
+ * Hermes' custom provider client uses Bearer locally; this bridge translates
11
+ * that local credential into the provider's declared upstream header style.
12
+ */
13
+ export declare function createHermesBridge(input: Pick<HarnessLaunchInput, "baseUrl" | "protocol" | "authStyle" | "model" | "models" | "credential">): {
14
+ baseUrl: string;
15
+ token: string;
16
+ cleanup: () => Promise<void>;
17
+ };
18
+ /** Prepare a Hermes run with isolated config, catalog and durable session state. */
19
+ export declare function prepareHermesLaunch(input: HarnessLaunchInput): Promise<PreparedLaunch>;
package/dist/index.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 = {}) {
@@ -0,0 +1,13 @@
1
+ type ObjectValue = Record<string, unknown>;
2
+ /** Read native policies as data; native config inspection itself may write project files. */
3
+ export declare function kiloSettings(cwd: string, stateDir: string, env?: NodeJS.ProcessEnv, options?: {
4
+ writeInstructions?: boolean;
5
+ }): Promise<{
6
+ config: ObjectValue;
7
+ global: ObjectValue;
8
+ instructions: string[];
9
+ sourcePaths: string[];
10
+ }>;
11
+ /** Kilo substitutes variables before parsing JSON; only the generated key reference is active. */
12
+ export declare function kiloConfigText(config: Record<string, unknown>, providerID?: string, keyEnv?: string): string;
13
+ export {};
package/dist/kilo.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { HarnessLaunchInput, PreparedLaunch } from "./harness-types";
2
+ export declare function validateKiloArgs(args: string[]): void;
3
+ /** Validate native policy and instruction inputs without credentials or generated files. */
4
+ export declare function validateKiloConfiguration(cwd: string, args?: string[]): Promise<void>;
5
+ export declare function prepareKilo(input: HarnessLaunchInput, providerBaseUrl: string): Promise<PreparedLaunch>;
@@ -19,16 +19,17 @@ type OriPreparationOptions = Pick<LaunchOptions, "oriExecutable" | "args" | "res
19
19
  stateDir?: string;
20
20
  cwd?: string;
21
21
  };
22
+ type OriSupportedHarness = Exclude<LaunchPlan["profile"]["harness"], "omp" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "opencode" | "kilo">;
22
23
  export declare function validateOriForPlan(plan: LaunchPlan, options?: Pick<OriPreparationOptions, "oriExecutable" | "args" | "credentialEnv" | "cwd">): Promise<{
23
24
  contract: OriContract;
24
25
  request: ReturnType<typeof buildOriRequest>;
25
26
  warnings: string[];
26
27
  }>;
27
- declare function buildOriRequest(plan: LaunchPlan, catalogPath: string | undefined, args: string[]): {
28
- readonly target: "claude" | "codex" | "grok" | "opencode2" | "pi";
28
+ declare function buildOriRequest(plan: LaunchPlan, target: OriSupportedHarness, catalogPath: string | undefined, args: string[]): {
29
+ readonly target: OriSupportedHarness;
29
30
  readonly provider: string;
30
31
  readonly providerBaseUrl: string;
31
- readonly protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
32
+ readonly protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
32
33
  readonly model: string;
33
34
  readonly catalog: {
34
35
  readonly codexModelCatalogPath?: string | undefined;
package/dist/mcp/index.js CHANGED
@@ -8,9 +8,9 @@ import { z as z2 } from "zod";
8
8
 
9
9
  // src/domain.ts
10
10
  import { z } from "zod";
11
- var VERSION = "0.1.1";
12
- var harnessSchema = z.enum(["claude", "codex", "grok", "opencode2", "pi"]);
13
- var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat"]);
11
+ var VERSION = "0.1.2";
12
+ var harnessSchema = z.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
13
+ var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
14
14
  var idSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
15
15
  var label = z.string().min(1).max(200);
16
16
  var envRef = z.string().regex(/^SWITCHER_PROVIDER_[A-Z0-9_]+$/);
@@ -42,7 +42,8 @@ var modelSchema = z.object({
42
42
  maxOutputTokens: z.number().int().positive().optional(),
43
43
  inputModalities: z.array(z.string().max(50)).max(20).optional(),
44
44
  outputModalities: z.array(z.string().max(50)).max(20).optional(),
45
- supportedParameters: z.array(z.string().max(100)).max(100).optional()
45
+ supportedParameters: z.array(z.string().max(100)).max(100).optional(),
46
+ supportedGenerationMethods: z.array(z.string().min(1).max(100)).max(100).optional()
46
47
  }).strict();
47
48
  var providerInputSchema = z.object({
48
49
  id: idSchema,
@@ -50,10 +51,10 @@ var providerInputSchema = z.object({
50
51
  baseUrl: urlSchema,
51
52
  protocol: protocolSchema,
52
53
  credentialEnv: envRef.optional(),
53
- authStyle: z.enum(["bearer", "x-api-key"]).default("bearer"),
54
+ authStyle: z.enum(["bearer", "x-api-key", "api-key"]).default("bearer"),
54
55
  catalogBaseUrl: urlSchema.optional(),
55
- catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]).optional(),
56
- catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
56
+ catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]).optional(),
57
+ catalogAuthStyle: z.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
57
58
  catalogCredentialEnv: envRef.optional(),
58
59
  catalogAccountId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
59
60
  modelsPath: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
@@ -67,10 +68,10 @@ var providerPresetSchema = z.object({
67
68
  protocols: z.array(z.object({
68
69
  protocol: protocolSchema,
69
70
  baseUrl: urlSchema.optional(),
70
- authStyle: z.enum(["bearer", "x-api-key"]),
71
+ authStyle: z.enum(["bearer", "x-api-key", "api-key"]),
71
72
  catalogBaseUrl: urlSchema.optional(),
72
- catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]),
73
- catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
73
+ catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]),
74
+ catalogAuthStyle: z.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
74
75
  modelsPath: z.string(),
75
76
  notes: z.array(z.string())
76
77
  }).strict()).min(1),
@@ -174,7 +175,18 @@ var providerPresets = [
174
175
  ], ["https://api-docs.deepseek.com/guides/anthropic_api", "https://api-docs.deepseek.com/api/list-models"], "DEEPSEEK_API_KEY"),
175
176
  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"),
176
177
  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"),
178
+ preset("gemini", "Google Gemini", [route("gemini-generate-content", "https://generativelanguage.googleapis.com/v1beta", {
179
+ authStyle: "x-api-key",
180
+ catalogBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
181
+ catalogFormat: "gemini",
182
+ catalogAuthStyle: "x-api-key",
183
+ notes: ["Gemini CLI uses the native generateContent wire with x-goog-api-key authentication; model IDs are returned as models/{id}."]
184
+ }), 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"),
177
185
  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"),
186
+ preset("azure-openai", "Azure OpenAI (v1)", [
187
+ 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."] }),
188
+ 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."] })
189
+ ], ["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"),
178
190
  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"),
179
191
  preset("ollama", "Ollama", ["openai-chat", "openai-responses"].map((protocol) => route(protocol, "http://127.0.0.1:11434/v1", {
180
192
  catalogBaseUrl: "http://127.0.0.1:11434",
@@ -234,6 +246,26 @@ class SwitcherError extends Error {
234
246
  this.requestId = requestId;
235
247
  }
236
248
  }
249
+ function apiError(status, data, apiKey) {
250
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
251
+ const error = object(data) && object(data.error) ? data.error : {};
252
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
253
+ const base64 = Buffer.from(apiKey, "utf8").toString("base64");
254
+ const literal = [apiKey, JSON.stringify(apiKey).slice(1, -1), base64, base64.replace(/=+$/, ""), Buffer.from(apiKey, "utf8").toString("base64url")];
255
+ const encoded = [encodeURIComponent(apiKey), new URLSearchParams({ key: apiKey }).toString().slice(4)];
256
+ const patterns = [...new Set(literal)].sort((a, b) => b.length - a.length).map(escape);
257
+ for (const value of encoded) {
258
+ patterns.unshift(escape(value).replace(/%[0-9A-F]{2}/g, (part) => part.replace(/[A-F]/g, (letter) => `[${letter}${letter.toLowerCase()}]`)));
259
+ }
260
+ const reflected = new RegExp(patterns.join("|"), "g");
261
+ const redact = (value) => value.replace(reflected, "[REDACTED]");
262
+ const identifier = (value, pattern) => typeof value === "string" && pattern.test(value) && redact(value) === value ? value : undefined;
263
+ const code = identifier(error.code, /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/) ?? "api_error";
264
+ const requestId = identifier(error.requestId, /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
265
+ const fallback = `Switcher API returned HTTP ${status}.`;
266
+ 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;
267
+ return new SwitcherError(status, code, message, requestId);
268
+ }
237
269
 
238
270
  class SwitcherClient {
239
271
  options;
@@ -270,7 +302,7 @@ class SwitcherClient {
270
302
  throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
271
303
  }
272
304
  if (!response.ok)
273
- throw new SwitcherError(response.status, data?.error?.code ?? "api_error", data?.error?.message ?? `Switcher API returned HTTP ${response.status}.`, data?.error?.requestId);
305
+ throw apiError(response.status, data, apiKey);
274
306
  return data;
275
307
  }
276
308
  query(options = {}) {
@@ -0,0 +1,7 @@
1
+ import type { HarnessLaunchInput, PreparedLaunch } from "./harness-types";
2
+ /**
3
+ * Prepare an isolated OMP configuration. OMP reads models.yml as JSON-valid
4
+ * YAML, and resolves the apiKey/header references from the child environment.
5
+ * The credential therefore never reaches the generated files or command line.
6
+ */
7
+ export declare function prepareOmpLaunch(input: HarnessLaunchInput): Promise<PreparedLaunch>;
@@ -4,13 +4,13 @@ declare const rule: z.ZodObject<{
4
4
  resource: z.ZodString;
5
5
  effect: z.ZodEnum<["allow", "deny", "ask"]>;
6
6
  }, "strict", z.ZodTypeAny, {
7
+ effect: "allow" | "deny" | "ask";
7
8
  action: string;
8
9
  resource: string;
9
- effect: "allow" | "deny" | "ask";
10
10
  }, {
11
+ effect: "allow" | "deny" | "ask";
11
12
  action: string;
12
13
  resource: string;
13
- effect: "allow" | "deny" | "ask";
14
14
  }>;
15
15
  type Rule = z.infer<typeof rule>;
16
16
  type Agent = {
@@ -35,9 +35,9 @@ export declare function isolateOpenCode2(cwd: string, stateDir: string, provider
35
35
  };
36
36
  default_agent?: string | undefined;
37
37
  permissions: {
38
+ effect: "allow" | "deny" | "ask";
38
39
  action: string;
39
40
  resource: string;
40
- effect: "allow" | "deny" | "ask";
41
41
  }[];
42
42
  agents: Record<string, Agent>;
43
43
  };
@@ -1,6 +1,6 @@
1
1
  /** The Ori 0.12.1 command name for a Switcher harness. */
2
- export type OriTarget = "claude" | "codex" | "grok" | "opencode2" | "pi";
3
- export type OriProtocol = "anthropic-messages" | "openai-responses" | "openai-chat";
2
+ export type OriTarget = "claude" | "codex" | "grok" | "opencode2" | "pi" | "dsh" | "gemini" | "aider" | "kilo";
3
+ export type OriProtocol = "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
4
4
  export type OriReasoningEffort = "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none";
5
5
  export type OriProviderCatalogEntry = {
6
6
  id: "openrouter";
package/dist/presets.d.ts CHANGED
@@ -6,14 +6,14 @@ export declare function getProviderPreset(id: string): {
6
6
  name: string;
7
7
  credentialAliases: string[];
8
8
  protocols: {
9
- protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
10
- authStyle: "bearer" | "x-api-key";
11
- catalogFormat: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
9
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
10
+ authStyle: "bearer" | "x-api-key" | "api-key";
11
+ catalogFormat: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none";
12
12
  modelsPath: string;
13
13
  notes: string[];
14
14
  baseUrl?: string | undefined;
15
15
  catalogBaseUrl?: string | undefined;
16
- catalogAuthStyle?: "bearer" | "x-api-key" | "none" | undefined;
16
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none" | undefined;
17
17
  }[];
18
18
  sources: string[];
19
19
  verification: "documented";
@@ -25,10 +25,10 @@ export type PresetOptions = {
25
25
  harness?: Profile["harness"];
26
26
  baseUrl?: string;
27
27
  credentialEnv?: string;
28
- authStyle?: "bearer" | "x-api-key";
28
+ authStyle?: "bearer" | "x-api-key" | "api-key";
29
29
  catalogBaseUrl?: string;
30
30
  catalogCredentialEnv?: string;
31
- catalogAuthStyle?: "bearer" | "x-api-key" | "none";
31
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none";
32
32
  modelsPath?: string;
33
33
  catalogFormat?: ProviderInput["catalogFormat"];
34
34
  catalogAccountId?: string;
@@ -37,8 +37,8 @@ export declare function providerFromPreset(presetId: string, options?: PresetOpt
37
37
  id: string;
38
38
  name: string;
39
39
  baseUrl: string;
40
- protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
41
- authStyle: "bearer" | "x-api-key";
40
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
41
+ authStyle: "bearer" | "x-api-key" | "api-key";
42
42
  modelsPath: string;
43
43
  manualModels: {
44
44
  id: string;
@@ -50,11 +50,12 @@ export declare function providerFromPreset(presetId: string, options?: PresetOpt
50
50
  inputModalities?: string[] | undefined;
51
51
  outputModalities?: string[] | undefined;
52
52
  supportedParameters?: string[] | undefined;
53
+ supportedGenerationMethods?: string[] | undefined;
53
54
  }[];
54
55
  credentialEnv?: string | undefined;
55
56
  catalogBaseUrl?: string | undefined;
56
- catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none" | undefined;
57
- catalogAuthStyle?: "bearer" | "x-api-key" | "none" | undefined;
57
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none" | undefined;
58
+ catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none" | undefined;
58
59
  catalogCredentialEnv?: string | undefined;
59
60
  catalogAccountId?: string | undefined;
60
61
  };