@hasna/switcher 0.1.0 → 0.1.1

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/dist/mcp/index.js CHANGED
@@ -8,8 +8,8 @@ import { z as z2 } from "zod";
8
8
 
9
9
  // src/domain.ts
10
10
  import { z } from "zod";
11
- var VERSION = "0.1.0";
12
- var harnessSchema = z.enum(["claude", "codex", "grok", "opencode2"]);
11
+ var VERSION = "0.1.1";
12
+ var harnessSchema = z.enum(["claude", "codex", "grok", "opencode2", "pi"]);
13
13
  var protocolSchema = z.enum(["anthropic-messages", "openai-responses", "openai-chat"]);
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);
@@ -37,6 +37,7 @@ var modelSchema = z.object({
37
37
  id: z.string().min(1).max(300),
38
38
  name: label,
39
39
  description: z.string().max(8000).optional(),
40
+ available: z.boolean().optional(),
40
41
  contextWindow: z.number().int().positive().optional(),
41
42
  maxOutputTokens: z.number().int().positive().optional(),
42
43
  inputModalities: z.array(z.string().max(50)).max(20).optional(),
@@ -50,9 +51,32 @@ var providerInputSchema = z.object({
50
51
  protocol: protocolSchema,
51
52
  credentialEnv: envRef.optional(),
52
53
  authStyle: z.enum(["bearer", "x-api-key"]).default("bearer"),
54
+ 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(),
57
+ catalogCredentialEnv: envRef.optional(),
58
+ catalogAccountId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
53
59
  modelsPath: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
54
60
  manualModels: z.array(modelSchema).max(1e4).default([])
55
61
  }).strict().refine((p) => !p.modelsPath.split("/").includes("..") && !p.modelsPath.startsWith("/"), "modelsPath must be relative");
62
+ var providerPresetSchema = z.object({
63
+ id: idSchema,
64
+ name: label,
65
+ credentialEnv: envRef.optional(),
66
+ credentialAliases: z.array(z.string().regex(/^[A-Z][A-Z0-9_]+$/)),
67
+ protocols: z.array(z.object({
68
+ protocol: protocolSchema,
69
+ baseUrl: urlSchema.optional(),
70
+ authStyle: z.enum(["bearer", "x-api-key"]),
71
+ catalogBaseUrl: urlSchema.optional(),
72
+ catalogFormat: z.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "none"]),
73
+ catalogAuthStyle: z.enum(["bearer", "x-api-key", "none"]).optional(),
74
+ modelsPath: z.string(),
75
+ notes: z.array(z.string())
76
+ }).strict()).min(1),
77
+ sources: z.array(z.string().url()),
78
+ verification: z.literal("documented")
79
+ }).strict();
56
80
  var profileInputSchema = z.object({
57
81
  id: idSchema,
58
82
  name: label,
@@ -80,6 +104,12 @@ class Fault extends Error {
80
104
  this.code = code;
81
105
  }
82
106
  }
107
+ function parse(schema, value) {
108
+ const result = schema.safeParse(value);
109
+ if (!result.success)
110
+ throw new Fault(400, "invalid_request", result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "));
111
+ return result.data;
112
+ }
83
113
 
84
114
  // src/http.ts
85
115
  var MAX_BYTES = 16 * 1024 * 1024;
@@ -118,6 +148,81 @@ async function boundedJson(response, maxBytes = MAX_BYTES) {
118
148
  // src/sdk.ts
119
149
  import { resolveCredential } from "@hasna/contracts/client";
120
150
 
151
+ // src/presets.ts
152
+ var route = (protocol, baseUrl, options = {}) => ({
153
+ protocol,
154
+ baseUrl,
155
+ authStyle: "bearer",
156
+ catalogFormat: "openai",
157
+ modelsPath: "models",
158
+ notes: [],
159
+ ...options
160
+ });
161
+ var preset = (id, name, protocols, sources, alias) => parse(providerPresetSchema, {
162
+ id,
163
+ name,
164
+ protocols,
165
+ sources,
166
+ credentialAliases: alias ? [alias] : [],
167
+ credentialEnv: alias ? `SWITCHER_PROVIDER_${id.toUpperCase().replace(/-/g, "_")}` : undefined,
168
+ verification: "documented"
169
+ });
170
+ var providerPresets = [
171
+ preset("deepseek", "DeepSeek", [
172
+ route("openai-chat", "https://api.deepseek.com", { catalogBaseUrl: "https://api.deepseek.com" }),
173
+ route("anthropic-messages", "https://api.deepseek.com/anthropic/v1", { catalogBaseUrl: "https://api.deepseek.com" })
174
+ ], ["https://api-docs.deepseek.com/guides/anthropic_api", "https://api-docs.deepseek.com/api/list-models"], "DEEPSEEK_API_KEY"),
175
+ 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
+ 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"),
177
+ 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"),
178
+ 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
+ preset("ollama", "Ollama", ["openai-chat", "openai-responses"].map((protocol) => route(protocol, "http://127.0.0.1:11434/v1", {
180
+ catalogBaseUrl: "http://127.0.0.1:11434",
181
+ modelsPath: "api/tags",
182
+ catalogFormat: "ollama",
183
+ catalogAuthStyle: "none",
184
+ notes: protocol === "openai-responses" ? ["Requires Ollama 0.13.3 or newer; only stateless Responses are supported."] : []
185
+ })), ["https://docs.ollama.com/api/openai-compatibility", "https://docs.ollama.com/api/tags"]),
186
+ preset("lmstudio", "LM Studio", ["openai-chat", "openai-responses", "anthropic-messages"].map((protocol) => route(protocol, "http://127.0.0.1:1234/v1", {
187
+ notes: ["Server authentication is optional. Use --credential-env when authentication is enabled."]
188
+ })), ["https://lmstudio.ai/docs/developer/rest"]),
189
+ preset("vllm", "vLLM (operator endpoint)", [
190
+ route("openai-chat", undefined, { notes: ["Pass the operator's OpenAI-compatible URL, normally ending in /v1. vLLM exposes Chat Completions at /chat/completions and GET /models under that prefix; configure --credential-env only when the server was started with --api-key or VLLM_API_KEY."] }),
191
+ route("openai-responses", undefined, { notes: ["Pass the operator's OpenAI-compatible URL, normally ending in /v1. Responses is available for supported text-generation models at /responses; configure --credential-env only when the server was started with --api-key or VLLM_API_KEY."] }),
192
+ route("anthropic-messages", undefined, { notes: ["Pass the operator's URL, normally ending in /v1. vLLM exposes the Anthropic Messages API at /messages for supported deployments. Chat templates and the configured tool parser determine whether streaming and tool calls work for a served model; configure --credential-env only when the server was started with --api-key or VLLM_API_KEY."] })
193
+ ], ["https://docs.vllm.ai/en/latest/serving/online_serving/openai_compatible_server/", "https://github.com/vllm-project/vllm/blob/main/docs/serving/online_serving/README.md"]),
194
+ preset("litellm", "LiteLLM Proxy (operator endpoint)", [
195
+ route("openai-chat", undefined, { notes: ["Pass the proxy's documented OpenAI-compatible base URL exactly; the official quick start uses the root server URL, while a deployment may add a prefix such as /v1. LiteLLM serves Chat Completions and GET /models relative to that URL; use --credential-env for the proxy's configured master key or other bearer token."] }),
196
+ route("openai-responses", undefined, { notes: ["Pass the proxy's documented OpenAI-compatible base URL exactly; LiteLLM documents the Responses API under the same proxy root or deployment prefix. Use --credential-env for the proxy's configured master key or other bearer token."] }),
197
+ route("anthropic-messages", undefined, { notes: ["Pass the complete inference prefix ending in /v1, including any deployment prefix. LiteLLM registers /v1/messages; Switcher appends /messages to the stored prefix and discovers /models there. This is a gateway adapter: streaming and tool behavior depend on the configured upstream model and route, so verify those capabilities independently. Use --credential-env for the proxy's configured master key or other bearer token."] })
198
+ ], ["https://docs.litellm.ai/", "https://docs.litellm.ai/docs/proxy/quick_start", "https://github.com/BerriAI/litellm/blob/main/litellm/proxy/anthropic_endpoints/endpoints.py"]),
199
+ preset("groq", "Groq", [route("openai-chat", "https://api.groq.com/openai/v1"), route("openai-responses", "https://api.groq.com/openai/v1", { notes: ["Responses is an upstream beta API."] })], ["https://console.groq.com/docs/api-reference"], "GROQ_API_KEY"),
200
+ preset("cerebras", "Cerebras", [route("openai-chat", "https://api.cerebras.ai/v1")], ["https://inference-docs.cerebras.ai/api-reference/chat-completions"], "CEREBRAS_API_KEY"),
201
+ preset("mistral", "Mistral", [route("openai-chat", "https://api.mistral.ai/v1", { catalogFormat: "mistral" })], ["https://docs.mistral.ai/api/endpoint/chat", "https://docs.mistral.ai/api/endpoint/models"], "MISTRAL_API_KEY"),
202
+ preset("together", "Together AI", [route("openai-chat", "https://api.together.ai/v1", { catalogFormat: "together" })], ["https://docs.together.ai/docs/inference/openai-compatibility", "https://docs.together.ai/reference/models"], "TOGETHER_API_KEY"),
203
+ preset("fireworks", "Fireworks AI", [
204
+ route("openai-chat", "https://api.fireworks.ai/inference/v1", { catalogFormat: "fireworks", notes: ["Model discovery uses GET /v1/accounts/{account_id}/models; provide --catalog-account-id or --catalog-url."] }),
205
+ route("openai-responses", "https://api.fireworks.ai/inference/v1", { catalogFormat: "fireworks", notes: ["Model discovery uses GET /v1/accounts/{account_id}/models; provide --catalog-account-id or --catalog-url."] }),
206
+ route("anthropic-messages", "https://api.fireworks.ai/inference/v1", { catalogFormat: "fireworks", notes: ["Model discovery uses GET /v1/accounts/{account_id}/models; provide --catalog-account-id or --catalog-url."] })
207
+ ], ["https://docs.fireworks.ai/getting-started/quickstart", "https://docs.fireworks.ai/tools-sdks/python-client/api-reference", "https://docs.fireworks.ai/api-reference/anthropic-messages", "https://docs.fireworks.ai/api-reference/post-chatcompletions", "https://docs.fireworks.ai/api-reference/list-models"], "FIREWORKS_API_KEY"),
208
+ preset("moonshot", "Moonshot AI (Kimi)", [route("openai-chat", "https://api.moonshot.ai/v1", { catalogBaseUrl: "https://api.moonshot.ai/v1" })], ["https://platform.kimi.ai/docs/api/chat", "https://platform.kimi.ai/docs/api/list-models"], "MOONSHOT_API_KEY"),
209
+ preset("dashscope", "Alibaba Cloud Model Studio (Qwen)", [route("openai-chat", "https://dashscope-us.aliyuncs.com/compatible-mode/v1", {
210
+ catalogFormat: "none",
211
+ notes: ["Inference keys and endpoints are region/workspace-specific. Model discovery uses GET /api/v1/models on a documented region or workspace catalog URL; pass --catalog-url and --catalog-format dashscope."]
212
+ })], ["https://help.aliyun.com/en/model-studio/base-url", "https://help.aliyun.com/en/model-studio/compatibility-of-openai-with-dashscope", "https://help.aliyun.com/en/model-studio/list-models"], "DASHSCOPE_API_KEY"),
213
+ preset("zai", "Z.AI", [route("openai-chat", "https://api.z.ai/api/paas/v4", {
214
+ catalogFormat: "none",
215
+ notes: ["The published API reference documents inference endpoints but no model-list endpoint; use manual models or provide an explicit catalog URL and parser."]
216
+ })], ["https://docs.z.ai/api-reference/introduction", "https://docs.z.ai/devpack/quick-start"], "ZAI_API_KEY"),
217
+ preset("minimax", "MiniMax", [
218
+ route("openai-chat", "https://api.minimax.cn/v1", { catalogBaseUrl: "https://api.minimax.cn/v1", notes: ["The Open Platform contract uses api.minimax.cn and Bearer auth. Token Plan documentation uses api.minimaxi.com; select that authority explicitly with --url and matching auth/credential settings."] }),
219
+ route("anthropic-messages", "https://api.minimax.cn/anthropic/v1", { authStyle: "x-api-key", catalogBaseUrl: "https://api.minimax.cn/anthropic/v1", catalogAuthStyle: "x-api-key", notes: ["The Open Platform contract uses api.minimax.cn/anthropic/v1 and X-Api-Key. Token Plan documentation uses api.minimaxi.com/anthropic; select that authority explicitly with --url and matching auth/credential settings."] })
220
+ ], ["https://platform.minimaxi.com/docs/api-reference/text-chat-anthropic", "https://platform.minimaxi.com/docs/api-reference/models/anthropic/list-models", "https://platform.minimaxi.com/docs/api-reference/models/openai/list-models", "https://platform.minimaxi.com/docs/token-plan/other-tools"], "MINIMAX_API_KEY"),
221
+ 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"),
222
+ ...["anthropic-messages", "openai-responses", "openai-chat"].map((protocol) => preset(`generic-${protocol}`, `Custom ${protocol}`, [route(protocol)], []))
223
+ ];
224
+
225
+ // src/sdk.ts
121
226
  class SwitcherError extends Error {
122
227
  status;
123
228
  code;
@@ -140,7 +245,7 @@ class SwitcherClient {
140
245
  this.options = { ...options };
141
246
  }
142
247
  async request(method, path, body, options = {}) {
143
- if (!/^\/v1\/[a-zA-Z0-9/?&=._%+-]+$/.test(path) || path.includes(".."))
248
+ if (!/^\/v1\/[a-zA-Z0-9/?&=._%+-]+$/.test(path) && !["/health", "/ready", "/version"].includes(path) || path.includes(".."))
144
249
  throw new Error("Invalid API path.");
145
250
  const apiKey = typeof this.options.apiKey === "function" ? this.options.apiKey() : this.options.apiKey;
146
251
  if (!apiKey || /[\r\n]/.test(apiKey))
@@ -171,6 +276,21 @@ class SwitcherClient {
171
276
  query(options = {}) {
172
277
  return new URLSearchParams(Object.entries(options).filter(([, v]) => v !== undefined).map(([k, v]) => [k, String(v)])).toString();
173
278
  }
279
+ health() {
280
+ return this.request("GET", "/health");
281
+ }
282
+ ready() {
283
+ return this.request("GET", "/ready");
284
+ }
285
+ version() {
286
+ return this.request("GET", "/version");
287
+ }
288
+ listProviderPresets() {
289
+ return this.request("GET", "/v1/provider-presets");
290
+ }
291
+ getProviderPreset(id) {
292
+ return this.request("GET", `/v1/provider-presets/${encodeURIComponent(id)}`);
293
+ }
174
294
  listProviders(options = {}) {
175
295
  return this.request("GET", `/v1/providers?${this.query(options)}`);
176
296
  }
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ declare const rule: z.ZodObject<{
3
+ action: z.ZodString;
4
+ resource: z.ZodString;
5
+ effect: z.ZodEnum<["allow", "deny", "ask"]>;
6
+ }, "strict", z.ZodTypeAny, {
7
+ action: string;
8
+ resource: string;
9
+ effect: "allow" | "deny" | "ask";
10
+ }, {
11
+ action: string;
12
+ resource: string;
13
+ effect: "allow" | "deny" | "ask";
14
+ }>;
15
+ type Rule = z.infer<typeof rule>;
16
+ type Agent = {
17
+ system?: string;
18
+ description?: string;
19
+ mode?: "primary" | "subagent" | "all";
20
+ hidden?: boolean;
21
+ color?: string;
22
+ steps?: number;
23
+ disabled?: boolean;
24
+ permissions?: Rule[];
25
+ };
26
+ /** Preserve native policy and prompts as data, without running native plugins or loading provider settings. */
27
+ export declare function isolateOpenCode2(cwd: string, stateDir: string, providerID: string, env?: NodeJS.ProcessEnv): Promise<{
28
+ config: {
29
+ experimental: {
30
+ policies: {
31
+ action: string;
32
+ resource: string;
33
+ effect: string;
34
+ }[];
35
+ };
36
+ default_agent?: string | undefined;
37
+ permissions: {
38
+ action: string;
39
+ resource: string;
40
+ effect: "allow" | "deny" | "ask";
41
+ }[];
42
+ agents: Record<string, Agent>;
43
+ };
44
+ env: {
45
+ HOME: string;
46
+ XDG_CONFIG_HOME: string;
47
+ OPENCODE_CONFIG_DIR: string;
48
+ XDG_CACHE_HOME: string;
49
+ XDG_STATE_HOME: string;
50
+ XDG_DATA_HOME: string;
51
+ OPENCODE_DISABLE_PROJECT_CONFIG: string;
52
+ OPENCODE_CONFIG_PROJECT_DISABLE: string;
53
+ OPENCODE_DISABLE_MODELS_FETCH: string;
54
+ };
55
+ instructionFile: string;
56
+ sourcePaths: string[];
57
+ }>;
58
+ /** Native substitutes variables before parsing JSON; quote literal prompt/model tokens without evaluating them. */
59
+ export declare function openCode2ConfigText(config: Record<string, unknown>, providerID: string, keyEnv: string): string;
60
+ export {};
@@ -0,0 +1,89 @@
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";
4
+ export type OriReasoningEffort = "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none";
5
+ export type OriProviderCatalogEntry = {
6
+ id: "openrouter";
7
+ displayName: "OpenRouter";
8
+ credentialEnv: "OPENROUTER_API_KEY";
9
+ publicModelsUrl: "https://openrouter.ai/api/v1/models";
10
+ entitledModelsUrl: "https://openrouter.ai/api/v1/models/user";
11
+ protocols: readonly OriProtocol[];
12
+ };
13
+ /**
14
+ * Ori is an OpenRouter launcher, rather than a general provider adapter.
15
+ * Keep this list immutable so callers cannot accidentally broaden the boundary.
16
+ */
17
+ export declare const oriProviderCatalog: readonly OriProviderCatalogEntry[];
18
+ /** Native Ori flags verified from `ori <target> --help` for 0.12.1. */
19
+ export declare const oriNativeFlags: Readonly<Record<"claude" | "codex" | "grok", readonly string[]>>;
20
+ export type OriCatalogInput = {
21
+ /** IDs from Switcher's OpenRouter catalog; Ori does not own discovery. */
22
+ modelIds: readonly string[];
23
+ source: "switcher-openrouter";
24
+ /** A Switcher-generated Codex catalog, appended after Ori's provider config. */
25
+ codexModelCatalogPath?: string;
26
+ };
27
+ export type OriHarnessAvailability = {
28
+ kind: string;
29
+ displayName?: string;
30
+ installed: boolean;
31
+ path?: string;
32
+ };
33
+ export type OriContract = {
34
+ executable: string;
35
+ version: string;
36
+ harnesses: OriHarnessAvailability[];
37
+ };
38
+ export type OriEnvironment = NodeJS.ProcessEnv & {
39
+ OPENROUTER_API_KEY?: string;
40
+ ORI_FORCE_OPENROUTER_API_KEY?: string;
41
+ ORI_REQUIRE_LOGIN?: string;
42
+ };
43
+ export type OriLaunchRequest = {
44
+ target: OriTarget;
45
+ provider: string;
46
+ providerBaseUrl?: string;
47
+ protocol?: OriProtocol;
48
+ catalog?: OriCatalogInput;
49
+ model: string;
50
+ args?: string[];
51
+ reasoningEffort?: OriReasoningEffort;
52
+ };
53
+ export type OriLaunchPlan = {
54
+ executable: string;
55
+ args: string[];
56
+ env: Record<string, string>;
57
+ target: OriTarget;
58
+ provider: "openrouter";
59
+ model: string;
60
+ warnings: string[];
61
+ };
62
+ export declare class OriBackendError extends Error {
63
+ readonly code: string;
64
+ constructor(code: string, message: string);
65
+ }
66
+ export declare function assertOriLoginAllowed(environment: NodeJS.ProcessEnv): void;
67
+ /** Read Ori version and launchable harness inventory without auth or mutation. */
68
+ export declare function inspectOri(options?: {
69
+ executable?: string;
70
+ environment?: NodeJS.ProcessEnv;
71
+ cwd?: string;
72
+ }): Promise<OriContract>;
73
+ /** Keep the installed 0.12.x contract explicit; newer Ori builds need review. */
74
+ export declare function assertSupportedOriVersion(contract: OriContract): void;
75
+ /** Fail before Ori's interactive installer offer when the native binary is absent. */
76
+ export declare function requireOriHarness(contract: OriContract, target: OriTarget): OriHarnessAvailability;
77
+ /** Assert the exact OpenRouter provider boundary before constructing argv. */
78
+ export declare function oriProvider(id: string): OriProviderCatalogEntry;
79
+ /** Validate target, provider and catalog authority before resolving a key. */
80
+ export declare function validateOriLaunchRequest(request: OriLaunchRequest): void;
81
+ /**
82
+ * Construct an argv-only Ori launch. This function does no process start and
83
+ * does not read credentials; callers hand it the already-authorized environment.
84
+ */
85
+ export declare function prepareOriLaunch(request: OriLaunchRequest & {
86
+ executable?: string;
87
+ environment?: NodeJS.ProcessEnv;
88
+ }): OriLaunchPlan;
89
+ export declare function oriLaunchWarnings(target: OriTarget): string[];
@@ -0,0 +1,63 @@
1
+ import { type ProviderInput, type ProviderPreset, type Profile } from "./domain";
2
+ type Protocol = ProviderInput["protocol"];
3
+ export declare const providerPresets: readonly ProviderPreset[];
4
+ export declare function getProviderPreset(id: string): {
5
+ id: string;
6
+ name: string;
7
+ credentialAliases: string[];
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";
12
+ modelsPath: string;
13
+ notes: string[];
14
+ baseUrl?: string | undefined;
15
+ catalogBaseUrl?: string | undefined;
16
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none" | undefined;
17
+ }[];
18
+ sources: string[];
19
+ verification: "documented";
20
+ credentialEnv?: string | undefined;
21
+ };
22
+ export type PresetOptions = {
23
+ id?: string;
24
+ protocol?: Protocol;
25
+ harness?: Profile["harness"];
26
+ baseUrl?: string;
27
+ credentialEnv?: string;
28
+ authStyle?: "bearer" | "x-api-key";
29
+ catalogBaseUrl?: string;
30
+ catalogCredentialEnv?: string;
31
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none";
32
+ modelsPath?: string;
33
+ catalogFormat?: ProviderInput["catalogFormat"];
34
+ catalogAccountId?: string;
35
+ };
36
+ export declare function providerFromPreset(presetId: string, options?: PresetOptions): {
37
+ id: string;
38
+ name: string;
39
+ baseUrl: string;
40
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
41
+ authStyle: "bearer" | "x-api-key";
42
+ modelsPath: string;
43
+ manualModels: {
44
+ id: string;
45
+ name: string;
46
+ description?: string | undefined;
47
+ available?: boolean | undefined;
48
+ contextWindow?: number | undefined;
49
+ maxOutputTokens?: number | undefined;
50
+ inputModalities?: string[] | undefined;
51
+ outputModalities?: string[] | undefined;
52
+ supportedParameters?: string[] | undefined;
53
+ }[];
54
+ credentialEnv?: string | undefined;
55
+ catalogBaseUrl?: string | undefined;
56
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none" | undefined;
57
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none" | undefined;
58
+ catalogCredentialEnv?: string | undefined;
59
+ catalogAccountId?: string | undefined;
60
+ };
61
+ /** Resolve an alias only for the preset's exact credential reference and origin. */
62
+ export declare function providerCredential(provider: ProviderInput, env?: NodeJS.ProcessEnv): string | undefined;
63
+ export {};
@@ -0,0 +1,20 @@
1
+ import { SwitcherClient } from "./sdk";
2
+ import type { CatalogCredentialResolver } from "./catalog";
3
+ export declare function switcherHome(env?: NodeJS.ProcessEnv): string;
4
+ export declare function privateDirectory(path: string): Promise<void>;
5
+ /** Data access always uses HTTP, including the per-command owned local service. */
6
+ export declare function openCliRuntime(env?: NodeJS.ProcessEnv, resolveCredential?: CatalogCredentialResolver): Promise<{
7
+ client: SwitcherClient;
8
+ mode: "remote";
9
+ providerEnv: {
10
+ [k: string]: string | undefined;
11
+ };
12
+ close: () => Promise<void>;
13
+ } | {
14
+ client: SwitcherClient;
15
+ mode: "local";
16
+ providerEnv: {
17
+ [k: string]: string | undefined;
18
+ };
19
+ close: () => Promise<void>;
20
+ }>;
package/dist/sdk.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { components } from "./generated/api";
2
+ export { providerFromPreset } from "./presets";
3
+ export type ProviderPreset = components["schemas"]["ProviderPreset"];
2
4
  export type ProviderInput = components["schemas"]["ProviderInput"];
3
5
  export type Provider = components["schemas"]["Provider"];
4
6
  export type ProfileInput = components["schemas"]["ProfileInput"];
@@ -34,6 +36,39 @@ export declare class SwitcherClient {
34
36
  idempotencyKey?: string;
35
37
  }): Promise<T>;
36
38
  private query;
39
+ health(): Promise<{
40
+ status: "ok" | "degraded" | "unavailable";
41
+ version: string;
42
+ backend: "sqlite" | "postgresql";
43
+ }>;
44
+ ready(): Promise<{
45
+ ready: boolean;
46
+ reason?: string;
47
+ }>;
48
+ version(): Promise<{
49
+ version: string;
50
+ }>;
51
+ listProviderPresets(): Promise<{
52
+ data: ProviderPreset[];
53
+ }>;
54
+ getProviderPreset(id: string): Promise<{
55
+ id: string;
56
+ name: string;
57
+ credentialEnv?: string;
58
+ credentialAliases: string[];
59
+ protocols: {
60
+ protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
61
+ baseUrl?: string;
62
+ authStyle: "bearer" | "x-api-key";
63
+ catalogBaseUrl?: string;
64
+ catalogFormat: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
65
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none";
66
+ modelsPath: string;
67
+ notes: string[];
68
+ }[];
69
+ sources: string[];
70
+ verification: "documented";
71
+ }>;
37
72
  listProviders(options?: {}): Promise<Page<{
38
73
  id: string;
39
74
  name: string;
@@ -41,11 +76,17 @@ export declare class SwitcherClient {
41
76
  protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
42
77
  credentialEnv?: string;
43
78
  authStyle: "bearer" | "x-api-key";
79
+ catalogBaseUrl?: string;
80
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
81
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none";
82
+ catalogCredentialEnv?: string;
83
+ catalogAccountId?: string;
44
84
  modelsPath: string;
45
85
  manualModels: {
46
86
  id: string;
47
87
  name: string;
48
88
  description?: string;
89
+ available?: boolean;
49
90
  contextWindow?: number;
50
91
  maxOutputTokens?: number;
51
92
  inputModalities?: string[];
@@ -62,11 +103,17 @@ export declare class SwitcherClient {
62
103
  protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
63
104
  credentialEnv?: string;
64
105
  authStyle: "bearer" | "x-api-key";
106
+ catalogBaseUrl?: string;
107
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
108
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none";
109
+ catalogCredentialEnv?: string;
110
+ catalogAccountId?: string;
65
111
  modelsPath: string;
66
112
  manualModels: {
67
113
  id: string;
68
114
  name: string;
69
115
  description?: string;
116
+ available?: boolean;
70
117
  contextWindow?: number;
71
118
  maxOutputTokens?: number;
72
119
  inputModalities?: string[];
@@ -83,11 +130,17 @@ export declare class SwitcherClient {
83
130
  protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
84
131
  credentialEnv?: string;
85
132
  authStyle: "bearer" | "x-api-key";
133
+ catalogBaseUrl?: string;
134
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
135
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none";
136
+ catalogCredentialEnv?: string;
137
+ catalogAccountId?: string;
86
138
  modelsPath: string;
87
139
  manualModels: {
88
140
  id: string;
89
141
  name: string;
90
142
  description?: string;
143
+ available?: boolean;
91
144
  contextWindow?: number;
92
145
  maxOutputTokens?: number;
93
146
  inputModalities?: string[];
@@ -104,11 +157,17 @@ export declare class SwitcherClient {
104
157
  protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
105
158
  credentialEnv?: string;
106
159
  authStyle: "bearer" | "x-api-key";
160
+ catalogBaseUrl?: string;
161
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
162
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none";
163
+ catalogCredentialEnv?: string;
164
+ catalogAccountId?: string;
107
165
  modelsPath: string;
108
166
  manualModels: {
109
167
  id: string;
110
168
  name: string;
111
169
  description?: string;
170
+ available?: boolean;
112
171
  contextWindow?: number;
113
172
  maxOutputTokens?: number;
114
173
  inputModalities?: string[];
@@ -126,6 +185,7 @@ export declare class SwitcherClient {
126
185
  id: string;
127
186
  name: string;
128
187
  description?: string;
188
+ available?: boolean;
129
189
  contextWindow?: number;
130
190
  maxOutputTokens?: number;
131
191
  inputModalities?: string[];
@@ -140,6 +200,7 @@ export declare class SwitcherClient {
140
200
  id: string;
141
201
  name: string;
142
202
  description?: string;
203
+ available?: boolean;
143
204
  contextWindow?: number;
144
205
  maxOutputTokens?: number;
145
206
  inputModalities?: string[];
@@ -157,7 +218,7 @@ export declare class SwitcherClient {
157
218
  id: string;
158
219
  name: string;
159
220
  providerId: string;
160
- harness: "claude" | "codex" | "grok" | "opencode2";
221
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
161
222
  model: string;
162
223
  version: number;
163
224
  updatedAt: string;
@@ -166,7 +227,7 @@ export declare class SwitcherClient {
166
227
  id: string;
167
228
  name: string;
168
229
  providerId: string;
169
- harness: "claude" | "codex" | "grok" | "opencode2";
230
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
170
231
  model: string;
171
232
  version: number;
172
233
  updatedAt: string;
@@ -175,7 +236,7 @@ export declare class SwitcherClient {
175
236
  id: string;
176
237
  name: string;
177
238
  providerId: string;
178
- harness: "claude" | "codex" | "grok" | "opencode2";
239
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
179
240
  model: string;
180
241
  version: number;
181
242
  updatedAt: string;
@@ -184,7 +245,7 @@ export declare class SwitcherClient {
184
245
  id: string;
185
246
  name: string;
186
247
  providerId: string;
187
- harness: "claude" | "codex" | "grok" | "opencode2";
248
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
188
249
  model: string;
189
250
  version: number;
190
251
  updatedAt: string;
@@ -200,11 +261,17 @@ export declare class SwitcherClient {
200
261
  protocol: "anthropic-messages" | "openai-responses" | "openai-chat";
201
262
  credentialEnv?: string;
202
263
  authStyle: "bearer" | "x-api-key";
264
+ catalogBaseUrl?: string;
265
+ catalogFormat?: "openai" | "ollama" | "mistral" | "together" | "fireworks" | "dashscope" | "none";
266
+ catalogAuthStyle?: "bearer" | "x-api-key" | "none";
267
+ catalogCredentialEnv?: string;
268
+ catalogAccountId?: string;
203
269
  modelsPath: string;
204
270
  manualModels: {
205
271
  id: string;
206
272
  name: string;
207
273
  description?: string;
274
+ available?: boolean;
208
275
  contextWindow?: number;
209
276
  maxOutputTokens?: number;
210
277
  inputModalities?: string[];
@@ -218,7 +285,7 @@ export declare class SwitcherClient {
218
285
  id: string;
219
286
  name: string;
220
287
  providerId: string;
221
- harness: "claude" | "codex" | "grok" | "opencode2";
288
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
222
289
  model: string;
223
290
  version: number;
224
291
  updatedAt: string;
@@ -228,6 +295,7 @@ export declare class SwitcherClient {
228
295
  id: string;
229
296
  name: string;
230
297
  description?: string;
298
+ available?: boolean;
231
299
  contextWindow?: number;
232
300
  maxOutputTokens?: number;
233
301
  inputModalities?: string[];
@@ -242,7 +310,7 @@ export declare class SwitcherClient {
242
310
  }>;
243
311
  listRuns(options?: {}): Promise<Page<{
244
312
  profileId: string;
245
- harness: "claude" | "codex" | "grok" | "opencode2";
313
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
246
314
  model: string;
247
315
  planToken: string;
248
316
  version: number;
@@ -258,7 +326,7 @@ export declare class SwitcherClient {
258
326
  }>>;
259
327
  getRun(id: string): Promise<{
260
328
  profileId: string;
261
- harness: "claude" | "codex" | "grok" | "opencode2";
329
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
262
330
  model: string;
263
331
  planToken: string;
264
332
  version: number;
@@ -274,7 +342,7 @@ export declare class SwitcherClient {
274
342
  }>;
275
343
  createRun(input: components["schemas"]["RunInput"], idempotencyKey?: string): Promise<{
276
344
  profileId: string;
277
- harness: "claude" | "codex" | "grok" | "opencode2";
345
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
278
346
  model: string;
279
347
  planToken: string;
280
348
  version: number;
@@ -293,7 +361,7 @@ export declare class SwitcherClient {
293
361
  exitCode: number;
294
362
  }, idempotencyKey?: string): Promise<{
295
363
  profileId: string;
296
- harness: "claude" | "codex" | "grok" | "opencode2";
364
+ harness: "claude" | "codex" | "grok" | "opencode2" | "pi";
297
365
  model: string;
298
366
  planToken: string;
299
367
  version: number;