@hasna/switcher 0.1.2 → 0.1.4
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/README.md +20 -8
- package/dist/cli/index.js +2725 -621
- package/dist/cli.d.ts +2 -0
- package/dist/codex-model-policy.d.ts +65 -0
- package/dist/credentials.d.ts +71 -8
- package/dist/direct-launch.d.ts +2 -2
- package/dist/domain.d.ts +259 -2
- package/dist/gemini-model-policy.d.ts +56 -0
- package/dist/generated/api.d.ts +192 -0
- package/dist/harness-types.d.ts +9 -0
- package/dist/hermes-model-policy.d.ts +30 -0
- package/dist/index.js +138 -48
- package/dist/inference-gateway.d.ts +24 -0
- package/dist/launcher.d.ts +8 -3
- package/dist/mcp/index.js +153 -62
- package/dist/model-policy-schema.d.ts +137 -0
- package/dist/model-policy.d.ts +31 -0
- package/dist/native-model-policy.d.ts +23 -0
- package/dist/opencode-model-policy.d.ts +33 -0
- package/dist/ori-model-policy.d.ts +8 -0
- package/dist/sdk.d.ts +248 -6
- package/dist/sdk.js +138 -48
- package/dist/serve/index.js +1044 -88
- package/docs/MODEL-POLICY.md +82 -0
- package/openapi.json +836 -1
- package/package.json +3 -2
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
|
|
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
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var
|
|
16
|
-
|
|
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.4";
|
|
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,74 +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 =
|
|
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 =
|
|
37
|
-
id:
|
|
83
|
+
var modelSchema = z2.object({
|
|
84
|
+
id: z2.string().min(1).max(300),
|
|
38
85
|
name: label,
|
|
39
|
-
description:
|
|
40
|
-
available:
|
|
41
|
-
contextWindow:
|
|
42
|
-
maxOutputTokens:
|
|
43
|
-
inputModalities:
|
|
44
|
-
outputModalities:
|
|
45
|
-
supportedParameters:
|
|
46
|
-
supportedGenerationMethods:
|
|
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()
|
|
47
94
|
}).strict();
|
|
48
|
-
var providerInputSchema =
|
|
95
|
+
var providerInputSchema = z2.object({
|
|
49
96
|
id: idSchema,
|
|
50
97
|
name: label,
|
|
51
98
|
baseUrl: urlSchema,
|
|
52
99
|
protocol: protocolSchema,
|
|
53
100
|
credentialEnv: envRef.optional(),
|
|
54
|
-
authStyle:
|
|
101
|
+
authStyle: z2.enum(["bearer", "x-api-key", "api-key"]).default("bearer"),
|
|
55
102
|
catalogBaseUrl: urlSchema.optional(),
|
|
56
|
-
catalogFormat:
|
|
57
|
-
catalogAuthStyle:
|
|
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(),
|
|
58
105
|
catalogCredentialEnv: envRef.optional(),
|
|
59
|
-
catalogAccountId:
|
|
60
|
-
modelsPath:
|
|
61
|
-
manualModels:
|
|
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([])
|
|
62
109
|
}).strict().refine((p) => !p.modelsPath.split("/").includes("..") && !p.modelsPath.startsWith("/"), "modelsPath must be relative");
|
|
63
|
-
var providerPresetSchema =
|
|
110
|
+
var providerPresetSchema = z2.object({
|
|
64
111
|
id: idSchema,
|
|
65
112
|
name: label,
|
|
66
113
|
credentialEnv: envRef.optional(),
|
|
67
|
-
credentialAliases:
|
|
68
|
-
protocols:
|
|
114
|
+
credentialAliases: z2.array(z2.string().regex(/^[A-Z][A-Z0-9_]+$/)),
|
|
115
|
+
protocols: z2.array(z2.object({
|
|
69
116
|
protocol: protocolSchema,
|
|
70
117
|
baseUrl: urlSchema.optional(),
|
|
71
|
-
authStyle:
|
|
118
|
+
authStyle: z2.enum(["bearer", "x-api-key", "api-key"]),
|
|
72
119
|
catalogBaseUrl: urlSchema.optional(),
|
|
73
|
-
catalogFormat:
|
|
74
|
-
catalogAuthStyle:
|
|
75
|
-
modelsPath:
|
|
76
|
-
notes:
|
|
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())
|
|
77
124
|
}).strict()).min(1),
|
|
78
|
-
sources:
|
|
79
|
-
verification:
|
|
125
|
+
sources: z2.array(z2.string().url()),
|
|
126
|
+
verification: z2.literal("documented")
|
|
80
127
|
}).strict();
|
|
81
|
-
var profileInputSchema =
|
|
128
|
+
var profileInputSchema = z2.object({
|
|
82
129
|
id: idSchema,
|
|
83
130
|
name: label,
|
|
84
131
|
providerId: idSchema,
|
|
85
132
|
harness: harnessSchema,
|
|
86
|
-
model:
|
|
133
|
+
model: z2.string().min(1).max(300),
|
|
134
|
+
modelPolicy: modelPolicySchema.optional()
|
|
87
135
|
}).strict();
|
|
88
|
-
var runInputSchema =
|
|
136
|
+
var runInputSchema = z2.object({
|
|
137
|
+
modelPolicyVersion: z2.literal(1),
|
|
89
138
|
profileId: idSchema,
|
|
90
139
|
harness: harnessSchema,
|
|
91
|
-
model:
|
|
92
|
-
|
|
140
|
+
model: z2.string().min(1).max(300),
|
|
141
|
+
modelPolicy: modelPolicySchema.optional(),
|
|
142
|
+
planToken: z2.string().regex(/^[a-f0-9]{64}$/)
|
|
93
143
|
}).strict();
|
|
94
|
-
var runUpdateSchema =
|
|
95
|
-
status:
|
|
96
|
-
exitCode:
|
|
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()
|
|
97
149
|
}).strict();
|
|
98
150
|
|
|
99
151
|
class Fault extends Error {
|
|
@@ -147,7 +199,7 @@ async function boundedJson(response, maxBytes = MAX_BYTES) {
|
|
|
147
199
|
}
|
|
148
200
|
|
|
149
201
|
// src/sdk.ts
|
|
150
|
-
import {
|
|
202
|
+
import { createClientTransport } from "@hasna/contracts/client";
|
|
151
203
|
|
|
152
204
|
// src/presets.ts
|
|
153
205
|
var route = (protocol, baseUrl, options = {}) => ({
|
|
@@ -269,6 +321,7 @@ function apiError(status, data, apiKey) {
|
|
|
269
321
|
|
|
270
322
|
class SwitcherClient {
|
|
271
323
|
options;
|
|
324
|
+
transport;
|
|
272
325
|
baseUrl;
|
|
273
326
|
constructor(options) {
|
|
274
327
|
this.baseUrl = endpoint(options.baseUrl).replace(/\/v1$/, "");
|
|
@@ -276,9 +329,50 @@ class SwitcherClient {
|
|
|
276
329
|
throw new Error("Switcher API key is required.");
|
|
277
330
|
this.options = { ...options };
|
|
278
331
|
}
|
|
332
|
+
static fromEnvironment(env = process.env, options = {}) {
|
|
333
|
+
const shared = createClientTransport("switcher", env, {
|
|
334
|
+
credentials: options.credentials,
|
|
335
|
+
timeoutMs: options.timeoutMs ?? 120000,
|
|
336
|
+
retry: false,
|
|
337
|
+
fetchImpl: async (input, init) => {
|
|
338
|
+
const address = String(input).replace(/\/v1\/(health|ready|version)$/, "/$1");
|
|
339
|
+
const key = new Headers(init?.headers).get("x-api-key") ?? "";
|
|
340
|
+
let response;
|
|
341
|
+
try {
|
|
342
|
+
response = await (options.fetch ?? fetch)(address, init);
|
|
343
|
+
} catch {
|
|
344
|
+
throw new SwitcherError(0, "connection_failed", "Switcher API request failed; check endpoint and service availability.");
|
|
345
|
+
}
|
|
346
|
+
if (response.status === 401 || response.status === 403) {
|
|
347
|
+
await response.body?.cancel().catch(() => {});
|
|
348
|
+
throw new SwitcherError(response.status, "api_error", `Switcher API returned HTTP ${response.status}. Check the configured credential; no alternate identity was selected.`);
|
|
349
|
+
}
|
|
350
|
+
let data;
|
|
351
|
+
try {
|
|
352
|
+
data = await boundedJson(response);
|
|
353
|
+
} catch {
|
|
354
|
+
throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
|
|
355
|
+
}
|
|
356
|
+
if (!response.ok)
|
|
357
|
+
throw apiError(response.status, data, key);
|
|
358
|
+
return Response.json(data);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
const client = new SwitcherClient({ baseUrl: shared.resolution.baseUrl, apiKey: () => {
|
|
362
|
+
throw new Error("Shared credential transport was not invoked.");
|
|
363
|
+
} });
|
|
364
|
+
client.transport = shared.client;
|
|
365
|
+
return client;
|
|
366
|
+
}
|
|
279
367
|
async request(method, path, body, options = {}) {
|
|
280
368
|
if (!/^\/v1\/[a-zA-Z0-9/?&=._%+-]+$/.test(path) && !["/health", "/ready", "/version"].includes(path) || path.includes(".."))
|
|
281
369
|
throw new Error("Invalid API path.");
|
|
370
|
+
if (this.transport)
|
|
371
|
+
return this.transport.request(method, path.startsWith("/v1/") ? path.slice(3) : path, body, {
|
|
372
|
+
idempotencyKey: method === "GET" ? undefined : options.idempotencyKey ?? crypto.randomUUID(),
|
|
373
|
+
headers: options.version === undefined ? undefined : { "if-match": String(options.version) },
|
|
374
|
+
retry: false
|
|
375
|
+
});
|
|
282
376
|
const apiKey = typeof this.options.apiKey === "function" ? this.options.apiKey() : this.options.apiKey;
|
|
283
377
|
if (!apiKey || /[\r\n]/.test(apiKey))
|
|
284
378
|
throw new Error("Switcher API key is required.");
|
|
@@ -369,22 +463,19 @@ class SwitcherClient {
|
|
|
369
463
|
return this.request("GET", `/v1/runs/${encodeURIComponent(id)}`);
|
|
370
464
|
}
|
|
371
465
|
createRun(input, idempotencyKey) {
|
|
372
|
-
return this.request("POST", "/v1/runs", input, { idempotencyKey });
|
|
466
|
+
return this.request("POST", "/v1/runs", { ...input, modelPolicyVersion: 1 }, { idempotencyKey });
|
|
373
467
|
}
|
|
374
468
|
finishRun(id, version, input, idempotencyKey) {
|
|
375
469
|
return this.request("PATCH", `/v1/runs/${encodeURIComponent(id)}`, input, { version, idempotencyKey });
|
|
376
470
|
}
|
|
377
471
|
}
|
|
378
|
-
function clientFromEnv(env = process.env) {
|
|
379
|
-
|
|
380
|
-
if (!env.HASNA_SWITCHER_API_URL || !credential())
|
|
381
|
-
throw new Error("Set HASNA_SWITCHER_API_URL and HASNA_SWITCHER_API_KEY; no local database fallback is available.");
|
|
382
|
-
return new SwitcherClient({ baseUrl: env.HASNA_SWITCHER_API_URL, apiKey: credential });
|
|
472
|
+
function clientFromEnv(env = process.env, options = {}) {
|
|
473
|
+
return SwitcherClient.fromEnvironment(env, options);
|
|
383
474
|
}
|
|
384
475
|
|
|
385
476
|
// src/mcp.ts
|
|
386
477
|
var server = new McpServer({ name: "switcher", version: VERSION });
|
|
387
|
-
var page = { limit:
|
|
478
|
+
var page = { limit: z3.number().int().min(1).max(1000).optional(), offset: z3.number().int().nonnegative().optional(), search: z3.string().optional() };
|
|
388
479
|
function tool(name, description, schema, run) {
|
|
389
480
|
server.tool(name, description, schema, async (input) => {
|
|
390
481
|
try {
|
|
@@ -395,26 +486,26 @@ function tool(name, description, schema, run) {
|
|
|
395
486
|
});
|
|
396
487
|
}
|
|
397
488
|
tool("providers_list", "List provider profiles.", page, (p) => clientFromEnv().listProviders(p));
|
|
398
|
-
tool("providers_get", "Get a provider.", { id:
|
|
489
|
+
tool("providers_get", "Get a provider.", { id: z3.string() }, (p) => clientFromEnv().getProvider(p.id));
|
|
399
490
|
tool("providers_create", "Create a provider using credential environment references only.", providerInputSchema.innerType().shape, (p) => clientFromEnv().createProvider(p));
|
|
400
|
-
tool("providers_update", "Replace a provider at its current version.", { provider: providerInputSchema, version:
|
|
401
|
-
tool("providers_delete", "Delete an unreferenced provider.", { id:
|
|
402
|
-
tool("models_list", "List catalog with capability information.", { id:
|
|
491
|
+
tool("providers_update", "Replace a provider at its current version.", { provider: providerInputSchema, version: z3.number().int() }, (p) => clientFromEnv().updateProvider(p.provider, p.version));
|
|
492
|
+
tool("providers_delete", "Delete an unreferenced provider.", { id: z3.string(), version: z3.number().int() }, (p) => clientFromEnv().deleteProvider(p.id, p.version));
|
|
493
|
+
tool("models_list", "List catalog with capability information.", { id: z3.string(), ...page }, (p) => {
|
|
403
494
|
const { id, ...rest } = p;
|
|
404
495
|
return clientFromEnv().listModels(id, rest);
|
|
405
496
|
});
|
|
406
|
-
tool("models_refresh", "Discover provider models.", { id:
|
|
497
|
+
tool("models_refresh", "Discover provider models.", { id: z3.string() }, (p) => clientFromEnv().refreshModels(p.id));
|
|
407
498
|
tool("profiles_list", "List harness launch profiles.", page, (p) => clientFromEnv().listProfiles(p));
|
|
408
|
-
tool("profiles_get", "Get a harness profile.", { id:
|
|
499
|
+
tool("profiles_get", "Get a harness profile.", { id: z3.string() }, (p) => clientFromEnv().getProfile(p.id));
|
|
409
500
|
tool("profiles_create", "Create a harness launch profile.", profileInputSchema.shape, (p) => clientFromEnv().createProfile(p));
|
|
410
|
-
tool("profiles_update", "Replace a harness profile at its version.", { profile: profileInputSchema, version:
|
|
411
|
-
tool("profiles_delete", "Delete a profile without run history.", { id:
|
|
412
|
-
tool("launch_plan", "Validate a local launch plan; does not execute a remote process.", { profileId:
|
|
501
|
+
tool("profiles_update", "Replace a harness profile at its version.", { profile: profileInputSchema, version: z3.number().int() }, (p) => clientFromEnv().updateProfile(p.profile, p.version));
|
|
502
|
+
tool("profiles_delete", "Delete a profile without run history.", { id: z3.string(), version: z3.number().int() }, (p) => clientFromEnv().deleteProfile(p.id, p.version));
|
|
503
|
+
tool("launch_plan", "Validate a local launch plan; does not execute a remote process.", { profileId: z3.string() }, (p) => clientFromEnv().launchPlan(p.profileId));
|
|
413
504
|
tool("runs_list", "List launch metadata.", page, (p) => clientFromEnv().listRuns(p));
|
|
414
|
-
tool("runs_get", "Get launch metadata.", { id:
|
|
505
|
+
tool("runs_get", "Get launch metadata.", { id: z3.string() }, (p) => clientFromEnv().getRun(p.id));
|
|
415
506
|
if (process.argv.includes("--version"))
|
|
416
507
|
console.log(VERSION);
|
|
417
508
|
else if (process.argv.includes("--help"))
|
|
418
|
-
console.log("switcher-mcp: authenticated Switcher API tools over MCP stdio.
|
|
509
|
+
console.log("switcher-mcp: authenticated Switcher API tools over MCP stdio. Resolves API URL/key through @hasna/contracts (Keychain, canonical config/credentials, or environment).");
|
|
419
510
|
else
|
|
420
511
|
await server.connect(new StdioServerTransport);
|
|
@@ -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,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 {};
|
|
@@ -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
|
+
};
|