@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.
- package/README.md +146 -7
- package/dist/aider-args.d.ts +7 -0
- package/dist/aider-config.d.ts +6 -0
- package/dist/auth.d.ts +3 -0
- package/dist/cli/index.js +7548 -2558
- package/dist/cli.d.ts +2 -0
- package/dist/cline-backend.d.ts +7 -0
- package/dist/codex-model-policy.d.ts +65 -0
- package/dist/credentials.d.ts +5 -5
- package/dist/direct-launch.d.ts +3 -3
- package/dist/domain.d.ts +320 -50
- package/dist/dsh-args.d.ts +5 -0
- package/dist/gemini-bridge.d.ts +6 -0
- package/dist/gemini-config.d.ts +12 -0
- package/dist/gemini-model-policy.d.ts +56 -0
- package/dist/generated/api.d.ts +220 -21
- package/dist/harness-arguments.d.ts +1 -0
- package/dist/harness-installation.d.ts +19 -0
- package/dist/harness-types.d.ts +10 -0
- package/dist/harnesses.d.ts +5 -2
- package/dist/hermes-backend.d.ts +19 -0
- package/dist/hermes-model-policy.d.ts +30 -0
- package/dist/index.js +136 -43
- package/dist/inference-gateway.d.ts +24 -0
- package/dist/kilo-config.d.ts +13 -0
- package/dist/kilo.d.ts +5 -0
- package/dist/launcher.d.ts +12 -6
- package/dist/mcp/index.js +139 -55
- 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/omp-backend.d.ts +7 -0
- package/dist/opencode-model-policy.d.ts +33 -0
- package/dist/opencode2-config.d.ts +3 -3
- package/dist/ori-backend.d.ts +2 -2
- package/dist/ori-model-policy.d.ts +8 -0
- package/dist/presets.d.ts +11 -10
- package/dist/sdk.d.ts +279 -38
- package/dist/sdk.js +136 -43
- package/dist/serve/index.js +1306 -125
- package/docs/MODEL-POLICY.md +82 -0
- package/openapi.json +973 -14
- package/package.json +14 -3
package/dist/serve/index.js
CHANGED
|
@@ -11,13 +11,63 @@ import { chmod, mkdir } from "fs/promises";
|
|
|
11
11
|
import { dirname, resolve } from "path";
|
|
12
12
|
|
|
13
13
|
// src/domain.ts
|
|
14
|
+
import { z as z2 } from "zod";
|
|
15
|
+
|
|
16
|
+
// src/model-policy-schema.ts
|
|
14
17
|
import { z } from "zod";
|
|
15
|
-
var
|
|
16
|
-
var
|
|
17
|
-
var
|
|
18
|
-
var
|
|
19
|
-
var
|
|
20
|
-
|
|
18
|
+
var policyModelIdSchema = z.string().min(1).max(300).regex(/^[^\u0000-\u001f\u007f]+$/);
|
|
19
|
+
var modelPolicyRoleSchema = z.enum(["subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"]);
|
|
20
|
+
var boundedModelList = z.array(policyModelIdSchema).max(500);
|
|
21
|
+
var aliasSchema = z.string().regex(/^[A-Za-z0-9._/-]{1,120}$/).refine((v) => !["__proto__", "prototype", "constructor"].includes(v));
|
|
22
|
+
var boundedModelMap = z.record(aliasSchema, policyModelIdSchema).superRefine((value, ctx) => {
|
|
23
|
+
if (Object.keys(value).length > 200)
|
|
24
|
+
ctx.addIssue({ code: "custom", message: "Model policy maps may contain at most 200 entries." });
|
|
25
|
+
});
|
|
26
|
+
var fallbacksSchema = z.record(policyModelIdSchema, z.array(policyModelIdSchema).max(20)).superRefine((value, ctx) => {
|
|
27
|
+
if (Object.keys(value).length > 200)
|
|
28
|
+
ctx.addIssue({ code: "custom", message: "Model policy fallback maps may contain at most 200 entries." });
|
|
29
|
+
});
|
|
30
|
+
var modelPolicySchema = z.object({
|
|
31
|
+
version: z.literal(1).default(1),
|
|
32
|
+
roles: z.object({
|
|
33
|
+
subagent: policyModelIdSchema.optional(),
|
|
34
|
+
fast: policyModelIdSchema.optional(),
|
|
35
|
+
planning: policyModelIdSchema.optional(),
|
|
36
|
+
review: policyModelIdSchema.optional(),
|
|
37
|
+
summary: policyModelIdSchema.optional(),
|
|
38
|
+
compaction: policyModelIdSchema.optional(),
|
|
39
|
+
weak: policyModelIdSchema.optional(),
|
|
40
|
+
editor: policyModelIdSchema.optional()
|
|
41
|
+
}).strict().optional(),
|
|
42
|
+
allowedModels: boundedModelList.optional(),
|
|
43
|
+
aliases: boundedModelMap.optional(),
|
|
44
|
+
fallbacks: fallbacksSchema.optional()
|
|
45
|
+
}).strict();
|
|
46
|
+
var routingDecisionSchema = z.enum(["allow", "alias", "reject", "fallback"]);
|
|
47
|
+
var routingEventRoleSchema = z.enum(["main", ...modelPolicyRoleSchema.options]);
|
|
48
|
+
var routingEventSchema = z.object({
|
|
49
|
+
at: z.string().datetime({ offset: true }),
|
|
50
|
+
requestId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/),
|
|
51
|
+
requestedModel: policyModelIdSchema,
|
|
52
|
+
resolvedModel: policyModelIdSchema.optional(),
|
|
53
|
+
reportedModel: policyModelIdSchema.optional(),
|
|
54
|
+
decision: routingDecisionSchema,
|
|
55
|
+
role: routingEventRoleSchema.optional(),
|
|
56
|
+
reason: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional(),
|
|
57
|
+
upstreamStatus: z.number().int().min(100).max(599).optional()
|
|
58
|
+
}).strict();
|
|
59
|
+
var routingEventsSchema = z.array(routingEventSchema).max(1000);
|
|
60
|
+
function canonicalPolicyJSON(value) {
|
|
61
|
+
return JSON.stringify(value, (_key, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b))) : item);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/domain.ts
|
|
65
|
+
var VERSION = "0.1.3";
|
|
66
|
+
var harnessSchema = z2.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
|
|
67
|
+
var protocolSchema = z2.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
|
|
68
|
+
var idSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
|
|
69
|
+
var label = z2.string().min(1).max(200);
|
|
70
|
+
var envRef = z2.string().regex(/^SWITCHER_PROVIDER_[A-Z0-9_]+$/);
|
|
21
71
|
function endpoint(value) {
|
|
22
72
|
let url;
|
|
23
73
|
try {
|
|
@@ -30,73 +80,79 @@ function endpoint(value) {
|
|
|
30
80
|
throw new Fault(400, "invalid_url", "URL must use HTTPS, contain no credentials/query/fragment, or use HTTP on loopback.");
|
|
31
81
|
return url.href.replace(/\/+$/, "");
|
|
32
82
|
}
|
|
33
|
-
var urlSchema =
|
|
83
|
+
var urlSchema = z2.string().max(2000).superRefine((v, ctx) => {
|
|
34
84
|
try {
|
|
35
85
|
endpoint(v);
|
|
36
86
|
} catch {
|
|
37
87
|
ctx.addIssue({ code: "custom", message: "Invalid endpoint URL" });
|
|
38
88
|
}
|
|
39
89
|
}).transform(endpoint);
|
|
40
|
-
var modelSchema =
|
|
41
|
-
id:
|
|
90
|
+
var modelSchema = z2.object({
|
|
91
|
+
id: z2.string().min(1).max(300),
|
|
42
92
|
name: label,
|
|
43
|
-
description:
|
|
44
|
-
available:
|
|
45
|
-
contextWindow:
|
|
46
|
-
maxOutputTokens:
|
|
47
|
-
inputModalities:
|
|
48
|
-
outputModalities:
|
|
49
|
-
supportedParameters:
|
|
93
|
+
description: z2.string().max(8000).optional(),
|
|
94
|
+
available: z2.boolean().optional(),
|
|
95
|
+
contextWindow: z2.number().int().positive().optional(),
|
|
96
|
+
maxOutputTokens: z2.number().int().positive().optional(),
|
|
97
|
+
inputModalities: z2.array(z2.string().max(50)).max(20).optional(),
|
|
98
|
+
outputModalities: z2.array(z2.string().max(50)).max(20).optional(),
|
|
99
|
+
supportedParameters: z2.array(z2.string().max(100)).max(100).optional(),
|
|
100
|
+
supportedGenerationMethods: z2.array(z2.string().min(1).max(100)).max(100).optional()
|
|
50
101
|
}).strict();
|
|
51
|
-
var providerInputSchema =
|
|
102
|
+
var providerInputSchema = z2.object({
|
|
52
103
|
id: idSchema,
|
|
53
104
|
name: label,
|
|
54
105
|
baseUrl: urlSchema,
|
|
55
106
|
protocol: protocolSchema,
|
|
56
107
|
credentialEnv: envRef.optional(),
|
|
57
|
-
authStyle:
|
|
108
|
+
authStyle: z2.enum(["bearer", "x-api-key", "api-key"]).default("bearer"),
|
|
58
109
|
catalogBaseUrl: urlSchema.optional(),
|
|
59
|
-
catalogFormat:
|
|
60
|
-
catalogAuthStyle:
|
|
110
|
+
catalogFormat: z2.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]).optional(),
|
|
111
|
+
catalogAuthStyle: z2.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
|
|
61
112
|
catalogCredentialEnv: envRef.optional(),
|
|
62
|
-
catalogAccountId:
|
|
63
|
-
modelsPath:
|
|
64
|
-
manualModels:
|
|
113
|
+
catalogAccountId: z2.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
|
|
114
|
+
modelsPath: z2.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
|
|
115
|
+
manualModels: z2.array(modelSchema).max(1e4).default([])
|
|
65
116
|
}).strict().refine((p) => !p.modelsPath.split("/").includes("..") && !p.modelsPath.startsWith("/"), "modelsPath must be relative");
|
|
66
|
-
var providerPresetSchema =
|
|
117
|
+
var providerPresetSchema = z2.object({
|
|
67
118
|
id: idSchema,
|
|
68
119
|
name: label,
|
|
69
120
|
credentialEnv: envRef.optional(),
|
|
70
|
-
credentialAliases:
|
|
71
|
-
protocols:
|
|
121
|
+
credentialAliases: z2.array(z2.string().regex(/^[A-Z][A-Z0-9_]+$/)),
|
|
122
|
+
protocols: z2.array(z2.object({
|
|
72
123
|
protocol: protocolSchema,
|
|
73
124
|
baseUrl: urlSchema.optional(),
|
|
74
|
-
authStyle:
|
|
125
|
+
authStyle: z2.enum(["bearer", "x-api-key", "api-key"]),
|
|
75
126
|
catalogBaseUrl: urlSchema.optional(),
|
|
76
|
-
catalogFormat:
|
|
77
|
-
catalogAuthStyle:
|
|
78
|
-
modelsPath:
|
|
79
|
-
notes:
|
|
127
|
+
catalogFormat: z2.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]),
|
|
128
|
+
catalogAuthStyle: z2.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
|
|
129
|
+
modelsPath: z2.string(),
|
|
130
|
+
notes: z2.array(z2.string())
|
|
80
131
|
}).strict()).min(1),
|
|
81
|
-
sources:
|
|
82
|
-
verification:
|
|
132
|
+
sources: z2.array(z2.string().url()),
|
|
133
|
+
verification: z2.literal("documented")
|
|
83
134
|
}).strict();
|
|
84
|
-
var profileInputSchema =
|
|
135
|
+
var profileInputSchema = z2.object({
|
|
85
136
|
id: idSchema,
|
|
86
137
|
name: label,
|
|
87
138
|
providerId: idSchema,
|
|
88
139
|
harness: harnessSchema,
|
|
89
|
-
model:
|
|
140
|
+
model: z2.string().min(1).max(300),
|
|
141
|
+
modelPolicy: modelPolicySchema.optional()
|
|
90
142
|
}).strict();
|
|
91
|
-
var runInputSchema =
|
|
143
|
+
var runInputSchema = z2.object({
|
|
144
|
+
modelPolicyVersion: z2.literal(1),
|
|
92
145
|
profileId: idSchema,
|
|
93
146
|
harness: harnessSchema,
|
|
94
|
-
model:
|
|
95
|
-
|
|
147
|
+
model: z2.string().min(1).max(300),
|
|
148
|
+
modelPolicy: modelPolicySchema.optional(),
|
|
149
|
+
planToken: z2.string().regex(/^[a-f0-9]{64}$/)
|
|
96
150
|
}).strict();
|
|
97
|
-
var runUpdateSchema =
|
|
98
|
-
status:
|
|
99
|
-
exitCode:
|
|
151
|
+
var runUpdateSchema = z2.object({
|
|
152
|
+
status: z2.enum(["exited", "failed", "interrupted"]),
|
|
153
|
+
exitCode: z2.number().int().min(0).max(255),
|
|
154
|
+
routingEvents: routingEventsSchema.optional(),
|
|
155
|
+
routingEventsDropped: z2.number().int().min(0).max(1e6).optional()
|
|
100
156
|
}).strict();
|
|
101
157
|
|
|
102
158
|
class Fault extends Error {
|
|
@@ -115,10 +171,27 @@ function parse(schema, value) {
|
|
|
115
171
|
return result.data;
|
|
116
172
|
}
|
|
117
173
|
function compatible(harness, protocol) {
|
|
118
|
-
|
|
174
|
+
if (harness === "claude")
|
|
175
|
+
return protocol === "anthropic-messages";
|
|
176
|
+
if (harness === "codex")
|
|
177
|
+
return protocol === "openai-responses";
|
|
178
|
+
if (harness === "gemini")
|
|
179
|
+
return protocol === "gemini-generate-content";
|
|
180
|
+
if (protocol === "gemini-generate-content")
|
|
181
|
+
return false;
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
function validateHarnessProvider(harness, provider) {
|
|
185
|
+
if (!compatible(harness, provider.protocol))
|
|
186
|
+
throw new Fault(422, "protocol_mismatch", "Harness does not support this provider protocol.");
|
|
187
|
+
if (harness === "gemini" && provider.authStyle !== "x-api-key")
|
|
188
|
+
throw new Fault(422, "auth_mismatch", "Gemini CLI requires x-api-key authentication for its native generateContent protocol.");
|
|
119
189
|
}
|
|
120
190
|
function codingEligible(model) {
|
|
121
|
-
return model.available !== false && (!model.outputModalities || model.outputModalities.includes("text")) && (!model.supportedParameters || model.supportedParameters.includes("tools"));
|
|
191
|
+
return model.available !== false && (!model.supportedGenerationMethods || model.supportedGenerationMethods.includes("generateContent")) && (!model.outputModalities || model.outputModalities.includes("text")) && (!model.supportedParameters || model.supportedParameters.includes("tools"));
|
|
192
|
+
}
|
|
193
|
+
function harnessEligible(model, harness) {
|
|
194
|
+
return harness === "aider" ? model.available !== false && (!model.supportedGenerationMethods || model.supportedGenerationMethods.includes("generateContent")) && (!model.inputModalities || model.inputModalities.includes("text")) && (!model.outputModalities || model.outputModalities.includes("text")) : codingEligible(model);
|
|
122
195
|
}
|
|
123
196
|
|
|
124
197
|
// src/store.ts
|
|
@@ -280,8 +353,8 @@ class Store {
|
|
|
280
353
|
}
|
|
281
354
|
|
|
282
355
|
// src/service.ts
|
|
283
|
-
import { createHash, timingSafeEqual } from "crypto";
|
|
284
|
-
import { z as
|
|
356
|
+
import { createHash as createHash2, timingSafeEqual } from "crypto";
|
|
357
|
+
import { z as z3 } from "zod";
|
|
285
358
|
|
|
286
359
|
// src/http.ts
|
|
287
360
|
var MAX_BYTES = 16 * 1024 * 1024;
|
|
@@ -317,6 +390,13 @@ async function boundedJson(response, maxBytes = MAX_BYTES) {
|
|
|
317
390
|
}
|
|
318
391
|
}
|
|
319
392
|
|
|
393
|
+
// src/auth.ts
|
|
394
|
+
function authHeader(style, credential) {
|
|
395
|
+
if (/[^\x20-\x7e]/.test(credential))
|
|
396
|
+
throw new Error("Provider credential contains invalid header characters.");
|
|
397
|
+
return style === "bearer" ? ["authorization", `Bearer ${credential}`] : [style, credential];
|
|
398
|
+
}
|
|
399
|
+
|
|
320
400
|
// src/catalog.ts
|
|
321
401
|
var positive = (v) => typeof v === "number" && Number.isInteger(v) && v > 0 ? v : undefined;
|
|
322
402
|
var strings = (v) => Array.isArray(v) && v.every((i) => typeof i === "string") ? v : undefined;
|
|
@@ -327,6 +407,64 @@ var modalities = (v) => {
|
|
|
327
407
|
throw new Fault(502, "invalid_catalog", "Provider returned malformed modality metadata.");
|
|
328
408
|
return v.map((i) => i.toLowerCase());
|
|
329
409
|
};
|
|
410
|
+
var CATALOG_REQUEST_TIMEOUT_MS = 20000;
|
|
411
|
+
var CATALOG_REFRESH_DEADLINE_MS = 60000;
|
|
412
|
+
var CATALOG_MAX_RETRIES = 2;
|
|
413
|
+
var TRANSIENT_CATALOG_STATUSES = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
414
|
+
function retryAfterMs(value) {
|
|
415
|
+
if (!value)
|
|
416
|
+
return;
|
|
417
|
+
const trimmed = value.trim();
|
|
418
|
+
if (/^\d+$/.test(trimmed)) {
|
|
419
|
+
try {
|
|
420
|
+
const milliseconds = BigInt(trimmed) * 1000n;
|
|
421
|
+
if (milliseconds > BigInt(Number.MAX_SAFE_INTEGER))
|
|
422
|
+
return Number.POSITIVE_INFINITY;
|
|
423
|
+
return Number(milliseconds);
|
|
424
|
+
} catch {
|
|
425
|
+
return Number.POSITIVE_INFINITY;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const date = Date.parse(trimmed);
|
|
429
|
+
return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
|
|
430
|
+
}
|
|
431
|
+
function catalogDeadlineFault() {
|
|
432
|
+
return new Fault(502, "provider_unavailable", "Provider catalog refresh exceeded its bounded deadline.");
|
|
433
|
+
}
|
|
434
|
+
async function waitForCatalogRetry(delayMs, deadline) {
|
|
435
|
+
if (Date.now() + delayMs > deadline)
|
|
436
|
+
throw catalogDeadlineFault();
|
|
437
|
+
if (delayMs > 0)
|
|
438
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
439
|
+
}
|
|
440
|
+
async function fetchCatalogPage(url, headers, deadline) {
|
|
441
|
+
for (let retry = 0;; retry++) {
|
|
442
|
+
const remaining = deadline - Date.now();
|
|
443
|
+
if (remaining <= 0)
|
|
444
|
+
throw catalogDeadlineFault();
|
|
445
|
+
let response;
|
|
446
|
+
try {
|
|
447
|
+
response = await fetch(url, {
|
|
448
|
+
headers,
|
|
449
|
+
redirect: "manual",
|
|
450
|
+
signal: AbortSignal.timeout(Math.max(1, Math.min(CATALOG_REQUEST_TIMEOUT_MS, remaining)))
|
|
451
|
+
});
|
|
452
|
+
} catch {
|
|
453
|
+
if (retry >= CATALOG_MAX_RETRIES)
|
|
454
|
+
throw new Fault(502, "provider_unavailable", "Provider catalog request failed.");
|
|
455
|
+
await waitForCatalogRetry(100 * 2 ** retry, deadline);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (response.ok)
|
|
459
|
+
return response;
|
|
460
|
+
const retryable = TRANSIENT_CATALOG_STATUSES.has(response.status);
|
|
461
|
+
await response.body?.cancel().catch(() => {});
|
|
462
|
+
if (!retryable || retry >= CATALOG_MAX_RETRIES)
|
|
463
|
+
throw new Fault(502, "provider_rejected", `Provider catalog returned HTTP ${response.status}.`);
|
|
464
|
+
const delay = retryAfterMs(response.headers.get("retry-after"));
|
|
465
|
+
await waitForCatalogRetry(delay ?? 100 * 2 ** retry, deadline);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
330
468
|
async function discover(provider, env = process.env, resolveCredential) {
|
|
331
469
|
const refreshedAt = new Date().toISOString();
|
|
332
470
|
if (provider.manualModels.length)
|
|
@@ -352,7 +490,8 @@ async function discover(provider, env = process.env, resolveCredential) {
|
|
|
352
490
|
throw new Fault(422, "credential_missing", "Provider credential environment variable is not available on the server.");
|
|
353
491
|
if (/[\r\n]/.test(credential))
|
|
354
492
|
throw new Fault(422, "credential_invalid", "Catalog credential contains invalid header characters.");
|
|
355
|
-
|
|
493
|
+
const [header, value] = authHeader(authStyle, credential);
|
|
494
|
+
headers[provider.catalogFormat === "gemini" && header === "x-api-key" ? "x-goog-api-key" : header] = value;
|
|
356
495
|
}
|
|
357
496
|
if (provider.protocol === "anthropic-messages" && url.hostname !== "openrouter.ai")
|
|
358
497
|
headers["anthropic-version"] = "2023-06-01";
|
|
@@ -361,18 +500,10 @@ async function discover(provider, env = process.env, resolveCredential) {
|
|
|
361
500
|
const models = new Map;
|
|
362
501
|
const seenCursors = new Set;
|
|
363
502
|
const seenPages = new Set([url.href]);
|
|
503
|
+
const deadline = Date.now() + CATALOG_REFRESH_DEADLINE_MS;
|
|
364
504
|
let fireworksTotal;
|
|
365
505
|
for (let page = 0;page < 100; page++) {
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
response = await fetch(url, { headers, redirect: "manual", signal: AbortSignal.timeout(20000) });
|
|
369
|
-
} catch {
|
|
370
|
-
throw new Fault(502, "provider_unavailable", "Provider catalog request failed.");
|
|
371
|
-
}
|
|
372
|
-
if (!response.ok) {
|
|
373
|
-
await response.body?.cancel();
|
|
374
|
-
throw new Fault(502, "provider_rejected", `Provider catalog returned HTTP ${response.status}.`);
|
|
375
|
-
}
|
|
506
|
+
const response = await fetchCatalogPage(url, headers, deadline);
|
|
376
507
|
const data = await boundedJson(response);
|
|
377
508
|
if (provider.catalogFormat === "fireworks" && data?.totalSize !== undefined) {
|
|
378
509
|
if (typeof data.totalSize !== "number" || !Number.isInteger(data.totalSize) || data.totalSize < 0)
|
|
@@ -381,11 +512,11 @@ async function discover(provider, env = process.env, resolveCredential) {
|
|
|
381
512
|
throw new Fault(502, "incomplete_catalog", "Provider catalog count changed during pagination; retry the refresh.");
|
|
382
513
|
fireworksTotal = data.totalSize;
|
|
383
514
|
}
|
|
384
|
-
const rows = provider.catalogFormat === "together" ? data : provider.catalogFormat === "ollama" ? data?.models : provider.catalogFormat === "fireworks" ? data?.models : provider.catalogFormat === "dashscope" ? data?.output?.models : data?.data;
|
|
515
|
+
const rows = provider.catalogFormat === "together" ? data : provider.catalogFormat === "ollama" ? data?.models : provider.catalogFormat === "fireworks" || provider.catalogFormat === "gemini" ? data?.models : provider.catalogFormat === "dashscope" ? data?.output?.models : data?.data;
|
|
385
516
|
if (!Array.isArray(rows))
|
|
386
517
|
throw new Fault(502, "invalid_catalog", "Expected a provider catalog with a model array matching its configured format.");
|
|
387
518
|
for (const row of rows) {
|
|
388
|
-
const id = provider.catalogFormat === "ollama" ? row?.model ?? row?.name : provider.catalogFormat === "fireworks" ? row?.name : provider.catalogFormat === "dashscope" ? row?.model : row?.id;
|
|
519
|
+
const id = provider.catalogFormat === "ollama" ? row?.model ?? row?.name : provider.catalogFormat === "fireworks" ? row?.name : provider.catalogFormat === "gemini" ? typeof row?.name === "string" ? row.name.replace(/^models\//, "") : undefined : provider.catalogFormat === "dashscope" ? row?.model : row?.id;
|
|
389
520
|
if (typeof id !== "string")
|
|
390
521
|
throw new Fault(502, "invalid_catalog", "Catalog entry is missing a model ID.");
|
|
391
522
|
const candidate = {
|
|
@@ -393,11 +524,12 @@ async function discover(provider, env = process.env, resolveCredential) {
|
|
|
393
524
|
name: row.displayName ?? row.name ?? row.display_name ?? id,
|
|
394
525
|
available: provider.catalogFormat === "mistral" && typeof row.archived === "boolean" ? !row.archived : undefined,
|
|
395
526
|
description: typeof row.description === "string" ? row.description.slice(0, 8000) : undefined,
|
|
396
|
-
contextWindow: positive(row.context_length ?? row.context_window ?? row.contextLength ?? row.model_info?.context_window ?? (provider.catalogFormat === "mistral" ? row.max_context_length : undefined)),
|
|
397
|
-
maxOutputTokens: positive(row.top_provider?.max_completion_tokens ?? row.max_output_tokens ?? row.model_info?.max_output_tokens),
|
|
527
|
+
contextWindow: positive(row.context_length ?? row.context_window ?? row.contextLength ?? row.inputTokenLimit ?? row.model_info?.context_window ?? (provider.catalogFormat === "mistral" ? row.max_context_length : undefined)),
|
|
528
|
+
maxOutputTokens: positive(row.top_provider?.max_completion_tokens ?? row.max_output_tokens ?? row.outputTokenLimit ?? row.model_info?.max_output_tokens),
|
|
398
529
|
inputModalities: strings(row.architecture?.input_modalities ?? row.input_modalities) ?? modalities(row.inference_metadata?.request_modality),
|
|
399
530
|
outputModalities: strings(row.architecture?.output_modalities ?? row.output_modalities) ?? modalities(row.inference_metadata?.response_modality),
|
|
400
|
-
supportedParameters: strings(row.supported_parameters)
|
|
531
|
+
supportedParameters: strings(row.supported_parameters),
|
|
532
|
+
...provider.catalogFormat === "gemini" && row.supportedGenerationMethods !== undefined ? { supportedGenerationMethods: row.supportedGenerationMethods } : {}
|
|
401
533
|
};
|
|
402
534
|
if (provider.catalogFormat === "mistral") {
|
|
403
535
|
const capabilities = row.capabilities;
|
|
@@ -468,6 +600,21 @@ async function discover(provider, env = process.env, resolveCredential) {
|
|
|
468
600
|
throw new Fault(502, "incomplete_catalog", "Provider catalog count does not match the collected models; retry the refresh.");
|
|
469
601
|
return { models: [...models.values()], source: "remote", refreshedAt };
|
|
470
602
|
}
|
|
603
|
+
if (provider.catalogFormat === "gemini") {
|
|
604
|
+
const nextPageToken = data.nextPageToken;
|
|
605
|
+
if (nextPageToken !== undefined && nextPageToken !== null) {
|
|
606
|
+
if (typeof nextPageToken !== "string" || nextPageToken.length > 2000)
|
|
607
|
+
throw new Fault(502, "invalid_catalog", "Gemini catalog pagination token is malformed.");
|
|
608
|
+
if (nextPageToken) {
|
|
609
|
+
if (seenCursors.has(nextPageToken))
|
|
610
|
+
throw new Fault(502, "invalid_catalog", "Provider catalog pagination did not advance.");
|
|
611
|
+
seenCursors.add(nextPageToken);
|
|
612
|
+
url.searchParams.set("pageToken", nextPageToken);
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return { models: [...models.values()], source: "remote", refreshedAt };
|
|
617
|
+
}
|
|
471
618
|
if (provider.catalogFormat === "dashscope") {
|
|
472
619
|
const output = data.output;
|
|
473
620
|
const total = output?.total;
|
|
@@ -503,6 +650,69 @@ async function discover(provider, env = process.env, resolveCredential) {
|
|
|
503
650
|
throw new Fault(502, "catalog_too_large", "Provider catalog pagination exceeded 100 pages.");
|
|
504
651
|
}
|
|
505
652
|
|
|
653
|
+
// src/model-policy.ts
|
|
654
|
+
import { createHash } from "crypto";
|
|
655
|
+
var roles = ["subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"];
|
|
656
|
+
var id = (v, label2) => {
|
|
657
|
+
if (typeof v !== "string" || !v || v.length > 300 || /[\u0000-\u001f\u007f]/.test(v))
|
|
658
|
+
throw new Fault(400, "invalid_model_policy", `${label2} is invalid.`);
|
|
659
|
+
return v;
|
|
660
|
+
};
|
|
661
|
+
var uniq = (xs) => [...new Set(xs)];
|
|
662
|
+
function stable(value) {
|
|
663
|
+
return JSON.stringify(value, (_k, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b))) : v);
|
|
664
|
+
}
|
|
665
|
+
function compileModelPolicy(model, catalog, policy) {
|
|
666
|
+
const selected = id(model, "model");
|
|
667
|
+
const available = new Set(catalog.filter((m) => m.available !== false).map((m) => m.id));
|
|
668
|
+
if (!available.has(selected))
|
|
669
|
+
throw new Fault(422, "model_unavailable", "Selected model is not present in the eligible catalog.");
|
|
670
|
+
if (policy && policy.version !== undefined && policy.version !== 1)
|
|
671
|
+
throw new Fault(400, "invalid_model_policy", "Unsupported model policy version.");
|
|
672
|
+
const p = parse(modelPolicySchema, policy ?? {});
|
|
673
|
+
const roleInput = p.roles ?? {};
|
|
674
|
+
if (Object.keys(roleInput).some((k) => !roles.includes(k)))
|
|
675
|
+
throw new Fault(400, "invalid_model_policy", "Unknown model policy role.");
|
|
676
|
+
const compiledRoles = Object.fromEntries(roles.map((role) => [role, id(roleInput[role] ?? selected, `roles.${role}`)]));
|
|
677
|
+
for (const value of Object.values(compiledRoles))
|
|
678
|
+
if (!available.has(value))
|
|
679
|
+
throw new Fault(422, "model_unavailable", "A model policy role is not present in the eligible catalog.");
|
|
680
|
+
const explicitAllowed = (p.allowedModels ?? []).map((v, i) => id(v, `allowedModels[${i}]`));
|
|
681
|
+
if (explicitAllowed.length > 500)
|
|
682
|
+
throw new Fault(400, "invalid_model_policy", "Model policy allowedModels is too large.");
|
|
683
|
+
for (const value of explicitAllowed)
|
|
684
|
+
if (!available.has(value))
|
|
685
|
+
throw new Fault(422, "model_unavailable", "An allowed model is not present in the eligible catalog.");
|
|
686
|
+
const aliases = Object.create(null);
|
|
687
|
+
for (const [name, target] of Object.entries(p.aliases ?? {})) {
|
|
688
|
+
if (Object.keys(aliases).length >= 200 || !/^[A-Za-z0-9._/-]{1,120}$/.test(name) || ["__proto__", "prototype", "constructor"].includes(name))
|
|
689
|
+
throw new Fault(400, "invalid_model_policy", "A model alias is invalid or too numerous.");
|
|
690
|
+
const canonical = id(target, `aliases.${name}`);
|
|
691
|
+
if (available.has(name) && name !== canonical)
|
|
692
|
+
throw new Fault(400, "invalid_model_policy", "A model alias cannot shadow a real model ID.");
|
|
693
|
+
if (!available.has(canonical))
|
|
694
|
+
throw new Fault(422, "model_unavailable", "A model alias target is not present in the eligible catalog.");
|
|
695
|
+
aliases[name] = canonical;
|
|
696
|
+
}
|
|
697
|
+
const fallbacks = Object.create(null);
|
|
698
|
+
const allowedSources = new Set([selected, ...Object.values(compiledRoles), ...explicitAllowed]);
|
|
699
|
+
for (const [source, targets] of Object.entries(p.fallbacks ?? {})) {
|
|
700
|
+
if (!allowedSources.has(source) || !available.has(source) || !Array.isArray(targets) || Object.keys(fallbacks).length >= 200 || targets.length > 20)
|
|
701
|
+
throw new Fault(400, "invalid_model_policy", "Fallback source or list is invalid.");
|
|
702
|
+
const values = targets.map((v, i) => id(v, `fallbacks.${source}[${i}]`));
|
|
703
|
+
if (values.some((v) => !available.has(v)))
|
|
704
|
+
throw new Fault(422, "model_unavailable", "A fallback model is not present in the eligible catalog.");
|
|
705
|
+
if (values.includes(source))
|
|
706
|
+
throw new Fault(400, "invalid_model_policy", "A model cannot fall back to itself.");
|
|
707
|
+
fallbacks[source] = uniq(values);
|
|
708
|
+
}
|
|
709
|
+
const allowedModels = uniq([selected, ...Object.values(compiledRoles), ...explicitAllowed, ...Object.values(fallbacks).flat()]).sort();
|
|
710
|
+
if (Object.values(aliases).some((value) => !allowedModels.includes(value)))
|
|
711
|
+
throw new Fault(422, "model_not_allowed", "A model alias target must be explicitly allowed by the launch policy.");
|
|
712
|
+
const result = { version: 1, model: selected, roles: compiledRoles, allowedModels, aliases, fallbacks };
|
|
713
|
+
return { ...result, digest: createHash("sha256").update(stable(result)).digest("hex") };
|
|
714
|
+
}
|
|
715
|
+
|
|
506
716
|
// src/presets.ts
|
|
507
717
|
var route = (protocol, baseUrl, options = {}) => ({
|
|
508
718
|
protocol,
|
|
@@ -513,13 +723,13 @@ var route = (protocol, baseUrl, options = {}) => ({
|
|
|
513
723
|
notes: [],
|
|
514
724
|
...options
|
|
515
725
|
});
|
|
516
|
-
var preset = (
|
|
517
|
-
id,
|
|
726
|
+
var preset = (id2, name, protocols, sources, alias) => parse(providerPresetSchema, {
|
|
727
|
+
id: id2,
|
|
518
728
|
name,
|
|
519
729
|
protocols,
|
|
520
730
|
sources,
|
|
521
731
|
credentialAliases: alias ? [alias] : [],
|
|
522
|
-
credentialEnv: alias ? `SWITCHER_PROVIDER_${
|
|
732
|
+
credentialEnv: alias ? `SWITCHER_PROVIDER_${id2.toUpperCase().replace(/-/g, "_")}` : undefined,
|
|
523
733
|
verification: "documented"
|
|
524
734
|
});
|
|
525
735
|
var providerPresets = [
|
|
@@ -529,7 +739,18 @@ var providerPresets = [
|
|
|
529
739
|
], ["https://api-docs.deepseek.com/guides/anthropic_api", "https://api-docs.deepseek.com/api/list-models"], "DEEPSEEK_API_KEY"),
|
|
530
740
|
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"),
|
|
531
741
|
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"),
|
|
742
|
+
preset("gemini", "Google Gemini", [route("gemini-generate-content", "https://generativelanguage.googleapis.com/v1beta", {
|
|
743
|
+
authStyle: "x-api-key",
|
|
744
|
+
catalogBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
745
|
+
catalogFormat: "gemini",
|
|
746
|
+
catalogAuthStyle: "x-api-key",
|
|
747
|
+
notes: ["Gemini CLI uses the native generateContent wire with x-goog-api-key authentication; model IDs are returned as models/{id}."]
|
|
748
|
+
}), 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"),
|
|
532
749
|
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"),
|
|
750
|
+
preset("azure-openai", "Azure OpenAI (v1)", [
|
|
751
|
+
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."] }),
|
|
752
|
+
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."] })
|
|
753
|
+
], ["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"),
|
|
533
754
|
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"),
|
|
534
755
|
preset("ollama", "Ollama", ["openai-chat", "openai-responses"].map((protocol) => route(protocol, "http://127.0.0.1:11434/v1", {
|
|
535
756
|
catalogBaseUrl: "http://127.0.0.1:11434",
|
|
@@ -576,8 +797,8 @@ var providerPresets = [
|
|
|
576
797
|
preset("siliconflow", "SiliconFlow", [route("openai-chat", "https://api.siliconflow.cn/v1", { catalogBaseUrl: "https://api.siliconflow.cn/v1", catalogFormat: "openai", notes: ["The official SiliconCloud OpenAPI contract defines GET /models with Bearer auth and data[] model rows; optional type and sub_type filters are available at the upstream endpoint."] })], ["https://github.com/siliconflow/siliconcloud/blob/main/openapi.yaml", "https://docs.siliconflow.cn/docs/userguide/quickstart", "https://docs.siliconflow.cn/docs/api/chat-completions-post"], "SILICONFLOW_API_KEY"),
|
|
577
798
|
...["anthropic-messages", "openai-responses", "openai-chat"].map((protocol) => preset(`generic-${protocol}`, `Custom ${protocol}`, [route(protocol)], []))
|
|
578
799
|
];
|
|
579
|
-
function getProviderPreset(
|
|
580
|
-
const entry = providerPresets.find((p) => p.id ===
|
|
800
|
+
function getProviderPreset(id2) {
|
|
801
|
+
const entry = providerPresets.find((p) => p.id === id2);
|
|
581
802
|
if (!entry)
|
|
582
803
|
throw new Fault(404, "preset_not_found", "Unknown provider preset. Use switcher providers presets to list available presets.");
|
|
583
804
|
return structuredClone(entry);
|
|
@@ -587,7 +808,7 @@ var openapi_default = {
|
|
|
587
808
|
openapi: "3.0.3",
|
|
588
809
|
info: {
|
|
589
810
|
title: "Switcher API",
|
|
590
|
-
version: "0.1.
|
|
811
|
+
version: "0.1.3",
|
|
591
812
|
description: "Authenticated provider/profile/catalog control plane. Launches run locally; the API never returns provider credentials."
|
|
592
813
|
},
|
|
593
814
|
security: [
|
|
@@ -1801,7 +2022,8 @@ var openapi_default = {
|
|
|
1801
2022
|
enum: [
|
|
1802
2023
|
"anthropic-messages",
|
|
1803
2024
|
"openai-responses",
|
|
1804
|
-
"openai-chat"
|
|
2025
|
+
"openai-chat",
|
|
2026
|
+
"gemini-generate-content"
|
|
1805
2027
|
]
|
|
1806
2028
|
},
|
|
1807
2029
|
baseUrl: {
|
|
@@ -1812,7 +2034,8 @@ var openapi_default = {
|
|
|
1812
2034
|
type: "string",
|
|
1813
2035
|
enum: [
|
|
1814
2036
|
"bearer",
|
|
1815
|
-
"x-api-key"
|
|
2037
|
+
"x-api-key",
|
|
2038
|
+
"api-key"
|
|
1816
2039
|
]
|
|
1817
2040
|
},
|
|
1818
2041
|
catalogBaseUrl: {
|
|
@@ -1828,6 +2051,7 @@ var openapi_default = {
|
|
|
1828
2051
|
"together",
|
|
1829
2052
|
"fireworks",
|
|
1830
2053
|
"dashscope",
|
|
2054
|
+
"gemini",
|
|
1831
2055
|
"none"
|
|
1832
2056
|
]
|
|
1833
2057
|
},
|
|
@@ -1836,6 +2060,7 @@ var openapi_default = {
|
|
|
1836
2060
|
enum: [
|
|
1837
2061
|
"bearer",
|
|
1838
2062
|
"x-api-key",
|
|
2063
|
+
"api-key",
|
|
1839
2064
|
"none"
|
|
1840
2065
|
]
|
|
1841
2066
|
},
|
|
@@ -1905,7 +2130,8 @@ var openapi_default = {
|
|
|
1905
2130
|
enum: [
|
|
1906
2131
|
"anthropic-messages",
|
|
1907
2132
|
"openai-responses",
|
|
1908
|
-
"openai-chat"
|
|
2133
|
+
"openai-chat",
|
|
2134
|
+
"gemini-generate-content"
|
|
1909
2135
|
]
|
|
1910
2136
|
},
|
|
1911
2137
|
credentialEnv: {
|
|
@@ -1916,7 +2142,8 @@ var openapi_default = {
|
|
|
1916
2142
|
type: "string",
|
|
1917
2143
|
enum: [
|
|
1918
2144
|
"bearer",
|
|
1919
|
-
"x-api-key"
|
|
2145
|
+
"x-api-key",
|
|
2146
|
+
"api-key"
|
|
1920
2147
|
],
|
|
1921
2148
|
default: "bearer"
|
|
1922
2149
|
},
|
|
@@ -1933,6 +2160,7 @@ var openapi_default = {
|
|
|
1933
2160
|
"together",
|
|
1934
2161
|
"fireworks",
|
|
1935
2162
|
"dashscope",
|
|
2163
|
+
"gemini",
|
|
1936
2164
|
"none"
|
|
1937
2165
|
]
|
|
1938
2166
|
},
|
|
@@ -1941,6 +2169,7 @@ var openapi_default = {
|
|
|
1941
2169
|
enum: [
|
|
1942
2170
|
"bearer",
|
|
1943
2171
|
"x-api-key",
|
|
2172
|
+
"api-key",
|
|
1944
2173
|
"none"
|
|
1945
2174
|
]
|
|
1946
2175
|
},
|
|
@@ -2013,6 +2242,15 @@ var openapi_default = {
|
|
|
2013
2242
|
maxLength: 100
|
|
2014
2243
|
},
|
|
2015
2244
|
maxItems: 100
|
|
2245
|
+
},
|
|
2246
|
+
supportedGenerationMethods: {
|
|
2247
|
+
type: "array",
|
|
2248
|
+
items: {
|
|
2249
|
+
type: "string",
|
|
2250
|
+
minLength: 1,
|
|
2251
|
+
maxLength: 100
|
|
2252
|
+
},
|
|
2253
|
+
maxItems: 100
|
|
2016
2254
|
}
|
|
2017
2255
|
},
|
|
2018
2256
|
required: [
|
|
@@ -2054,7 +2292,8 @@ var openapi_default = {
|
|
|
2054
2292
|
enum: [
|
|
2055
2293
|
"anthropic-messages",
|
|
2056
2294
|
"openai-responses",
|
|
2057
|
-
"openai-chat"
|
|
2295
|
+
"openai-chat",
|
|
2296
|
+
"gemini-generate-content"
|
|
2058
2297
|
]
|
|
2059
2298
|
},
|
|
2060
2299
|
credentialEnv: {
|
|
@@ -2065,7 +2304,8 @@ var openapi_default = {
|
|
|
2065
2304
|
type: "string",
|
|
2066
2305
|
enum: [
|
|
2067
2306
|
"bearer",
|
|
2068
|
-
"x-api-key"
|
|
2307
|
+
"x-api-key",
|
|
2308
|
+
"api-key"
|
|
2069
2309
|
],
|
|
2070
2310
|
default: "bearer"
|
|
2071
2311
|
},
|
|
@@ -2082,6 +2322,7 @@ var openapi_default = {
|
|
|
2082
2322
|
"together",
|
|
2083
2323
|
"fireworks",
|
|
2084
2324
|
"dashscope",
|
|
2325
|
+
"gemini",
|
|
2085
2326
|
"none"
|
|
2086
2327
|
]
|
|
2087
2328
|
},
|
|
@@ -2090,6 +2331,7 @@ var openapi_default = {
|
|
|
2090
2331
|
enum: [
|
|
2091
2332
|
"bearer",
|
|
2092
2333
|
"x-api-key",
|
|
2334
|
+
"api-key",
|
|
2093
2335
|
"none"
|
|
2094
2336
|
]
|
|
2095
2337
|
},
|
|
@@ -2162,6 +2404,15 @@ var openapi_default = {
|
|
|
2162
2404
|
maxLength: 100
|
|
2163
2405
|
},
|
|
2164
2406
|
maxItems: 100
|
|
2407
|
+
},
|
|
2408
|
+
supportedGenerationMethods: {
|
|
2409
|
+
type: "array",
|
|
2410
|
+
items: {
|
|
2411
|
+
type: "string",
|
|
2412
|
+
minLength: 1,
|
|
2413
|
+
maxLength: 100
|
|
2414
|
+
},
|
|
2415
|
+
maxItems: 100
|
|
2165
2416
|
}
|
|
2166
2417
|
},
|
|
2167
2418
|
required: [
|
|
@@ -2214,14 +2465,122 @@ var openapi_default = {
|
|
|
2214
2465
|
"claude",
|
|
2215
2466
|
"codex",
|
|
2216
2467
|
"grok",
|
|
2468
|
+
"opencode",
|
|
2217
2469
|
"opencode2",
|
|
2218
|
-
"pi"
|
|
2470
|
+
"pi",
|
|
2471
|
+
"omp",
|
|
2472
|
+
"dsh",
|
|
2473
|
+
"cline",
|
|
2474
|
+
"hermes",
|
|
2475
|
+
"prime-agent",
|
|
2476
|
+
"gemini",
|
|
2477
|
+
"aider",
|
|
2478
|
+
"kilo"
|
|
2219
2479
|
]
|
|
2220
2480
|
},
|
|
2221
2481
|
model: {
|
|
2222
2482
|
type: "string",
|
|
2223
2483
|
minLength: 1,
|
|
2224
2484
|
maxLength: 300
|
|
2485
|
+
},
|
|
2486
|
+
modelPolicy: {
|
|
2487
|
+
type: "object",
|
|
2488
|
+
properties: {
|
|
2489
|
+
version: {
|
|
2490
|
+
type: "number",
|
|
2491
|
+
enum: [
|
|
2492
|
+
1
|
|
2493
|
+
],
|
|
2494
|
+
default: 1
|
|
2495
|
+
},
|
|
2496
|
+
roles: {
|
|
2497
|
+
type: "object",
|
|
2498
|
+
properties: {
|
|
2499
|
+
subagent: {
|
|
2500
|
+
type: "string",
|
|
2501
|
+
minLength: 1,
|
|
2502
|
+
maxLength: 300,
|
|
2503
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2504
|
+
},
|
|
2505
|
+
fast: {
|
|
2506
|
+
type: "string",
|
|
2507
|
+
minLength: 1,
|
|
2508
|
+
maxLength: 300,
|
|
2509
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2510
|
+
},
|
|
2511
|
+
planning: {
|
|
2512
|
+
type: "string",
|
|
2513
|
+
minLength: 1,
|
|
2514
|
+
maxLength: 300,
|
|
2515
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2516
|
+
},
|
|
2517
|
+
review: {
|
|
2518
|
+
type: "string",
|
|
2519
|
+
minLength: 1,
|
|
2520
|
+
maxLength: 300,
|
|
2521
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2522
|
+
},
|
|
2523
|
+
summary: {
|
|
2524
|
+
type: "string",
|
|
2525
|
+
minLength: 1,
|
|
2526
|
+
maxLength: 300,
|
|
2527
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2528
|
+
},
|
|
2529
|
+
compaction: {
|
|
2530
|
+
type: "string",
|
|
2531
|
+
minLength: 1,
|
|
2532
|
+
maxLength: 300,
|
|
2533
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2534
|
+
},
|
|
2535
|
+
weak: {
|
|
2536
|
+
type: "string",
|
|
2537
|
+
minLength: 1,
|
|
2538
|
+
maxLength: 300,
|
|
2539
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2540
|
+
},
|
|
2541
|
+
editor: {
|
|
2542
|
+
type: "string",
|
|
2543
|
+
minLength: 1,
|
|
2544
|
+
maxLength: 300,
|
|
2545
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2546
|
+
}
|
|
2547
|
+
},
|
|
2548
|
+
additionalProperties: false
|
|
2549
|
+
},
|
|
2550
|
+
allowedModels: {
|
|
2551
|
+
type: "array",
|
|
2552
|
+
items: {
|
|
2553
|
+
type: "string",
|
|
2554
|
+
minLength: 1,
|
|
2555
|
+
maxLength: 300,
|
|
2556
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2557
|
+
},
|
|
2558
|
+
maxItems: 500
|
|
2559
|
+
},
|
|
2560
|
+
aliases: {
|
|
2561
|
+
type: "object",
|
|
2562
|
+
additionalProperties: {
|
|
2563
|
+
type: "string",
|
|
2564
|
+
minLength: 1,
|
|
2565
|
+
maxLength: 300,
|
|
2566
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2567
|
+
}
|
|
2568
|
+
},
|
|
2569
|
+
fallbacks: {
|
|
2570
|
+
type: "object",
|
|
2571
|
+
additionalProperties: {
|
|
2572
|
+
type: "array",
|
|
2573
|
+
items: {
|
|
2574
|
+
type: "string",
|
|
2575
|
+
minLength: 1,
|
|
2576
|
+
maxLength: 300,
|
|
2577
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2578
|
+
},
|
|
2579
|
+
maxItems: 20
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
},
|
|
2583
|
+
additionalProperties: false
|
|
2225
2584
|
}
|
|
2226
2585
|
},
|
|
2227
2586
|
required: [
|
|
@@ -2255,8 +2614,17 @@ var openapi_default = {
|
|
|
2255
2614
|
"claude",
|
|
2256
2615
|
"codex",
|
|
2257
2616
|
"grok",
|
|
2617
|
+
"opencode",
|
|
2258
2618
|
"opencode2",
|
|
2259
|
-
"pi"
|
|
2619
|
+
"pi",
|
|
2620
|
+
"omp",
|
|
2621
|
+
"dsh",
|
|
2622
|
+
"cline",
|
|
2623
|
+
"hermes",
|
|
2624
|
+
"prime-agent",
|
|
2625
|
+
"gemini",
|
|
2626
|
+
"aider",
|
|
2627
|
+
"kilo"
|
|
2260
2628
|
]
|
|
2261
2629
|
},
|
|
2262
2630
|
model: {
|
|
@@ -2264,6 +2632,105 @@ var openapi_default = {
|
|
|
2264
2632
|
minLength: 1,
|
|
2265
2633
|
maxLength: 300
|
|
2266
2634
|
},
|
|
2635
|
+
modelPolicy: {
|
|
2636
|
+
type: "object",
|
|
2637
|
+
properties: {
|
|
2638
|
+
version: {
|
|
2639
|
+
type: "number",
|
|
2640
|
+
enum: [
|
|
2641
|
+
1
|
|
2642
|
+
],
|
|
2643
|
+
default: 1
|
|
2644
|
+
},
|
|
2645
|
+
roles: {
|
|
2646
|
+
type: "object",
|
|
2647
|
+
properties: {
|
|
2648
|
+
subagent: {
|
|
2649
|
+
type: "string",
|
|
2650
|
+
minLength: 1,
|
|
2651
|
+
maxLength: 300,
|
|
2652
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2653
|
+
},
|
|
2654
|
+
fast: {
|
|
2655
|
+
type: "string",
|
|
2656
|
+
minLength: 1,
|
|
2657
|
+
maxLength: 300,
|
|
2658
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2659
|
+
},
|
|
2660
|
+
planning: {
|
|
2661
|
+
type: "string",
|
|
2662
|
+
minLength: 1,
|
|
2663
|
+
maxLength: 300,
|
|
2664
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2665
|
+
},
|
|
2666
|
+
review: {
|
|
2667
|
+
type: "string",
|
|
2668
|
+
minLength: 1,
|
|
2669
|
+
maxLength: 300,
|
|
2670
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2671
|
+
},
|
|
2672
|
+
summary: {
|
|
2673
|
+
type: "string",
|
|
2674
|
+
minLength: 1,
|
|
2675
|
+
maxLength: 300,
|
|
2676
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2677
|
+
},
|
|
2678
|
+
compaction: {
|
|
2679
|
+
type: "string",
|
|
2680
|
+
minLength: 1,
|
|
2681
|
+
maxLength: 300,
|
|
2682
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2683
|
+
},
|
|
2684
|
+
weak: {
|
|
2685
|
+
type: "string",
|
|
2686
|
+
minLength: 1,
|
|
2687
|
+
maxLength: 300,
|
|
2688
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2689
|
+
},
|
|
2690
|
+
editor: {
|
|
2691
|
+
type: "string",
|
|
2692
|
+
minLength: 1,
|
|
2693
|
+
maxLength: 300,
|
|
2694
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2695
|
+
}
|
|
2696
|
+
},
|
|
2697
|
+
additionalProperties: false
|
|
2698
|
+
},
|
|
2699
|
+
allowedModels: {
|
|
2700
|
+
type: "array",
|
|
2701
|
+
items: {
|
|
2702
|
+
type: "string",
|
|
2703
|
+
minLength: 1,
|
|
2704
|
+
maxLength: 300,
|
|
2705
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2706
|
+
},
|
|
2707
|
+
maxItems: 500
|
|
2708
|
+
},
|
|
2709
|
+
aliases: {
|
|
2710
|
+
type: "object",
|
|
2711
|
+
additionalProperties: {
|
|
2712
|
+
type: "string",
|
|
2713
|
+
minLength: 1,
|
|
2714
|
+
maxLength: 300,
|
|
2715
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2716
|
+
}
|
|
2717
|
+
},
|
|
2718
|
+
fallbacks: {
|
|
2719
|
+
type: "object",
|
|
2720
|
+
additionalProperties: {
|
|
2721
|
+
type: "array",
|
|
2722
|
+
items: {
|
|
2723
|
+
type: "string",
|
|
2724
|
+
minLength: 1,
|
|
2725
|
+
maxLength: 300,
|
|
2726
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2727
|
+
},
|
|
2728
|
+
maxItems: 20
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
},
|
|
2732
|
+
additionalProperties: false
|
|
2733
|
+
},
|
|
2267
2734
|
version: {
|
|
2268
2735
|
type: "integer",
|
|
2269
2736
|
exclusiveMinimum: true,
|
|
@@ -2337,6 +2804,15 @@ var openapi_default = {
|
|
|
2337
2804
|
maxLength: 100
|
|
2338
2805
|
},
|
|
2339
2806
|
maxItems: 100
|
|
2807
|
+
},
|
|
2808
|
+
supportedGenerationMethods: {
|
|
2809
|
+
type: "array",
|
|
2810
|
+
items: {
|
|
2811
|
+
type: "string",
|
|
2812
|
+
minLength: 1,
|
|
2813
|
+
maxLength: 100
|
|
2814
|
+
},
|
|
2815
|
+
maxItems: 100
|
|
2340
2816
|
}
|
|
2341
2817
|
},
|
|
2342
2818
|
required: [
|
|
@@ -2345,6 +2821,175 @@ var openapi_default = {
|
|
|
2345
2821
|
],
|
|
2346
2822
|
additionalProperties: false
|
|
2347
2823
|
},
|
|
2824
|
+
ModelPolicy: {
|
|
2825
|
+
type: "object",
|
|
2826
|
+
properties: {
|
|
2827
|
+
version: {
|
|
2828
|
+
type: "number",
|
|
2829
|
+
enum: [
|
|
2830
|
+
1
|
|
2831
|
+
],
|
|
2832
|
+
default: 1
|
|
2833
|
+
},
|
|
2834
|
+
roles: {
|
|
2835
|
+
type: "object",
|
|
2836
|
+
properties: {
|
|
2837
|
+
subagent: {
|
|
2838
|
+
type: "string",
|
|
2839
|
+
minLength: 1,
|
|
2840
|
+
maxLength: 300,
|
|
2841
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2842
|
+
},
|
|
2843
|
+
fast: {
|
|
2844
|
+
type: "string",
|
|
2845
|
+
minLength: 1,
|
|
2846
|
+
maxLength: 300,
|
|
2847
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2848
|
+
},
|
|
2849
|
+
planning: {
|
|
2850
|
+
type: "string",
|
|
2851
|
+
minLength: 1,
|
|
2852
|
+
maxLength: 300,
|
|
2853
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2854
|
+
},
|
|
2855
|
+
review: {
|
|
2856
|
+
type: "string",
|
|
2857
|
+
minLength: 1,
|
|
2858
|
+
maxLength: 300,
|
|
2859
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2860
|
+
},
|
|
2861
|
+
summary: {
|
|
2862
|
+
type: "string",
|
|
2863
|
+
minLength: 1,
|
|
2864
|
+
maxLength: 300,
|
|
2865
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2866
|
+
},
|
|
2867
|
+
compaction: {
|
|
2868
|
+
type: "string",
|
|
2869
|
+
minLength: 1,
|
|
2870
|
+
maxLength: 300,
|
|
2871
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2872
|
+
},
|
|
2873
|
+
weak: {
|
|
2874
|
+
type: "string",
|
|
2875
|
+
minLength: 1,
|
|
2876
|
+
maxLength: 300,
|
|
2877
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2878
|
+
},
|
|
2879
|
+
editor: {
|
|
2880
|
+
type: "string",
|
|
2881
|
+
minLength: 1,
|
|
2882
|
+
maxLength: 300,
|
|
2883
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2884
|
+
}
|
|
2885
|
+
},
|
|
2886
|
+
additionalProperties: false
|
|
2887
|
+
},
|
|
2888
|
+
allowedModels: {
|
|
2889
|
+
type: "array",
|
|
2890
|
+
items: {
|
|
2891
|
+
type: "string",
|
|
2892
|
+
minLength: 1,
|
|
2893
|
+
maxLength: 300,
|
|
2894
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2895
|
+
},
|
|
2896
|
+
maxItems: 500
|
|
2897
|
+
},
|
|
2898
|
+
aliases: {
|
|
2899
|
+
type: "object",
|
|
2900
|
+
additionalProperties: {
|
|
2901
|
+
type: "string",
|
|
2902
|
+
minLength: 1,
|
|
2903
|
+
maxLength: 300,
|
|
2904
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2905
|
+
}
|
|
2906
|
+
},
|
|
2907
|
+
fallbacks: {
|
|
2908
|
+
type: "object",
|
|
2909
|
+
additionalProperties: {
|
|
2910
|
+
type: "array",
|
|
2911
|
+
items: {
|
|
2912
|
+
type: "string",
|
|
2913
|
+
minLength: 1,
|
|
2914
|
+
maxLength: 300,
|
|
2915
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2916
|
+
},
|
|
2917
|
+
maxItems: 20
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
},
|
|
2921
|
+
additionalProperties: false
|
|
2922
|
+
},
|
|
2923
|
+
RoutingEvent: {
|
|
2924
|
+
type: "object",
|
|
2925
|
+
properties: {
|
|
2926
|
+
at: {
|
|
2927
|
+
type: "string",
|
|
2928
|
+
format: "date-time"
|
|
2929
|
+
},
|
|
2930
|
+
requestId: {
|
|
2931
|
+
type: "string",
|
|
2932
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
|
|
2933
|
+
},
|
|
2934
|
+
requestedModel: {
|
|
2935
|
+
type: "string",
|
|
2936
|
+
minLength: 1,
|
|
2937
|
+
maxLength: 300,
|
|
2938
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2939
|
+
},
|
|
2940
|
+
resolvedModel: {
|
|
2941
|
+
type: "string",
|
|
2942
|
+
minLength: 1,
|
|
2943
|
+
maxLength: 300,
|
|
2944
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2945
|
+
},
|
|
2946
|
+
reportedModel: {
|
|
2947
|
+
type: "string",
|
|
2948
|
+
minLength: 1,
|
|
2949
|
+
maxLength: 300,
|
|
2950
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
2951
|
+
},
|
|
2952
|
+
decision: {
|
|
2953
|
+
type: "string",
|
|
2954
|
+
enum: [
|
|
2955
|
+
"allow",
|
|
2956
|
+
"alias",
|
|
2957
|
+
"reject",
|
|
2958
|
+
"fallback"
|
|
2959
|
+
]
|
|
2960
|
+
},
|
|
2961
|
+
role: {
|
|
2962
|
+
type: "string",
|
|
2963
|
+
enum: [
|
|
2964
|
+
"main",
|
|
2965
|
+
"subagent",
|
|
2966
|
+
"fast",
|
|
2967
|
+
"planning",
|
|
2968
|
+
"review",
|
|
2969
|
+
"summary",
|
|
2970
|
+
"compaction",
|
|
2971
|
+
"weak",
|
|
2972
|
+
"editor"
|
|
2973
|
+
]
|
|
2974
|
+
},
|
|
2975
|
+
reason: {
|
|
2976
|
+
type: "string",
|
|
2977
|
+
pattern: "^[a-z][a-z0-9_]{0,63}$"
|
|
2978
|
+
},
|
|
2979
|
+
upstreamStatus: {
|
|
2980
|
+
type: "integer",
|
|
2981
|
+
minimum: 100,
|
|
2982
|
+
maximum: 599
|
|
2983
|
+
}
|
|
2984
|
+
},
|
|
2985
|
+
required: [
|
|
2986
|
+
"at",
|
|
2987
|
+
"requestId",
|
|
2988
|
+
"requestedModel",
|
|
2989
|
+
"decision"
|
|
2990
|
+
],
|
|
2991
|
+
additionalProperties: false
|
|
2992
|
+
},
|
|
2348
2993
|
ModelPage: {
|
|
2349
2994
|
type: "object",
|
|
2350
2995
|
properties: {
|
|
@@ -2404,6 +3049,15 @@ var openapi_default = {
|
|
|
2404
3049
|
},
|
|
2405
3050
|
maxItems: 100
|
|
2406
3051
|
},
|
|
3052
|
+
supportedGenerationMethods: {
|
|
3053
|
+
type: "array",
|
|
3054
|
+
items: {
|
|
3055
|
+
type: "string",
|
|
3056
|
+
minLength: 1,
|
|
3057
|
+
maxLength: 100
|
|
3058
|
+
},
|
|
3059
|
+
maxItems: 100
|
|
3060
|
+
},
|
|
2407
3061
|
codingEligible: {
|
|
2408
3062
|
type: "boolean"
|
|
2409
3063
|
}
|
|
@@ -2504,6 +3158,15 @@ var openapi_default = {
|
|
|
2504
3158
|
maxLength: 100
|
|
2505
3159
|
},
|
|
2506
3160
|
maxItems: 100
|
|
3161
|
+
},
|
|
3162
|
+
supportedGenerationMethods: {
|
|
3163
|
+
type: "array",
|
|
3164
|
+
items: {
|
|
3165
|
+
type: "string",
|
|
3166
|
+
minLength: 1,
|
|
3167
|
+
maxLength: 100
|
|
3168
|
+
},
|
|
3169
|
+
maxItems: 100
|
|
2507
3170
|
}
|
|
2508
3171
|
},
|
|
2509
3172
|
required: [
|
|
@@ -2555,7 +3218,8 @@ var openapi_default = {
|
|
|
2555
3218
|
enum: [
|
|
2556
3219
|
"anthropic-messages",
|
|
2557
3220
|
"openai-responses",
|
|
2558
|
-
"openai-chat"
|
|
3221
|
+
"openai-chat",
|
|
3222
|
+
"gemini-generate-content"
|
|
2559
3223
|
]
|
|
2560
3224
|
},
|
|
2561
3225
|
credentialEnv: {
|
|
@@ -2566,7 +3230,8 @@ var openapi_default = {
|
|
|
2566
3230
|
type: "string",
|
|
2567
3231
|
enum: [
|
|
2568
3232
|
"bearer",
|
|
2569
|
-
"x-api-key"
|
|
3233
|
+
"x-api-key",
|
|
3234
|
+
"api-key"
|
|
2570
3235
|
],
|
|
2571
3236
|
default: "bearer"
|
|
2572
3237
|
},
|
|
@@ -2583,6 +3248,7 @@ var openapi_default = {
|
|
|
2583
3248
|
"together",
|
|
2584
3249
|
"fireworks",
|
|
2585
3250
|
"dashscope",
|
|
3251
|
+
"gemini",
|
|
2586
3252
|
"none"
|
|
2587
3253
|
]
|
|
2588
3254
|
},
|
|
@@ -2591,6 +3257,7 @@ var openapi_default = {
|
|
|
2591
3257
|
enum: [
|
|
2592
3258
|
"bearer",
|
|
2593
3259
|
"x-api-key",
|
|
3260
|
+
"api-key",
|
|
2594
3261
|
"none"
|
|
2595
3262
|
]
|
|
2596
3263
|
},
|
|
@@ -2663,6 +3330,15 @@ var openapi_default = {
|
|
|
2663
3330
|
maxLength: 100
|
|
2664
3331
|
},
|
|
2665
3332
|
maxItems: 100
|
|
3333
|
+
},
|
|
3334
|
+
supportedGenerationMethods: {
|
|
3335
|
+
type: "array",
|
|
3336
|
+
items: {
|
|
3337
|
+
type: "string",
|
|
3338
|
+
minLength: 1,
|
|
3339
|
+
maxLength: 100
|
|
3340
|
+
},
|
|
3341
|
+
maxItems: 100
|
|
2666
3342
|
}
|
|
2667
3343
|
},
|
|
2668
3344
|
required: [
|
|
@@ -2715,8 +3391,17 @@ var openapi_default = {
|
|
|
2715
3391
|
"claude",
|
|
2716
3392
|
"codex",
|
|
2717
3393
|
"grok",
|
|
3394
|
+
"opencode",
|
|
2718
3395
|
"opencode2",
|
|
2719
|
-
"pi"
|
|
3396
|
+
"pi",
|
|
3397
|
+
"omp",
|
|
3398
|
+
"dsh",
|
|
3399
|
+
"cline",
|
|
3400
|
+
"hermes",
|
|
3401
|
+
"prime-agent",
|
|
3402
|
+
"gemini",
|
|
3403
|
+
"aider",
|
|
3404
|
+
"kilo"
|
|
2720
3405
|
]
|
|
2721
3406
|
},
|
|
2722
3407
|
model: {
|
|
@@ -2724,6 +3409,105 @@ var openapi_default = {
|
|
|
2724
3409
|
minLength: 1,
|
|
2725
3410
|
maxLength: 300
|
|
2726
3411
|
},
|
|
3412
|
+
modelPolicy: {
|
|
3413
|
+
type: "object",
|
|
3414
|
+
properties: {
|
|
3415
|
+
version: {
|
|
3416
|
+
type: "number",
|
|
3417
|
+
enum: [
|
|
3418
|
+
1
|
|
3419
|
+
],
|
|
3420
|
+
default: 1
|
|
3421
|
+
},
|
|
3422
|
+
roles: {
|
|
3423
|
+
type: "object",
|
|
3424
|
+
properties: {
|
|
3425
|
+
subagent: {
|
|
3426
|
+
type: "string",
|
|
3427
|
+
minLength: 1,
|
|
3428
|
+
maxLength: 300,
|
|
3429
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3430
|
+
},
|
|
3431
|
+
fast: {
|
|
3432
|
+
type: "string",
|
|
3433
|
+
minLength: 1,
|
|
3434
|
+
maxLength: 300,
|
|
3435
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3436
|
+
},
|
|
3437
|
+
planning: {
|
|
3438
|
+
type: "string",
|
|
3439
|
+
minLength: 1,
|
|
3440
|
+
maxLength: 300,
|
|
3441
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3442
|
+
},
|
|
3443
|
+
review: {
|
|
3444
|
+
type: "string",
|
|
3445
|
+
minLength: 1,
|
|
3446
|
+
maxLength: 300,
|
|
3447
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3448
|
+
},
|
|
3449
|
+
summary: {
|
|
3450
|
+
type: "string",
|
|
3451
|
+
minLength: 1,
|
|
3452
|
+
maxLength: 300,
|
|
3453
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3454
|
+
},
|
|
3455
|
+
compaction: {
|
|
3456
|
+
type: "string",
|
|
3457
|
+
minLength: 1,
|
|
3458
|
+
maxLength: 300,
|
|
3459
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3460
|
+
},
|
|
3461
|
+
weak: {
|
|
3462
|
+
type: "string",
|
|
3463
|
+
minLength: 1,
|
|
3464
|
+
maxLength: 300,
|
|
3465
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3466
|
+
},
|
|
3467
|
+
editor: {
|
|
3468
|
+
type: "string",
|
|
3469
|
+
minLength: 1,
|
|
3470
|
+
maxLength: 300,
|
|
3471
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3472
|
+
}
|
|
3473
|
+
},
|
|
3474
|
+
additionalProperties: false
|
|
3475
|
+
},
|
|
3476
|
+
allowedModels: {
|
|
3477
|
+
type: "array",
|
|
3478
|
+
items: {
|
|
3479
|
+
type: "string",
|
|
3480
|
+
minLength: 1,
|
|
3481
|
+
maxLength: 300,
|
|
3482
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3483
|
+
},
|
|
3484
|
+
maxItems: 500
|
|
3485
|
+
},
|
|
3486
|
+
aliases: {
|
|
3487
|
+
type: "object",
|
|
3488
|
+
additionalProperties: {
|
|
3489
|
+
type: "string",
|
|
3490
|
+
minLength: 1,
|
|
3491
|
+
maxLength: 300,
|
|
3492
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3493
|
+
}
|
|
3494
|
+
},
|
|
3495
|
+
fallbacks: {
|
|
3496
|
+
type: "object",
|
|
3497
|
+
additionalProperties: {
|
|
3498
|
+
type: "array",
|
|
3499
|
+
items: {
|
|
3500
|
+
type: "string",
|
|
3501
|
+
minLength: 1,
|
|
3502
|
+
maxLength: 300,
|
|
3503
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3504
|
+
},
|
|
3505
|
+
maxItems: 20
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
},
|
|
3509
|
+
additionalProperties: false
|
|
3510
|
+
},
|
|
2727
3511
|
version: {
|
|
2728
3512
|
type: "integer",
|
|
2729
3513
|
exclusiveMinimum: true,
|
|
@@ -2802,6 +3586,15 @@ var openapi_default = {
|
|
|
2802
3586
|
maxLength: 100
|
|
2803
3587
|
},
|
|
2804
3588
|
maxItems: 100
|
|
3589
|
+
},
|
|
3590
|
+
supportedGenerationMethods: {
|
|
3591
|
+
type: "array",
|
|
3592
|
+
items: {
|
|
3593
|
+
type: "string",
|
|
3594
|
+
minLength: 1,
|
|
3595
|
+
maxLength: 100
|
|
3596
|
+
},
|
|
3597
|
+
maxItems: 100
|
|
2805
3598
|
}
|
|
2806
3599
|
},
|
|
2807
3600
|
required: [
|
|
@@ -2851,6 +3644,12 @@ var openapi_default = {
|
|
|
2851
3644
|
RunInput: {
|
|
2852
3645
|
type: "object",
|
|
2853
3646
|
properties: {
|
|
3647
|
+
modelPolicyVersion: {
|
|
3648
|
+
type: "number",
|
|
3649
|
+
enum: [
|
|
3650
|
+
1
|
|
3651
|
+
]
|
|
3652
|
+
},
|
|
2854
3653
|
profileId: {
|
|
2855
3654
|
type: "string",
|
|
2856
3655
|
pattern: "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$"
|
|
@@ -2861,8 +3660,17 @@ var openapi_default = {
|
|
|
2861
3660
|
"claude",
|
|
2862
3661
|
"codex",
|
|
2863
3662
|
"grok",
|
|
3663
|
+
"opencode",
|
|
2864
3664
|
"opencode2",
|
|
2865
|
-
"pi"
|
|
3665
|
+
"pi",
|
|
3666
|
+
"omp",
|
|
3667
|
+
"dsh",
|
|
3668
|
+
"cline",
|
|
3669
|
+
"hermes",
|
|
3670
|
+
"prime-agent",
|
|
3671
|
+
"gemini",
|
|
3672
|
+
"aider",
|
|
3673
|
+
"kilo"
|
|
2866
3674
|
]
|
|
2867
3675
|
},
|
|
2868
3676
|
model: {
|
|
@@ -2870,12 +3678,112 @@ var openapi_default = {
|
|
|
2870
3678
|
minLength: 1,
|
|
2871
3679
|
maxLength: 300
|
|
2872
3680
|
},
|
|
3681
|
+
modelPolicy: {
|
|
3682
|
+
type: "object",
|
|
3683
|
+
properties: {
|
|
3684
|
+
version: {
|
|
3685
|
+
type: "number",
|
|
3686
|
+
enum: [
|
|
3687
|
+
1
|
|
3688
|
+
],
|
|
3689
|
+
default: 1
|
|
3690
|
+
},
|
|
3691
|
+
roles: {
|
|
3692
|
+
type: "object",
|
|
3693
|
+
properties: {
|
|
3694
|
+
subagent: {
|
|
3695
|
+
type: "string",
|
|
3696
|
+
minLength: 1,
|
|
3697
|
+
maxLength: 300,
|
|
3698
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3699
|
+
},
|
|
3700
|
+
fast: {
|
|
3701
|
+
type: "string",
|
|
3702
|
+
minLength: 1,
|
|
3703
|
+
maxLength: 300,
|
|
3704
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3705
|
+
},
|
|
3706
|
+
planning: {
|
|
3707
|
+
type: "string",
|
|
3708
|
+
minLength: 1,
|
|
3709
|
+
maxLength: 300,
|
|
3710
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3711
|
+
},
|
|
3712
|
+
review: {
|
|
3713
|
+
type: "string",
|
|
3714
|
+
minLength: 1,
|
|
3715
|
+
maxLength: 300,
|
|
3716
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3717
|
+
},
|
|
3718
|
+
summary: {
|
|
3719
|
+
type: "string",
|
|
3720
|
+
minLength: 1,
|
|
3721
|
+
maxLength: 300,
|
|
3722
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3723
|
+
},
|
|
3724
|
+
compaction: {
|
|
3725
|
+
type: "string",
|
|
3726
|
+
minLength: 1,
|
|
3727
|
+
maxLength: 300,
|
|
3728
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3729
|
+
},
|
|
3730
|
+
weak: {
|
|
3731
|
+
type: "string",
|
|
3732
|
+
minLength: 1,
|
|
3733
|
+
maxLength: 300,
|
|
3734
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3735
|
+
},
|
|
3736
|
+
editor: {
|
|
3737
|
+
type: "string",
|
|
3738
|
+
minLength: 1,
|
|
3739
|
+
maxLength: 300,
|
|
3740
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3741
|
+
}
|
|
3742
|
+
},
|
|
3743
|
+
additionalProperties: false
|
|
3744
|
+
},
|
|
3745
|
+
allowedModels: {
|
|
3746
|
+
type: "array",
|
|
3747
|
+
items: {
|
|
3748
|
+
type: "string",
|
|
3749
|
+
minLength: 1,
|
|
3750
|
+
maxLength: 300,
|
|
3751
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3752
|
+
},
|
|
3753
|
+
maxItems: 500
|
|
3754
|
+
},
|
|
3755
|
+
aliases: {
|
|
3756
|
+
type: "object",
|
|
3757
|
+
additionalProperties: {
|
|
3758
|
+
type: "string",
|
|
3759
|
+
minLength: 1,
|
|
3760
|
+
maxLength: 300,
|
|
3761
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3762
|
+
}
|
|
3763
|
+
},
|
|
3764
|
+
fallbacks: {
|
|
3765
|
+
type: "object",
|
|
3766
|
+
additionalProperties: {
|
|
3767
|
+
type: "array",
|
|
3768
|
+
items: {
|
|
3769
|
+
type: "string",
|
|
3770
|
+
minLength: 1,
|
|
3771
|
+
maxLength: 300,
|
|
3772
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3773
|
+
},
|
|
3774
|
+
maxItems: 20
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
},
|
|
3778
|
+
additionalProperties: false
|
|
3779
|
+
},
|
|
2873
3780
|
planToken: {
|
|
2874
3781
|
type: "string",
|
|
2875
3782
|
pattern: "^[a-f0-9]{64}$"
|
|
2876
3783
|
}
|
|
2877
3784
|
},
|
|
2878
3785
|
required: [
|
|
3786
|
+
"modelPolicyVersion",
|
|
2879
3787
|
"profileId",
|
|
2880
3788
|
"harness",
|
|
2881
3789
|
"model",
|
|
@@ -2898,6 +3806,85 @@ var openapi_default = {
|
|
|
2898
3806
|
type: "integer",
|
|
2899
3807
|
minimum: 0,
|
|
2900
3808
|
maximum: 255
|
|
3809
|
+
},
|
|
3810
|
+
routingEvents: {
|
|
3811
|
+
type: "array",
|
|
3812
|
+
items: {
|
|
3813
|
+
type: "object",
|
|
3814
|
+
properties: {
|
|
3815
|
+
at: {
|
|
3816
|
+
type: "string",
|
|
3817
|
+
format: "date-time"
|
|
3818
|
+
},
|
|
3819
|
+
requestId: {
|
|
3820
|
+
type: "string",
|
|
3821
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
|
|
3822
|
+
},
|
|
3823
|
+
requestedModel: {
|
|
3824
|
+
type: "string",
|
|
3825
|
+
minLength: 1,
|
|
3826
|
+
maxLength: 300,
|
|
3827
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3828
|
+
},
|
|
3829
|
+
resolvedModel: {
|
|
3830
|
+
type: "string",
|
|
3831
|
+
minLength: 1,
|
|
3832
|
+
maxLength: 300,
|
|
3833
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3834
|
+
},
|
|
3835
|
+
reportedModel: {
|
|
3836
|
+
type: "string",
|
|
3837
|
+
minLength: 1,
|
|
3838
|
+
maxLength: 300,
|
|
3839
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3840
|
+
},
|
|
3841
|
+
decision: {
|
|
3842
|
+
type: "string",
|
|
3843
|
+
enum: [
|
|
3844
|
+
"allow",
|
|
3845
|
+
"alias",
|
|
3846
|
+
"reject",
|
|
3847
|
+
"fallback"
|
|
3848
|
+
]
|
|
3849
|
+
},
|
|
3850
|
+
role: {
|
|
3851
|
+
type: "string",
|
|
3852
|
+
enum: [
|
|
3853
|
+
"main",
|
|
3854
|
+
"subagent",
|
|
3855
|
+
"fast",
|
|
3856
|
+
"planning",
|
|
3857
|
+
"review",
|
|
3858
|
+
"summary",
|
|
3859
|
+
"compaction",
|
|
3860
|
+
"weak",
|
|
3861
|
+
"editor"
|
|
3862
|
+
]
|
|
3863
|
+
},
|
|
3864
|
+
reason: {
|
|
3865
|
+
type: "string",
|
|
3866
|
+
pattern: "^[a-z][a-z0-9_]{0,63}$"
|
|
3867
|
+
},
|
|
3868
|
+
upstreamStatus: {
|
|
3869
|
+
type: "integer",
|
|
3870
|
+
minimum: 100,
|
|
3871
|
+
maximum: 599
|
|
3872
|
+
}
|
|
3873
|
+
},
|
|
3874
|
+
required: [
|
|
3875
|
+
"at",
|
|
3876
|
+
"requestId",
|
|
3877
|
+
"requestedModel",
|
|
3878
|
+
"decision"
|
|
3879
|
+
],
|
|
3880
|
+
additionalProperties: false
|
|
3881
|
+
},
|
|
3882
|
+
maxItems: 1000
|
|
3883
|
+
},
|
|
3884
|
+
routingEventsDropped: {
|
|
3885
|
+
type: "integer",
|
|
3886
|
+
minimum: 0,
|
|
3887
|
+
maximum: 1e6
|
|
2901
3888
|
}
|
|
2902
3889
|
},
|
|
2903
3890
|
required: [
|
|
@@ -2909,6 +3896,12 @@ var openapi_default = {
|
|
|
2909
3896
|
Run: {
|
|
2910
3897
|
type: "object",
|
|
2911
3898
|
properties: {
|
|
3899
|
+
modelPolicyVersion: {
|
|
3900
|
+
type: "number",
|
|
3901
|
+
enum: [
|
|
3902
|
+
1
|
|
3903
|
+
]
|
|
3904
|
+
},
|
|
2912
3905
|
profileId: {
|
|
2913
3906
|
type: "string",
|
|
2914
3907
|
pattern: "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$"
|
|
@@ -2919,8 +3912,17 @@ var openapi_default = {
|
|
|
2919
3912
|
"claude",
|
|
2920
3913
|
"codex",
|
|
2921
3914
|
"grok",
|
|
3915
|
+
"opencode",
|
|
2922
3916
|
"opencode2",
|
|
2923
|
-
"pi"
|
|
3917
|
+
"pi",
|
|
3918
|
+
"omp",
|
|
3919
|
+
"dsh",
|
|
3920
|
+
"cline",
|
|
3921
|
+
"hermes",
|
|
3922
|
+
"prime-agent",
|
|
3923
|
+
"gemini",
|
|
3924
|
+
"aider",
|
|
3925
|
+
"kilo"
|
|
2924
3926
|
]
|
|
2925
3927
|
},
|
|
2926
3928
|
model: {
|
|
@@ -2928,6 +3930,105 @@ var openapi_default = {
|
|
|
2928
3930
|
minLength: 1,
|
|
2929
3931
|
maxLength: 300
|
|
2930
3932
|
},
|
|
3933
|
+
modelPolicy: {
|
|
3934
|
+
type: "object",
|
|
3935
|
+
properties: {
|
|
3936
|
+
version: {
|
|
3937
|
+
type: "number",
|
|
3938
|
+
enum: [
|
|
3939
|
+
1
|
|
3940
|
+
],
|
|
3941
|
+
default: 1
|
|
3942
|
+
},
|
|
3943
|
+
roles: {
|
|
3944
|
+
type: "object",
|
|
3945
|
+
properties: {
|
|
3946
|
+
subagent: {
|
|
3947
|
+
type: "string",
|
|
3948
|
+
minLength: 1,
|
|
3949
|
+
maxLength: 300,
|
|
3950
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3951
|
+
},
|
|
3952
|
+
fast: {
|
|
3953
|
+
type: "string",
|
|
3954
|
+
minLength: 1,
|
|
3955
|
+
maxLength: 300,
|
|
3956
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3957
|
+
},
|
|
3958
|
+
planning: {
|
|
3959
|
+
type: "string",
|
|
3960
|
+
minLength: 1,
|
|
3961
|
+
maxLength: 300,
|
|
3962
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3963
|
+
},
|
|
3964
|
+
review: {
|
|
3965
|
+
type: "string",
|
|
3966
|
+
minLength: 1,
|
|
3967
|
+
maxLength: 300,
|
|
3968
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3969
|
+
},
|
|
3970
|
+
summary: {
|
|
3971
|
+
type: "string",
|
|
3972
|
+
minLength: 1,
|
|
3973
|
+
maxLength: 300,
|
|
3974
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3975
|
+
},
|
|
3976
|
+
compaction: {
|
|
3977
|
+
type: "string",
|
|
3978
|
+
minLength: 1,
|
|
3979
|
+
maxLength: 300,
|
|
3980
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3981
|
+
},
|
|
3982
|
+
weak: {
|
|
3983
|
+
type: "string",
|
|
3984
|
+
minLength: 1,
|
|
3985
|
+
maxLength: 300,
|
|
3986
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3987
|
+
},
|
|
3988
|
+
editor: {
|
|
3989
|
+
type: "string",
|
|
3990
|
+
minLength: 1,
|
|
3991
|
+
maxLength: 300,
|
|
3992
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
3993
|
+
}
|
|
3994
|
+
},
|
|
3995
|
+
additionalProperties: false
|
|
3996
|
+
},
|
|
3997
|
+
allowedModels: {
|
|
3998
|
+
type: "array",
|
|
3999
|
+
items: {
|
|
4000
|
+
type: "string",
|
|
4001
|
+
minLength: 1,
|
|
4002
|
+
maxLength: 300,
|
|
4003
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
4004
|
+
},
|
|
4005
|
+
maxItems: 500
|
|
4006
|
+
},
|
|
4007
|
+
aliases: {
|
|
4008
|
+
type: "object",
|
|
4009
|
+
additionalProperties: {
|
|
4010
|
+
type: "string",
|
|
4011
|
+
minLength: 1,
|
|
4012
|
+
maxLength: 300,
|
|
4013
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
4014
|
+
}
|
|
4015
|
+
},
|
|
4016
|
+
fallbacks: {
|
|
4017
|
+
type: "object",
|
|
4018
|
+
additionalProperties: {
|
|
4019
|
+
type: "array",
|
|
4020
|
+
items: {
|
|
4021
|
+
type: "string",
|
|
4022
|
+
minLength: 1,
|
|
4023
|
+
maxLength: 300,
|
|
4024
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
4025
|
+
},
|
|
4026
|
+
maxItems: 20
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
},
|
|
4030
|
+
additionalProperties: false
|
|
4031
|
+
},
|
|
2931
4032
|
planToken: {
|
|
2932
4033
|
type: "string",
|
|
2933
4034
|
pattern: "^[a-f0-9]{64}$"
|
|
@@ -2975,6 +4076,85 @@ var openapi_default = {
|
|
|
2975
4076
|
},
|
|
2976
4077
|
exitCode: {
|
|
2977
4078
|
type: "integer"
|
|
4079
|
+
},
|
|
4080
|
+
routingEvents: {
|
|
4081
|
+
type: "array",
|
|
4082
|
+
items: {
|
|
4083
|
+
type: "object",
|
|
4084
|
+
properties: {
|
|
4085
|
+
at: {
|
|
4086
|
+
type: "string",
|
|
4087
|
+
format: "date-time"
|
|
4088
|
+
},
|
|
4089
|
+
requestId: {
|
|
4090
|
+
type: "string",
|
|
4091
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
|
|
4092
|
+
},
|
|
4093
|
+
requestedModel: {
|
|
4094
|
+
type: "string",
|
|
4095
|
+
minLength: 1,
|
|
4096
|
+
maxLength: 300,
|
|
4097
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
4098
|
+
},
|
|
4099
|
+
resolvedModel: {
|
|
4100
|
+
type: "string",
|
|
4101
|
+
minLength: 1,
|
|
4102
|
+
maxLength: 300,
|
|
4103
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
4104
|
+
},
|
|
4105
|
+
reportedModel: {
|
|
4106
|
+
type: "string",
|
|
4107
|
+
minLength: 1,
|
|
4108
|
+
maxLength: 300,
|
|
4109
|
+
pattern: "^[^\\u0000-\\u001f\\u007f]+$"
|
|
4110
|
+
},
|
|
4111
|
+
decision: {
|
|
4112
|
+
type: "string",
|
|
4113
|
+
enum: [
|
|
4114
|
+
"allow",
|
|
4115
|
+
"alias",
|
|
4116
|
+
"reject",
|
|
4117
|
+
"fallback"
|
|
4118
|
+
]
|
|
4119
|
+
},
|
|
4120
|
+
role: {
|
|
4121
|
+
type: "string",
|
|
4122
|
+
enum: [
|
|
4123
|
+
"main",
|
|
4124
|
+
"subagent",
|
|
4125
|
+
"fast",
|
|
4126
|
+
"planning",
|
|
4127
|
+
"review",
|
|
4128
|
+
"summary",
|
|
4129
|
+
"compaction",
|
|
4130
|
+
"weak",
|
|
4131
|
+
"editor"
|
|
4132
|
+
]
|
|
4133
|
+
},
|
|
4134
|
+
reason: {
|
|
4135
|
+
type: "string",
|
|
4136
|
+
pattern: "^[a-z][a-z0-9_]{0,63}$"
|
|
4137
|
+
},
|
|
4138
|
+
upstreamStatus: {
|
|
4139
|
+
type: "integer",
|
|
4140
|
+
minimum: 100,
|
|
4141
|
+
maximum: 599
|
|
4142
|
+
}
|
|
4143
|
+
},
|
|
4144
|
+
required: [
|
|
4145
|
+
"at",
|
|
4146
|
+
"requestId",
|
|
4147
|
+
"requestedModel",
|
|
4148
|
+
"decision"
|
|
4149
|
+
],
|
|
4150
|
+
additionalProperties: false
|
|
4151
|
+
},
|
|
4152
|
+
maxItems: 1000
|
|
4153
|
+
},
|
|
4154
|
+
routingEventsDropped: {
|
|
4155
|
+
type: "integer",
|
|
4156
|
+
minimum: 0,
|
|
4157
|
+
maximum: 1e6
|
|
2978
4158
|
}
|
|
2979
4159
|
},
|
|
2980
4160
|
required: [
|
|
@@ -3101,8 +4281,8 @@ var openapi_default = {
|
|
|
3101
4281
|
};
|
|
3102
4282
|
|
|
3103
4283
|
// src/service.ts
|
|
3104
|
-
var snapshot = (profile, provider, catalog) =>
|
|
3105
|
-
var hash = (s) =>
|
|
4284
|
+
var snapshot = (profile, provider, catalog) => createHash2("sha256").update(JSON.stringify([profile, provider, { models: catalog.models, source: catalog.source }])).digest("hex");
|
|
4285
|
+
var hash = (s) => createHash2("sha256").update(s).digest();
|
|
3106
4286
|
function createHandler(store, apiKey, providerEnv = process.env, resolveCredential) {
|
|
3107
4287
|
if (!apiKey || apiKey.length < 24)
|
|
3108
4288
|
throw new Fault(500, "auth_config", "Set HASNA_SWITCHER_API_KEY to a random token of at least 24 characters.");
|
|
@@ -3133,23 +4313,23 @@ function createHandler(store, apiKey, providerEnv = process.env, resolveCredenti
|
|
|
3133
4313
|
if (parts[0] !== "v1")
|
|
3134
4314
|
throw new Fault(404, "not_found", "Route was not found.");
|
|
3135
4315
|
const resource = parts[1];
|
|
3136
|
-
const
|
|
3137
|
-
if (
|
|
3138
|
-
parse(idSchema,
|
|
3139
|
-
const page = () => parse(
|
|
3140
|
-
limit:
|
|
3141
|
-
offset:
|
|
3142
|
-
search:
|
|
4316
|
+
const id2 = parts[2];
|
|
4317
|
+
if (id2)
|
|
4318
|
+
parse(idSchema, id2);
|
|
4319
|
+
const page = () => parse(z3.object({
|
|
4320
|
+
limit: z3.coerce.number().int().min(1).max(1000).default(100),
|
|
4321
|
+
offset: z3.coerce.number().int().min(0).max(1e6).default(0),
|
|
4322
|
+
search: z3.string().max(200).default("")
|
|
3143
4323
|
}).strict(), Object.fromEntries(url.searchParams));
|
|
3144
4324
|
if (request.method === "GET") {
|
|
3145
4325
|
if (resource === "provider-presets" && parts.length <= 3)
|
|
3146
|
-
return json(
|
|
4326
|
+
return json(id2 ? getProviderPreset(id2) : { data: providerPresets });
|
|
3147
4327
|
if (["providers", "profiles", "runs"].includes(resource) && parts.length <= 3) {
|
|
3148
4328
|
const kind = resource;
|
|
3149
|
-
return json(
|
|
4329
|
+
return json(id2 ? await store.get(kind, id2) : await store.list(kind, page()));
|
|
3150
4330
|
}
|
|
3151
|
-
if (resource === "providers" &&
|
|
3152
|
-
const catalog = await store.get("catalogs",
|
|
4331
|
+
if (resource === "providers" && id2 && parts[3] === "models" && parts.length === 4) {
|
|
4332
|
+
const catalog = await store.get("catalogs", id2);
|
|
3153
4333
|
const p = page();
|
|
3154
4334
|
const filtered = catalog.models.filter((m) => [m.id, m.name].some((s) => s.toLowerCase().includes(p.search.toLowerCase())));
|
|
3155
4335
|
return json({ ...catalog, models: undefined, data: filtered.slice(p.offset, p.offset + p.limit).map((m) => ({ ...m, codingEligible: codingEligible(m) })), total: filtered.length, ...p });
|
|
@@ -3180,55 +4360,53 @@ function createHandler(store, apiKey, providerEnv = process.env, resolveCredenti
|
|
|
3180
4360
|
};
|
|
3181
4361
|
const replay = await store.replay(key, fingerprint);
|
|
3182
4362
|
if (replay.found)
|
|
3183
|
-
return json(replay.value, request.method === "POST" && ["providers", "profiles", "runs"].includes(resource) && !
|
|
4363
|
+
return json(replay.value, request.method === "POST" && ["providers", "profiles", "runs"].includes(resource) && !id2 ? 201 : 200);
|
|
3184
4364
|
let refreshed;
|
|
3185
|
-
if (resource === "providers" &&
|
|
3186
|
-
parse(
|
|
3187
|
-
const provider = await store.get("providers",
|
|
4365
|
+
if (resource === "providers" && id2 && parts[3] === "refresh" && parts.length === 4 && request.method === "POST") {
|
|
4366
|
+
parse(z3.object({}).strict(), body);
|
|
4367
|
+
const provider = await store.get("providers", id2);
|
|
3188
4368
|
refreshed = { provider, catalog: await discover(provider, providerEnv, resolveCredential) };
|
|
3189
4369
|
}
|
|
3190
4370
|
const result = await store.mutate(key, fingerprint, async (db) => {
|
|
3191
4371
|
if ((resource === "providers" || resource === "profiles") && parts.length <= 3) {
|
|
3192
|
-
if (request.method === "DELETE" &&
|
|
3193
|
-
return store.remove(resource,
|
|
3194
|
-
if (request.method === "POST" && !
|
|
4372
|
+
if (request.method === "DELETE" && id2)
|
|
4373
|
+
return store.remove(resource, id2, version(), db);
|
|
4374
|
+
if (request.method === "POST" && !id2 || request.method === "PUT" && id2) {
|
|
3195
4375
|
const value = resource === "providers" ? parse(providerInputSchema, body) : parse(profileInputSchema, body);
|
|
3196
|
-
if (
|
|
4376
|
+
if (id2 && value.id !== id2)
|
|
3197
4377
|
throw new Fault(400, "id_mismatch", "Path and body IDs must match.");
|
|
3198
4378
|
if (resource === "profiles") {
|
|
3199
4379
|
const profile = value;
|
|
3200
4380
|
const provider = await store.get("providers", profile.providerId, db);
|
|
3201
|
-
|
|
3202
|
-
throw new Fault(422, "protocol_mismatch", "Harness does not support this provider protocol.");
|
|
4381
|
+
validateHarnessProvider(profile.harness, provider);
|
|
3203
4382
|
}
|
|
3204
|
-
const saved = await store.put(resource, value,
|
|
3205
|
-
if (resource === "providers" &&
|
|
3206
|
-
await db.unsafe("DELETE FROM switcher_catalogs WHERE id = $1", [
|
|
4383
|
+
const saved = await store.put(resource, value, id2 ? version() : undefined, db);
|
|
4384
|
+
if (resource === "providers" && id2)
|
|
4385
|
+
await db.unsafe("DELETE FROM switcher_catalogs WHERE id = $1", [id2]);
|
|
3207
4386
|
return saved;
|
|
3208
4387
|
}
|
|
3209
4388
|
}
|
|
3210
|
-
if (resource === "providers" &&
|
|
4389
|
+
if (resource === "providers" && id2 && parts[3] === "refresh" && parts.length === 4 && request.method === "POST") {
|
|
3211
4390
|
if (store.engine === "postgresql")
|
|
3212
|
-
await db.unsafe("SELECT id FROM switcher_providers WHERE id = $1 FOR SHARE", [
|
|
3213
|
-
const provider = await store.get("providers",
|
|
4391
|
+
await db.unsafe("SELECT id FROM switcher_providers WHERE id = $1 FOR SHARE", [id2]);
|
|
4392
|
+
const provider = await store.get("providers", id2, db);
|
|
3214
4393
|
if (!refreshed || provider.version !== refreshed.provider.version)
|
|
3215
4394
|
throw new Fault(409, "provider_changed", "Provider changed during discovery; refresh again.");
|
|
3216
4395
|
const catalog = refreshed.catalog;
|
|
3217
4396
|
let old;
|
|
3218
4397
|
try {
|
|
3219
|
-
old = await store.get("catalogs",
|
|
4398
|
+
old = await store.get("catalogs", id2, db);
|
|
3220
4399
|
} catch (e) {
|
|
3221
4400
|
if (!(e instanceof Fault && e.status === 404))
|
|
3222
4401
|
throw e;
|
|
3223
4402
|
}
|
|
3224
|
-
return store.put("catalogs", { id, ...catalog }, old?.version, db);
|
|
4403
|
+
return store.put("catalogs", { id: id2, ...catalog }, old?.version, db);
|
|
3225
4404
|
}
|
|
3226
|
-
if (resource === "launch-plans" && !
|
|
3227
|
-
const { profileId } = parse(
|
|
4405
|
+
if (resource === "launch-plans" && !id2 && request.method === "POST") {
|
|
4406
|
+
const { profileId } = parse(z3.object({ profileId: idSchema }).strict(), body);
|
|
3228
4407
|
const profile = await store.get("profiles", profileId, db);
|
|
3229
4408
|
const provider = await store.get("providers", profile.providerId, db);
|
|
3230
|
-
|
|
3231
|
-
throw new Fault(422, "protocol_mismatch", "Harness does not support this provider protocol.");
|
|
4409
|
+
validateHarnessProvider(profile.harness, provider);
|
|
3232
4410
|
let catalog;
|
|
3233
4411
|
try {
|
|
3234
4412
|
catalog = await store.get("catalogs", provider.id, db);
|
|
@@ -3240,10 +4418,11 @@ function createHandler(store, apiKey, providerEnv = process.env, resolveCredenti
|
|
|
3240
4418
|
const selected = catalog.models.find((m) => m.id === profile.model);
|
|
3241
4419
|
if (!selected)
|
|
3242
4420
|
throw new Fault(422, "model_missing", "Selected model is not in the provider catalog.");
|
|
3243
|
-
if (!
|
|
3244
|
-
throw new Fault(422, "model_ineligible", "Selected model explicitly lacks text output or tool support.");
|
|
4421
|
+
if (!harnessEligible(selected, profile.harness))
|
|
4422
|
+
throw new Fault(422, "model_ineligible", "Selected model is unavailable or explicitly lacks a required generation method, text output or tool support.");
|
|
4423
|
+
compileModelPolicy(profile.model, catalog.models.filter((model) => harnessEligible(model, profile.harness)), profile.modelPolicy);
|
|
3245
4424
|
const warnings = [];
|
|
3246
|
-
if (!selected.supportedParameters)
|
|
4425
|
+
if (profile.harness !== "aider" && !selected.supportedParameters)
|
|
3247
4426
|
warnings.push("Provider does not declare tool capabilities; execution compatibility is unverified.");
|
|
3248
4427
|
if (profile.harness === "claude" && !/claude/i.test(profile.model))
|
|
3249
4428
|
warnings.push("Anthropic does not support non-Claude models in Claude Code; this combination is experimental.");
|
|
@@ -3251,7 +4430,9 @@ function createHandler(store, apiKey, providerEnv = process.env, resolveCredenti
|
|
|
3251
4430
|
warnings.push("Catalog snapshot is older than five minutes; refresh before launching.");
|
|
3252
4431
|
return { profile, provider, catalog, warnings, planToken: snapshot(profile, provider, catalog) };
|
|
3253
4432
|
}
|
|
3254
|
-
if (resource === "runs" && !
|
|
4433
|
+
if (resource === "runs" && !id2 && request.method === "POST") {
|
|
4434
|
+
if (body?.modelPolicyVersion !== 1)
|
|
4435
|
+
throw new Fault(409, "launcher_upgrade_required", "This API requires a launcher with automatic model policy version 1; upgrade the Switcher CLI/SDK.");
|
|
3255
4436
|
const input = parse(runInputSchema, body);
|
|
3256
4437
|
if (store.engine === "postgresql")
|
|
3257
4438
|
await db.unsafe("SELECT id FROM switcher_profiles WHERE id = $1 FOR SHARE", [input.profileId]);
|
|
@@ -3269,20 +4450,20 @@ function createHandler(store, apiKey, providerEnv = process.env, resolveCredenti
|
|
|
3269
4450
|
throw new Fault(409, "plan_changed", "Catalog changed; request a fresh launch plan.");
|
|
3270
4451
|
throw error;
|
|
3271
4452
|
}
|
|
3272
|
-
if (profile.harness !== input.harness || profile.model !== input.model || snapshot(profile, provider, catalog) !== input.planToken)
|
|
3273
|
-
throw new Fault(409, "plan_changed", "Provider, profile or catalog changed; request a fresh launch plan.");
|
|
3274
|
-
return store.put("runs", { ...input, providerId: provider.id, providerVersion: provider.version, profileVersion: profile.version, id: crypto.randomUUID(), status: "running", startedAt: new Date().toISOString() }, undefined, db);
|
|
4453
|
+
if (profile.harness !== input.harness || profile.model !== input.model || canonicalPolicyJSON(profile.modelPolicy ?? null) !== canonicalPolicyJSON(input.modelPolicy ?? profile.modelPolicy ?? null) || snapshot(profile, provider, catalog) !== input.planToken)
|
|
4454
|
+
throw new Fault(409, "plan_changed", "Provider, profile, model policy or catalog changed; request a fresh launch plan.");
|
|
4455
|
+
return store.put("runs", { ...input, modelPolicy: profile.modelPolicy, providerId: provider.id, providerVersion: provider.version, profileVersion: profile.version, id: crypto.randomUUID(), status: "running", startedAt: new Date().toISOString() }, undefined, db);
|
|
3275
4456
|
}
|
|
3276
|
-
if (resource === "runs" &&
|
|
4457
|
+
if (resource === "runs" && id2 && request.method === "PATCH" && parts.length === 3) {
|
|
3277
4458
|
const input = parse(runUpdateSchema, body);
|
|
3278
|
-
const run = await store.get("runs",
|
|
4459
|
+
const run = await store.get("runs", id2, db);
|
|
3279
4460
|
if (run.status !== "running")
|
|
3280
4461
|
throw new Fault(409, "run_finished", "Run has already finished.");
|
|
3281
4462
|
return store.put("runs", { ...run, ...input, endedAt: new Date().toISOString() }, version(), db);
|
|
3282
4463
|
}
|
|
3283
4464
|
throw new Fault(404, "not_found", "Route was not found.");
|
|
3284
4465
|
});
|
|
3285
|
-
return json(result, request.method === "POST" && ["providers", "profiles", "runs"].includes(resource) && !
|
|
4466
|
+
return json(result, request.method === "POST" && ["providers", "profiles", "runs"].includes(resource) && !id2 ? 201 : 200);
|
|
3286
4467
|
} catch (error) {
|
|
3287
4468
|
const safe = error instanceof Fault ? error : new Fault(500, "internal_error", "Request failed.");
|
|
3288
4469
|
return json({ error: { code: safe.code, message: safe.message, requestId } }, safe.status);
|