@visual-z/xeno 0.8.4 → 0.9.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/CHANGELOG.md +19 -0
- package/README.md +31 -746
- package/bin/xeno.exe +26 -0
- package/install.cjs +88 -0
- package/package.json +18 -48
- package/docs/app-server.md +0 -115
- package/docs/foundations.md +0 -88
- package/docs/install.md +0 -244
- package/docs/providers.md +0 -172
- package/docs/research/foundation-clients-2026-10-09.md +0 -27
- package/docs/research/foundation-editing-2026-10-09.md +0 -56
- package/docs/research/foundation-network-2026-10-09.md +0 -37
- package/docs/research/foundation-workflow-2026-10-09.md +0 -37
- package/src/apps/acp-server.ts +0 -41
- package/src/apps/agent-profiles.ts +0 -19
- package/src/apps/app-server.ts +0 -30
- package/src/apps/approve.ts +0 -49
- package/src/apps/cli.ts +0 -99
- package/src/apps/commands.ts +0 -319
- package/src/apps/completions.ts +0 -87
- package/src/apps/control-transport.ts +0 -90
- package/src/apps/export-cli.ts +0 -24
- package/src/apps/goal-flow.ts +0 -70
- package/src/apps/image-draft.ts +0 -46
- package/src/apps/input.ts +0 -312
- package/src/apps/model-catalog.ts +0 -69
- package/src/apps/preferences.ts +0 -20
- package/src/apps/project-trust.ts +0 -9
- package/src/apps/provider-connect.ts +0 -346
- package/src/apps/question-ui.ts +0 -27
- package/src/apps/render.ts +0 -181
- package/src/apps/resources.ts +0 -66
- package/src/apps/review.ts +0 -109
- package/src/apps/session-hub.ts +0 -96
- package/src/apps/session-lifecycle.ts +0 -64
- package/src/apps/shell.ts +0 -2071
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1818
- package/src/apps/ui/control-model.ts +0 -43
- package/src/apps/ui/extension-host.ts +0 -194
- package/src/apps/ui/image-cache.ts +0 -121
- package/src/apps/ui/palette.ts +0 -88
- package/src/apps/ui/project.ts +0 -127
- package/src/apps/ui/standalone.ts +0 -148
- package/src/apps/ui/status-model.ts +0 -68
- package/src/apps/ui/surface.ts +0 -91
- package/src/apps/ui/theme.ts +0 -86
- package/src/apps/update-check.ts +0 -112
- package/src/apps/workflow.ts +0 -58
- package/src/core/acceptance.ts +0 -139
- package/src/core/action-defaults.ts +0 -58
- package/src/core/activity-status.ts +0 -52
- package/src/core/agent-profile.ts +0 -130
- package/src/core/attachment-input.ts +0 -17
- package/src/core/blind-checks.ts +0 -72
- package/src/core/boundary.ts +0 -174
- package/src/core/branch-receipt.ts +0 -26
- package/src/core/catalog-merge.ts +0 -19
- package/src/core/checkpoints.ts +0 -85
- package/src/core/churn.ts +0 -61
- package/src/core/command-artifacts.ts +0 -94
- package/src/core/command-contract.ts +0 -151
- package/src/core/completion-action.ts +0 -20
- package/src/core/completion-review.ts +0 -98
- package/src/core/context-detail.ts +0 -37
- package/src/core/context-policy.ts +0 -133
- package/src/core/context-window.ts +0 -54
- package/src/core/continuation.ts +0 -130
- package/src/core/control-protocol.ts +0 -69
- package/src/core/control-view.ts +0 -39
- package/src/core/cycle.ts +0 -17
- package/src/core/data-query.ts +0 -201
- package/src/core/deadline.ts +0 -40
- package/src/core/derive.ts +0 -516
- package/src/core/draft-images.ts +0 -56
- package/src/core/edit-plan.ts +0 -200
- package/src/core/environment.ts +0 -181
- package/src/core/event-validation.ts +0 -220
- package/src/core/events.ts +0 -670
- package/src/core/evidence-ladder.ts +0 -62
- package/src/core/evidence-links.ts +0 -68
- package/src/core/evidence.ts +0 -240
- package/src/core/execution.ts +0 -18
- package/src/core/exhaustive.ts +0 -18
- package/src/core/export.ts +0 -179
- package/src/core/extension-flags.ts +0 -14
- package/src/core/external-effects.ts +0 -488
- package/src/core/failure-recovery.ts +0 -173
- package/src/core/file-precondition.ts +0 -78
- package/src/core/file-references.ts +0 -35
- package/src/core/frontmatter.ts +0 -213
- package/src/core/git-status.ts +0 -29
- package/src/core/goal-view.ts +0 -64
- package/src/core/goal.ts +0 -200
- package/src/core/handoff.ts +0 -479
- package/src/core/history-query.ts +0 -139
- package/src/core/history.ts +0 -237
- package/src/core/image-limits.ts +0 -72
- package/src/core/images.ts +0 -46
- package/src/core/integration-health.ts +0 -34
- package/src/core/json-event.ts +0 -131
- package/src/core/json-pointer.ts +0 -46
- package/src/core/keymap.ts +0 -153
- package/src/core/lsp-framing.ts +0 -34
- package/src/core/mcp-config.ts +0 -68
- package/src/core/model-picker.ts +0 -64
- package/src/core/model-preferences.ts +0 -28
- package/src/core/model-replay.ts +0 -22
- package/src/core/model-scope.ts +0 -53
- package/src/core/note-guidance.ts +0 -34
- package/src/core/oauth-fields.ts +0 -44
- package/src/core/observation-view.ts +0 -152
- package/src/core/outcome.ts +0 -44
- package/src/core/overflow.ts +0 -59
- package/src/core/packages.ts +0 -47
- package/src/core/patch.ts +0 -130
- package/src/core/path-suggestions.ts +0 -59
- package/src/core/permissions.ts +0 -410
- package/src/core/process-receipt.ts +0 -105
- package/src/core/process-state.ts +0 -37
- package/src/core/program-status.ts +0 -40
- package/src/core/progress.ts +0 -150
- package/src/core/project.ts +0 -431
- package/src/core/prompt-policy.ts +0 -46
- package/src/core/prompts.ts +0 -111
- package/src/core/question.ts +0 -94
- package/src/core/queue.ts +0 -78
- package/src/core/read-bounds.ts +0 -24
- package/src/core/read-observation.ts +0 -106
- package/src/core/recovery-sources.ts +0 -74
- package/src/core/regex-dialect.ts +0 -25
- package/src/core/request-budget.ts +0 -23
- package/src/core/request-progress.ts +0 -87
- package/src/core/request-state.ts +0 -273
- package/src/core/resource-selection.ts +0 -36
- package/src/core/response-recovery.ts +0 -59
- package/src/core/resume.ts +0 -59
- package/src/core/review.ts +0 -68
- package/src/core/ripgrep-release.ts +0 -64
- package/src/core/runtime-target.ts +0 -94
- package/src/core/scheduling.ts +0 -8
- package/src/core/search-observation.ts +0 -97
- package/src/core/secrets.ts +0 -188
- package/src/core/session-archive.ts +0 -4
- package/src/core/session-browser.ts +0 -82
- package/src/core/session-entry.ts +0 -61
- package/src/core/session-exit.ts +0 -42
- package/src/core/session-format.ts +0 -169
- package/src/core/session-position.ts +0 -26
- package/src/core/session-tree.ts +0 -147
- package/src/core/settings.ts +0 -192
- package/src/core/skills.ts +0 -165
- package/src/core/sql-query.ts +0 -211
- package/src/core/stall.ts +0 -128
- package/src/core/stop-report.ts +0 -68
- package/src/core/text-normalize.ts +0 -81
- package/src/core/theme.ts +0 -166
- package/src/core/thinking.ts +0 -26
- package/src/core/tokens.ts +0 -151
- package/src/core/tool-contract.ts +0 -153
- package/src/core/tool-discovery.ts +0 -87
- package/src/core/tool-failure.ts +0 -81
- package/src/core/tool-input.ts +0 -44
- package/src/core/transcript.ts +0 -108
- package/src/core/tui-mode.ts +0 -29
- package/src/core/update-check.ts +0 -157
- package/src/core/version.ts +0 -55
- package/src/core/web-content.ts +0 -80
- package/src/core/work-state.ts +0 -154
- package/src/core/workflow-mode.ts +0 -34
- package/src/core/working-set.ts +0 -119
- package/src/core/yaml.ts +0 -282
- package/src/runtime/acp-agent.ts +0 -289
- package/src/runtime/agent-profiles.ts +0 -21
- package/src/runtime/auth.ts +0 -367
- package/src/runtime/blind-check.ts +0 -36
- package/src/runtime/branch-jobs.ts +0 -36
- package/src/runtime/catalog.ts +0 -285
- package/src/runtime/clipboard.ts +0 -125
- package/src/runtime/command-artifacts.ts +0 -64
- package/src/runtime/connection-reuse.ts +0 -40
- package/src/runtime/control-client.ts +0 -149
- package/src/runtime/control-connection.ts +0 -139
- package/src/runtime/counter-check.ts +0 -74
- package/src/runtime/environment-tools.ts +0 -60
- package/src/runtime/evidence-notes.ts +0 -35
- package/src/runtime/exec-sessions.ts +0 -456
- package/src/runtime/execution-control.ts +0 -40
- package/src/runtime/extension-loader.ts +0 -37
- package/src/runtime/extension-ui.ts +0 -47
- package/src/runtime/extensions.ts +0 -370
- package/src/runtime/formatter-tool.ts +0 -38
- package/src/runtime/formatter.ts +0 -50
- package/src/runtime/foundation.ts +0 -116
- package/src/runtime/history-cache.ts +0 -57
- package/src/runtime/history-tools.ts +0 -168
- package/src/runtime/images.ts +0 -80
- package/src/runtime/integrations.ts +0 -216
- package/src/runtime/loop.ts +0 -3108
- package/src/runtime/lsp-client.ts +0 -201
- package/src/runtime/mcp-client.ts +0 -264
- package/src/runtime/mcp-oauth.ts +0 -168
- package/src/runtime/mcp-schema.ts +0 -51
- package/src/runtime/model-config.ts +0 -232
- package/src/runtime/model-data.LICENSE +0 -27
- package/src/runtime/model-data.ts +0 -21
- package/src/runtime/model-discovery.ts +0 -208
- package/src/runtime/model-replay.ts +0 -10
- package/src/runtime/model-scope.ts +0 -20
- package/src/runtime/model-sources.ts +0 -151
- package/src/runtime/model.ts +0 -686
- package/src/runtime/models-fetch.ts +0 -57
- package/src/runtime/oauth-device.ts +0 -86
- package/src/runtime/oauth.ts +0 -853
- package/src/runtime/open-image.ts +0 -42
- package/src/runtime/openai-responses.ts +0 -304
- package/src/runtime/openai.ts +0 -697
- package/src/runtime/output-schema.ts +0 -26
- package/src/runtime/package-manager.ts +0 -125
- package/src/runtime/patch-tool.ts +0 -38
- package/src/runtime/permission-resources.ts +0 -18
- package/src/runtime/pi-provider.ts +0 -423
- package/src/runtime/program-status.ts +0 -38
- package/src/runtime/progress.ts +0 -26
- package/src/runtime/provider-access.ts +0 -21
- package/src/runtime/provider-connect.ts +0 -109
- package/src/runtime/provider-presets.ts +0 -98
- package/src/runtime/provider-protocols.ts +0 -29
- package/src/runtime/provider-request.ts +0 -87
- package/src/runtime/provider-tool-policy.ts +0 -33
- package/src/runtime/provider.ts +0 -580
- package/src/runtime/question-tool.ts +0 -62
- package/src/runtime/retry.ts +0 -447
- package/src/runtime/review-tool.ts +0 -48
- package/src/runtime/rich-text.ts +0 -36
- package/src/runtime/runtime-target.ts +0 -149
- package/src/runtime/secrets.ts +0 -79
- package/src/runtime/self-update.ts +0 -74
- package/src/runtime/session-control.ts +0 -265
- package/src/runtime/session-hub.ts +0 -85
- package/src/runtime/stream-idle.ts +0 -80
- package/src/runtime/tool-broker.ts +0 -137
- package/src/runtime/tool-discovery.ts +0 -58
- package/src/runtime/tool-queue.ts +0 -77
- package/src/runtime/tools.ts +0 -1820
- package/src/runtime/turn-effects.ts +0 -31
- package/src/runtime/update-check.ts +0 -48
- package/src/runtime/usage.ts +0 -253
- package/src/runtime/version.ts +0 -12
- package/src/runtime/web-http.ts +0 -56
- package/src/runtime/web-tools.ts +0 -88
- package/src/runtime/working-notes.ts +0 -60
- package/src/runtime/workspace-lifecycle.ts +0 -141
- package/src/store/acceptance.ts +0 -15
- package/src/store/agent-profile-files.ts +0 -13
- package/src/store/atomic.ts +0 -31
- package/src/store/auth-store.ts +0 -50
- package/src/store/blind-checks.ts +0 -32
- package/src/store/blobs.ts +0 -143
- package/src/store/checkpoint-history.ts +0 -32
- package/src/store/command-artifacts.ts +0 -96
- package/src/store/command.ts +0 -111
- package/src/store/config.ts +0 -69
- package/src/store/context-files.ts +0 -436
- package/src/store/file-checkpoints.ts +0 -108
- package/src/store/file-index.ts +0 -174
- package/src/store/foundation-config.ts +0 -24
- package/src/store/git-checkpoints.ts +0 -67
- package/src/store/git-status.ts +0 -15
- package/src/store/history-search.ts +0 -19
- package/src/store/log.ts +0 -725
- package/src/store/lsp-document.ts +0 -30
- package/src/store/mcp-credentials.ts +0 -85
- package/src/store/models.ts +0 -60
- package/src/store/observations.ts +0 -224
- package/src/store/packages.ts +0 -98
- package/src/store/patch.ts +0 -92
- package/src/store/permission-paths.ts +0 -34
- package/src/store/process-receipts.ts +0 -34
- package/src/store/query-source.ts +0 -49
- package/src/store/recovery.ts +0 -44
- package/src/store/replay.ts +0 -76
- package/src/store/resource-index.ts +0 -170
- package/src/store/resources.ts +0 -285
- package/src/store/restore.ts +0 -110
- package/src/store/review.ts +0 -109
- package/src/store/runtime-target.ts +0 -64
- package/src/store/search.ts +0 -728
- package/src/store/secrets.ts +0 -44
- package/src/store/serial.ts +0 -13
- package/src/store/session-archive.ts +0 -165
- package/src/store/session-browser.ts +0 -68
- package/src/store/session-position.ts +0 -27
- package/src/store/sessions.ts +0 -525
- package/src/store/source-cancellation.ts +0 -6
- package/src/store/tool-binaries.ts +0 -90
- package/src/store/trust.ts +0 -82
- package/src/store/update-cache.ts +0 -36
- package/src/store/user-settings.ts +0 -30
- package/src/store/workspace-clone.ts +0 -42
- package/src/store/workspace-observation.ts +0 -113
- package/src/store/workspace.ts +0 -115
- package/src/store/worktree.ts +0 -278
- package/src/tui/ansi-spans.ts +0 -126
- package/src/tui/diff.ts +0 -160
- package/src/tui/exhaustive.ts +0 -14
- package/src/tui/fuzzy.ts +0 -75
- package/src/tui/style.ts +0 -208
- package/src/tui/ui/atomic-draft.ts +0 -121
- package/src/tui/ui/branch-panel.ts +0 -47
- package/src/tui/ui/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -199
- package/src/tui/ui/control-panel.ts +0 -25
- package/src/tui/ui/control-sidebar.ts +0 -49
- package/src/tui/ui/dialog.ts +0 -398
- package/src/tui/ui/draft.ts +0 -23
- package/src/tui/ui/duration.ts +0 -16
- package/src/tui/ui/edge.ts +0 -186
- package/src/tui/ui/image-size.ts +0 -44
- package/src/tui/ui/ink.ts +0 -210
- package/src/tui/ui/items.ts +0 -42
- package/src/tui/ui/keys.ts +0 -40
- package/src/tui/ui/layout.ts +0 -74
- package/src/tui/ui/list-model.ts +0 -86
- package/src/tui/ui/list-view.ts +0 -172
- package/src/tui/ui/mascot.ts +0 -67
- package/src/tui/ui/messages.ts +0 -518
- package/src/tui/ui/motion.ts +0 -28
- package/src/tui/ui/paths.ts +0 -10
- package/src/tui/ui/pictures.ts +0 -108
- package/src/tui/ui/prompt-history.ts +0 -60
- package/src/tui/ui/screen.ts +0 -579
- package/src/tui/ui/status.ts +0 -403
- package/src/tui/ui/styled.ts +0 -85
- package/src/tui/ui/tool-card.ts +0 -150
- package/src/tui/width.ts +0 -567
- package/src/vendor/pi-rich/LICENSE.upstream +0 -21
- package/src/vendor/pi-rich/UPSTREAM.json +0 -17
- package/src/vendor/pi-rich/clipboard-command.ts +0 -44
- package/src/vendor/pi-rich/exif-orientation.ts +0 -182
- package/src/vendor/pi-rich/html.ts +0 -51
- package/src/vendor/pi-rich/image-convert.ts +0 -49
- package/src/vendor/pi-rich/image-resize-core.ts +0 -164
- package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
- package/src/vendor/pi-rich/image-resize.ts +0 -123
- package/src/vendor/pi-rich/mime.ts +0 -116
- package/src/vendor/pi-rich/photon.ts +0 -139
- package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live model discovery: ask the provider which models this key can use.
|
|
3
|
-
*
|
|
4
|
-
* Wire vocabulary (URLs, auth headers, response shapes) belongs to runtime,
|
|
5
|
-
* not the SDK-free core (D13). Everything except `discoverModels` is pure so
|
|
6
|
-
* the URL and parsing rules are unit tested without a network.
|
|
7
|
-
*
|
|
8
|
-
* Endpoints, per the vendors' own list APIs and what magpie / CC Switch /
|
|
9
|
-
* Crush converged on (docs/tasks/feat-provider-connect.md):
|
|
10
|
-
* OpenAI-compatible GET {base}/models Authorization: Bearer
|
|
11
|
-
* Anthropic GET {root}/v1/models x-api-key + anthropic-version
|
|
12
|
-
* (paged: has_more / last_id → ?after_id=)
|
|
13
|
-
* Gemini GET {base}/models x-goog-api-key
|
|
14
|
-
* (paged: nextPageToken → ?pageToken=; keep generateContent)
|
|
15
|
-
*
|
|
16
|
-
* A pasted full endpoint (…/chat/completions, …/v1/messages) is cut back to
|
|
17
|
-
* its base, and a base without a version segment also tries `/v1`, because
|
|
18
|
-
* gateways disagree on where the version lives. Anthropic-compatible
|
|
19
|
-
* gateways often want Bearer instead of x-api-key; both are sent (magpie).
|
|
20
|
-
*/
|
|
21
|
-
import type { ProviderApi } from "./provider-protocols.ts";
|
|
22
|
-
|
|
23
|
-
export type DiscoveryFamily = "openai" | "anthropic" | "google";
|
|
24
|
-
|
|
25
|
-
export interface DiscoveredModel {
|
|
26
|
-
id: string;
|
|
27
|
-
name?: string;
|
|
28
|
-
contextWindow?: number;
|
|
29
|
-
maxTokens?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type DiscoveryResult =
|
|
33
|
-
| { ok: true; models: DiscoveredModel[]; url: string }
|
|
34
|
-
| { ok: false; reason: string; status?: number };
|
|
35
|
-
|
|
36
|
-
export function discoveryFamily(api: ProviderApi): DiscoveryFamily | undefined {
|
|
37
|
-
switch (api) {
|
|
38
|
-
case "openai-completions":
|
|
39
|
-
case "openai-responses":
|
|
40
|
-
case "mistral-conversations":
|
|
41
|
-
return "openai";
|
|
42
|
-
case "anthropic-messages":
|
|
43
|
-
return "anthropic";
|
|
44
|
-
case "google-generative-ai":
|
|
45
|
-
return "google";
|
|
46
|
-
default:
|
|
47
|
-
// Vertex, Bedrock, Azure, Codex and Pi need cloud auth or have no
|
|
48
|
-
// listing endpoint; their catalog entries stay authoritative.
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const ENDPOINT_SUFFIXES = [/\/chat\/completions\/?$/, /\/responses\/?$/, /\/completions\/?$/, /\/messages\/?$/, /\/models\/?$/];
|
|
54
|
-
|
|
55
|
-
/** Strip whitespace, trailing slashes and a pasted operation path. */
|
|
56
|
-
export function normalizeBaseUrl(raw: string): string {
|
|
57
|
-
let url = raw.trim().replace(/\/+$/, "");
|
|
58
|
-
for (const suffix of ENDPOINT_SUFFIXES) url = url.replace(suffix, "");
|
|
59
|
-
return url.replace(/\/+$/, "");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const VERSIONED = /\/v\d+(beta\d*|alpha\d*)?$/;
|
|
63
|
-
|
|
64
|
-
export function discoveryUrls(family: DiscoveryFamily, baseUrl: string): string[] {
|
|
65
|
-
const base = normalizeBaseUrl(baseUrl);
|
|
66
|
-
const urls: string[] = [];
|
|
67
|
-
const add = (url: string) => { if (!urls.includes(url)) urls.push(url); };
|
|
68
|
-
switch (family) {
|
|
69
|
-
case "openai":
|
|
70
|
-
add(`${base}/models`);
|
|
71
|
-
if (!VERSIONED.test(base)) add(`${base}/v1/models`);
|
|
72
|
-
break;
|
|
73
|
-
case "anthropic": {
|
|
74
|
-
const root = base.replace(/\/v1$/, "");
|
|
75
|
-
add(`${root}/v1/models`);
|
|
76
|
-
add(`${base}/models`);
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
case "google":
|
|
80
|
-
add(VERSIONED.test(base) ? `${base}/models` : `${base}/v1beta/models`);
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
return urls;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function discoveryHeaders(family: DiscoveryFamily, key: string): Record<string, string> {
|
|
87
|
-
if (key.length === 0) return {};
|
|
88
|
-
switch (family) {
|
|
89
|
-
case "openai": return { authorization: `Bearer ${key}` };
|
|
90
|
-
case "anthropic": return { "x-api-key": key, authorization: `Bearer ${key}`, "anthropic-version": "2023-06-01" };
|
|
91
|
-
case "google": return { "x-goog-api-key": key };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function record(value: unknown): value is Record<string, unknown> {
|
|
96
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
97
|
-
}
|
|
98
|
-
function positive(value: unknown): number | undefined {
|
|
99
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
100
|
-
}
|
|
101
|
-
function text(value: unknown): string | undefined {
|
|
102
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** One page of a list response. `next` is the query to append for the following page. */
|
|
106
|
-
export function parseModelPage(family: DiscoveryFamily, json: unknown): { models: DiscoveredModel[]; next?: string } {
|
|
107
|
-
if (!record(json)) return { models: [] };
|
|
108
|
-
const rows = Array.isArray(json["data"]) ? json["data"] : Array.isArray(json["models"]) ? json["models"] : [];
|
|
109
|
-
const models: DiscoveredModel[] = [];
|
|
110
|
-
for (const row of rows) {
|
|
111
|
-
if (!record(row)) continue;
|
|
112
|
-
if (family === "google") {
|
|
113
|
-
const methods = row["supportedGenerationMethods"];
|
|
114
|
-
if (Array.isArray(methods) && !methods.includes("generateContent")) continue;
|
|
115
|
-
const id = text(row["name"])?.replace(/^models\//, "") ?? text(row["id"]);
|
|
116
|
-
if (!id) continue;
|
|
117
|
-
models.push(compact({ id, name: text(row["displayName"]), contextWindow: positive(row["inputTokenLimit"]), maxTokens: positive(row["outputTokenLimit"]) }));
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
const id = text(row["id"]) ?? text(row["name"]);
|
|
121
|
-
if (!id) continue;
|
|
122
|
-
// OpenRouter and several gateways publish limits on the listing itself.
|
|
123
|
-
const top = record(row["top_provider"]) ? row["top_provider"] : {};
|
|
124
|
-
models.push(compact({
|
|
125
|
-
id,
|
|
126
|
-
name: text(row["display_name"]) ?? (text(row["name"]) !== id ? text(row["name"]) : undefined),
|
|
127
|
-
contextWindow: positive(row["context_length"]) ?? positive(row["context_window"]) ?? positive(top["context_length"]),
|
|
128
|
-
maxTokens: positive(row["max_output_tokens"]) ?? positive(top["max_completion_tokens"]),
|
|
129
|
-
}));
|
|
130
|
-
}
|
|
131
|
-
if (family === "anthropic" && json["has_more"] === true && text(json["last_id"])) return { models, next: `after_id=${encodeURIComponent(String(json["last_id"]))}` };
|
|
132
|
-
if (family === "google" && text(json["nextPageToken"])) return { models, next: `pageToken=${encodeURIComponent(String(json["nextPageToken"]))}` };
|
|
133
|
-
return { models };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function compact(model: { id: string; name: string | undefined; contextWindow: number | undefined; maxTokens: number | undefined }): DiscoveredModel {
|
|
137
|
-
return { id: model.id, ...(model.name !== undefined ? { name: model.name } : {}),
|
|
138
|
-
...(model.contextWindow !== undefined ? { contextWindow: model.contextWindow } : {}),
|
|
139
|
-
...(model.maxTokens !== undefined ? { maxTokens: model.maxTokens } : {}) };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* `/models` on OpenAI-style endpoints also lists embeddings, speech, image and
|
|
144
|
-
* moderation models. They can never run a coding turn, so the picker should
|
|
145
|
-
* not offer them. Pattern list, not a guess per model: ids that match are
|
|
146
|
-
* still shown in the wizard under "other", just not preselected.
|
|
147
|
-
*/
|
|
148
|
-
const NON_CHAT = /(^|[-_/.:])(embed|embedding|embeddings|tts|whisper|transcribe|dall-e|dalle|moderation|image|imagen|audio|realtime|speech|rerank|reranker|search|veo|sora|davinci|babbage)([-_/.:]|\d|$)/i;
|
|
149
|
-
export function likelyChatModel(id: string): boolean {
|
|
150
|
-
return !NON_CHAT.test(id);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Network IO. Candidates are tried in order; 404/405 moves on, an auth
|
|
155
|
-
* failure stops (another URL will not make a wrong key right), and any
|
|
156
|
-
* other failure is reported with the URL that produced it.
|
|
157
|
-
* Timeout 10s per request: assumed; long enough for a slow gateway's first
|
|
158
|
-
* response, short enough that a dead host is reported while the user waits.
|
|
159
|
-
*/
|
|
160
|
-
const DISCOVERY_TIMEOUT_MS = 10_000;
|
|
161
|
-
/** Pagination bound: Anthropic pages at 20 by default; 25 pages covers every known list. Assumed. */
|
|
162
|
-
const MAX_PAGES = 25;
|
|
163
|
-
|
|
164
|
-
export async function discoverModels(opts: {
|
|
165
|
-
family: DiscoveryFamily;
|
|
166
|
-
baseUrl: string;
|
|
167
|
-
key: string;
|
|
168
|
-
headers?: Record<string, string>;
|
|
169
|
-
fetch: (input: string, init?: RequestInit) => Promise<Response>;
|
|
170
|
-
signal?: AbortSignal;
|
|
171
|
-
timeoutMs?: number;
|
|
172
|
-
}): Promise<DiscoveryResult> {
|
|
173
|
-
const headers = { accept: "application/json", ...discoveryHeaders(opts.family, opts.key), ...opts.headers };
|
|
174
|
-
let last: DiscoveryResult = { ok: false, reason: "no discovery URL" };
|
|
175
|
-
for (const url of discoveryUrls(opts.family, opts.baseUrl)) {
|
|
176
|
-
const models: DiscoveredModel[] = [];
|
|
177
|
-
let query: string | undefined;
|
|
178
|
-
let failed: DiscoveryResult | undefined;
|
|
179
|
-
for (let page = 0; page < MAX_PAGES; page++) {
|
|
180
|
-
const target = query ? `${url}${url.includes("?") ? "&" : "?"}${query}` : url;
|
|
181
|
-
let response: Response;
|
|
182
|
-
try {
|
|
183
|
-
const timeout = AbortSignal.timeout(opts.timeoutMs ?? DISCOVERY_TIMEOUT_MS);
|
|
184
|
-
response = await opts.fetch(target, { headers, signal: opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout });
|
|
185
|
-
} catch (error) {
|
|
186
|
-
if (opts.signal?.aborted) return { ok: false, reason: "cancelled" };
|
|
187
|
-
failed = { ok: false, reason: `${url}: ${error instanceof Error && error.name === "TimeoutError" ? "timed out" : error instanceof Error ? error.message : String(error)}` };
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
if (response.status === 401 || response.status === 403) {
|
|
191
|
-
return { ok: false, status: response.status, reason: `${url}: authentication failed (HTTP ${response.status}); check the API key` };
|
|
192
|
-
}
|
|
193
|
-
if (!response.ok) { failed = { ok: false, status: response.status, reason: `${url}: HTTP ${response.status}` }; break; }
|
|
194
|
-
let json: unknown;
|
|
195
|
-
try { json = await response.json(); } catch { failed = { ok: false, reason: `${url}: response is not JSON` }; break; }
|
|
196
|
-
const parsed = parseModelPage(opts.family, json);
|
|
197
|
-
models.push(...parsed.models);
|
|
198
|
-
if (!parsed.next) break;
|
|
199
|
-
query = parsed.next;
|
|
200
|
-
}
|
|
201
|
-
if (!failed && models.length > 0) {
|
|
202
|
-
const seen = new Set<string>();
|
|
203
|
-
return { ok: true, url, models: models.filter(model => !seen.has(model.id) && seen.add(model.id)) };
|
|
204
|
-
}
|
|
205
|
-
last = failed ?? { ok: false, reason: `${url}: the provider returned no models` };
|
|
206
|
-
}
|
|
207
|
-
return last;
|
|
208
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,151 +0,0 @@
|
|
|
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
|
-
}
|