@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/README.md +73 -12
- package/dist/catalog.d.ts +3 -2
- package/dist/cli/index.js +5448 -198
- package/dist/credentials.d.ts +178 -0
- package/dist/direct-launch.d.ts +6 -0
- package/dist/domain.d.ts +118 -8
- package/dist/generated/api.d.ts +150 -5
- package/dist/grok-args.d.ts +1 -0
- package/dist/harness-arguments.d.ts +5 -0
- package/dist/harness-environment.d.ts +1 -0
- package/dist/harness-process.d.ts +11 -0
- package/dist/harness-types.d.ts +1 -0
- package/dist/harnesses.d.ts +33 -2
- package/dist/index.js +163 -2
- package/dist/launcher.d.ts +38 -3
- package/dist/mcp/index.js +123 -3
- package/dist/opencode2-config.d.ts +60 -0
- package/dist/ori-backend.d.ts +89 -0
- package/dist/presets.d.ts +63 -0
- package/dist/runtime.d.ts +20 -0
- package/dist/sdk.d.ts +77 -9
- package/dist/sdk.js +163 -2
- package/dist/serve/index.js +656 -70
- package/dist/server.d.ts +16 -0
- package/dist/service.d.ts +2 -1
- package/dist/terminal-descriptors.d.ts +5 -0
- package/hasna.contract.json +1 -1
- package/openapi.json +320 -6
- package/package.json +9 -4
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.
|
|
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;
|