@hasna/switcher 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +146 -7
  2. package/dist/aider-args.d.ts +7 -0
  3. package/dist/aider-config.d.ts +6 -0
  4. package/dist/auth.d.ts +3 -0
  5. package/dist/cli/index.js +7548 -2558
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cline-backend.d.ts +7 -0
  8. package/dist/codex-model-policy.d.ts +65 -0
  9. package/dist/credentials.d.ts +5 -5
  10. package/dist/direct-launch.d.ts +3 -3
  11. package/dist/domain.d.ts +320 -50
  12. package/dist/dsh-args.d.ts +5 -0
  13. package/dist/gemini-bridge.d.ts +6 -0
  14. package/dist/gemini-config.d.ts +12 -0
  15. package/dist/gemini-model-policy.d.ts +56 -0
  16. package/dist/generated/api.d.ts +220 -21
  17. package/dist/harness-arguments.d.ts +1 -0
  18. package/dist/harness-installation.d.ts +19 -0
  19. package/dist/harness-types.d.ts +10 -0
  20. package/dist/harnesses.d.ts +5 -2
  21. package/dist/hermes-backend.d.ts +19 -0
  22. package/dist/hermes-model-policy.d.ts +30 -0
  23. package/dist/index.js +136 -43
  24. package/dist/inference-gateway.d.ts +24 -0
  25. package/dist/kilo-config.d.ts +13 -0
  26. package/dist/kilo.d.ts +5 -0
  27. package/dist/launcher.d.ts +12 -6
  28. package/dist/mcp/index.js +139 -55
  29. package/dist/model-policy-schema.d.ts +137 -0
  30. package/dist/model-policy.d.ts +31 -0
  31. package/dist/native-model-policy.d.ts +23 -0
  32. package/dist/omp-backend.d.ts +7 -0
  33. package/dist/opencode-model-policy.d.ts +33 -0
  34. package/dist/opencode2-config.d.ts +3 -3
  35. package/dist/ori-backend.d.ts +2 -2
  36. package/dist/ori-model-policy.d.ts +8 -0
  37. package/dist/presets.d.ts +11 -10
  38. package/dist/sdk.d.ts +279 -38
  39. package/dist/sdk.js +136 -43
  40. package/dist/serve/index.js +1306 -125
  41. package/docs/MODEL-POLICY.md +82 -0
  42. package/openapi.json +973 -14
  43. package/package.json +14 -3
package/dist/mcp/index.js CHANGED
@@ -4,16 +4,63 @@
4
4
  // src/mcp.ts
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import { z as z2 } from "zod";
7
+ import { z as z3 } from "zod";
8
8
 
9
9
  // src/domain.ts
10
+ import { z as z2 } from "zod";
11
+
12
+ // src/model-policy-schema.ts
10
13
  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"]);
14
- var idSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
15
- var label = z.string().min(1).max(200);
16
- var envRef = z.string().regex(/^SWITCHER_PROVIDER_[A-Z0-9_]+$/);
14
+ var policyModelIdSchema = z.string().min(1).max(300).regex(/^[^\u0000-\u001f\u007f]+$/);
15
+ var modelPolicyRoleSchema = z.enum(["subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"]);
16
+ var boundedModelList = z.array(policyModelIdSchema).max(500);
17
+ var aliasSchema = z.string().regex(/^[A-Za-z0-9._/-]{1,120}$/).refine((v) => !["__proto__", "prototype", "constructor"].includes(v));
18
+ var boundedModelMap = z.record(aliasSchema, policyModelIdSchema).superRefine((value, ctx) => {
19
+ if (Object.keys(value).length > 200)
20
+ ctx.addIssue({ code: "custom", message: "Model policy maps may contain at most 200 entries." });
21
+ });
22
+ var fallbacksSchema = z.record(policyModelIdSchema, z.array(policyModelIdSchema).max(20)).superRefine((value, ctx) => {
23
+ if (Object.keys(value).length > 200)
24
+ ctx.addIssue({ code: "custom", message: "Model policy fallback maps may contain at most 200 entries." });
25
+ });
26
+ var modelPolicySchema = z.object({
27
+ version: z.literal(1).default(1),
28
+ roles: z.object({
29
+ subagent: policyModelIdSchema.optional(),
30
+ fast: policyModelIdSchema.optional(),
31
+ planning: policyModelIdSchema.optional(),
32
+ review: policyModelIdSchema.optional(),
33
+ summary: policyModelIdSchema.optional(),
34
+ compaction: policyModelIdSchema.optional(),
35
+ weak: policyModelIdSchema.optional(),
36
+ editor: policyModelIdSchema.optional()
37
+ }).strict().optional(),
38
+ allowedModels: boundedModelList.optional(),
39
+ aliases: boundedModelMap.optional(),
40
+ fallbacks: fallbacksSchema.optional()
41
+ }).strict();
42
+ var routingDecisionSchema = z.enum(["allow", "alias", "reject", "fallback"]);
43
+ var routingEventRoleSchema = z.enum(["main", ...modelPolicyRoleSchema.options]);
44
+ var routingEventSchema = z.object({
45
+ at: z.string().datetime({ offset: true }),
46
+ requestId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/),
47
+ requestedModel: policyModelIdSchema,
48
+ resolvedModel: policyModelIdSchema.optional(),
49
+ reportedModel: policyModelIdSchema.optional(),
50
+ decision: routingDecisionSchema,
51
+ role: routingEventRoleSchema.optional(),
52
+ reason: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional(),
53
+ upstreamStatus: z.number().int().min(100).max(599).optional()
54
+ }).strict();
55
+ var routingEventsSchema = z.array(routingEventSchema).max(1000);
56
+
57
+ // src/domain.ts
58
+ var VERSION = "0.1.3";
59
+ var harnessSchema = z2.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
60
+ var protocolSchema = z2.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
61
+ var idSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
62
+ var label = z2.string().min(1).max(200);
63
+ var envRef = z2.string().regex(/^SWITCHER_PROVIDER_[A-Z0-9_]+$/);
17
64
  function endpoint(value) {
18
65
  let url;
19
66
  try {
@@ -26,73 +73,79 @@ function endpoint(value) {
26
73
  throw new Fault(400, "invalid_url", "URL must use HTTPS, contain no credentials/query/fragment, or use HTTP on loopback.");
27
74
  return url.href.replace(/\/+$/, "");
28
75
  }
29
- var urlSchema = z.string().max(2000).superRefine((v, ctx) => {
76
+ var urlSchema = z2.string().max(2000).superRefine((v, ctx) => {
30
77
  try {
31
78
  endpoint(v);
32
79
  } catch {
33
80
  ctx.addIssue({ code: "custom", message: "Invalid endpoint URL" });
34
81
  }
35
82
  }).transform(endpoint);
36
- var modelSchema = z.object({
37
- id: z.string().min(1).max(300),
83
+ var modelSchema = z2.object({
84
+ id: z2.string().min(1).max(300),
38
85
  name: label,
39
- description: z.string().max(8000).optional(),
40
- available: z.boolean().optional(),
41
- contextWindow: z.number().int().positive().optional(),
42
- maxOutputTokens: z.number().int().positive().optional(),
43
- inputModalities: z.array(z.string().max(50)).max(20).optional(),
44
- outputModalities: z.array(z.string().max(50)).max(20).optional(),
45
- supportedParameters: z.array(z.string().max(100)).max(100).optional()
86
+ description: z2.string().max(8000).optional(),
87
+ available: z2.boolean().optional(),
88
+ contextWindow: z2.number().int().positive().optional(),
89
+ maxOutputTokens: z2.number().int().positive().optional(),
90
+ inputModalities: z2.array(z2.string().max(50)).max(20).optional(),
91
+ outputModalities: z2.array(z2.string().max(50)).max(20).optional(),
92
+ supportedParameters: z2.array(z2.string().max(100)).max(100).optional(),
93
+ supportedGenerationMethods: z2.array(z2.string().min(1).max(100)).max(100).optional()
46
94
  }).strict();
47
- var providerInputSchema = z.object({
95
+ var providerInputSchema = z2.object({
48
96
  id: idSchema,
49
97
  name: label,
50
98
  baseUrl: urlSchema,
51
99
  protocol: protocolSchema,
52
100
  credentialEnv: envRef.optional(),
53
- authStyle: z.enum(["bearer", "x-api-key"]).default("bearer"),
101
+ authStyle: z2.enum(["bearer", "x-api-key", "api-key"]).default("bearer"),
54
102
  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(),
103
+ catalogFormat: z2.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]).optional(),
104
+ catalogAuthStyle: z2.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
57
105
  catalogCredentialEnv: envRef.optional(),
58
- catalogAccountId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
59
- modelsPath: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
60
- manualModels: z.array(modelSchema).max(1e4).default([])
106
+ catalogAccountId: z2.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
107
+ modelsPath: z2.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
108
+ manualModels: z2.array(modelSchema).max(1e4).default([])
61
109
  }).strict().refine((p) => !p.modelsPath.split("/").includes("..") && !p.modelsPath.startsWith("/"), "modelsPath must be relative");
62
- var providerPresetSchema = z.object({
110
+ var providerPresetSchema = z2.object({
63
111
  id: idSchema,
64
112
  name: label,
65
113
  credentialEnv: envRef.optional(),
66
- credentialAliases: z.array(z.string().regex(/^[A-Z][A-Z0-9_]+$/)),
67
- protocols: z.array(z.object({
114
+ credentialAliases: z2.array(z2.string().regex(/^[A-Z][A-Z0-9_]+$/)),
115
+ protocols: z2.array(z2.object({
68
116
  protocol: protocolSchema,
69
117
  baseUrl: urlSchema.optional(),
70
- authStyle: z.enum(["bearer", "x-api-key"]),
118
+ authStyle: z2.enum(["bearer", "x-api-key", "api-key"]),
71
119
  catalogBaseUrl: urlSchema.optional(),
72
- catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]),
73
- catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
74
- modelsPath: z.string(),
75
- notes: z.array(z.string())
120
+ catalogFormat: z2.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]),
121
+ catalogAuthStyle: z2.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
122
+ modelsPath: z2.string(),
123
+ notes: z2.array(z2.string())
76
124
  }).strict()).min(1),
77
- sources: z.array(z.string().url()),
78
- verification: z.literal("documented")
125
+ sources: z2.array(z2.string().url()),
126
+ verification: z2.literal("documented")
79
127
  }).strict();
80
- var profileInputSchema = z.object({
128
+ var profileInputSchema = z2.object({
81
129
  id: idSchema,
82
130
  name: label,
83
131
  providerId: idSchema,
84
132
  harness: harnessSchema,
85
- model: z.string().min(1).max(300)
133
+ model: z2.string().min(1).max(300),
134
+ modelPolicy: modelPolicySchema.optional()
86
135
  }).strict();
87
- var runInputSchema = z.object({
136
+ var runInputSchema = z2.object({
137
+ modelPolicyVersion: z2.literal(1),
88
138
  profileId: idSchema,
89
139
  harness: harnessSchema,
90
- model: z.string().min(1).max(300),
91
- planToken: z.string().regex(/^[a-f0-9]{64}$/)
140
+ model: z2.string().min(1).max(300),
141
+ modelPolicy: modelPolicySchema.optional(),
142
+ planToken: z2.string().regex(/^[a-f0-9]{64}$/)
92
143
  }).strict();
93
- var runUpdateSchema = z.object({
94
- status: z.enum(["exited", "failed", "interrupted"]),
95
- exitCode: z.number().int().min(0).max(255)
144
+ var runUpdateSchema = z2.object({
145
+ status: z2.enum(["exited", "failed", "interrupted"]),
146
+ exitCode: z2.number().int().min(0).max(255),
147
+ routingEvents: routingEventsSchema.optional(),
148
+ routingEventsDropped: z2.number().int().min(0).max(1e6).optional()
96
149
  }).strict();
97
150
 
98
151
  class Fault extends Error {
@@ -174,7 +227,18 @@ var providerPresets = [
174
227
  ], ["https://api-docs.deepseek.com/guides/anthropic_api", "https://api-docs.deepseek.com/api/list-models"], "DEEPSEEK_API_KEY"),
175
228
  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
229
  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"),
230
+ preset("gemini", "Google Gemini", [route("gemini-generate-content", "https://generativelanguage.googleapis.com/v1beta", {
231
+ authStyle: "x-api-key",
232
+ catalogBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
233
+ catalogFormat: "gemini",
234
+ catalogAuthStyle: "x-api-key",
235
+ notes: ["Gemini CLI uses the native generateContent wire with x-goog-api-key authentication; model IDs are returned as models/{id}."]
236
+ }), 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
237
  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"),
238
+ preset("azure-openai", "Azure OpenAI (v1)", [
239
+ 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."] }),
240
+ 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."] })
241
+ ], ["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
242
  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
243
  preset("ollama", "Ollama", ["openai-chat", "openai-responses"].map((protocol) => route(protocol, "http://127.0.0.1:11434/v1", {
180
244
  catalogBaseUrl: "http://127.0.0.1:11434",
@@ -234,6 +298,26 @@ class SwitcherError extends Error {
234
298
  this.requestId = requestId;
235
299
  }
236
300
  }
301
+ function apiError(status, data, apiKey) {
302
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
303
+ const error = object(data) && object(data.error) ? data.error : {};
304
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
305
+ const base64 = Buffer.from(apiKey, "utf8").toString("base64");
306
+ const literal = [apiKey, JSON.stringify(apiKey).slice(1, -1), base64, base64.replace(/=+$/, ""), Buffer.from(apiKey, "utf8").toString("base64url")];
307
+ const encoded = [encodeURIComponent(apiKey), new URLSearchParams({ key: apiKey }).toString().slice(4)];
308
+ const patterns = [...new Set(literal)].sort((a, b) => b.length - a.length).map(escape);
309
+ for (const value of encoded) {
310
+ patterns.unshift(escape(value).replace(/%[0-9A-F]{2}/g, (part) => part.replace(/[A-F]/g, (letter) => `[${letter}${letter.toLowerCase()}]`)));
311
+ }
312
+ const reflected = new RegExp(patterns.join("|"), "g");
313
+ const redact = (value) => value.replace(reflected, "[REDACTED]");
314
+ const identifier = (value, pattern) => typeof value === "string" && pattern.test(value) && redact(value) === value ? value : undefined;
315
+ const code = identifier(error.code, /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/) ?? "api_error";
316
+ const requestId = identifier(error.requestId, /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
317
+ const fallback = `Switcher API returned HTTP ${status}.`;
318
+ 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;
319
+ return new SwitcherError(status, code, message, requestId);
320
+ }
237
321
 
238
322
  class SwitcherClient {
239
323
  options;
@@ -270,7 +354,7 @@ class SwitcherClient {
270
354
  throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
271
355
  }
272
356
  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);
357
+ throw apiError(response.status, data, apiKey);
274
358
  return data;
275
359
  }
276
360
  query(options = {}) {
@@ -337,7 +421,7 @@ class SwitcherClient {
337
421
  return this.request("GET", `/v1/runs/${encodeURIComponent(id)}`);
338
422
  }
339
423
  createRun(input, idempotencyKey) {
340
- return this.request("POST", "/v1/runs", input, { idempotencyKey });
424
+ return this.request("POST", "/v1/runs", { ...input, modelPolicyVersion: 1 }, { idempotencyKey });
341
425
  }
342
426
  finishRun(id, version, input, idempotencyKey) {
343
427
  return this.request("PATCH", `/v1/runs/${encodeURIComponent(id)}`, input, { version, idempotencyKey });
@@ -352,7 +436,7 @@ function clientFromEnv(env = process.env) {
352
436
 
353
437
  // src/mcp.ts
354
438
  var server = new McpServer({ name: "switcher", version: VERSION });
355
- var page = { limit: z2.number().int().min(1).max(1000).optional(), offset: z2.number().int().nonnegative().optional(), search: z2.string().optional() };
439
+ var page = { limit: z3.number().int().min(1).max(1000).optional(), offset: z3.number().int().nonnegative().optional(), search: z3.string().optional() };
356
440
  function tool(name, description, schema, run) {
357
441
  server.tool(name, description, schema, async (input) => {
358
442
  try {
@@ -363,23 +447,23 @@ function tool(name, description, schema, run) {
363
447
  });
364
448
  }
365
449
  tool("providers_list", "List provider profiles.", page, (p) => clientFromEnv().listProviders(p));
366
- tool("providers_get", "Get a provider.", { id: z2.string() }, (p) => clientFromEnv().getProvider(p.id));
450
+ tool("providers_get", "Get a provider.", { id: z3.string() }, (p) => clientFromEnv().getProvider(p.id));
367
451
  tool("providers_create", "Create a provider using credential environment references only.", providerInputSchema.innerType().shape, (p) => clientFromEnv().createProvider(p));
368
- tool("providers_update", "Replace a provider at its current version.", { provider: providerInputSchema, version: z2.number().int() }, (p) => clientFromEnv().updateProvider(p.provider, p.version));
369
- tool("providers_delete", "Delete an unreferenced provider.", { id: z2.string(), version: z2.number().int() }, (p) => clientFromEnv().deleteProvider(p.id, p.version));
370
- tool("models_list", "List catalog with capability information.", { id: z2.string(), ...page }, (p) => {
452
+ tool("providers_update", "Replace a provider at its current version.", { provider: providerInputSchema, version: z3.number().int() }, (p) => clientFromEnv().updateProvider(p.provider, p.version));
453
+ tool("providers_delete", "Delete an unreferenced provider.", { id: z3.string(), version: z3.number().int() }, (p) => clientFromEnv().deleteProvider(p.id, p.version));
454
+ tool("models_list", "List catalog with capability information.", { id: z3.string(), ...page }, (p) => {
371
455
  const { id, ...rest } = p;
372
456
  return clientFromEnv().listModels(id, rest);
373
457
  });
374
- tool("models_refresh", "Discover provider models.", { id: z2.string() }, (p) => clientFromEnv().refreshModels(p.id));
458
+ tool("models_refresh", "Discover provider models.", { id: z3.string() }, (p) => clientFromEnv().refreshModels(p.id));
375
459
  tool("profiles_list", "List harness launch profiles.", page, (p) => clientFromEnv().listProfiles(p));
376
- tool("profiles_get", "Get a harness profile.", { id: z2.string() }, (p) => clientFromEnv().getProfile(p.id));
460
+ tool("profiles_get", "Get a harness profile.", { id: z3.string() }, (p) => clientFromEnv().getProfile(p.id));
377
461
  tool("profiles_create", "Create a harness launch profile.", profileInputSchema.shape, (p) => clientFromEnv().createProfile(p));
378
- tool("profiles_update", "Replace a harness profile at its version.", { profile: profileInputSchema, version: z2.number().int() }, (p) => clientFromEnv().updateProfile(p.profile, p.version));
379
- tool("profiles_delete", "Delete a profile without run history.", { id: z2.string(), version: z2.number().int() }, (p) => clientFromEnv().deleteProfile(p.id, p.version));
380
- tool("launch_plan", "Validate a local launch plan; does not execute a remote process.", { profileId: z2.string() }, (p) => clientFromEnv().launchPlan(p.profileId));
462
+ tool("profiles_update", "Replace a harness profile at its version.", { profile: profileInputSchema, version: z3.number().int() }, (p) => clientFromEnv().updateProfile(p.profile, p.version));
463
+ tool("profiles_delete", "Delete a profile without run history.", { id: z3.string(), version: z3.number().int() }, (p) => clientFromEnv().deleteProfile(p.id, p.version));
464
+ tool("launch_plan", "Validate a local launch plan; does not execute a remote process.", { profileId: z3.string() }, (p) => clientFromEnv().launchPlan(p.profileId));
381
465
  tool("runs_list", "List launch metadata.", page, (p) => clientFromEnv().listRuns(p));
382
- tool("runs_get", "Get launch metadata.", { id: z2.string() }, (p) => clientFromEnv().getRun(p.id));
466
+ tool("runs_get", "Get launch metadata.", { id: z3.string() }, (p) => clientFromEnv().getRun(p.id));
383
467
  if (process.argv.includes("--version"))
384
468
  console.log(VERSION);
385
469
  else if (process.argv.includes("--help"))
@@ -0,0 +1,137 @@
1
+ import { z } from "zod";
2
+ /** Model references are opaque provider IDs, never prompts or credentials. */
3
+ export declare const policyModelIdSchema: z.ZodString;
4
+ export declare const modelPolicyRoleSchema: z.ZodEnum<["subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"]>;
5
+ export type ModelPolicyRole = z.infer<typeof modelPolicyRoleSchema>;
6
+ export declare const modelPolicySchema: z.ZodObject<{
7
+ version: z.ZodDefault<z.ZodLiteral<1>>;
8
+ roles: z.ZodOptional<z.ZodObject<{
9
+ subagent: z.ZodOptional<z.ZodString>;
10
+ fast: z.ZodOptional<z.ZodString>;
11
+ planning: z.ZodOptional<z.ZodString>;
12
+ review: z.ZodOptional<z.ZodString>;
13
+ summary: z.ZodOptional<z.ZodString>;
14
+ compaction: z.ZodOptional<z.ZodString>;
15
+ weak: z.ZodOptional<z.ZodString>;
16
+ editor: z.ZodOptional<z.ZodString>;
17
+ }, "strict", z.ZodTypeAny, {
18
+ subagent?: string | undefined;
19
+ fast?: string | undefined;
20
+ planning?: string | undefined;
21
+ review?: string | undefined;
22
+ summary?: string | undefined;
23
+ compaction?: string | undefined;
24
+ weak?: string | undefined;
25
+ editor?: string | undefined;
26
+ }, {
27
+ subagent?: string | undefined;
28
+ fast?: string | undefined;
29
+ planning?: string | undefined;
30
+ review?: string | undefined;
31
+ summary?: string | undefined;
32
+ compaction?: string | undefined;
33
+ weak?: string | undefined;
34
+ editor?: string | undefined;
35
+ }>>;
36
+ allowedModels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
37
+ aliases: z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodString>, Record<string, string>, Record<string, string>>>;
38
+ fallbacks: z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>, Record<string, string[]>, Record<string, string[]>>>;
39
+ }, "strict", z.ZodTypeAny, {
40
+ version: 1;
41
+ roles?: {
42
+ subagent?: string | undefined;
43
+ fast?: string | undefined;
44
+ planning?: string | undefined;
45
+ review?: string | undefined;
46
+ summary?: string | undefined;
47
+ compaction?: string | undefined;
48
+ weak?: string | undefined;
49
+ editor?: string | undefined;
50
+ } | undefined;
51
+ allowedModels?: string[] | undefined;
52
+ aliases?: Record<string, string> | undefined;
53
+ fallbacks?: Record<string, string[]> | undefined;
54
+ }, {
55
+ version?: 1 | undefined;
56
+ roles?: {
57
+ subagent?: string | undefined;
58
+ fast?: string | undefined;
59
+ planning?: string | undefined;
60
+ review?: string | undefined;
61
+ summary?: string | undefined;
62
+ compaction?: string | undefined;
63
+ weak?: string | undefined;
64
+ editor?: string | undefined;
65
+ } | undefined;
66
+ allowedModels?: string[] | undefined;
67
+ aliases?: Record<string, string> | undefined;
68
+ fallbacks?: Record<string, string[]> | undefined;
69
+ }>;
70
+ export type ModelPolicy = z.infer<typeof modelPolicySchema>;
71
+ export declare const routingDecisionSchema: z.ZodEnum<["allow", "alias", "reject", "fallback"]>;
72
+ export declare const routingEventRoleSchema: z.ZodEnum<["main", "subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"]>;
73
+ export declare const routingEventSchema: z.ZodObject<{
74
+ at: z.ZodString;
75
+ requestId: z.ZodString;
76
+ requestedModel: z.ZodString;
77
+ resolvedModel: z.ZodOptional<z.ZodString>;
78
+ reportedModel: z.ZodOptional<z.ZodString>;
79
+ decision: z.ZodEnum<["allow", "alias", "reject", "fallback"]>;
80
+ role: z.ZodOptional<z.ZodEnum<["main", "subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"]>>;
81
+ reason: z.ZodOptional<z.ZodString>;
82
+ upstreamStatus: z.ZodOptional<z.ZodNumber>;
83
+ }, "strict", z.ZodTypeAny, {
84
+ at: string;
85
+ requestId: string;
86
+ requestedModel: string;
87
+ decision: "allow" | "alias" | "reject" | "fallback";
88
+ resolvedModel?: string | undefined;
89
+ reportedModel?: string | undefined;
90
+ role?: "main" | "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor" | undefined;
91
+ reason?: string | undefined;
92
+ upstreamStatus?: number | undefined;
93
+ }, {
94
+ at: string;
95
+ requestId: string;
96
+ requestedModel: string;
97
+ decision: "allow" | "alias" | "reject" | "fallback";
98
+ resolvedModel?: string | undefined;
99
+ reportedModel?: string | undefined;
100
+ role?: "main" | "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor" | undefined;
101
+ reason?: string | undefined;
102
+ upstreamStatus?: number | undefined;
103
+ }>;
104
+ export type RoutingEvent = z.infer<typeof routingEventSchema>;
105
+ export declare const routingEventsSchema: z.ZodArray<z.ZodObject<{
106
+ at: z.ZodString;
107
+ requestId: z.ZodString;
108
+ requestedModel: z.ZodString;
109
+ resolvedModel: z.ZodOptional<z.ZodString>;
110
+ reportedModel: z.ZodOptional<z.ZodString>;
111
+ decision: z.ZodEnum<["allow", "alias", "reject", "fallback"]>;
112
+ role: z.ZodOptional<z.ZodEnum<["main", "subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"]>>;
113
+ reason: z.ZodOptional<z.ZodString>;
114
+ upstreamStatus: z.ZodOptional<z.ZodNumber>;
115
+ }, "strict", z.ZodTypeAny, {
116
+ at: string;
117
+ requestId: string;
118
+ requestedModel: string;
119
+ decision: "allow" | "alias" | "reject" | "fallback";
120
+ resolvedModel?: string | undefined;
121
+ reportedModel?: string | undefined;
122
+ role?: "main" | "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor" | undefined;
123
+ reason?: string | undefined;
124
+ upstreamStatus?: number | undefined;
125
+ }, {
126
+ at: string;
127
+ requestId: string;
128
+ requestedModel: string;
129
+ decision: "allow" | "alias" | "reject" | "fallback";
130
+ resolvedModel?: string | undefined;
131
+ reportedModel?: string | undefined;
132
+ role?: "main" | "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor" | undefined;
133
+ reason?: string | undefined;
134
+ upstreamStatus?: number | undefined;
135
+ }>, "many">;
136
+ /** Stable object-key order; ordered fallback arrays retain their precedence. */
137
+ export declare function canonicalPolicyJSON(value: unknown): string;
@@ -0,0 +1,31 @@
1
+ import { type Model } from "./domain";
2
+ export declare const MODEL_POLICY_VERSION: 1;
3
+ export type ModelRole = "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor";
4
+ export type ModelPolicy = {
5
+ version?: 1;
6
+ roles?: Partial<Record<ModelRole, string>>;
7
+ allowedModels?: string[];
8
+ aliases?: Record<string, string>;
9
+ fallbacks?: Record<string, string[]>;
10
+ };
11
+ export type CompiledModelPolicy = {
12
+ version: 1;
13
+ model: string;
14
+ roles: Record<ModelRole, string>;
15
+ allowedModels: string[];
16
+ aliases: Record<string, string>;
17
+ fallbacks: Record<string, string[]>;
18
+ digest: string;
19
+ };
20
+ export type ModelGuidanceContext = {
21
+ harness: string;
22
+ providerId?: string;
23
+ baseUrl?: string;
24
+ model: string;
25
+ compiled: CompiledModelPolicy;
26
+ catalogPath?: string;
27
+ };
28
+ export declare function compileModelPolicy(model: string, catalog: readonly Model[], policy?: ModelPolicy): CompiledModelPolicy;
29
+ export declare function resolvePolicyModel(compiled: CompiledModelPolicy, requested: string): string;
30
+ export declare function renderModelGuidance(context: ModelGuidanceContext): string;
31
+ export declare function injectModelGuidance(protocol: "anthropic-messages" | "openai-chat" | "openai-responses" | "gemini-generate-content", body: unknown, guidance: string, operation?: string): unknown;
@@ -0,0 +1,23 @@
1
+ /** Pure compilation of the documented native model-policy surfaces.
2
+ *
3
+ * This module deliberately does not launch processes, read configuration, or
4
+ * infer provider aliases. Callers provide the exact model IDs they have
5
+ * already selected and may then serialize the returned env/config values.
6
+ */
7
+ export declare const nativeRoles: readonly ["main", "subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"];
8
+ export type NativeRole = typeof nativeRoles[number];
9
+ export type NativePolicyHarness = "claude" | "codex" | "grok" | "opencode" | "opencode2" | "omp" | "hermes" | "aider" | "kilo" | "gemini" | "cline" | "dsh" | "pi" | "prime-agent";
10
+ export type NativeModelPolicyInput = {
11
+ harness: NativePolicyHarness;
12
+ mainModel: string;
13
+ roles?: Partial<Record<NativeRole, string>>;
14
+ version?: string;
15
+ };
16
+ export type NativeModelPolicy = {
17
+ harness: NativePolicyHarness;
18
+ env: Record<string, string>;
19
+ config: Record<string, unknown>;
20
+ unsupportedRoles: NativeRole[];
21
+ };
22
+ /** Compile exact role assignments into native settings/env without inventing knobs. */
23
+ export declare function compileNativeModelPolicy(input: NativeModelPolicyInput): NativeModelPolicy;
@@ -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>;
@@ -0,0 +1,33 @@
1
+ import type { ModelPolicy } from "./model-policy-schema";
2
+ type Dict = Record<string, unknown>;
3
+ export type OpenCodeRole = "main" | "subagent" | "planning" | "summary" | "compaction";
4
+ export type OpenCodeCompiledRoles = Partial<Record<OpenCodeRole, string>>;
5
+ export type PreservedOpenCodeAgent = Dict & {
6
+ name: string;
7
+ };
8
+ export type OpenCodeModelPolicyInput = {
9
+ providerId: string;
10
+ mainModel: string;
11
+ roles?: OpenCodeCompiledRoles | ModelPolicy["roles"];
12
+ preservedAgents?: PreservedOpenCodeAgent[];
13
+ /** OpenCode 2's native `agents` table or legacy OpenCode's `agent` table. */
14
+ format?: "v2" | "legacy";
15
+ };
16
+ export type OpenCodeModelPolicyResult = {
17
+ model: string;
18
+ agents: PreservedOpenCodeAgent[];
19
+ /** Only native OpenCode fields are emitted. */
20
+ config: {
21
+ model: string;
22
+ agents?: Record<string, Dict>;
23
+ agent?: Record<string, Dict>;
24
+ };
25
+ };
26
+ /**
27
+ * Merge Switcher routing into native OpenCode agent declarations. Prompts,
28
+ * permissions, modes and other native fields are copied without alteration.
29
+ */
30
+ export declare function compileOpenCodeModelPolicy(input: OpenCodeModelPolicyInput): OpenCodeModelPolicyResult;
31
+ /** Match OpenCode's explicit agent selection without interpreting prompt values as flags. */
32
+ export declare function openCodeInvocationModel(args: readonly string[], policy: OpenCodeModelPolicyResult, defaultAgent?: string): string;
33
+ export {};
@@ -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";
@@ -0,0 +1,8 @@
1
+ import type { PreparedLaunch } from "./harness-types";
2
+ /** Execute the verified native launch after Ori finishes its OpenRouter setup.
3
+ * Credentials are remapped from process environment, never written into the shim. */
4
+ export declare function prepareOriModelPolicy(target: "codex" | "grok", prepared: Pick<PreparedLaunch, "executable" | "args" | "env">): {
5
+ name: "codex" | "grok";
6
+ env: Record<string, string>;
7
+ script: string;
8
+ };
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
  };