@visual-z/xeno 0.1.3 → 0.2.0
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/CHANGELOG.md +19 -0
- package/README.md +143 -33
- package/docs/install.md +18 -6
- package/docs/providers.md +147 -0
- package/package.json +8 -2
- package/src/apps/cli.ts +42 -14
- package/src/apps/commands.ts +32 -4
- package/src/apps/completions.ts +9 -6
- package/src/apps/export-cli.ts +24 -0
- package/src/apps/extension-ui.ts +330 -0
- package/src/apps/input.ts +16 -1
- package/src/apps/model-catalog.ts +42 -0
- package/src/apps/project-trust.ts +49 -0
- package/src/apps/render.ts +1 -0
- package/src/apps/resource-browser.ts +75 -0
- package/src/apps/resources.ts +25 -0
- package/src/apps/session-browser.ts +113 -0
- package/src/apps/session-picker.ts +55 -0
- package/src/apps/shell.ts +1177 -378
- package/src/apps/startup.ts +400 -268
- package/src/core/action-defaults.ts +306 -0
- package/src/core/attachment-input.ts +17 -0
- package/src/core/catalog-merge.ts +19 -0
- package/src/core/event-validation.ts +19 -2
- package/src/core/events.ts +45 -2
- package/src/core/export.ts +6 -2
- package/src/core/extension-flags.ts +14 -0
- package/src/core/images.ts +46 -0
- package/src/core/json-event.ts +2 -0
- package/src/core/keymap.ts +43 -119
- package/src/core/model-replay.ts +22 -0
- package/src/core/model-scope.ts +53 -0
- package/src/core/packages.ts +47 -0
- package/src/core/progress.ts +9 -0
- package/src/core/project.ts +21 -3
- package/src/core/prompts.ts +33 -5
- package/src/core/queue.ts +18 -0
- package/src/core/resource-selection.ts +36 -0
- package/src/{runtime → core}/resume.ts +15 -22
- package/src/core/session-archive.ts +4 -0
- package/src/core/session-browser.ts +82 -0
- package/src/core/session-entry.ts +56 -0
- package/src/core/session-position.ts +26 -0
- package/src/core/settings.ts +169 -154
- package/src/core/theme.ts +15 -6
- package/src/core/thinking.ts +26 -0
- package/src/core/tokens.ts +2 -0
- package/src/core/transcript.ts +15 -5
- package/src/runtime/auth.ts +116 -11
- package/src/runtime/catalog.ts +54 -151
- package/src/runtime/clipboard.ts +114 -0
- package/src/runtime/extension-loader.ts +37 -0
- package/src/runtime/extension-ui.ts +55 -0
- package/src/runtime/extensions.ts +273 -0
- package/src/runtime/images.ts +76 -0
- package/src/runtime/loop.ts +213 -83
- package/src/runtime/model-config.ts +232 -0
- package/src/runtime/model-data.LICENSE +27 -0
- package/src/runtime/model-data.ts +21 -0
- package/src/runtime/model-replay.ts +10 -0
- package/src/runtime/model-scope.ts +20 -0
- package/src/runtime/model-sources.ts +151 -0
- package/src/runtime/model.ts +74 -11
- package/src/runtime/models-fetch.ts +6 -104
- package/src/runtime/oauth.ts +4 -3
- package/src/runtime/openai-responses.ts +12 -4
- package/src/runtime/openai.ts +13 -4
- package/src/runtime/package-manager.ts +125 -0
- package/src/runtime/pi-provider.ts +235 -0
- package/src/runtime/progress.ts +26 -0
- package/src/runtime/provider-access.ts +21 -0
- package/src/runtime/provider-protocols.ts +29 -0
- package/src/runtime/provider-request.ts +73 -0
- package/src/runtime/provider.ts +26 -6
- package/src/runtime/rich-text.ts +36 -0
- package/src/runtime/tools.ts +41 -5
- package/src/store/command.ts +7 -1
- package/src/store/context-files.ts +1 -1
- package/src/store/log.ts +65 -0
- package/src/store/models.ts +28 -9
- package/src/store/observations.ts +4 -2
- package/src/store/packages.ts +98 -0
- package/src/store/recovery.ts +8 -0
- package/src/store/resource-index.ts +170 -0
- package/src/store/resources.ts +75 -142
- package/src/store/session-archive.ts +162 -0
- package/src/store/session-browser.ts +68 -0
- package/src/store/session-position.ts +27 -0
- package/src/store/sessions.ts +87 -66
- package/src/store/trust.ts +13 -0
- package/src/store/workspace.ts +6 -0
- package/src/tui/chat-viewport.ts +47 -0
- package/src/tui/editor.ts +62 -15
- package/src/tui/host.ts +50 -9
- package/src/tui/input.ts +8 -2
- package/src/tui/select.ts +24 -6
- package/src/tui/settings.ts +4 -0
- package/src/tui/system-theme.ts +700 -0
- package/src/tui/transcript.ts +63 -11
- package/src/tui/tree.ts +161 -95
- package/src/vendor/pi-rich/LICENSE.upstream +21 -0
- package/src/vendor/pi-rich/UPSTREAM.json +17 -0
- package/src/vendor/pi-rich/clipboard-command.ts +44 -0
- package/src/vendor/pi-rich/exif-orientation.ts +182 -0
- package/src/vendor/pi-rich/html.ts +51 -0
- package/src/vendor/pi-rich/image-convert.ts +49 -0
- package/src/vendor/pi-rich/image-resize-core.ts +164 -0
- package/src/vendor/pi-rich/image-resize-worker.ts +42 -0
- package/src/vendor/pi-rich/image-resize.ts +123 -0
- package/src/vendor/pi-rich/mime.ts +116 -0
- package/src/vendor/pi-rich/photon.ts +139 -0
- package/src/vendor/pi-rich/syntax-highlight.ts +214 -0
- package/src/vendor/pi-tui/UPSTREAM.json +50 -0
- package/src/vendor/pi-tui/VENDOR.md +7 -5
- package/src/vendor/pi-tui/autocomplete.ts +35 -10
- package/src/vendor/pi-tui/colors.ts +367 -0
- package/src/vendor/pi-tui/components/box.ts +8 -9
- package/src/vendor/pi-tui/components/editor.ts +4 -2
- package/src/vendor/pi-tui/components/markdown.ts +8 -2
- package/src/vendor/pi-tui/index.ts +31 -1
- package/src/vendor/pi-tui/native/UPSTREAM.json +23 -0
- package/src/vendor/pi-tui/native/clipboard.h +208 -0
- package/src/vendor/pi-tui/native/darwin/README.md +22 -0
- package/src/vendor/pi-tui/native/darwin/build.sh +69 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +132 -0
- package/src/vendor/pi-tui/native/linux/README.md +23 -0
- package/src/vendor/pi-tui/native/linux/build.sh +65 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +70 -0
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +274 -0
- package/src/vendor/pi-tui/native/napi.h +91 -0
- package/src/vendor/pi-tui/native/win32/README.md +31 -0
- package/src/vendor/pi-tui/native/win32/build.mjs +232 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +241 -0
- package/src/vendor/pi-tui/native-platform.ts +2 -0
- package/src/vendor/pi-tui/oklab.ts +233 -0
- package/src/vendor/pi-tui/terminal-colors.ts +26 -8
- package/src/vendor/pi-tui/terminal-image.ts +43 -8
- package/src/vendor/pi-tui/terminal.ts +16 -9
- package/src/vendor/pi-tui/tui-alt-screen.ts +26 -18
- package/src/vendor/pi-tui/tui.ts +112 -75
- package/src/vendor/pi-tui/utils.ts +89 -49
- package/src/vendor/pi-tui/wheel-scroll.ts +82 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { replayDescriptor } from "../core/model-replay.ts";
|
|
3
|
+
import { hasProviderAccess } from "./provider-access.ts";
|
|
4
|
+
import type { ModelSpec } from "./model.ts";
|
|
5
|
+
|
|
6
|
+
export function nativeReplay(spec: ModelSpec): boolean { return spec.driver === "pi" || hasProviderAccess(spec); }
|
|
7
|
+
/** The log keeps a digest rather than endpoint values, which can contain private routing information. */
|
|
8
|
+
export function providerReplayKey(spec: ModelSpec): string {
|
|
9
|
+
return createHash("sha256").update(replayDescriptor({ ...spec, driver: nativeReplay(spec) ? "pi" : "legacy" })).digest("hex");
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Bun's existing glob implementation supplies matching; ordered scope policy remains pure. */
|
|
2
|
+
import { resolveScope } from "../core/model-scope.ts";
|
|
3
|
+
import { modelKey } from "../core/session-position.ts";
|
|
4
|
+
import { asThinkingLevel } from "../core/thinking.ts";
|
|
5
|
+
import { findModelByPattern } from "./catalog.ts";
|
|
6
|
+
import type { ModelSpec } from "./model.ts";
|
|
7
|
+
|
|
8
|
+
export function scopedModels(patterns: readonly string[], models: readonly ModelSpec[], strict = false) {
|
|
9
|
+
return resolveScope(patterns, pattern => {
|
|
10
|
+
const lower = pattern.toLowerCase();
|
|
11
|
+
const exact = models.filter(model => modelKey(model).toLowerCase() === lower || model.id.toLowerCase() === lower);
|
|
12
|
+
if (exact.length === 1) return exact;
|
|
13
|
+
if (!strict && /[*?\[]/.test(pattern)) {
|
|
14
|
+
if (asThinkingLevel(pattern.slice(pattern.lastIndexOf(":") + 1))) return [];
|
|
15
|
+
const glob = new Bun.Glob(lower);
|
|
16
|
+
return models.filter(model => glob.match(modelKey(model).toLowerCase()) || glob.match(model.id.toLowerCase()));
|
|
17
|
+
}
|
|
18
|
+
const found = findModelByPattern(pattern, models); return found.ok ? [found.spec] : [];
|
|
19
|
+
}, strict);
|
|
20
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/** Provider wire metadata is interpreted here, not in the SDK-free core (D13). */
|
|
2
|
+
import { getBuiltinModels, getBuiltinProviders } from "@earendil-works/pi-ai/providers/all";
|
|
3
|
+
import { mergeCatalogs } from "../core/catalog-merge.ts";
|
|
4
|
+
import { THINKING_LEVELS } from "../core/thinking.ts";
|
|
5
|
+
import type { Api, ModelSpec, Price, Effort } from "./model.ts";
|
|
6
|
+
import { isProviderApi, SDK_APIS } from "./provider-protocols.ts";
|
|
7
|
+
|
|
8
|
+
export interface ProviderDefinition { name: string; env: string[]; api?: Api; baseUrl?: string }
|
|
9
|
+
export interface CatalogExclusion { provider: string; id: string; reason: string }
|
|
10
|
+
export interface SourceCatalog {
|
|
11
|
+
models: ModelSpec[];
|
|
12
|
+
providers: Record<string, ProviderDefinition>;
|
|
13
|
+
excluded: CatalogExclusion[];
|
|
14
|
+
}
|
|
15
|
+
export function record(value: unknown): value is Record<string, unknown> {
|
|
16
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
export function finitePositive(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value) && value > 0; }
|
|
19
|
+
export function finiteNonnegative(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value) && value >= 0; }
|
|
20
|
+
|
|
21
|
+
/** Published unknown prices remain unknown, even when an SDK requires numeric placeholders. */
|
|
22
|
+
function sourcePrice(value: unknown): Price | undefined {
|
|
23
|
+
if (!record(value) || !finiteNonnegative(value["input"]) || !finiteNonnegative(value["output"])) return undefined;
|
|
24
|
+
return { input: value["input"], output: value["output"],
|
|
25
|
+
...(finiteNonnegative(value["cache_read"]) ? { cacheRead: value["cache_read"] } : {}),
|
|
26
|
+
...(finiteNonnegative(value["cache_write"]) ? { cacheWrite: value["cache_write"] } : {}) };
|
|
27
|
+
}
|
|
28
|
+
function priced(spec: ModelSpec, price: Price | undefined): ModelSpec {
|
|
29
|
+
if (!price) return spec;
|
|
30
|
+
return { ...spec, price, ...(price.input > 0 && price.cacheRead !== undefined && price.cacheWrite !== undefined
|
|
31
|
+
? { cache: spec.api === "anthropic-messages" ? "explicit" as const : "automatic" as const,
|
|
32
|
+
rates: { read: price.cacheRead / price.input, write: price.cacheWrite / price.input } } : {}) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function piSourceCatalog(): SourceCatalog {
|
|
36
|
+
const out: SourceCatalog = { models: [], providers: {}, excluded: [] };
|
|
37
|
+
for (const provider of getBuiltinProviders()) {
|
|
38
|
+
for (const model of getBuiltinModels(provider)) {
|
|
39
|
+
if (!isProviderApi(model.api)) { out.excluded.push({ provider, id: model.id, reason: `unsupported Pi API ${model.api}` }); continue; }
|
|
40
|
+
const spec: ModelSpec = { id: model.id, provider, name: model.name, api: model.api, driver: "pi",
|
|
41
|
+
baseUrl: model.baseUrl.replace(/(?<!\$)\{([A-Z][A-Z_0-9]*)\}/g, "$${$1}"),
|
|
42
|
+
input: model.input, reasoning: model.reasoning, contextWindow: model.contextWindow, maxTokens: model.maxTokens,
|
|
43
|
+
...(model.compat ? { nativeCompat: model.compat as Record<string, unknown> } : {}),
|
|
44
|
+
...(model.thinkingLevelMap ? { thinkingLevelMap: model.thinkingLevelMap } : {}),
|
|
45
|
+
...(model.samplingParams ? { samplingParams: model.samplingParams } : {}),
|
|
46
|
+
...(model.headers ? { defaultHeaders: model.headers } : {}) };
|
|
47
|
+
// Pi fills missing cost slots with zero. Do not turn those placeholders into measured prices.
|
|
48
|
+
const cost = model.cost;
|
|
49
|
+
out.models.push(priced(spec, finiteNonnegative(cost.input) && finiteNonnegative(cost.output) && (cost.input || cost.output) ? { input: cost.input, output: cost.output,
|
|
50
|
+
...(finitePositive(cost.cacheRead) ? { cacheRead: cost.cacheRead } : {}), ...(finitePositive(cost.cacheWrite) ? { cacheWrite: cost.cacheWrite } : {}) } : undefined));
|
|
51
|
+
out.providers[provider] ??= { name: provider, env: [], api: spec.api, ...(spec.baseUrl ? { baseUrl: spec.baseUrl } : {}) };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// SDK defaults missing from Models.dev. These are URLs, not protocol guesses.
|
|
58
|
+
// DeepInfra docs/quickstart.mdx; the other defaults are Pi's published provider models.
|
|
59
|
+
const ENDPOINTS: Readonly<Record<string, string>> = {
|
|
60
|
+
deepinfra: "https://api.deepinfra.com/v1/openai", vercel: "https://ai-gateway.vercel.sh",
|
|
61
|
+
togetherai: "https://api.together.xyz/v1", azure: "", "azure-cognitive-services": "",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Dedicated SDK names need not imply dedicated protocols. Only documented compatible endpoints
|
|
65
|
+
// are mapped here (provider-union task card); unknown SDKs remain explicit exclusions.
|
|
66
|
+
const COMPATIBLE_ENDPOINTS: Readonly<Record<string, string>> = {
|
|
67
|
+
"@ai-sdk/cohere": "https://api.cohere.ai/compatibility/v1",
|
|
68
|
+
"venice-ai-sdk-provider": "https://api.venice.ai/api/v1",
|
|
69
|
+
"@aihubmix/ai-sdk-provider": "https://aihubmix.com/v1",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function reasoningMap(value: unknown): ModelSpec["thinkingLevelMap"] {
|
|
73
|
+
if (!Array.isArray(value)) return undefined;
|
|
74
|
+
const effort = value.find((entry: unknown) => record(entry) && entry["type"] === "effort");
|
|
75
|
+
if (!record(effort) || !Array.isArray(effort["values"])) return undefined;
|
|
76
|
+
const values = effort["values"].filter((x): x is string => typeof x === "string");
|
|
77
|
+
const map: Partial<Record<Effort, string | null>> = {};
|
|
78
|
+
for (const level of THINKING_LEVELS) map[level] = values.includes(level) ? level : null;
|
|
79
|
+
return map;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The same normalization feeds the bundled snapshot and explicit refresh. No whitelist of providers. */
|
|
83
|
+
export function modelsDevCatalog(json: unknown, pi: SourceCatalog = piSourceCatalog()): SourceCatalog {
|
|
84
|
+
const out: SourceCatalog = { models: [], providers: {}, excluded: [] };
|
|
85
|
+
if (!record(json)) return out;
|
|
86
|
+
const piModels = new Map(pi.models.map(model => [`${model.provider}/${model.id}`, model]));
|
|
87
|
+
for (const [rawProvider, value] of Object.entries(json)) {
|
|
88
|
+
if (!record(value) || !record(value["models"])) continue;
|
|
89
|
+
// Normalize the upstream spelling to the existing xeno/Pi provider identity before merging.
|
|
90
|
+
const provider = rawProvider === "togetherai" ? "together" : rawProvider;
|
|
91
|
+
const piDefault = pi.providers[provider];
|
|
92
|
+
const env = Array.isArray(value["env"]) ? value["env"].filter((x): x is string => typeof x === "string") : [];
|
|
93
|
+
const definition: ProviderDefinition = { name: typeof value["name"] === "string" ? value["name"] : provider, env };
|
|
94
|
+
out.providers[provider] = definition;
|
|
95
|
+
for (const [key, raw] of Object.entries(value["models"])) {
|
|
96
|
+
if (!record(raw)) continue;
|
|
97
|
+
const id = typeof raw["id"] === "string" ? raw["id"] : key;
|
|
98
|
+
const skip = (reason: string) => { out.excluded.push({ provider, id, reason }); };
|
|
99
|
+
const modalities = record(raw["modalities"]) ? raw["modalities"] : {};
|
|
100
|
+
if (raw["tool_call"] === false) { skip("model declares no tool calling"); continue; }
|
|
101
|
+
if (Array.isArray(modalities["output"]) && !modalities["output"].includes("text")) { skip("non-text output model"); continue; }
|
|
102
|
+
const modelProvider = record(raw["provider"]) ? raw["provider"] : {};
|
|
103
|
+
const npm = typeof modelProvider["npm"] === "string" ? modelProvider["npm"] : typeof value["npm"] === "string" ? value["npm"] : "@ai-sdk/openai-compatible";
|
|
104
|
+
const matched = piModels.get(`${provider}/${id}`);
|
|
105
|
+
const compatibleEndpoint = COMPATIBLE_ENDPOINTS[npm];
|
|
106
|
+
const api = matched?.api ?? SDK_APIS[npm] ?? (compatibleEndpoint ? "openai-completions" : undefined);
|
|
107
|
+
if (!api) { skip(`requires dedicated SDK ${npm}`); continue; }
|
|
108
|
+
const baseUrl = matched?.baseUrl ?? compatibleEndpoint ?? (typeof modelProvider["api"] === "string" ? modelProvider["api"] : typeof value["api"] === "string" ? value["api"] : ENDPOINTS[rawProvider] ?? piDefault?.baseUrl);
|
|
109
|
+
if (!baseUrl && !["bedrock-converse-stream", "google-vertex", "azure-openai-responses"].includes(api)) { skip("provider has no verified endpoint; configure baseUrl"); continue; }
|
|
110
|
+
const limit = record(raw["limit"]) ? raw["limit"] : {};
|
|
111
|
+
const effort = reasoningMap(raw["reasoning_options"]);
|
|
112
|
+
const spec: ModelSpec = { ...(matched ?? {}), id, provider, name: typeof raw["name"] === "string" ? raw["name"] : id,
|
|
113
|
+
api, driver: "pi", reasoning: raw["reasoning"] === true, credentialEnv: env,
|
|
114
|
+
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
115
|
+
...(Array.isArray(modalities["input"]) ? { input: modalities["input"].filter((x): x is "text" | "image" => x === "text" || x === "image") } : {}),
|
|
116
|
+
...(finitePositive(limit["context"]) ? { contextWindow: limit["context"] } : {}),
|
|
117
|
+
...(finitePositive(limit["output"]) ? { maxTokens: limit["output"] } : {}),
|
|
118
|
+
...(!matched?.thinkingLevelMap && effort ? { thinkingLevelMap: effort } : {}),
|
|
119
|
+
...(!matched && api === "openai-completions" ? { nativeCompat: {
|
|
120
|
+
supportsStore: false, supportsDeveloperRole: false, maxTokensField: "max_tokens",
|
|
121
|
+
supportsReasoningEffort: effort !== undefined || provider === "cohere",
|
|
122
|
+
...(provider === "cohere" ? { supportsUsageInStreaming: false } : {}),
|
|
123
|
+
} } : {}),
|
|
124
|
+
...(!matched && provider === "cohere" && raw["reasoning"] === true ? {
|
|
125
|
+
thinkingLevelMap: { off: "none", minimal: "high", low: "high", medium: "high", high: "high", xhigh: "high", max: "high" },
|
|
126
|
+
} : {}) };
|
|
127
|
+
// Native Pi compatibility wins over less expressive directory metadata.
|
|
128
|
+
out.models.push(priced(spec, sourcePrice(raw["cost"])));
|
|
129
|
+
definition.api ??= api;
|
|
130
|
+
if (baseUrl) definition.baseUrl ??= baseUrl;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function unionSourceCatalog(json: unknown): SourceCatalog {
|
|
137
|
+
const pi = piSourceCatalog();
|
|
138
|
+
const remote = modelsDevCatalog(json, pi);
|
|
139
|
+
const denied = new Set(remote.excluded.filter(row => row.reason.includes("no tool") || row.reason.includes("non-text")).map(row => `${row.provider}/${row.id}`));
|
|
140
|
+
const directory = new Map(remote.models.map(model => [`${model.provider}/${model.id}`, model]));
|
|
141
|
+
const models = mergeCatalogs(remote.models, pi.models.filter(model => !denied.has(`${model.provider}/${model.id}`)))
|
|
142
|
+
.map(model => {
|
|
143
|
+
// Pi supplies dialects; the directory supplies public credential names and explicit prices.
|
|
144
|
+
const metadata = directory.get(`${model.provider}/${model.id}`);
|
|
145
|
+
return { ...model, ...(metadata?.credentialEnv ? { credentialEnv: metadata.credentialEnv } : {}),
|
|
146
|
+
...(metadata?.price ? { price: metadata.price } : {}) };
|
|
147
|
+
});
|
|
148
|
+
const included = new Set(models.map(model => `${model.provider}/${model.id}`));
|
|
149
|
+
return { models, providers: { ...pi.providers, ...remote.providers },
|
|
150
|
+
excluded: [...pi.excluded, ...remote.excluded].filter(row => !included.has(`${row.provider}/${row.id}`)) };
|
|
151
|
+
}
|
package/src/runtime/model.ts
CHANGED
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* The
|
|
18
|
-
* supporting dozens of providers (research appendix, line 176); this project
|
|
19
|
-
* needs two of them so far, and adding a third should be adding a member here
|
|
20
|
-
* plus an adapter, never a conditional inside an existing adapter.
|
|
17
|
+
* The protocol vocabulary is local even when the runtime delegates wire IO to Pi.
|
|
21
18
|
*/
|
|
22
|
-
|
|
19
|
+
import type { ProviderApi } from "./provider-protocols.ts";
|
|
20
|
+
import { SOURCE_MODELS } from "./model-data.ts";
|
|
21
|
+
import { mergeCatalogs } from "../core/catalog-merge.ts";
|
|
22
|
+
export type Api = ProviderApi;
|
|
23
23
|
|
|
24
|
-
/** How hard the model should think.
|
|
25
|
-
|
|
24
|
+
/** How hard the model should think. Capability and wire vocabulary are mapped per model. */
|
|
25
|
+
import type { ThinkingLevel } from "../core/thinking.ts";
|
|
26
|
+
export type Effort = ThinkingLevel;
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Who decides what gets cached.
|
|
@@ -140,6 +141,7 @@ export const KIMI_K3_COMPAT: Compat = {
|
|
|
140
141
|
thinkingFormat: "openai",
|
|
141
142
|
};
|
|
142
143
|
export const KIMI_K3_EFFORT: Partial<Record<Effort, string | null>> = {
|
|
144
|
+
off: null, minimal: "low",
|
|
143
145
|
low: "low", medium: "high", high: "high", xhigh: "max", max: "max",
|
|
144
146
|
};
|
|
145
147
|
|
|
@@ -160,6 +162,7 @@ export const GEMINI_COMPAT: Compat = {
|
|
|
160
162
|
thinkingFormat: "openai",
|
|
161
163
|
};
|
|
162
164
|
export const GEMINI_EFFORT: Partial<Record<Effort, string | null>> = {
|
|
165
|
+
off: null, minimal: "low",
|
|
163
166
|
low: "low", medium: "medium", high: "high", xhigh: "high", max: "high",
|
|
164
167
|
};
|
|
165
168
|
|
|
@@ -173,8 +176,18 @@ export const DEFAULT_COMPAT: Compat = {
|
|
|
173
176
|
};
|
|
174
177
|
|
|
175
178
|
export interface ModelSpec {
|
|
179
|
+
/** Public variable names from the source directory, never their values. */
|
|
180
|
+
credentialEnv?: readonly string[];
|
|
181
|
+
/** Native Pi transport is runtime-only. Existing models retain their verified adapter. */
|
|
182
|
+
driver?: "pi";
|
|
183
|
+
/** Public catalog metadata only. User credentials/headers live in provider-access's sidecar. */
|
|
184
|
+
nativeCompat?: Record<string, unknown>;
|
|
185
|
+
samplingParams?: Record<string, unknown>;
|
|
186
|
+
defaultHeaders?: Record<string, string>;
|
|
176
187
|
/** The id sent on the wire. */
|
|
177
188
|
id: string;
|
|
189
|
+
/** Optional upstream deployment/model id when a user gives it a local catalog alias. */
|
|
190
|
+
wireId?: string;
|
|
178
191
|
/** For humans, in `/model` listings. */
|
|
179
192
|
name: string;
|
|
180
193
|
api: Api;
|
|
@@ -187,6 +200,8 @@ export interface ModelSpec {
|
|
|
187
200
|
*/
|
|
188
201
|
baseUrl?: string;
|
|
189
202
|
reasoning: boolean;
|
|
203
|
+
/** Explicit capability from the model catalog. Unknown input capability is text-only. */
|
|
204
|
+
input?: readonly ("text" | "image")[];
|
|
190
205
|
/**
|
|
191
206
|
* Absent when the provider's caching terms are not in the source we
|
|
192
207
|
* trust. Filling `automatic` + `write: 0` here would be DeepSeek's
|
|
@@ -206,9 +221,8 @@ export interface ModelSpec {
|
|
|
206
221
|
/** Only meaningful for `openai-completions`. Absent means `DEFAULT_COMPAT`. */
|
|
207
222
|
compat?: Compat;
|
|
208
223
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* our level through unchanged.
|
|
224
|
+
* Pi's effort names onto the wire vocabulary. `null` disables a level.
|
|
225
|
+
* Unmapped xhigh/max are unsupported; other missing names pass through.
|
|
212
226
|
*/
|
|
213
227
|
thinkingLevelMap?: Partial<Record<Effort, string | null>>;
|
|
214
228
|
}
|
|
@@ -223,10 +237,14 @@ export const ANTHROPIC_RATES: CacheRates = { read: 0.1, write: 1.25 };
|
|
|
223
237
|
|
|
224
238
|
export const CLAUDE_OPUS_5: ModelSpec = {
|
|
225
239
|
id: "claude-opus-5",
|
|
240
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
241
|
+
input: ["text","image"],
|
|
226
242
|
name: "Claude Opus 5",
|
|
227
243
|
api: "anthropic-messages",
|
|
228
244
|
provider: "anthropic",
|
|
229
245
|
reasoning: true,
|
|
246
|
+
// Preserve the built-in request vocabulary while the generic capability policy becomes explicit.
|
|
247
|
+
thinkingLevelMap: { minimal: "low", xhigh: "xhigh", max: "max" },
|
|
230
248
|
cache: "explicit",
|
|
231
249
|
rates: ANTHROPIC_RATES,
|
|
232
250
|
// Cost from models.dev on 2026-09-21. Ratios match ANTHROPIC_RATES.
|
|
@@ -257,6 +275,7 @@ const DEEPSEEK_COMPAT: Compat = {
|
|
|
257
275
|
* sending a level the API has to guess about.
|
|
258
276
|
*/
|
|
259
277
|
const DEEPSEEK_EFFORT: Partial<Record<Effort, string | null>> = {
|
|
278
|
+
minimal: "low",
|
|
260
279
|
low: "low",
|
|
261
280
|
medium: "high",
|
|
262
281
|
high: "high",
|
|
@@ -275,6 +294,8 @@ const DEEPSEEK_EFFORT: Partial<Record<Effort, string | null>> = {
|
|
|
275
294
|
*/
|
|
276
295
|
export const DEEPSEEK_V4_PRO: ModelSpec = {
|
|
277
296
|
id: "deepseek-v4-pro",
|
|
297
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
298
|
+
input: ["text"],
|
|
278
299
|
name: "DeepSeek V4 Pro",
|
|
279
300
|
api: "openai-completions",
|
|
280
301
|
provider: "deepseek",
|
|
@@ -291,6 +312,8 @@ export const DEEPSEEK_V4_PRO: ModelSpec = {
|
|
|
291
312
|
|
|
292
313
|
export const DEEPSEEK_FLASH: ModelSpec = {
|
|
293
314
|
id: "deepseek-flash",
|
|
315
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
316
|
+
input: ["text","image"],
|
|
294
317
|
name: "DeepSeek V4.1 Flash",
|
|
295
318
|
api: "openai-completions",
|
|
296
319
|
provider: "deepseek",
|
|
@@ -356,10 +379,12 @@ function responsesModel(spec: Omit<ModelSpec, "api">): ModelSpec {
|
|
|
356
379
|
* *rates* (multiples) stay omitted unless both cache dollars are present,
|
|
357
380
|
* except DeepSeek (verified against its own page).
|
|
358
381
|
*/
|
|
359
|
-
export const
|
|
382
|
+
export const CURATED_CATALOG: readonly ModelSpec[] = [
|
|
360
383
|
CLAUDE_OPUS_5,
|
|
361
384
|
anthropicModel({
|
|
362
385
|
id: "claude-fable-5-1",
|
|
386
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
387
|
+
input: ["text","image"],
|
|
363
388
|
name: "Claude Fable 5.1",
|
|
364
389
|
reasoning: true,
|
|
365
390
|
cache: "explicit",
|
|
@@ -371,6 +396,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
371
396
|
|
|
372
397
|
responsesModel({
|
|
373
398
|
id: "gpt-5.5-pro",
|
|
399
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
400
|
+
input: ["text","image"],
|
|
374
401
|
name: "GPT-5.5 Pro",
|
|
375
402
|
provider: "openai",
|
|
376
403
|
baseUrl: "https://api.openai.com/v1",
|
|
@@ -382,6 +409,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
382
409
|
}),
|
|
383
410
|
responsesModel({
|
|
384
411
|
id: "gpt-5.4-pro",
|
|
412
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
413
|
+
input: ["text","image"],
|
|
385
414
|
name: "GPT-5.4 Pro",
|
|
386
415
|
provider: "openai",
|
|
387
416
|
baseUrl: "https://api.openai.com/v1",
|
|
@@ -394,6 +423,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
394
423
|
|
|
395
424
|
completionsModel({
|
|
396
425
|
id: "gemini-3.1-pro-preview",
|
|
426
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
427
|
+
input: ["text","image"],
|
|
397
428
|
name: "Gemini 3.1 Pro Preview",
|
|
398
429
|
provider: "google",
|
|
399
430
|
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
@@ -411,6 +442,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
411
442
|
|
|
412
443
|
completionsModel({
|
|
413
444
|
id: "grok-4.6",
|
|
445
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
446
|
+
input: ["text","image"],
|
|
414
447
|
name: "Grok 4.6",
|
|
415
448
|
provider: "xai",
|
|
416
449
|
baseUrl: "https://api.x.ai/v1",
|
|
@@ -422,6 +455,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
422
455
|
}),
|
|
423
456
|
completionsModel({
|
|
424
457
|
id: "grok-4.5",
|
|
458
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
459
|
+
input: ["text","image"],
|
|
425
460
|
name: "Grok 4.5",
|
|
426
461
|
provider: "xai",
|
|
427
462
|
baseUrl: "https://api.x.ai/v1",
|
|
@@ -434,6 +469,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
434
469
|
|
|
435
470
|
completionsModel({
|
|
436
471
|
id: "llama-3.3-70b-versatile",
|
|
472
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
473
|
+
input: ["text"],
|
|
437
474
|
name: "Llama 3.3 70B",
|
|
438
475
|
provider: "groq",
|
|
439
476
|
baseUrl: "https://api.groq.com/openai/v1",
|
|
@@ -444,6 +481,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
444
481
|
}),
|
|
445
482
|
completionsModel({
|
|
446
483
|
id: "qwen/qwen3.6-27b",
|
|
484
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
485
|
+
input: ["text","image"],
|
|
447
486
|
name: "Qwen3.6 27B",
|
|
448
487
|
provider: "groq",
|
|
449
488
|
baseUrl: "https://api.groq.com/openai/v1",
|
|
@@ -455,6 +494,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
455
494
|
|
|
456
495
|
completionsModel({
|
|
457
496
|
id: "gpt-oss-120b",
|
|
497
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
498
|
+
input: ["text"],
|
|
458
499
|
name: "GPT OSS 120B",
|
|
459
500
|
provider: "cerebras",
|
|
460
501
|
baseUrl: "https://api.cerebras.ai/v1",
|
|
@@ -465,6 +506,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
465
506
|
}),
|
|
466
507
|
completionsModel({
|
|
467
508
|
id: "qwen-3.8-27b",
|
|
509
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
510
|
+
input: ["text","image"],
|
|
468
511
|
name: "Qwen3.8 27B",
|
|
469
512
|
provider: "cerebras",
|
|
470
513
|
baseUrl: "https://api.cerebras.ai/v1",
|
|
@@ -476,6 +519,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
476
519
|
|
|
477
520
|
completionsModel({
|
|
478
521
|
id: "magistral-medium-latest",
|
|
522
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
523
|
+
input: ["text"],
|
|
479
524
|
name: "Magistral Medium (latest)",
|
|
480
525
|
provider: "mistral",
|
|
481
526
|
baseUrl: "https://api.mistral.ai/v1",
|
|
@@ -486,6 +531,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
486
531
|
}),
|
|
487
532
|
completionsModel({
|
|
488
533
|
id: "mistral-large-2411",
|
|
534
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
535
|
+
input: ["text"],
|
|
489
536
|
name: "Mistral Large 2.1",
|
|
490
537
|
provider: "mistral",
|
|
491
538
|
baseUrl: "https://api.mistral.ai/v1",
|
|
@@ -497,6 +544,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
497
544
|
|
|
498
545
|
completionsModel({
|
|
499
546
|
id: "openai/gpt-5.5-pro",
|
|
547
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
548
|
+
input: ["text","image"],
|
|
500
549
|
name: "GPT-5.5 Pro",
|
|
501
550
|
provider: "openrouter",
|
|
502
551
|
baseUrl: "https://openrouter.ai/api/v1",
|
|
@@ -508,6 +557,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
508
557
|
}),
|
|
509
558
|
completionsModel({
|
|
510
559
|
id: "openai/gpt-5.4-pro",
|
|
560
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
561
|
+
input: ["text","image"],
|
|
511
562
|
name: "GPT-5.4 Pro",
|
|
512
563
|
provider: "openrouter",
|
|
513
564
|
baseUrl: "https://openrouter.ai/api/v1",
|
|
@@ -520,6 +571,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
520
571
|
|
|
521
572
|
completionsModel({
|
|
522
573
|
id: "glm-5.3",
|
|
574
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
575
|
+
input: ["text"],
|
|
523
576
|
name: "GLM-5.3",
|
|
524
577
|
provider: "zai",
|
|
525
578
|
baseUrl: "https://api.z.ai/api/paas/v4",
|
|
@@ -532,6 +585,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
532
585
|
}),
|
|
533
586
|
completionsModel({
|
|
534
587
|
id: "glm-5.2",
|
|
588
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
589
|
+
input: ["text"],
|
|
535
590
|
name: "GLM-5.2",
|
|
536
591
|
provider: "zai",
|
|
537
592
|
baseUrl: "https://api.z.ai/api/paas/v4",
|
|
@@ -545,6 +600,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
545
600
|
|
|
546
601
|
completionsModel({
|
|
547
602
|
id: "kimi-k3",
|
|
603
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
604
|
+
input: ["text","image"],
|
|
548
605
|
name: "Kimi K3",
|
|
549
606
|
provider: "moonshotai",
|
|
550
607
|
baseUrl: "https://api.moonshot.ai/v1",
|
|
@@ -557,6 +614,8 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
557
614
|
}),
|
|
558
615
|
completionsModel({
|
|
559
616
|
id: "kimi-k2.6",
|
|
617
|
+
// Input modalities from the existing models.dev catalog source, 2026-09-30.
|
|
618
|
+
input: ["text","image"],
|
|
560
619
|
name: "Kimi K2.6",
|
|
561
620
|
provider: "moonshotai",
|
|
562
621
|
baseUrl: "https://api.moonshot.ai/v1",
|
|
@@ -594,6 +653,10 @@ export const CATALOG: readonly ModelSpec[] = [
|
|
|
594
653
|
}),
|
|
595
654
|
];
|
|
596
655
|
|
|
656
|
+
// Keep existing default/provider order and verified wire dialects; add the source union once.
|
|
657
|
+
export const CATALOG: readonly ModelSpec[] = mergeCatalogs(CURATED_CATALOG,
|
|
658
|
+
SOURCE_MODELS.filter(model => !CURATED_CATALOG.some(curated => curated.provider === model.provider && curated.id === model.id)));
|
|
659
|
+
|
|
597
660
|
/**
|
|
598
661
|
* Output tokens to request, which is also the output reserve every window
|
|
599
662
|
* budget subtracts.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* starting".
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { modelsDevCatalog, unionSourceCatalog } from "./model-sources.ts";
|
|
13
13
|
|
|
14
14
|
export const MODELS_DEV_URL = "https://models.dev/api.json";
|
|
15
15
|
|
|
@@ -43,113 +43,15 @@ export async function refreshRemoteCatalog(opts: {
|
|
|
43
43
|
} catch (err) {
|
|
44
44
|
return { ok: false, reason: `could not refresh models: body is not JSON (${(err as Error).message})` };
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
if (models.length === 0) {
|
|
46
|
+
if (modelsDevCatalog(json).models.length === 0) {
|
|
48
47
|
return { ok: false, reason: `could not refresh models: ${url} named no provider we can speak` };
|
|
49
48
|
}
|
|
49
|
+
const catalog = unionSourceCatalog(json);
|
|
50
50
|
try {
|
|
51
|
-
|
|
51
|
+
// Provider metadata is not the user's executable provider configuration.
|
|
52
|
+
await opts.write({ schemaVersion: 1, models: catalog.models, providerMetadata: catalog.providers, excluded: catalog.excluded });
|
|
52
53
|
} catch (err) {
|
|
53
54
|
return { ok: false, reason: `could not write models: ${(err as Error).message}` };
|
|
54
55
|
}
|
|
55
|
-
return { ok: true, count: models.length };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function modelsFromRemote(json: unknown): ModelSpec[] {
|
|
59
|
-
if (typeof json !== "object" || json === null) return [];
|
|
60
|
-
const out: ModelSpec[] = [];
|
|
61
|
-
for (const [rawId, value] of Object.entries(json as Record<string, unknown>)) {
|
|
62
|
-
const providerId = rawId === "togetherai" ? "together" : rawId;
|
|
63
|
-
const wire = PROVIDER_WIRE[rawId] ?? PROVIDER_WIRE[providerId];
|
|
64
|
-
if (wire === undefined) continue;
|
|
65
|
-
if (typeof value !== "object" || value === null) continue;
|
|
66
|
-
const provider = value as Record<string, unknown>;
|
|
67
|
-
const models = provider["models"];
|
|
68
|
-
if (typeof models !== "object" || models === null) continue;
|
|
69
|
-
for (const model of Object.values(models as Record<string, unknown>)) {
|
|
70
|
-
const spec = specFromRemote(model, providerId, wire);
|
|
71
|
-
if (spec !== null) out.push(spec);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return out;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function specFromRemote(
|
|
78
|
-
raw: unknown,
|
|
79
|
-
provider: string,
|
|
80
|
-
wire: { api: Api; baseUrl?: string },
|
|
81
|
-
): ModelSpec | null {
|
|
82
|
-
if (typeof raw !== "object" || raw === null) return null;
|
|
83
|
-
const m = raw as Record<string, unknown>;
|
|
84
|
-
if (m["tool_call"] === false) return null;
|
|
85
|
-
if (typeof m["id"] !== "string" || m["id"].length === 0) return null;
|
|
86
|
-
const cost = m["cost"];
|
|
87
|
-
const limit = m["limit"];
|
|
88
|
-
const ctx = isRecord(limit) && typeof limit["context"] === "number" ? limit["context"] : undefined;
|
|
89
|
-
const max = isRecord(limit) && typeof limit["output"] === "number" ? limit["output"] : undefined;
|
|
90
|
-
const price = priceFromCost(cost);
|
|
91
|
-
const cache = cacheFromCost(provider, cost);
|
|
92
|
-
const spec: ModelSpec = {
|
|
93
|
-
id: m["id"],
|
|
94
|
-
name: typeof m["name"] === "string" && m["name"].length > 0 ? m["name"] : m["id"],
|
|
95
|
-
api: wire.api,
|
|
96
|
-
provider,
|
|
97
|
-
reasoning: m["reasoning"] === true,
|
|
98
|
-
...(wire.baseUrl !== undefined ? { baseUrl: wire.baseUrl } : {}),
|
|
99
|
-
...(ctx !== undefined ? { contextWindow: ctx } : {}),
|
|
100
|
-
...(max !== undefined ? { maxTokens: max } : {}),
|
|
101
|
-
...(price !== undefined ? { price } : {}),
|
|
102
|
-
...(cache !== undefined ? cache : {}),
|
|
103
|
-
};
|
|
104
|
-
return spec;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function priceFromCost(cost: unknown): Price | undefined {
|
|
108
|
-
if (!isRecord(cost)) return undefined;
|
|
109
|
-
const input = cost["input"];
|
|
110
|
-
const output = cost["output"];
|
|
111
|
-
if (typeof input !== "number" || typeof output !== "number") return undefined;
|
|
112
|
-
// Cache dollars are optional. Missing is missing — a 0 here would be
|
|
113
|
-
// DeepSeek's term, not a default for everyone else.
|
|
114
|
-
const price: Price = { input, output };
|
|
115
|
-
if (typeof cost["cache_read"] === "number") price.cacheRead = cost["cache_read"];
|
|
116
|
-
if (typeof cost["cache_write"] === "number") price.cacheWrite = cost["cache_write"];
|
|
117
|
-
return price;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function cacheFromCost(
|
|
121
|
-
provider: string,
|
|
122
|
-
cost: unknown,
|
|
123
|
-
): { cache: "explicit" | "automatic"; rates: { read: number; write: number } } | undefined {
|
|
124
|
-
if (!isRecord(cost)) return undefined;
|
|
125
|
-
const input = cost["input"];
|
|
126
|
-
const cacheRead = cost["cache_read"];
|
|
127
|
-
const cacheWrite = cost["cache_write"];
|
|
128
|
-
if (typeof input !== "number" || input === 0) return undefined;
|
|
129
|
-
if (typeof cacheRead !== "number" || typeof cacheWrite !== "number") return undefined;
|
|
130
|
-
return {
|
|
131
|
-
cache: provider === "anthropic" ? "explicit" : "automatic",
|
|
132
|
-
rates: { read: cacheRead / input, write: cacheWrite / input },
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function serializeSpec(spec: ModelSpec): Record<string, unknown> {
|
|
137
|
-
return {
|
|
138
|
-
id: spec.id,
|
|
139
|
-
name: spec.name,
|
|
140
|
-
api: spec.api,
|
|
141
|
-
provider: spec.provider,
|
|
142
|
-
reasoning: spec.reasoning,
|
|
143
|
-
...(spec.baseUrl !== undefined ? { baseUrl: spec.baseUrl } : {}),
|
|
144
|
-
...(spec.cache !== undefined ? { cache: spec.cache } : {}),
|
|
145
|
-
...(spec.rates !== undefined ? { rates: spec.rates } : {}),
|
|
146
|
-
...(spec.price !== undefined ? { price: spec.price } : {}),
|
|
147
|
-
...(spec.contextWindow !== undefined ? { contextWindow: spec.contextWindow } : {}),
|
|
148
|
-
...(spec.maxTokens !== undefined ? { maxTokens: spec.maxTokens } : {}),
|
|
149
|
-
...(spec.compat !== undefined ? { compat: spec.compat } : {}),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
154
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
56
|
+
return { ok: true, count: catalog.models.length };
|
|
155
57
|
}
|
package/src/runtime/oauth.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { createServer, type Server } from "node:http";
|
|
|
17
17
|
import { pollDeviceCode } from "./oauth-device.ts";
|
|
18
18
|
|
|
19
19
|
/** Matches package.json. Sent as User-Agent so the server can see who we are (D18). */
|
|
20
|
-
export const XENO_VERSION = "0.
|
|
20
|
+
export const XENO_VERSION = "0.2.0";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Minutes subtracted from server expiry so a request issued at the edge
|
|
@@ -31,7 +31,7 @@ export const SUBSCRIPTION_NOTICE =
|
|
|
31
31
|
|
|
32
32
|
export const SUBSCRIPTION_OPTIONS = [
|
|
33
33
|
{ id: "claude-pro", provider: "anthropic", label: "Claude Pro/Max" },
|
|
34
|
-
{ id: "codex", provider: "openai", label: "ChatGPT Plus/Pro (Codex)" },
|
|
34
|
+
{ id: "codex", provider: "openai-codex", label: "ChatGPT Plus/Pro (Codex)" },
|
|
35
35
|
{ id: "github-copilot", provider: "github-copilot", label: "GitHub Copilot" },
|
|
36
36
|
] as const;
|
|
37
37
|
|
|
@@ -501,6 +501,7 @@ export async function refreshOAuthCredential(
|
|
|
501
501
|
case "anthropic":
|
|
502
502
|
return refreshAnthropic(credential.refresh, opts);
|
|
503
503
|
case "openai":
|
|
504
|
+
case "openai-codex":
|
|
504
505
|
return refreshOpenAI(credential.refresh, opts);
|
|
505
506
|
case "github-copilot":
|
|
506
507
|
return refreshCopilot(credential.refresh, opts, credential.enterpriseUrl);
|
|
@@ -748,7 +749,7 @@ export async function loginSubscription(
|
|
|
748
749
|
case "claude-pro":
|
|
749
750
|
return { provider: "anthropic", credential: await loginAnthropic(interaction) };
|
|
750
751
|
case "codex":
|
|
751
|
-
return { provider: "openai", credential: await loginOpenAICodex(interaction) };
|
|
752
|
+
return { provider: "openai-codex", credential: await loginOpenAICodex(interaction) };
|
|
752
753
|
case "github-copilot":
|
|
753
754
|
return { provider: "github-copilot", credential: await loginGitHubCopilot(interaction) };
|
|
754
755
|
default: {
|