@hasna/switcher 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -6
- 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 +6481 -3510
- package/dist/cline-backend.d.ts +7 -0
- package/dist/credentials.d.ts +5 -5
- package/dist/direct-launch.d.ts +1 -1
- package/dist/domain.d.ts +62 -49
- 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/generated/api.d.ts +28 -21
- package/dist/harness-arguments.d.ts +1 -0
- package/dist/harness-installation.d.ts +19 -0
- package/dist/harness-types.d.ts +1 -0
- package/dist/harnesses.d.ts +5 -2
- package/dist/hermes-backend.d.ts +19 -0
- package/dist/index.js +53 -11
- package/dist/kilo-config.d.ts +13 -0
- package/dist/kilo.d.ts +5 -0
- package/dist/launcher.d.ts +4 -3
- package/dist/mcp/index.js +43 -11
- package/dist/omp-backend.d.ts +7 -0
- package/dist/opencode2-config.d.ts +3 -3
- package/dist/ori-backend.d.ts +2 -2
- package/dist/presets.d.ts +11 -10
- package/dist/sdk.d.ts +41 -33
- package/dist/sdk.js +53 -11
- package/dist/serve/index.js +274 -49
- package/openapi.json +138 -14
- package/package.json +12 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HarnessLaunchInput, PreparedLaunch } from "./harness-types";
|
|
2
|
+
/** Hermes' native runtime names for the three Switcher wire contracts. */
|
|
3
|
+
export declare const hermesApiMode: {
|
|
4
|
+
readonly "anthropic-messages": "anthropic_messages";
|
|
5
|
+
readonly "openai-responses": "codex_responses";
|
|
6
|
+
readonly "openai-chat": "chat_completions";
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Keep Hermes' picker and inference route on the exact Switcher catalog.
|
|
10
|
+
* Hermes' custom provider client uses Bearer locally; this bridge translates
|
|
11
|
+
* that local credential into the provider's declared upstream header style.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createHermesBridge(input: Pick<HarnessLaunchInput, "baseUrl" | "protocol" | "authStyle" | "model" | "models" | "credential">): {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
token: string;
|
|
16
|
+
cleanup: () => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
/** Prepare a Hermes run with isolated config, catalog and durable session state. */
|
|
19
|
+
export declare function prepareHermesLaunch(input: HarnessLaunchInput): Promise<PreparedLaunch>;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/domain.ts
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
var harnessSchema = z.enum(["claude", "codex", "grok", "opencode2", "pi"]);
|
|
4
|
-
var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat"]);
|
|
3
|
+
var harnessSchema = z.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
|
|
4
|
+
var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
|
|
5
5
|
var idSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
|
|
6
6
|
var label = z.string().min(1).max(200);
|
|
7
7
|
var envRef = z.string().regex(/^SWITCHER_PROVIDER_[A-Z0-9_]+$/);
|
|
@@ -33,7 +33,8 @@ var modelSchema = z.object({
|
|
|
33
33
|
maxOutputTokens: z.number().int().positive().optional(),
|
|
34
34
|
inputModalities: z.array(z.string().max(50)).max(20).optional(),
|
|
35
35
|
outputModalities: z.array(z.string().max(50)).max(20).optional(),
|
|
36
|
-
supportedParameters: z.array(z.string().max(100)).max(100).optional()
|
|
36
|
+
supportedParameters: z.array(z.string().max(100)).max(100).optional(),
|
|
37
|
+
supportedGenerationMethods: z.array(z.string().min(1).max(100)).max(100).optional()
|
|
37
38
|
}).strict();
|
|
38
39
|
var providerInputSchema = z.object({
|
|
39
40
|
id: idSchema,
|
|
@@ -41,10 +42,10 @@ var providerInputSchema = z.object({
|
|
|
41
42
|
baseUrl: urlSchema,
|
|
42
43
|
protocol: protocolSchema,
|
|
43
44
|
credentialEnv: envRef.optional(),
|
|
44
|
-
authStyle: z.enum(["bearer", "x-api-key"]).default("bearer"),
|
|
45
|
+
authStyle: z.enum(["bearer", "x-api-key", "api-key"]).default("bearer"),
|
|
45
46
|
catalogBaseUrl: urlSchema.optional(),
|
|
46
|
-
catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]).optional(),
|
|
47
|
-
catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
|
|
47
|
+
catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]).optional(),
|
|
48
|
+
catalogAuthStyle: z.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
|
|
48
49
|
catalogCredentialEnv: envRef.optional(),
|
|
49
50
|
catalogAccountId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
|
|
50
51
|
modelsPath: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
|
|
@@ -58,10 +59,10 @@ var providerPresetSchema = z.object({
|
|
|
58
59
|
protocols: z.array(z.object({
|
|
59
60
|
protocol: protocolSchema,
|
|
60
61
|
baseUrl: urlSchema.optional(),
|
|
61
|
-
authStyle: z.enum(["bearer", "x-api-key"]),
|
|
62
|
+
authStyle: z.enum(["bearer", "x-api-key", "api-key"]),
|
|
62
63
|
catalogBaseUrl: urlSchema.optional(),
|
|
63
|
-
catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]),
|
|
64
|
-
catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
|
|
64
|
+
catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]),
|
|
65
|
+
catalogAuthStyle: z.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
|
|
65
66
|
modelsPath: z.string(),
|
|
66
67
|
notes: z.array(z.string())
|
|
67
68
|
}).strict()).min(1),
|
|
@@ -102,7 +103,15 @@ function parse(schema, value) {
|
|
|
102
103
|
return result.data;
|
|
103
104
|
}
|
|
104
105
|
function compatible(harness, protocol) {
|
|
105
|
-
|
|
106
|
+
if (harness === "claude")
|
|
107
|
+
return protocol === "anthropic-messages";
|
|
108
|
+
if (harness === "codex")
|
|
109
|
+
return protocol === "openai-responses";
|
|
110
|
+
if (harness === "gemini")
|
|
111
|
+
return protocol === "gemini-generate-content";
|
|
112
|
+
if (protocol === "gemini-generate-content")
|
|
113
|
+
return false;
|
|
114
|
+
return true;
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
// src/http.ts
|
|
@@ -168,7 +177,18 @@ var providerPresets = [
|
|
|
168
177
|
], ["https://api-docs.deepseek.com/guides/anthropic_api", "https://api-docs.deepseek.com/api/list-models"], "DEEPSEEK_API_KEY"),
|
|
169
178
|
preset("openrouter", "OpenRouter", ["openai-chat", "openai-responses", "anthropic-messages"].map((protocol) => route(protocol, "https://openrouter.ai/api/v1", { catalogAuthStyle: "none" })), ["https://openrouter.ai/docs/api/api-reference/models/list-all-models-and-their-properties", "https://openrouter.ai/docs/guides/overview"], "OPENROUTER_API_KEY"),
|
|
170
179
|
preset("anthropic", "Anthropic", [route("anthropic-messages", "https://api.anthropic.com/v1", { authStyle: "x-api-key" })], ["https://platform.claude.com/docs/en/api/overview", "https://platform.claude.com/docs/en/api/models/list"], "ANTHROPIC_API_KEY"),
|
|
180
|
+
preset("gemini", "Google Gemini", [route("gemini-generate-content", "https://generativelanguage.googleapis.com/v1beta", {
|
|
181
|
+
authStyle: "x-api-key",
|
|
182
|
+
catalogBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
183
|
+
catalogFormat: "gemini",
|
|
184
|
+
catalogAuthStyle: "x-api-key",
|
|
185
|
+
notes: ["Gemini CLI uses the native generateContent wire with x-goog-api-key authentication; model IDs are returned as models/{id}."]
|
|
186
|
+
}), route("openai-chat", "https://generativelanguage.googleapis.com/v1beta/openai")], ["https://ai.google.dev/api", "https://ai.google.dev/api/models", "https://github.com/google-gemini/gemini-cli", "https://ai.google.dev/gemini-api/docs/openai"], "GEMINI_API_KEY"),
|
|
171
187
|
preset("openai", "OpenAI", [route("openai-responses", "https://api.openai.com/v1"), route("openai-chat", "https://api.openai.com/v1")], ["https://platform.openai.com/docs/api-reference/introduction", "https://platform.openai.com/docs/api-reference/models/list"], "OPENAI_API_KEY"),
|
|
188
|
+
preset("azure-openai", "Azure OpenAI (v1)", [
|
|
189
|
+
route("openai-responses", undefined, { authStyle: "api-key", catalogFormat: "none", notes: ["Pass the Azure OpenAI v1 resource endpoint ending in /openai/v1. The request model is your deployment name. Azure's model-definition list is not a deployment catalog, so configure manual deployment models or an explicit deployment catalog parser; Switcher does not synthesize deployment paths or api-version query parameters."] }),
|
|
190
|
+
route("openai-chat", undefined, { authStyle: "api-key", catalogFormat: "none", notes: ["Pass the Azure OpenAI v1 resource endpoint ending in /openai/v1. Chat Completions is POST /chat/completions and accepts the literal api-key header. The request model is your deployment name; configure manual deployment models or an explicit deployment catalog parser because GET /models does not establish deployment names."] })
|
|
191
|
+
], ["https://learn.microsoft.com/en-us/rest/api/aifoundry/azureopenai/models", "https://learn.microsoft.com/en-us/rest/api/microsoft-foundry/azureopenai/chat", "https://learn.microsoft.com/en-us/rest/api/aifoundry/azureopenai/responses"], "AZURE_OPENAI_API_KEY"),
|
|
172
192
|
preset("xai", "xAI", ["openai-chat", "openai-responses", "anthropic-messages"].map((protocol) => route(protocol, "https://api.x.ai/v1")), ["https://api.x.ai/docs/", "https://docs.x.ai/developers/model-capabilities/text/generate-text"], "XAI_API_KEY"),
|
|
173
193
|
preset("ollama", "Ollama", ["openai-chat", "openai-responses"].map((protocol) => route(protocol, "http://127.0.0.1:11434/v1", {
|
|
174
194
|
catalogBaseUrl: "http://127.0.0.1:11434",
|
|
@@ -229,6 +249,8 @@ function providerFromPreset(presetId, options = {}) {
|
|
|
229
249
|
const baseUrl = options.baseUrl ?? selected.baseUrl;
|
|
230
250
|
if (!baseUrl)
|
|
231
251
|
throw new Fault(400, "endpoint_required", "This preset requires an explicit --url for its inference endpoint.");
|
|
252
|
+
if (presetId === "azure-openai" && !/\/openai\/v1$/.test(endpoint(baseUrl)))
|
|
253
|
+
throw new Fault(400, "invalid_url", "Azure OpenAI v1 requires an explicit endpoint ending in /openai/v1; deployment and api-version URLs are unsupported.");
|
|
232
254
|
if (options.baseUrl && selected.baseUrl && new URL(endpoint(options.baseUrl)).origin !== new URL(selected.baseUrl).origin && preset2.credentialEnv && !options.credentialEnv)
|
|
233
255
|
throw new Fault(422, "credential_authority", "An endpoint on another origin requires an explicit --credential-env reference.");
|
|
234
256
|
const suffix = selected.protocol === "anthropic-messages" ? "messages" : selected.protocol === "openai-responses" ? "responses" : "chat";
|
|
@@ -265,6 +287,26 @@ class SwitcherError extends Error {
|
|
|
265
287
|
this.requestId = requestId;
|
|
266
288
|
}
|
|
267
289
|
}
|
|
290
|
+
function apiError(status, data, apiKey) {
|
|
291
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
292
|
+
const error = object(data) && object(data.error) ? data.error : {};
|
|
293
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
294
|
+
const base64 = Buffer.from(apiKey, "utf8").toString("base64");
|
|
295
|
+
const literal = [apiKey, JSON.stringify(apiKey).slice(1, -1), base64, base64.replace(/=+$/, ""), Buffer.from(apiKey, "utf8").toString("base64url")];
|
|
296
|
+
const encoded = [encodeURIComponent(apiKey), new URLSearchParams({ key: apiKey }).toString().slice(4)];
|
|
297
|
+
const patterns = [...new Set(literal)].sort((a, b) => b.length - a.length).map(escape);
|
|
298
|
+
for (const value of encoded) {
|
|
299
|
+
patterns.unshift(escape(value).replace(/%[0-9A-F]{2}/g, (part) => part.replace(/[A-F]/g, (letter) => `[${letter}${letter.toLowerCase()}]`)));
|
|
300
|
+
}
|
|
301
|
+
const reflected = new RegExp(patterns.join("|"), "g");
|
|
302
|
+
const redact = (value) => value.replace(reflected, "[REDACTED]");
|
|
303
|
+
const identifier = (value, pattern) => typeof value === "string" && pattern.test(value) && redact(value) === value ? value : undefined;
|
|
304
|
+
const code = identifier(error.code, /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/) ?? "api_error";
|
|
305
|
+
const requestId = identifier(error.requestId, /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
|
|
306
|
+
const fallback = `Switcher API returned HTTP ${status}.`;
|
|
307
|
+
const message = typeof error.message === "string" && error.message.length <= 4096 ? redact(error.message).replace(/[\x00-\x1f\x7f-\x9f]/g, " ").trim().slice(0, 2048) || fallback : fallback;
|
|
308
|
+
return new SwitcherError(status, code, message, requestId);
|
|
309
|
+
}
|
|
268
310
|
|
|
269
311
|
class SwitcherClient {
|
|
270
312
|
options;
|
|
@@ -301,7 +343,7 @@ class SwitcherClient {
|
|
|
301
343
|
throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
|
|
302
344
|
}
|
|
303
345
|
if (!response.ok)
|
|
304
|
-
throw
|
|
346
|
+
throw apiError(response.status, data, apiKey);
|
|
305
347
|
return data;
|
|
306
348
|
}
|
|
307
349
|
query(options = {}) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type ObjectValue = Record<string, unknown>;
|
|
2
|
+
/** Read native policies as data; native config inspection itself may write project files. */
|
|
3
|
+
export declare function kiloSettings(cwd: string, stateDir: string, env?: NodeJS.ProcessEnv, options?: {
|
|
4
|
+
writeInstructions?: boolean;
|
|
5
|
+
}): Promise<{
|
|
6
|
+
config: ObjectValue;
|
|
7
|
+
global: ObjectValue;
|
|
8
|
+
instructions: string[];
|
|
9
|
+
sourcePaths: string[];
|
|
10
|
+
}>;
|
|
11
|
+
/** Kilo substitutes variables before parsing JSON; only the generated key reference is active. */
|
|
12
|
+
export declare function kiloConfigText(config: Record<string, unknown>, providerID?: string, keyEnv?: string): string;
|
|
13
|
+
export {};
|
package/dist/kilo.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HarnessLaunchInput, PreparedLaunch } from "./harness-types";
|
|
2
|
+
export declare function validateKiloArgs(args: string[]): void;
|
|
3
|
+
/** Validate native policy and instruction inputs without credentials or generated files. */
|
|
4
|
+
export declare function validateKiloConfiguration(cwd: string, args?: string[]): Promise<void>;
|
|
5
|
+
export declare function prepareKilo(input: HarnessLaunchInput, providerBaseUrl: string): Promise<PreparedLaunch>;
|
package/dist/launcher.d.ts
CHANGED
|
@@ -19,16 +19,17 @@ type OriPreparationOptions = Pick<LaunchOptions, "oriExecutable" | "args" | "res
|
|
|
19
19
|
stateDir?: string;
|
|
20
20
|
cwd?: string;
|
|
21
21
|
};
|
|
22
|
+
type OriSupportedHarness = Exclude<LaunchPlan["profile"]["harness"], "omp" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "opencode" | "kilo">;
|
|
22
23
|
export declare function validateOriForPlan(plan: LaunchPlan, options?: Pick<OriPreparationOptions, "oriExecutable" | "args" | "credentialEnv" | "cwd">): Promise<{
|
|
23
24
|
contract: OriContract;
|
|
24
25
|
request: ReturnType<typeof buildOriRequest>;
|
|
25
26
|
warnings: string[];
|
|
26
27
|
}>;
|
|
27
|
-
declare function buildOriRequest(plan: LaunchPlan, catalogPath: string | undefined, args: string[]): {
|
|
28
|
-
readonly target:
|
|
28
|
+
declare function buildOriRequest(plan: LaunchPlan, target: OriSupportedHarness, catalogPath: string | undefined, args: string[]): {
|
|
29
|
+
readonly target: OriSupportedHarness;
|
|
29
30
|
readonly provider: string;
|
|
30
31
|
readonly providerBaseUrl: string;
|
|
31
|
-
readonly protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
|
|
32
|
+
readonly protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
|
|
32
33
|
readonly model: string;
|
|
33
34
|
readonly catalog: {
|
|
34
35
|
readonly codexModelCatalogPath?: string | undefined;
|
package/dist/mcp/index.js
CHANGED
|
@@ -8,9 +8,9 @@ import { z as z2 } from "zod";
|
|
|
8
8
|
|
|
9
9
|
// src/domain.ts
|
|
10
10
|
import { z } from "zod";
|
|
11
|
-
var VERSION = "0.1.
|
|
12
|
-
var harnessSchema = z.enum(["claude", "codex", "grok", "opencode2", "pi"]);
|
|
13
|
-
var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat"]);
|
|
11
|
+
var VERSION = "0.1.2";
|
|
12
|
+
var harnessSchema = z.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
|
|
13
|
+
var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
|
|
14
14
|
var idSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
|
|
15
15
|
var label = z.string().min(1).max(200);
|
|
16
16
|
var envRef = z.string().regex(/^SWITCHER_PROVIDER_[A-Z0-9_]+$/);
|
|
@@ -42,7 +42,8 @@ var modelSchema = z.object({
|
|
|
42
42
|
maxOutputTokens: z.number().int().positive().optional(),
|
|
43
43
|
inputModalities: z.array(z.string().max(50)).max(20).optional(),
|
|
44
44
|
outputModalities: z.array(z.string().max(50)).max(20).optional(),
|
|
45
|
-
supportedParameters: z.array(z.string().max(100)).max(100).optional()
|
|
45
|
+
supportedParameters: z.array(z.string().max(100)).max(100).optional(),
|
|
46
|
+
supportedGenerationMethods: z.array(z.string().min(1).max(100)).max(100).optional()
|
|
46
47
|
}).strict();
|
|
47
48
|
var providerInputSchema = z.object({
|
|
48
49
|
id: idSchema,
|
|
@@ -50,10 +51,10 @@ var providerInputSchema = z.object({
|
|
|
50
51
|
baseUrl: urlSchema,
|
|
51
52
|
protocol: protocolSchema,
|
|
52
53
|
credentialEnv: envRef.optional(),
|
|
53
|
-
authStyle: z.enum(["bearer", "x-api-key"]).default("bearer"),
|
|
54
|
+
authStyle: z.enum(["bearer", "x-api-key", "api-key"]).default("bearer"),
|
|
54
55
|
catalogBaseUrl: urlSchema.optional(),
|
|
55
|
-
catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]).optional(),
|
|
56
|
-
catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
|
|
56
|
+
catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]).optional(),
|
|
57
|
+
catalogAuthStyle: z.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
|
|
57
58
|
catalogCredentialEnv: envRef.optional(),
|
|
58
59
|
catalogAccountId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
|
|
59
60
|
modelsPath: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
|
|
@@ -67,10 +68,10 @@ var providerPresetSchema = z.object({
|
|
|
67
68
|
protocols: z.array(z.object({
|
|
68
69
|
protocol: protocolSchema,
|
|
69
70
|
baseUrl: urlSchema.optional(),
|
|
70
|
-
authStyle: z.enum(["bearer", "x-api-key"]),
|
|
71
|
+
authStyle: z.enum(["bearer", "x-api-key", "api-key"]),
|
|
71
72
|
catalogBaseUrl: urlSchema.optional(),
|
|
72
|
-
catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]),
|
|
73
|
-
catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
|
|
73
|
+
catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]),
|
|
74
|
+
catalogAuthStyle: z.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
|
|
74
75
|
modelsPath: z.string(),
|
|
75
76
|
notes: z.array(z.string())
|
|
76
77
|
}).strict()).min(1),
|
|
@@ -174,7 +175,18 @@ var providerPresets = [
|
|
|
174
175
|
], ["https://api-docs.deepseek.com/guides/anthropic_api", "https://api-docs.deepseek.com/api/list-models"], "DEEPSEEK_API_KEY"),
|
|
175
176
|
preset("openrouter", "OpenRouter", ["openai-chat", "openai-responses", "anthropic-messages"].map((protocol) => route(protocol, "https://openrouter.ai/api/v1", { catalogAuthStyle: "none" })), ["https://openrouter.ai/docs/api/api-reference/models/list-all-models-and-their-properties", "https://openrouter.ai/docs/guides/overview"], "OPENROUTER_API_KEY"),
|
|
176
177
|
preset("anthropic", "Anthropic", [route("anthropic-messages", "https://api.anthropic.com/v1", { authStyle: "x-api-key" })], ["https://platform.claude.com/docs/en/api/overview", "https://platform.claude.com/docs/en/api/models/list"], "ANTHROPIC_API_KEY"),
|
|
178
|
+
preset("gemini", "Google Gemini", [route("gemini-generate-content", "https://generativelanguage.googleapis.com/v1beta", {
|
|
179
|
+
authStyle: "x-api-key",
|
|
180
|
+
catalogBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
181
|
+
catalogFormat: "gemini",
|
|
182
|
+
catalogAuthStyle: "x-api-key",
|
|
183
|
+
notes: ["Gemini CLI uses the native generateContent wire with x-goog-api-key authentication; model IDs are returned as models/{id}."]
|
|
184
|
+
}), route("openai-chat", "https://generativelanguage.googleapis.com/v1beta/openai")], ["https://ai.google.dev/api", "https://ai.google.dev/api/models", "https://github.com/google-gemini/gemini-cli", "https://ai.google.dev/gemini-api/docs/openai"], "GEMINI_API_KEY"),
|
|
177
185
|
preset("openai", "OpenAI", [route("openai-responses", "https://api.openai.com/v1"), route("openai-chat", "https://api.openai.com/v1")], ["https://platform.openai.com/docs/api-reference/introduction", "https://platform.openai.com/docs/api-reference/models/list"], "OPENAI_API_KEY"),
|
|
186
|
+
preset("azure-openai", "Azure OpenAI (v1)", [
|
|
187
|
+
route("openai-responses", undefined, { authStyle: "api-key", catalogFormat: "none", notes: ["Pass the Azure OpenAI v1 resource endpoint ending in /openai/v1. The request model is your deployment name. Azure's model-definition list is not a deployment catalog, so configure manual deployment models or an explicit deployment catalog parser; Switcher does not synthesize deployment paths or api-version query parameters."] }),
|
|
188
|
+
route("openai-chat", undefined, { authStyle: "api-key", catalogFormat: "none", notes: ["Pass the Azure OpenAI v1 resource endpoint ending in /openai/v1. Chat Completions is POST /chat/completions and accepts the literal api-key header. The request model is your deployment name; configure manual deployment models or an explicit deployment catalog parser because GET /models does not establish deployment names."] })
|
|
189
|
+
], ["https://learn.microsoft.com/en-us/rest/api/aifoundry/azureopenai/models", "https://learn.microsoft.com/en-us/rest/api/microsoft-foundry/azureopenai/chat", "https://learn.microsoft.com/en-us/rest/api/aifoundry/azureopenai/responses"], "AZURE_OPENAI_API_KEY"),
|
|
178
190
|
preset("xai", "xAI", ["openai-chat", "openai-responses", "anthropic-messages"].map((protocol) => route(protocol, "https://api.x.ai/v1")), ["https://api.x.ai/docs/", "https://docs.x.ai/developers/model-capabilities/text/generate-text"], "XAI_API_KEY"),
|
|
179
191
|
preset("ollama", "Ollama", ["openai-chat", "openai-responses"].map((protocol) => route(protocol, "http://127.0.0.1:11434/v1", {
|
|
180
192
|
catalogBaseUrl: "http://127.0.0.1:11434",
|
|
@@ -234,6 +246,26 @@ class SwitcherError extends Error {
|
|
|
234
246
|
this.requestId = requestId;
|
|
235
247
|
}
|
|
236
248
|
}
|
|
249
|
+
function apiError(status, data, apiKey) {
|
|
250
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
251
|
+
const error = object(data) && object(data.error) ? data.error : {};
|
|
252
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
253
|
+
const base64 = Buffer.from(apiKey, "utf8").toString("base64");
|
|
254
|
+
const literal = [apiKey, JSON.stringify(apiKey).slice(1, -1), base64, base64.replace(/=+$/, ""), Buffer.from(apiKey, "utf8").toString("base64url")];
|
|
255
|
+
const encoded = [encodeURIComponent(apiKey), new URLSearchParams({ key: apiKey }).toString().slice(4)];
|
|
256
|
+
const patterns = [...new Set(literal)].sort((a, b) => b.length - a.length).map(escape);
|
|
257
|
+
for (const value of encoded) {
|
|
258
|
+
patterns.unshift(escape(value).replace(/%[0-9A-F]{2}/g, (part) => part.replace(/[A-F]/g, (letter) => `[${letter}${letter.toLowerCase()}]`)));
|
|
259
|
+
}
|
|
260
|
+
const reflected = new RegExp(patterns.join("|"), "g");
|
|
261
|
+
const redact = (value) => value.replace(reflected, "[REDACTED]");
|
|
262
|
+
const identifier = (value, pattern) => typeof value === "string" && pattern.test(value) && redact(value) === value ? value : undefined;
|
|
263
|
+
const code = identifier(error.code, /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/) ?? "api_error";
|
|
264
|
+
const requestId = identifier(error.requestId, /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
|
|
265
|
+
const fallback = `Switcher API returned HTTP ${status}.`;
|
|
266
|
+
const message = typeof error.message === "string" && error.message.length <= 4096 ? redact(error.message).replace(/[\x00-\x1f\x7f-\x9f]/g, " ").trim().slice(0, 2048) || fallback : fallback;
|
|
267
|
+
return new SwitcherError(status, code, message, requestId);
|
|
268
|
+
}
|
|
237
269
|
|
|
238
270
|
class SwitcherClient {
|
|
239
271
|
options;
|
|
@@ -270,7 +302,7 @@ class SwitcherClient {
|
|
|
270
302
|
throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
|
|
271
303
|
}
|
|
272
304
|
if (!response.ok)
|
|
273
|
-
throw
|
|
305
|
+
throw apiError(response.status, data, apiKey);
|
|
274
306
|
return data;
|
|
275
307
|
}
|
|
276
308
|
query(options = {}) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HarnessLaunchInput, PreparedLaunch } from "./harness-types";
|
|
2
|
+
/**
|
|
3
|
+
* Prepare an isolated OMP configuration. OMP reads models.yml as JSON-valid
|
|
4
|
+
* YAML, and resolves the apiKey/header references from the child environment.
|
|
5
|
+
* The credential therefore never reaches the generated files or command line.
|
|
6
|
+
*/
|
|
7
|
+
export declare function prepareOmpLaunch(input: HarnessLaunchInput): Promise<PreparedLaunch>;
|
|
@@ -4,13 +4,13 @@ declare const rule: z.ZodObject<{
|
|
|
4
4
|
resource: z.ZodString;
|
|
5
5
|
effect: z.ZodEnum<["allow", "deny", "ask"]>;
|
|
6
6
|
}, "strict", z.ZodTypeAny, {
|
|
7
|
+
effect: "allow" | "deny" | "ask";
|
|
7
8
|
action: string;
|
|
8
9
|
resource: string;
|
|
9
|
-
effect: "allow" | "deny" | "ask";
|
|
10
10
|
}, {
|
|
11
|
+
effect: "allow" | "deny" | "ask";
|
|
11
12
|
action: string;
|
|
12
13
|
resource: string;
|
|
13
|
-
effect: "allow" | "deny" | "ask";
|
|
14
14
|
}>;
|
|
15
15
|
type Rule = z.infer<typeof rule>;
|
|
16
16
|
type Agent = {
|
|
@@ -35,9 +35,9 @@ export declare function isolateOpenCode2(cwd: string, stateDir: string, provider
|
|
|
35
35
|
};
|
|
36
36
|
default_agent?: string | undefined;
|
|
37
37
|
permissions: {
|
|
38
|
+
effect: "allow" | "deny" | "ask";
|
|
38
39
|
action: string;
|
|
39
40
|
resource: string;
|
|
40
|
-
effect: "allow" | "deny" | "ask";
|
|
41
41
|
}[];
|
|
42
42
|
agents: Record<string, Agent>;
|
|
43
43
|
};
|
package/dist/ori-backend.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** The Ori 0.12.1 command name for a Switcher harness. */
|
|
2
|
-
export type OriTarget = "claude" | "codex" | "grok" | "opencode2" | "pi";
|
|
3
|
-
export type OriProtocol = "anthropic-messages" | "openai-responses" | "openai-chat";
|
|
2
|
+
export type OriTarget = "claude" | "codex" | "grok" | "opencode2" | "pi" | "dsh" | "gemini" | "aider" | "kilo";
|
|
3
|
+
export type OriProtocol = "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
|
|
4
4
|
export type OriReasoningEffort = "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none";
|
|
5
5
|
export type OriProviderCatalogEntry = {
|
|
6
6
|
id: "openrouter";
|
package/dist/presets.d.ts
CHANGED
|
@@ -6,14 +6,14 @@ export declare function getProviderPreset(id: string): {
|
|
|
6
6
|
name: string;
|
|
7
7
|
credentialAliases: string[];
|
|
8
8
|
protocols: {
|
|
9
|
-
protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
|
|
10
|
-
authStyle: "bearer" | "x-api-key";
|
|
11
|
-
catalogFormat: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
|
|
9
|
+
protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
|
|
10
|
+
authStyle: "bearer" | "x-api-key" | "api-key";
|
|
11
|
+
catalogFormat: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none";
|
|
12
12
|
modelsPath: string;
|
|
13
13
|
notes: string[];
|
|
14
14
|
baseUrl?: string | undefined;
|
|
15
15
|
catalogBaseUrl?: string | undefined;
|
|
16
|
-
catalogAuthStyle?: "bearer" | "x-api-key" | "none" | undefined;
|
|
16
|
+
catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none" | undefined;
|
|
17
17
|
}[];
|
|
18
18
|
sources: string[];
|
|
19
19
|
verification: "documented";
|
|
@@ -25,10 +25,10 @@ export type PresetOptions = {
|
|
|
25
25
|
harness?: Profile["harness"];
|
|
26
26
|
baseUrl?: string;
|
|
27
27
|
credentialEnv?: string;
|
|
28
|
-
authStyle?: "bearer" | "x-api-key";
|
|
28
|
+
authStyle?: "bearer" | "x-api-key" | "api-key";
|
|
29
29
|
catalogBaseUrl?: string;
|
|
30
30
|
catalogCredentialEnv?: string;
|
|
31
|
-
catalogAuthStyle?: "bearer" | "x-api-key" | "none";
|
|
31
|
+
catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none";
|
|
32
32
|
modelsPath?: string;
|
|
33
33
|
catalogFormat?: ProviderInput["catalogFormat"];
|
|
34
34
|
catalogAccountId?: string;
|
|
@@ -37,8 +37,8 @@ export declare function providerFromPreset(presetId: string, options?: PresetOpt
|
|
|
37
37
|
id: string;
|
|
38
38
|
name: string;
|
|
39
39
|
baseUrl: string;
|
|
40
|
-
protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
|
|
41
|
-
authStyle: "bearer" | "x-api-key";
|
|
40
|
+
protocol: "anthropic-messages" | "openai-responses" | "openai-chat" | "gemini-generate-content";
|
|
41
|
+
authStyle: "bearer" | "x-api-key" | "api-key";
|
|
42
42
|
modelsPath: string;
|
|
43
43
|
manualModels: {
|
|
44
44
|
id: string;
|
|
@@ -50,11 +50,12 @@ export declare function providerFromPreset(presetId: string, options?: PresetOpt
|
|
|
50
50
|
inputModalities?: string[] | undefined;
|
|
51
51
|
outputModalities?: string[] | undefined;
|
|
52
52
|
supportedParameters?: string[] | undefined;
|
|
53
|
+
supportedGenerationMethods?: string[] | undefined;
|
|
53
54
|
}[];
|
|
54
55
|
credentialEnv?: string | undefined;
|
|
55
56
|
catalogBaseUrl?: string | undefined;
|
|
56
|
-
catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none" | undefined;
|
|
57
|
-
catalogAuthStyle?: "bearer" | "x-api-key" | "none" | undefined;
|
|
57
|
+
catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "gemini" | "none" | undefined;
|
|
58
|
+
catalogAuthStyle?: "bearer" | "x-api-key" | "api-key" | "none" | undefined;
|
|
58
59
|
catalogCredentialEnv?: string | undefined;
|
|
59
60
|
catalogAccountId?: string | undefined;
|
|
60
61
|
};
|