@mono-agent/agent-app 0.6.1 → 0.8.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/README.md +210 -17
- package/dist/adapter-send-proxy.d.ts +18 -0
- package/dist/adapter-send-proxy.d.ts.map +1 -0
- package/dist/adapter-send-proxy.js +229 -0
- package/dist/adapter-send-proxy.js.map +1 -0
- package/dist/adapter-send-tools-main.js +62 -4
- package/dist/adapter-send-tools-main.js.map +1 -1
- package/dist/adapter-send-tools.d.ts +41 -8
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +563 -108
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +8 -0
- package/dist/app-config.js.map +1 -1
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +532 -134
- package/dist/app.js.map +1 -1
- package/dist/broadcast-recorder.d.ts.map +1 -1
- package/dist/broadcast-recorder.js +24 -2
- package/dist/broadcast-recorder.js.map +1 -1
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +18 -7
- package/dist/channels.js.map +1 -1
- package/dist/cli.d.ts +77 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2106 -99
- package/dist/cli.js.map +1 -1
- package/dist/config-reference.d.ts.map +1 -1
- package/dist/config-reference.js +195 -6
- package/dist/config-reference.js.map +1 -1
- package/dist/configuration-proposal-main.d.ts +3 -0
- package/dist/configuration-proposal-main.d.ts.map +1 -0
- package/dist/configuration-proposal-main.js +13 -0
- package/dist/configuration-proposal-main.js.map +1 -0
- package/dist/configuration-proposal-tool.d.ts +26 -0
- package/dist/configuration-proposal-tool.d.ts.map +1 -0
- package/dist/configuration-proposal-tool.js +139 -0
- package/dist/configuration-proposal-tool.js.map +1 -0
- package/dist/configured-agent.d.ts +12 -0
- package/dist/configured-agent.d.ts.map +1 -1
- package/dist/configured-agent.js +150 -18
- package/dist/configured-agent.js.map +1 -1
- package/dist/consumer-contract.d.ts +3 -0
- package/dist/consumer-contract.d.ts.map +1 -1
- package/dist/consumer-contract.js +44 -1
- package/dist/consumer-contract.js.map +1 -1
- package/dist/doctor.d.ts +27 -5
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +1198 -96
- package/dist/doctor.js.map +1 -1
- package/dist/first-run-managed-memory.d.ts +39 -0
- package/dist/first-run-managed-memory.d.ts.map +1 -0
- package/dist/first-run-managed-memory.js +521 -0
- package/dist/first-run-managed-memory.js.map +1 -0
- package/dist/first-run-readiness.d.ts +111 -0
- package/dist/first-run-readiness.d.ts.map +1 -0
- package/dist/first-run-readiness.js +757 -0
- package/dist/first-run-readiness.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +88 -3
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1212 -23
- package/dist/init.js.map +1 -1
- package/dist/interaction-bridge.d.ts +32 -2
- package/dist/interaction-bridge.d.ts.map +1 -1
- package/dist/interaction-bridge.js +309 -28
- package/dist/interaction-bridge.js.map +1 -1
- package/dist/local-configuration.d.ts +56 -0
- package/dist/local-configuration.d.ts.map +1 -0
- package/dist/local-configuration.js +914 -0
- package/dist/local-configuration.js.map +1 -0
- package/dist/memory-command.d.ts +4 -0
- package/dist/memory-command.d.ts.map +1 -1
- package/dist/memory-command.js +751 -27
- package/dist/memory-command.js.map +1 -1
- package/dist/memory-recall.d.ts +38 -10
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +82 -25
- package/dist/memory-recall.js.map +1 -1
- package/dist/memory-retrieval.d.ts +66 -0
- package/dist/memory-retrieval.d.ts.map +1 -0
- package/dist/memory-retrieval.js +332 -0
- package/dist/memory-retrieval.js.map +1 -0
- package/dist/modules/base.d.ts +6 -5
- package/dist/modules/base.d.ts.map +1 -1
- package/dist/modules/base.js +11 -7
- package/dist/modules/base.js.map +1 -1
- package/dist/modules/catalog.d.ts.map +1 -1
- package/dist/modules/catalog.js +54 -20
- package/dist/modules/catalog.js.map +1 -1
- package/dist/modules/index.d.ts +2 -2
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +1 -1
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/known-tools.d.ts +3 -0
- package/dist/modules/known-tools.d.ts.map +1 -1
- package/dist/modules/known-tools.js +11 -1
- package/dist/modules/known-tools.js.map +1 -1
- package/dist/modules/types.d.ts +20 -6
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/modules/types.js.map +1 -1
- package/dist/package-version.d.ts +3 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +14 -0
- package/dist/package-version.js.map +1 -0
- package/dist/pi-auth-store-inspection.d.ts +21 -0
- package/dist/pi-auth-store-inspection.d.ts.map +1 -0
- package/dist/pi-auth-store-inspection.js +120 -0
- package/dist/pi-auth-store-inspection.js.map +1 -0
- package/dist/pi-oauth-login-main.d.ts +3 -0
- package/dist/pi-oauth-login-main.d.ts.map +1 -0
- package/dist/pi-oauth-login-main.js +14 -0
- package/dist/pi-oauth-login-main.js.map +1 -0
- package/dist/pi-oauth-login.d.ts +22 -0
- package/dist/pi-oauth-login.d.ts.map +1 -0
- package/dist/pi-oauth-login.js +79 -0
- package/dist/pi-oauth-login.js.map +1 -0
- package/dist/project-skills.d.ts +40 -0
- package/dist/project-skills.d.ts.map +1 -0
- package/dist/project-skills.js +573 -0
- package/dist/project-skills.js.map +1 -0
- package/dist/provider-setup.d.ts +136 -5
- package/dist/provider-setup.d.ts.map +1 -1
- package/dist/provider-setup.js +1552 -33
- package/dist/provider-setup.js.map +1 -1
- package/dist/readiness-probe-worker.d.ts +12 -0
- package/dist/readiness-probe-worker.d.ts.map +1 -0
- package/dist/readiness-probe-worker.js +276 -0
- package/dist/readiness-probe-worker.js.map +1 -0
- package/dist/readiness-probe.d.ts +100 -0
- package/dist/readiness-probe.d.ts.map +1 -0
- package/dist/readiness-probe.js +780 -0
- package/dist/readiness-probe.js.map +1 -0
- package/dist/request-model-override.d.ts +43 -1
- package/dist/request-model-override.d.ts.map +1 -1
- package/dist/request-model-override.js +178 -19
- package/dist/request-model-override.js.map +1 -1
- package/dist/run-history.d.ts +32 -0
- package/dist/run-history.d.ts.map +1 -0
- package/dist/run-history.js +859 -0
- package/dist/run-history.js.map +1 -0
- package/dist/runs-health.d.ts.map +1 -1
- package/dist/runs-health.js +12 -4
- package/dist/runs-health.js.map +1 -1
- package/dist/runtime-option-extensions.d.ts +14 -0
- package/dist/runtime-option-extensions.d.ts.map +1 -0
- package/dist/runtime-option-extensions.js +98 -0
- package/dist/runtime-option-extensions.js.map +1 -0
- package/dist/runtime-routes.d.ts +9 -0
- package/dist/runtime-routes.d.ts.map +1 -0
- package/dist/runtime-routes.js +13 -0
- package/dist/runtime-routes.js.map +1 -0
- package/dist/sandbox-manager.d.ts +69 -0
- package/dist/sandbox-manager.d.ts.map +1 -0
- package/dist/sandbox-manager.js +856 -0
- package/dist/sandbox-manager.js.map +1 -0
- package/dist/supermemory-plugin.d.ts +54 -0
- package/dist/supermemory-plugin.d.ts.map +1 -0
- package/dist/supermemory-plugin.js +136 -0
- package/dist/supermemory-plugin.js.map +1 -0
- package/dist/tui-command.d.ts +15 -0
- package/dist/tui-command.d.ts.map +1 -1
- package/dist/tui-command.js +46 -6
- package/dist/tui-command.js.map +1 -1
- package/dist/web-command.d.ts +8 -0
- package/dist/web-command.d.ts.map +1 -1
- package/dist/web-command.js +174 -17
- package/dist/web-command.js.map +1 -1
- package/dist/wizard/answers.d.ts +31 -4
- package/dist/wizard/answers.d.ts.map +1 -1
- package/dist/wizard/answers.js +145 -16
- package/dist/wizard/answers.js.map +1 -1
- package/dist/wizard/from-flags.d.ts +7 -0
- package/dist/wizard/from-flags.d.ts.map +1 -1
- package/dist/wizard/from-flags.js +36 -2
- package/dist/wizard/from-flags.js.map +1 -1
- package/dist/wizard/index.d.ts +4 -4
- package/dist/wizard/index.d.ts.map +1 -1
- package/dist/wizard/index.js +2 -2
- package/dist/wizard/index.js.map +1 -1
- package/dist/wizard/model-discovery.d.ts +38 -3
- package/dist/wizard/model-discovery.d.ts.map +1 -1
- package/dist/wizard/model-discovery.js +927 -102
- package/dist/wizard/model-discovery.js.map +1 -1
- package/dist/wizard/presets.d.ts +1 -1
- package/dist/wizard/presets.d.ts.map +1 -1
- package/dist/wizard/presets.js +7 -12
- package/dist/wizard/presets.js.map +1 -1
- package/dist/wizard/prompts.d.ts +66 -11
- package/dist/wizard/prompts.d.ts.map +1 -1
- package/dist/wizard/prompts.js +190 -20
- package/dist/wizard/prompts.js.map +1 -1
- package/dist/wizard/run.d.ts +60 -3
- package/dist/wizard/run.d.ts.map +1 -1
- package/dist/wizard/run.js +1161 -145
- package/dist/wizard/run.js.map +1 -1
- package/package.json +21 -16
- package/resources/srt/package-lock.json +66 -0
- package/resources/srt/package.json +9 -0
- package/schema/mono-agent.config.schema.json +381 -17
- package/skills/mono-agent-composer/SKILL.md +4 -4
- package/skills/mono-agent-composer/references/config-blueprint.md +5 -4
- package/skills/mono-agent-composer/references/discovery-questions.md +8 -8
- package/skills/mono-agent-composer/references/feature-coverage.md +8 -6
- package/skills/mono-agent-composer/references/package-map.md +1 -1
- package/skills/mono-agent-composer/references/playbooks.md +8 -7
- package/skills/mono-agent-composer/references/validation.md +1 -1
|
@@ -1,34 +1,94 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { chmod, mkdir, mkdtemp, open, rm } from "node:fs/promises";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
7
|
+
import { getOAuthProviders } from "@earendil-works/pi-ai/oauth";
|
|
8
|
+
import { builtinModels } from "@earendil-works/pi-ai/providers/all";
|
|
9
|
+
import { discoverClaudeSdkModels } from "@mono-agent/runtime-adapter";
|
|
10
|
+
import { inspectPiAuthStore as inspectDefaultPiAuthStore, } from "../pi-auth-store-inspection.js";
|
|
11
|
+
import { credentialNeutralProviderStatusEnvironment, hasDurableProviderEnvironmentCredential, runBoundedProviderCommand, } from "../provider-setup.js";
|
|
12
|
+
/** Cloud Pi providers that guided onboarding can authenticate and prove end to end. */
|
|
13
|
+
export const GUIDED_PI_PROVIDER_IDS = [
|
|
14
|
+
"anthropic",
|
|
15
|
+
"github-copilot",
|
|
16
|
+
"openai-codex",
|
|
17
|
+
"opencode-go",
|
|
18
|
+
];
|
|
19
|
+
const GUIDED_PI_PROVIDERS = new Set(GUIDED_PI_PROVIDER_IDS);
|
|
20
|
+
const GUIDED_LOCAL_PI_PROVIDERS = new Set(["ollama", "lmstudio"]);
|
|
21
|
+
/** Reject known unsupported remote Pi integrations while leaving custom local ids available. */
|
|
22
|
+
export function guidedPiProviderProblem(provider) {
|
|
23
|
+
if (GUIDED_PI_PROVIDERS.has(provider) || GUIDED_LOCAL_PI_PROVIDERS.has(provider)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return builtinModels().getProvider(provider) === undefined
|
|
27
|
+
? "Custom Pi providers require a hand-authored providers.local[] entry. Guided init supports discovered Ollama and LM Studio routes."
|
|
28
|
+
: "Guided init supports Pi Anthropic, GitHub Copilot, OpenAI Codex, OpenCode-Go, Ollama, and LM Studio. Configure other Pi providers manually.";
|
|
29
|
+
}
|
|
30
|
+
function providerDiscoveryCommand(opts) {
|
|
31
|
+
if (opts.execFile !== undefined) {
|
|
32
|
+
return (file, args, commandOptions) => opts.execFile(file, args, {
|
|
33
|
+
...commandOptions,
|
|
34
|
+
env: commandOptions.env ?? safeDiscoveryProcessEnv(),
|
|
35
|
+
...(opts.abortSignal === undefined ? {} : { abortSignal: opts.abortSignal }),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return (file, args, commandOptions) => runBoundedProviderCommand(file, args, {
|
|
39
|
+
...commandOptions,
|
|
40
|
+
cwd: process.cwd(),
|
|
41
|
+
env: commandOptions.env ?? safeDiscoveryProcessEnv(),
|
|
42
|
+
...(opts.abortSignal === undefined ? {} : { abortSignal: opts.abortSignal }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
7
45
|
const DEFAULT_DISCOVERY_TIMEOUT_MS = 1200;
|
|
8
|
-
const
|
|
9
|
-
const
|
|
46
|
+
const DEFAULT_OPENCODE_DISCOVERY_TIMEOUT_MS = 5000;
|
|
47
|
+
const PI_OPENAI_CODEX_PROVIDER = "openai-codex";
|
|
48
|
+
const OPENAI_CODEX_MODELS = [
|
|
49
|
+
{ id: "gpt-5.6-terra", name: "GPT-5.6 Terra", minimumCodexCliVersion: [0, 144, 0] },
|
|
50
|
+
{ id: "gpt-5.6-sol", name: "GPT-5.6 Sol", minimumCodexCliVersion: [0, 144, 0] },
|
|
51
|
+
];
|
|
10
52
|
export const STATIC_MODEL_CANDIDATES = [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
53
|
+
...OPENAI_CODEX_MODELS.map(staticDirectCodexCandidate),
|
|
54
|
+
...[
|
|
55
|
+
["claude-sonnet-5", "Claude Sonnet 5", ["low", "medium", "high", "xhigh", "max"]],
|
|
56
|
+
["claude-opus-4-8[1m]", "Claude Opus 4.8 (1M context)", ["low", "medium", "high", "xhigh", "max"]],
|
|
57
|
+
["claude-haiku-4-5-20251001", "Claude Haiku 4.5", []],
|
|
58
|
+
].map(([model, label, efforts]) => ({
|
|
59
|
+
value: `claude:${model}`,
|
|
60
|
+
label: label,
|
|
61
|
+
hint: "SDK-versioned cached catalog; sign-in required",
|
|
62
|
+
source: "claude",
|
|
63
|
+
availability: "catalog_available",
|
|
64
|
+
authState: "auth_required",
|
|
65
|
+
supportedEfforts: efforts,
|
|
66
|
+
setupRequired: true,
|
|
67
|
+
})),
|
|
14
68
|
];
|
|
15
69
|
export async function discoverWizardModelCandidates(opts = {}) {
|
|
16
70
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS;
|
|
17
|
-
const [pi, opencode, ollama, lmstudio] = await Promise.all([
|
|
18
|
-
|
|
19
|
-
|
|
71
|
+
const [codex, pi, claude, opencode, ollama, lmstudio] = await Promise.all([
|
|
72
|
+
discoverDirectCodex({ ...opts, timeoutMs }),
|
|
73
|
+
discoverPiModels({ ...opts, timeoutMs }),
|
|
74
|
+
discoverClaudeModels({ ...opts, timeoutMs }),
|
|
75
|
+
discoverOpenCodeModels({
|
|
76
|
+
...opts,
|
|
77
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_OPENCODE_DISCOVERY_TIMEOUT_MS,
|
|
78
|
+
}),
|
|
20
79
|
discoverOllamaModels({ ...opts, timeoutMs }),
|
|
21
80
|
discoverLmStudioModels({ ...opts, timeoutMs }),
|
|
22
81
|
]);
|
|
23
82
|
return {
|
|
24
83
|
candidates: rankWizardModelCandidates([
|
|
25
|
-
...
|
|
84
|
+
...codex.candidates,
|
|
26
85
|
...pi.candidates,
|
|
86
|
+
...claude.candidates,
|
|
27
87
|
...opencode.candidates,
|
|
28
88
|
...ollama.candidates,
|
|
29
89
|
...lmstudio.candidates,
|
|
30
90
|
]),
|
|
31
|
-
statuses: [pi.status, opencode.status, ollama.status, lmstudio.status],
|
|
91
|
+
statuses: [codex.status, pi.status, claude.status, opencode.status, ollama.status, lmstudio.status],
|
|
32
92
|
};
|
|
33
93
|
}
|
|
34
94
|
export function rankWizardModelCandidates(candidates) {
|
|
@@ -42,71 +102,436 @@ export function rankWizardModelCandidates(candidates) {
|
|
|
42
102
|
export function formatModelDiscoveryStatus(statuses) {
|
|
43
103
|
return statuses.map((status) => `${status.provider}: ${status.detail}`).join("\n");
|
|
44
104
|
}
|
|
45
|
-
|
|
46
|
-
|
|
105
|
+
export function defaultEffortForModelRef(modelRef, reasoning) {
|
|
106
|
+
if (reasoning === true) {
|
|
107
|
+
return "medium";
|
|
108
|
+
}
|
|
109
|
+
if (reasoning === false) {
|
|
110
|
+
return "none";
|
|
111
|
+
}
|
|
112
|
+
if (!modelRef.startsWith("pi:")) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const [, provider, ...modelParts] = modelRef.split(":");
|
|
116
|
+
const model = modelParts.join(":");
|
|
117
|
+
if (provider === "opencode-go" || provider === "ollama" || provider === "lmstudio") {
|
|
118
|
+
return localModelDefaultEffort(model);
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
/** Detect the direct Codex CLI and its account catalog without claiming readiness. */
|
|
123
|
+
async function discoverDirectCodex(opts) {
|
|
124
|
+
const run = providerDiscoveryCommand(opts);
|
|
125
|
+
const verified = new Set(opts.verifiedModelRefs ?? []);
|
|
126
|
+
let version = "";
|
|
127
|
+
try {
|
|
128
|
+
const result = await run("codex", ["--version"], { timeout: opts.timeoutMs });
|
|
129
|
+
version = firstOutputLine(result.stdout);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return {
|
|
133
|
+
candidates: directCodexCandidates(curatedCodexCatalog(), "install-required", "", verified),
|
|
134
|
+
status: {
|
|
135
|
+
provider: "Codex",
|
|
136
|
+
status: "setup_available",
|
|
137
|
+
detail: "CLI not found; install Codex and sign in before the readiness check",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
let catalog = curatedCodexCatalog();
|
|
142
|
+
try {
|
|
143
|
+
// Injected command runners are normally deterministic unit-test seams; do
|
|
144
|
+
// not escape them by starting a real app-server unless a model-list seam
|
|
145
|
+
// was also supplied.
|
|
146
|
+
const discoveredCatalog = opts.codexModelList !== undefined
|
|
147
|
+
? [...await opts.codexModelList()]
|
|
148
|
+
: opts.execFile === undefined
|
|
149
|
+
? await requestCodexModelList(opts.timeoutMs, codexModelDiscoveryEnvironment(opts.persistedEnv), opts.abortSignal)
|
|
150
|
+
: catalog;
|
|
151
|
+
if (discoveredCatalog.length > 0)
|
|
152
|
+
catalog = discoveredCatalog;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// The exact curated Sol/Terra fallback remains selectable offline.
|
|
156
|
+
}
|
|
157
|
+
const durableCredential = hasDurableProviderEnvironmentCredential("codex:gpt-5.6-terra", opts.persistedEnv ?? {});
|
|
158
|
+
const shellOnlyCredential = !durableCredential
|
|
159
|
+
&& hasDurableProviderEnvironmentCredential("codex:gpt-5.6-terra", process.env);
|
|
160
|
+
try {
|
|
161
|
+
if (!durableCredential) {
|
|
162
|
+
await run("codex", ["login", "status"], {
|
|
163
|
+
timeout: opts.timeoutMs,
|
|
164
|
+
env: credentialNeutralProviderStatusEnvironment(process.env, opts.persistedEnv),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const candidates = directCodexCandidates(catalog, "credential-detected", version, verified);
|
|
168
|
+
const setupModels = candidates.filter((candidate) => candidate.setupRequired === true);
|
|
169
|
+
const setupDetails = [...new Set(setupModels.map((candidate) => candidate.hint ?? `${candidate.label} setup required`))];
|
|
170
|
+
return {
|
|
171
|
+
candidates,
|
|
172
|
+
status: {
|
|
173
|
+
provider: "Codex",
|
|
174
|
+
status: setupModels.length === 0 ? "detected" : "setup_available",
|
|
175
|
+
detail: `${version.length > 0 ? `${version}; ` : ""}${durableCredential ? "durable OPENAI_API_KEY detected" : "sign-in detected"} (live readiness not yet verified)${setupModels.length === 0
|
|
176
|
+
? ""
|
|
177
|
+
: `; ${setupDetails.join("; ")}`}`,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return {
|
|
183
|
+
candidates: directCodexCandidates(catalog, "login-required", version, verified),
|
|
184
|
+
status: {
|
|
185
|
+
provider: "Codex",
|
|
186
|
+
status: "setup_available",
|
|
187
|
+
detail: `${version.length > 0 ? `${version}; ` : "CLI installed; "}${shellOnlyCredential
|
|
188
|
+
? "durable sign-in required; shell-only OPENAI_API_KEY ignored"
|
|
189
|
+
: "sign-in required"}`,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function discoverPiModels(opts) {
|
|
195
|
+
const inspectAuthStore = opts.inspectPiAuthStore ?? inspectDefaultPiAuthStore;
|
|
47
196
|
const authPath = opts.piAuthPath ?? join(homedir(), ".pi", "agent", "auth.json");
|
|
197
|
+
let credentialProviders = new Set();
|
|
198
|
+
let status;
|
|
48
199
|
try {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
source: "pi",
|
|
59
|
-
discovered: true,
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
status: { provider: "Pi", status: "detected", detail: "OpenAI-Codex credentials found" },
|
|
200
|
+
const inspection = await inspectAuthStore(authPath);
|
|
201
|
+
if (inspection.status === "missing") {
|
|
202
|
+
status = { provider: "Pi", status: "setup_available", detail: "auth store not found; provider setup is available" };
|
|
203
|
+
}
|
|
204
|
+
else if (inspection.status === "unsafe") {
|
|
205
|
+
status = {
|
|
206
|
+
provider: "Pi",
|
|
207
|
+
status: "unavailable",
|
|
208
|
+
detail: `auth store rejected as unsafe (${inspection.reason}); catalog remains available`,
|
|
63
209
|
};
|
|
64
210
|
}
|
|
65
|
-
|
|
211
|
+
else {
|
|
212
|
+
const providers = readPiAuthProviderMap(inspection.auth);
|
|
213
|
+
credentialProviders = new Set(Object.entries(providers)
|
|
214
|
+
.filter(([provider, credential]) => GUIDED_PI_PROVIDERS.has(provider) && hasUsablePiCredential(credential))
|
|
215
|
+
.map(([provider]) => provider));
|
|
216
|
+
status = credentialProviders.size > 0
|
|
217
|
+
? {
|
|
218
|
+
provider: "Pi",
|
|
219
|
+
status: "detected",
|
|
220
|
+
detail: `${credentialProviders.size} provider credential entr${credentialProviders.size === 1 ? "y" : "ies"} detected (not yet verified)`,
|
|
221
|
+
}
|
|
222
|
+
: { provider: "Pi", status: "setup_available", detail: "credential store is empty; setup is available" };
|
|
223
|
+
}
|
|
66
224
|
}
|
|
67
225
|
catch {
|
|
68
|
-
|
|
226
|
+
status = { provider: "Pi", status: "unavailable", detail: "auth store unreadable; catalog remains available" };
|
|
227
|
+
}
|
|
228
|
+
const preEnvironmentStatus = status;
|
|
229
|
+
for (const provider of GUIDED_PI_PROVIDER_IDS) {
|
|
230
|
+
if (hasDurableProviderEnvironmentCredential(`pi:${provider}:credential-check`, opts.persistedEnv ?? {})) {
|
|
231
|
+
credentialProviders.add(provider);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (credentialProviders.size > 0 && preEnvironmentStatus.status !== "detected") {
|
|
235
|
+
status = {
|
|
236
|
+
provider: "Pi",
|
|
237
|
+
status: "detected",
|
|
238
|
+
detail: `${credentialProviders.size} provider credential source${credentialProviders.size === 1 ? "" : "s"} detected (not yet verified); ${preEnvironmentStatus.detail}`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
else if (credentialProviders.size === 0
|
|
242
|
+
&& hasDurableProviderEnvironmentCredential("pi:opencode-go:credential-check", process.env)) {
|
|
243
|
+
status = {
|
|
244
|
+
provider: "Pi",
|
|
245
|
+
status: "setup_available",
|
|
246
|
+
detail: `${preEnvironmentStatus.detail}; shell-only OPENCODE_API_KEY ignored until persisted in the agent .env or Pi auth store`,
|
|
247
|
+
};
|
|
69
248
|
}
|
|
249
|
+
const verified = new Set(opts.verifiedModelRefs ?? []);
|
|
250
|
+
const oauthProviders = new Set(getOAuthProviders().map((provider) => provider.id));
|
|
251
|
+
const models = builtinModels();
|
|
252
|
+
const providerNames = new Map(models.getProviders().map((provider) => [provider.id, provider.name]));
|
|
253
|
+
const candidates = models.getModels()
|
|
254
|
+
.filter((model) => GUIDED_PI_PROVIDERS.has(model.provider))
|
|
255
|
+
.map((model) => {
|
|
256
|
+
const value = `pi:${model.provider}:${model.id}`;
|
|
257
|
+
const supportedEfforts = normalizeEfforts(getSupportedThinkingLevels(model));
|
|
258
|
+
const authState = verified.has(value)
|
|
259
|
+
? "verified"
|
|
260
|
+
: credentialProviders.has(model.provider)
|
|
261
|
+
? "credential_detected"
|
|
262
|
+
: "auth_required";
|
|
263
|
+
const defaultEffort = exactDefaultEffortWhenUnambiguous(supportedEfforts);
|
|
264
|
+
const shellOnlyCredential = authState === "auth_required"
|
|
265
|
+
&& hasDurableProviderEnvironmentCredential(value, process.env);
|
|
266
|
+
const providerLabel = model.provider === "opencode-go"
|
|
267
|
+
? "OpenCode-Go"
|
|
268
|
+
: providerNames.get(model.provider) ?? model.provider;
|
|
269
|
+
return {
|
|
270
|
+
value,
|
|
271
|
+
label: `Pi ${providerLabel} · ${model.name || model.id}`,
|
|
272
|
+
hint: authState === "verified"
|
|
273
|
+
? "verified by live readiness"
|
|
274
|
+
: authState === "credential_detected"
|
|
275
|
+
? "credential detected; live readiness pending"
|
|
276
|
+
: shellOnlyCredential
|
|
277
|
+
? "shell-only credential ignored; persist it in the agent .env or provider store"
|
|
278
|
+
: oauthProviders.has(model.provider)
|
|
279
|
+
? "OAuth setup available"
|
|
280
|
+
: "API key or provider environment required",
|
|
281
|
+
source: "pi",
|
|
282
|
+
availability: "catalog_available",
|
|
283
|
+
authState,
|
|
284
|
+
supportedEfforts,
|
|
285
|
+
...(defaultEffort === undefined ? {} : { defaultEffort }),
|
|
286
|
+
...(authState === "verified" ? { discovered: true } : {}),
|
|
287
|
+
...(authState === "auth_required" ? { setupRequired: true } : {}),
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
return { candidates, status };
|
|
70
291
|
}
|
|
71
292
|
function readPiAuthProviderMap(auth) {
|
|
72
|
-
const nestedProviders = parseJsonObject(auth.providers);
|
|
73
|
-
|
|
293
|
+
const nestedProviders = auth.providers === undefined ? {} : parseJsonObject(auth.providers);
|
|
294
|
+
const { providers: _providers, ...topLevelProviders } = auth;
|
|
295
|
+
return { ...nestedProviders, ...topLevelProviders };
|
|
296
|
+
}
|
|
297
|
+
function hasUsablePiCredential(value) {
|
|
298
|
+
if (!isRecord(value))
|
|
299
|
+
return false;
|
|
300
|
+
if (value.type === "oauth") {
|
|
301
|
+
return isCredentialString(value.access) || isCredentialString(value.refresh);
|
|
302
|
+
}
|
|
303
|
+
return value.type === "api_key" && isCredentialString(value.key);
|
|
304
|
+
}
|
|
305
|
+
function isCredentialString(value) {
|
|
306
|
+
return typeof value === "string"
|
|
307
|
+
&& value.trim().length > 0
|
|
308
|
+
&& value.length <= 65_536
|
|
309
|
+
&& !value.includes("\0");
|
|
310
|
+
}
|
|
311
|
+
async function discoverClaudeModels(opts) {
|
|
312
|
+
const run = providerDiscoveryCommand(opts);
|
|
313
|
+
const verified = new Set(opts.verifiedModelRefs ?? []);
|
|
314
|
+
let credentialDetected = hasDurableProviderEnvironmentCredential("claude:claude-sonnet-5", opts.persistedEnv ?? {});
|
|
315
|
+
const durableCredential = credentialDetected;
|
|
316
|
+
const shellOnlyCredential = !durableCredential
|
|
317
|
+
&& hasDurableProviderEnvironmentCredential("claude:claude-sonnet-5", process.env);
|
|
318
|
+
if (!credentialDetected) {
|
|
319
|
+
try {
|
|
320
|
+
await run("claude", ["auth", "status", "--json"], {
|
|
321
|
+
timeout: opts.timeoutMs,
|
|
322
|
+
env: credentialNeutralProviderStatusEnvironment(process.env, opts.persistedEnv),
|
|
323
|
+
});
|
|
324
|
+
credentialDetected = true;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
// Catalog discovery is deliberately independent from account state.
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
let catalog = [];
|
|
331
|
+
try {
|
|
332
|
+
catalog = opts.claudeModelList !== undefined
|
|
333
|
+
? await opts.claudeModelList()
|
|
334
|
+
: opts.execFile === undefined
|
|
335
|
+
? await discoverClaudeSdkModels({ timeoutMs: Math.max(opts.timeoutMs, 2_000) })
|
|
336
|
+
: await discoverClaudeSdkModels({ timeoutMs: 1 });
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Static SDK-versioned rows remain available through STATIC_MODEL_CANDIDATES.
|
|
340
|
+
}
|
|
341
|
+
const candidates = catalog.map((model) => {
|
|
342
|
+
const value = model.reference;
|
|
343
|
+
const authState = verified.has(value)
|
|
344
|
+
? "verified"
|
|
345
|
+
: credentialDetected
|
|
346
|
+
? "credential_detected"
|
|
347
|
+
: "auth_required";
|
|
348
|
+
const supportedEfforts = normalizeEfforts(model.supportedEfforts);
|
|
349
|
+
return {
|
|
350
|
+
value,
|
|
351
|
+
label: model.displayName,
|
|
352
|
+
hint: `${model.source === "discovered" ? "discovered from Claude SDK" : "SDK-versioned cached catalog"}; ${authState === "verified" ? "verified" : authState === "credential_detected" ? "credential detected; live readiness pending" : "sign-in required"}`,
|
|
353
|
+
source: "claude",
|
|
354
|
+
availability: "catalog_available",
|
|
355
|
+
authState,
|
|
356
|
+
supportedEfforts,
|
|
357
|
+
...(authState === "verified" ? { discovered: true } : {}),
|
|
358
|
+
...(authState === "auth_required" ? { setupRequired: true } : {}),
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
candidates,
|
|
363
|
+
status: credentialDetected
|
|
364
|
+
? {
|
|
365
|
+
provider: "Claude",
|
|
366
|
+
status: "detected",
|
|
367
|
+
detail: `${durableCredential ? "durable provider credential" : "sign-in"} detected (live readiness not yet verified)`,
|
|
368
|
+
}
|
|
369
|
+
: {
|
|
370
|
+
provider: "Claude",
|
|
371
|
+
status: "setup_available",
|
|
372
|
+
detail: `${shellOnlyCredential
|
|
373
|
+
? "durable sign-in required; shell-only Claude credential ignored"
|
|
374
|
+
: "sign-in required"}; catalog remains available`,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
74
377
|
}
|
|
75
378
|
async function discoverOpenCodeModels(opts) {
|
|
76
|
-
const run = opts
|
|
379
|
+
const run = providerDiscoveryCommand(opts);
|
|
380
|
+
let isolation;
|
|
77
381
|
try {
|
|
78
|
-
|
|
79
|
-
const
|
|
382
|
+
isolation = await createOpenCodeDiscoveryIsolation();
|
|
383
|
+
const { stdout } = await run("opencode", ["models", "opencode-go", "--pure"], {
|
|
384
|
+
timeout: opts.timeoutMs,
|
|
385
|
+
env: isolation.env,
|
|
386
|
+
});
|
|
387
|
+
const bundledModelIds = new Set(builtinModels().getModels()
|
|
388
|
+
.filter((model) => model.provider === "opencode-go")
|
|
389
|
+
.map((model) => model.id));
|
|
390
|
+
const parsedModels = parseOpenCodeGoModels(stdout);
|
|
391
|
+
const models = parsedModels.filter((model) => bundledModelIds.has(model));
|
|
392
|
+
const unsupportedCount = parsedModels.length - models.length;
|
|
80
393
|
return {
|
|
81
|
-
candidates: models.map((model) =>
|
|
82
|
-
value
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
394
|
+
candidates: models.map((model) => {
|
|
395
|
+
const value = `pi:opencode-go:${model}`;
|
|
396
|
+
return {
|
|
397
|
+
value,
|
|
398
|
+
label: `OpenCode-Go ${displayModelName(model)}`,
|
|
399
|
+
hint: "discovered from opencode; OpenCode-Go API key required",
|
|
400
|
+
source: "opencode",
|
|
401
|
+
discovered: true,
|
|
402
|
+
availability: "catalog_available",
|
|
403
|
+
authState: "auth_required",
|
|
404
|
+
setupRequired: true,
|
|
405
|
+
supportedEfforts: [],
|
|
406
|
+
};
|
|
407
|
+
}),
|
|
88
408
|
status: models.length > 0
|
|
89
|
-
? {
|
|
90
|
-
|
|
409
|
+
? {
|
|
410
|
+
provider: "OpenCode-Go",
|
|
411
|
+
status: "detected",
|
|
412
|
+
detail: `${models.length} runtime-supported model${models.length === 1 ? "" : "s"} found${unsupportedCount === 0
|
|
413
|
+
? ""
|
|
414
|
+
: `; ${unsupportedCount} unsupported CLI row${unsupportedCount === 1 ? "" : "s"} ignored`}`,
|
|
415
|
+
}
|
|
416
|
+
: parsedModels.length > 0
|
|
417
|
+
? {
|
|
418
|
+
provider: "OpenCode-Go",
|
|
419
|
+
status: "unavailable",
|
|
420
|
+
detail: `no runtime-supported models; ${parsedModels.length} unsupported CLI row${parsedModels.length === 1 ? "" : "s"} ignored`,
|
|
421
|
+
}
|
|
422
|
+
: { provider: "OpenCode-Go", status: "unavailable", detail: "no models returned" },
|
|
91
423
|
};
|
|
92
424
|
}
|
|
93
425
|
catch {
|
|
94
|
-
return { candidates: [], status: { provider: "OpenCode", status: "unavailable", detail: "`opencode models --
|
|
426
|
+
return { candidates: [], status: { provider: "OpenCode-Go", status: "unavailable", detail: "`opencode models opencode-go --pure` unavailable" } };
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
await isolation?.cleanup();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function createOpenCodeDiscoveryIsolation() {
|
|
433
|
+
const root = await mkdtemp(join(tmpdir(), "mono-agent-opencode-discovery-"));
|
|
434
|
+
try {
|
|
435
|
+
await chmod(root, 0o700);
|
|
436
|
+
const home = await createPrivateDirectory(root, "home");
|
|
437
|
+
const config = await createPrivateDirectory(root, "config");
|
|
438
|
+
const opencodeConfig = await createPrivateDirectory(config, "opencode");
|
|
439
|
+
if (process.platform !== "win32")
|
|
440
|
+
await chmod(opencodeConfig, 0o500);
|
|
441
|
+
const data = await createPrivateDirectory(root, "data");
|
|
442
|
+
const state = await createPrivateDirectory(root, "state");
|
|
443
|
+
const cache = await createPrivateDirectory(root, "cache");
|
|
444
|
+
const opencodeData = await createPrivateDirectory(data, "opencode");
|
|
445
|
+
const database = join(opencodeData, "opencode.db");
|
|
446
|
+
const handle = await open(database, "wx", 0o600);
|
|
447
|
+
await handle.close();
|
|
448
|
+
await chmod(database, 0o600);
|
|
449
|
+
const env = safeDiscoveryProcessEnv();
|
|
450
|
+
Object.assign(env, {
|
|
451
|
+
OPENCODE_TEST_HOME: home,
|
|
452
|
+
XDG_CONFIG_HOME: config,
|
|
453
|
+
XDG_CONFIG_DIRS: config,
|
|
454
|
+
XDG_DATA_HOME: data,
|
|
455
|
+
XDG_DATA_DIRS: data,
|
|
456
|
+
XDG_STATE_HOME: state,
|
|
457
|
+
XDG_CACHE_HOME: cache,
|
|
458
|
+
OPENCODE_DB: database,
|
|
459
|
+
OPENCODE_CONFIG_CONTENT: JSON.stringify({ share: "disabled", autoshare: false }),
|
|
460
|
+
OPENCODE_DISABLE_PROJECT_CONFIG: "true",
|
|
461
|
+
OPENCODE_DISABLE_AUTOUPDATE: "true",
|
|
462
|
+
OPENCODE_DISABLE_SHARE: "true",
|
|
463
|
+
OPENCODE_AUTO_SHARE: "false",
|
|
464
|
+
OPENCODE_DISABLE_EXTERNAL_SKILLS: "true",
|
|
465
|
+
OPENCODE_DISABLE_LSP_DOWNLOAD: "true",
|
|
466
|
+
});
|
|
467
|
+
return {
|
|
468
|
+
env,
|
|
469
|
+
cleanup: async () => {
|
|
470
|
+
await rm(root, { recursive: true, force: true }).catch(() => undefined);
|
|
471
|
+
},
|
|
472
|
+
};
|
|
95
473
|
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
await rm(root, { recursive: true, force: true }).catch(() => undefined);
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function safeDiscoveryProcessEnv(source = process.env) {
|
|
480
|
+
const env = {};
|
|
481
|
+
for (const key of [
|
|
482
|
+
"PATH",
|
|
483
|
+
"LANG",
|
|
484
|
+
"LC_ALL",
|
|
485
|
+
"LC_CTYPE",
|
|
486
|
+
"SHELL",
|
|
487
|
+
"TMPDIR",
|
|
488
|
+
"TMP",
|
|
489
|
+
"TEMP",
|
|
490
|
+
"SystemRoot",
|
|
491
|
+
"WINDIR",
|
|
492
|
+
"ComSpec",
|
|
493
|
+
"PATHEXT",
|
|
494
|
+
]) {
|
|
495
|
+
const value = source[key];
|
|
496
|
+
if (typeof value === "string" && value.length > 0)
|
|
497
|
+
env[key] = value;
|
|
498
|
+
}
|
|
499
|
+
return env;
|
|
500
|
+
}
|
|
501
|
+
/** Exact non-secret environment used by live Codex model/list discovery. */
|
|
502
|
+
export function codexModelDiscoveryEnvironment(persistedEnv = {}, shellEnv = process.env) {
|
|
503
|
+
const neutral = credentialNeutralProviderStatusEnvironment(shellEnv, persistedEnv);
|
|
504
|
+
return {
|
|
505
|
+
...safeDiscoveryProcessEnv(shellEnv),
|
|
506
|
+
HOME: neutral.HOME ?? homedir(),
|
|
507
|
+
...(neutral.CODEX_HOME === undefined ? {} : { CODEX_HOME: neutral.CODEX_HOME }),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
async function createPrivateDirectory(parent, name) {
|
|
511
|
+
const path = join(parent, name);
|
|
512
|
+
await mkdir(path, { mode: 0o700 });
|
|
513
|
+
await chmod(path, 0o700);
|
|
514
|
+
return path;
|
|
96
515
|
}
|
|
97
516
|
async function discoverOllamaModels(opts) {
|
|
98
|
-
const run = opts
|
|
517
|
+
const run = providerDiscoveryCommand(opts);
|
|
99
518
|
try {
|
|
100
519
|
const { stdout } = await run("ollama", ["list"], { timeout: opts.timeoutMs });
|
|
101
520
|
const models = parseOllamaList(stdout);
|
|
102
521
|
return {
|
|
103
|
-
candidates: models.map((model) =>
|
|
104
|
-
value
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
522
|
+
candidates: models.map((model) => {
|
|
523
|
+
const value = `pi:ollama:${model}`;
|
|
524
|
+
return {
|
|
525
|
+
value,
|
|
526
|
+
label: `Ollama ${model}`,
|
|
527
|
+
hint: "discovered locally",
|
|
528
|
+
source: "ollama",
|
|
529
|
+
discovered: true,
|
|
530
|
+
availability: "catalog_available",
|
|
531
|
+
authState: "not_required",
|
|
532
|
+
supportedEfforts: [],
|
|
533
|
+
};
|
|
534
|
+
}),
|
|
110
535
|
status: models.length > 0
|
|
111
536
|
? { provider: "Ollama", status: "detected", detail: `${models.length} model(s) found` }
|
|
112
537
|
: { provider: "Ollama", status: "unavailable", detail: "no local models returned" },
|
|
@@ -120,21 +545,30 @@ async function discoverLmStudioModels(opts) {
|
|
|
120
545
|
const fetchImpl = opts.fetch ?? fetch;
|
|
121
546
|
const controller = new AbortController();
|
|
122
547
|
const timer = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
548
|
+
const onAbort = () => controller.abort();
|
|
549
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
123
550
|
try {
|
|
124
551
|
const response = await fetchImpl("http://localhost:1234/v1/models", { signal: controller.signal });
|
|
125
552
|
if (!response.ok) {
|
|
126
553
|
return { candidates: [], status: { provider: "LM Studio", status: "unavailable", detail: "server did not return models" } };
|
|
127
554
|
}
|
|
128
555
|
const body = await response.json();
|
|
129
|
-
const models =
|
|
556
|
+
const models = parseOpenAiModelEntriesBody(body);
|
|
130
557
|
return {
|
|
131
|
-
candidates: models.map((model) =>
|
|
132
|
-
value
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
558
|
+
candidates: models.map((model) => {
|
|
559
|
+
const value = `pi:lmstudio:${model.id}`;
|
|
560
|
+
return {
|
|
561
|
+
value,
|
|
562
|
+
label: `LM Studio ${model.id}`,
|
|
563
|
+
hint: "discovered locally",
|
|
564
|
+
source: "lmstudio",
|
|
565
|
+
discovered: true,
|
|
566
|
+
availability: "catalog_available",
|
|
567
|
+
authState: "not_required",
|
|
568
|
+
supportedEfforts: model.reasoning === false ? ["none"] : [],
|
|
569
|
+
...(model.reasoning === false ? { defaultEffort: "none" } : {}),
|
|
570
|
+
};
|
|
571
|
+
}),
|
|
138
572
|
status: models.length > 0
|
|
139
573
|
? { provider: "LM Studio", status: "detected", detail: `${models.length} model(s) found` }
|
|
140
574
|
: { provider: "LM Studio", status: "unavailable", detail: "no models returned" },
|
|
@@ -145,25 +579,68 @@ async function discoverLmStudioModels(opts) {
|
|
|
145
579
|
}
|
|
146
580
|
finally {
|
|
147
581
|
clearTimeout(timer);
|
|
582
|
+
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
148
583
|
}
|
|
149
584
|
}
|
|
150
585
|
function mergeCandidate(left, right) {
|
|
151
|
-
const
|
|
586
|
+
const { setupRequired: leftSetupRequired, defaultEffort: leftDefaultEffort, supportedEfforts: leftSupportedEfforts, ...leftRest } = left;
|
|
587
|
+
const { setupRequired: rightSetupRequired, defaultEffort: rightDefaultEffort, supportedEfforts: rightSupportedEfforts, ...rightRest } = right;
|
|
588
|
+
const rightHasDiscoveryState = right.discovered === true
|
|
589
|
+
|| right.setupRequired === true
|
|
590
|
+
|| right.availability !== undefined
|
|
591
|
+
|| right.authState !== undefined;
|
|
592
|
+
const preserveBuiltinPiMetadata = left.source === "pi"
|
|
593
|
+
&& (right.source === "opencode" || right.source === "ollama" || right.source === "lmstudio");
|
|
594
|
+
const hint = preserveBuiltinPiMetadata
|
|
595
|
+
? left.hint
|
|
596
|
+
: rightHasDiscoveryState ? right.hint ?? left.hint : left.hint ?? right.hint;
|
|
597
|
+
const discovered = left.discovered === true || right.discovered === true;
|
|
598
|
+
const rightReplacesCatalogMetadata = right.availability === "catalog_available"
|
|
599
|
+
&& left.source === right.source;
|
|
600
|
+
const defaultEffort = preserveBuiltinPiMetadata
|
|
601
|
+
? leftDefaultEffort
|
|
602
|
+
: rightReplacesCatalogMetadata
|
|
603
|
+
? rightDefaultEffort
|
|
604
|
+
: rightDefaultEffort ?? leftDefaultEffort;
|
|
605
|
+
const authState = preserveBuiltinPiMetadata
|
|
606
|
+
? left.authState
|
|
607
|
+
: strongerAuthState(left.authState, right.authState);
|
|
608
|
+
const setupRequired = preserveBuiltinPiMetadata
|
|
609
|
+
? leftSetupRequired === true || authState === "auth_required"
|
|
610
|
+
: rightSetupRequired === true || authState === "auth_required"
|
|
611
|
+
|| (right.authState === undefined && !discovered && leftSetupRequired === true);
|
|
152
612
|
return {
|
|
153
|
-
...
|
|
154
|
-
...
|
|
613
|
+
...leftRest,
|
|
614
|
+
...rightRest,
|
|
615
|
+
...(preserveBuiltinPiMetadata ? { source: left.source } : {}),
|
|
616
|
+
...(authState === undefined ? {} : { authState }),
|
|
617
|
+
supportedEfforts: preserveBuiltinPiMetadata
|
|
618
|
+
? [...(leftSupportedEfforts ?? [])]
|
|
619
|
+
: [...(rightSupportedEfforts ?? leftSupportedEfforts ?? [])],
|
|
155
620
|
...(hint === undefined ? {} : { hint }),
|
|
156
|
-
|
|
621
|
+
...(defaultEffort === undefined ? {} : { defaultEffort }),
|
|
622
|
+
discovered,
|
|
623
|
+
...(setupRequired ? { setupRequired: true } : {}),
|
|
157
624
|
};
|
|
158
625
|
}
|
|
159
626
|
function rank(candidate) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
627
|
+
const authRank = candidate.authState === "verified"
|
|
628
|
+
? 0
|
|
629
|
+
: candidate.authState === "credential_detected" || candidate.authState === "not_required"
|
|
630
|
+
? 1
|
|
631
|
+
: 2;
|
|
632
|
+
return authRank * 1_000 + modelRank(candidate);
|
|
633
|
+
}
|
|
634
|
+
function modelRank(candidate) {
|
|
635
|
+
if (candidate.providerDefault === true)
|
|
636
|
+
return -100;
|
|
637
|
+
const directCodexRank = OPENAI_CODEX_MODELS.findIndex((model) => candidate.value === directCodexRef(model));
|
|
638
|
+
if (directCodexRank >= 0)
|
|
639
|
+
return directCodexRank;
|
|
640
|
+
const piOpenAiCodexRank = OPENAI_CODEX_MODELS.findIndex((model) => candidate.value === piOpenAiCodexRef(model));
|
|
641
|
+
if (piOpenAiCodexRank >= 0)
|
|
642
|
+
return 10 + piOpenAiCodexRank;
|
|
643
|
+
if (candidate.source === "claude") {
|
|
167
644
|
return 20;
|
|
168
645
|
}
|
|
169
646
|
if (candidate.source === "opencode") {
|
|
@@ -177,6 +654,324 @@ function rank(candidate) {
|
|
|
177
654
|
}
|
|
178
655
|
return 90;
|
|
179
656
|
}
|
|
657
|
+
function directCodexRef(model) {
|
|
658
|
+
return `codex:${model.id}`;
|
|
659
|
+
}
|
|
660
|
+
function piOpenAiCodexRef(model) {
|
|
661
|
+
return `pi:${PI_OPENAI_CODEX_PROVIDER}:${model.id}`;
|
|
662
|
+
}
|
|
663
|
+
function staticDirectCodexCandidate(model) {
|
|
664
|
+
return {
|
|
665
|
+
value: directCodexRef(model),
|
|
666
|
+
label: `Codex ${model.name}`,
|
|
667
|
+
...(model.minimumCodexCliVersion === undefined
|
|
668
|
+
? {}
|
|
669
|
+
: { hint: `requires Codex CLI ${formatVersion(model.minimumCodexCliVersion)}+` }),
|
|
670
|
+
source: "codex",
|
|
671
|
+
availability: "catalog_available",
|
|
672
|
+
authState: "auth_required",
|
|
673
|
+
// Exact effort support/defaults come from Codex app-server model/list.
|
|
674
|
+
// Do not fabricate provider metadata when the live catalog is unavailable.
|
|
675
|
+
supportedEfforts: [],
|
|
676
|
+
providerDefault: model.id === "gpt-5.6-terra",
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function directCodexCandidates(catalog, state, version = "", verified = new Set()) {
|
|
680
|
+
return catalog.map((model) => directCodexCandidate(model, state, version, verified));
|
|
681
|
+
}
|
|
682
|
+
function directCodexCandidate(model, state, version = "", verified = new Set()) {
|
|
683
|
+
const reference = `codex:${model.id}`;
|
|
684
|
+
const curated = OPENAI_CODEX_MODELS.find((entry) => entry.id === model.id);
|
|
685
|
+
const authState = verified.has(reference)
|
|
686
|
+
? "verified"
|
|
687
|
+
: state === "credential-detected"
|
|
688
|
+
? "credential_detected"
|
|
689
|
+
: "auth_required";
|
|
690
|
+
if (state === "install-required") {
|
|
691
|
+
const minimum = curated?.minimumCodexCliVersion;
|
|
692
|
+
return {
|
|
693
|
+
value: reference,
|
|
694
|
+
label: `Codex ${model.displayName}`,
|
|
695
|
+
hint: minimum === undefined
|
|
696
|
+
? "install Codex CLI and sign in"
|
|
697
|
+
: `install Codex CLI ${formatVersion(minimum)}+ and sign in`,
|
|
698
|
+
source: "codex",
|
|
699
|
+
setupRequired: true,
|
|
700
|
+
availability: "catalog_available",
|
|
701
|
+
authState,
|
|
702
|
+
supportedEfforts: model.supportedEfforts,
|
|
703
|
+
...(model.isDefault === undefined ? {} : { providerDefault: model.isDefault }),
|
|
704
|
+
...(model.defaultEffort === undefined ? {} : { defaultEffort: model.defaultEffort }),
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
const prerequisites = [];
|
|
708
|
+
const minimum = curated?.minimumCodexCliVersion;
|
|
709
|
+
if (minimum !== undefined && !codexVersionMeetsMinimum(version, minimum)) {
|
|
710
|
+
prerequisites.push(parseCodexVersion(version) === undefined
|
|
711
|
+
? `Codex CLI ${formatVersion(minimum)}+ required; installed version could not be verified`
|
|
712
|
+
: `update Codex CLI to ${formatVersion(minimum)}+ (found ${version})`);
|
|
713
|
+
}
|
|
714
|
+
if (state === "login-required")
|
|
715
|
+
prerequisites.push("Codex sign-in required");
|
|
716
|
+
if (prerequisites.length === 0) {
|
|
717
|
+
return {
|
|
718
|
+
value: reference,
|
|
719
|
+
label: `Codex ${model.displayName}`,
|
|
720
|
+
hint: authState === "verified"
|
|
721
|
+
? `${version.length > 0 ? `${version}; ` : ""}verified by live readiness`
|
|
722
|
+
: `${version.length > 0 ? `${version}; ` : ""}sign-in detected; live readiness pending`,
|
|
723
|
+
source: "codex",
|
|
724
|
+
discovered: true,
|
|
725
|
+
availability: "catalog_available",
|
|
726
|
+
authState,
|
|
727
|
+
supportedEfforts: model.supportedEfforts,
|
|
728
|
+
...(model.isDefault === undefined ? {} : { providerDefault: model.isDefault }),
|
|
729
|
+
...(model.defaultEffort === undefined ? {} : { defaultEffort: model.defaultEffort }),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
value: reference,
|
|
734
|
+
label: `Codex ${model.displayName}`,
|
|
735
|
+
hint: prerequisites.join("; "),
|
|
736
|
+
source: "codex",
|
|
737
|
+
setupRequired: true,
|
|
738
|
+
availability: "catalog_available",
|
|
739
|
+
authState,
|
|
740
|
+
supportedEfforts: model.supportedEfforts,
|
|
741
|
+
...(model.isDefault === undefined ? {} : { providerDefault: model.isDefault }),
|
|
742
|
+
...(model.defaultEffort === undefined ? {} : { defaultEffort: model.defaultEffort }),
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function curatedCodexCatalog() {
|
|
746
|
+
return OPENAI_CODEX_MODELS.map((model) => ({
|
|
747
|
+
id: model.id,
|
|
748
|
+
displayName: model.name,
|
|
749
|
+
supportedEfforts: [],
|
|
750
|
+
// Offline product fallback only; live model/list replaces this flag.
|
|
751
|
+
isDefault: model.id === "gpt-5.6-terra",
|
|
752
|
+
}));
|
|
753
|
+
}
|
|
754
|
+
function strongerAuthState(left, right) {
|
|
755
|
+
const order = {
|
|
756
|
+
auth_required: 0,
|
|
757
|
+
not_required: 1,
|
|
758
|
+
credential_detected: 2,
|
|
759
|
+
verified: 3,
|
|
760
|
+
};
|
|
761
|
+
if (left === undefined)
|
|
762
|
+
return right ?? "auth_required";
|
|
763
|
+
if (right === undefined)
|
|
764
|
+
return left;
|
|
765
|
+
return order[right] > order[left] ? right : left;
|
|
766
|
+
}
|
|
767
|
+
async function requestCodexModelList(timeoutMs, environment, abortSignal) {
|
|
768
|
+
const child = spawn("codex", ["app-server", "--listen", "stdio://", "-c", "project_doc_max_bytes=0"], {
|
|
769
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
770
|
+
env: { ...environment },
|
|
771
|
+
});
|
|
772
|
+
const lines = createInterface({ input: child.stdout });
|
|
773
|
+
return await new Promise((resolveResult, rejectResult) => {
|
|
774
|
+
let settled = false;
|
|
775
|
+
let timer;
|
|
776
|
+
const ignoreStdinError = () => undefined;
|
|
777
|
+
child.stdin.on("error", ignoreStdinError);
|
|
778
|
+
const childClosedWithin = async (milliseconds) => {
|
|
779
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
780
|
+
return true;
|
|
781
|
+
return await new Promise((resolveClosed) => {
|
|
782
|
+
const onClose = () => {
|
|
783
|
+
clearTimeout(closeTimer);
|
|
784
|
+
resolveClosed(true);
|
|
785
|
+
};
|
|
786
|
+
const closeTimer = setTimeout(() => {
|
|
787
|
+
child.off("close", onClose);
|
|
788
|
+
resolveClosed(false);
|
|
789
|
+
}, milliseconds);
|
|
790
|
+
closeTimer.unref?.();
|
|
791
|
+
child.once("close", onClose);
|
|
792
|
+
});
|
|
793
|
+
};
|
|
794
|
+
const stopChild = async () => {
|
|
795
|
+
lines.removeAllListeners("line");
|
|
796
|
+
lines.close();
|
|
797
|
+
try {
|
|
798
|
+
child.stdin.end();
|
|
799
|
+
}
|
|
800
|
+
catch { /* best effort */ }
|
|
801
|
+
if (await childClosedWithin(25))
|
|
802
|
+
return;
|
|
803
|
+
try {
|
|
804
|
+
child.kill("SIGTERM");
|
|
805
|
+
}
|
|
806
|
+
catch { /* best effort */ }
|
|
807
|
+
if (await childClosedWithin(250))
|
|
808
|
+
return;
|
|
809
|
+
try {
|
|
810
|
+
child.kill("SIGKILL");
|
|
811
|
+
}
|
|
812
|
+
catch { /* best effort */ }
|
|
813
|
+
if (!await childClosedWithin(250)) {
|
|
814
|
+
child.stdin.destroy();
|
|
815
|
+
child.stdout.destroy();
|
|
816
|
+
child.unref();
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
let settle;
|
|
820
|
+
const onChildError = () => void settle(new Error("Codex app-server is unavailable."));
|
|
821
|
+
const onChildClose = () => void settle(new Error("Codex app-server closed before returning models."));
|
|
822
|
+
const onAbort = () => {
|
|
823
|
+
const error = new Error("Codex model catalog discovery was interrupted.");
|
|
824
|
+
error.name = "AbortError";
|
|
825
|
+
void settle(error);
|
|
826
|
+
};
|
|
827
|
+
settle = async (error, models = []) => {
|
|
828
|
+
if (settled)
|
|
829
|
+
return;
|
|
830
|
+
settled = true;
|
|
831
|
+
clearTimeout(timer);
|
|
832
|
+
child.off("error", onChildError);
|
|
833
|
+
child.off("close", onChildClose);
|
|
834
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
835
|
+
await stopChild();
|
|
836
|
+
child.stdin.off("error", ignoreStdinError);
|
|
837
|
+
if (error === undefined)
|
|
838
|
+
resolveResult(models);
|
|
839
|
+
else
|
|
840
|
+
rejectResult(error);
|
|
841
|
+
};
|
|
842
|
+
const writeMessage = (message) => {
|
|
843
|
+
if (settled || child.stdin.destroyed || child.stdin.writableEnded)
|
|
844
|
+
return;
|
|
845
|
+
try {
|
|
846
|
+
child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
847
|
+
}
|
|
848
|
+
catch { /* bounded shutdown handles the process */ }
|
|
849
|
+
};
|
|
850
|
+
timer = setTimeout(() => void settle(new Error("Codex model catalog discovery timed out.")), Math.max(250, Math.min(10_000, timeoutMs)));
|
|
851
|
+
timer.unref?.();
|
|
852
|
+
child.once("error", onChildError);
|
|
853
|
+
child.once("close", onChildClose);
|
|
854
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
855
|
+
lines.on("line", (line) => {
|
|
856
|
+
let message;
|
|
857
|
+
try {
|
|
858
|
+
message = JSON.parse(line);
|
|
859
|
+
}
|
|
860
|
+
catch {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (!isRecord(message))
|
|
864
|
+
return;
|
|
865
|
+
if (typeof message.method === "string" && (typeof message.id === "string" || typeof message.id === "number")) {
|
|
866
|
+
writeMessage({
|
|
867
|
+
id: message.id,
|
|
868
|
+
error: { code: -32601, message: "Model catalog discovery does not support server requests." },
|
|
869
|
+
});
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (message.id === 1 && isRecord(message.result)) {
|
|
873
|
+
writeMessage({
|
|
874
|
+
id: 2,
|
|
875
|
+
method: "model/list",
|
|
876
|
+
params: { includeHidden: false, limit: 1_000 },
|
|
877
|
+
});
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (message.id !== 2)
|
|
881
|
+
return;
|
|
882
|
+
if (!isRecord(message.result) || !Array.isArray(message.result.data)) {
|
|
883
|
+
void settle(new Error("Codex app-server returned an invalid model catalog."));
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
void settle(undefined, normalizeCodexCatalog(message.result.data));
|
|
887
|
+
});
|
|
888
|
+
writeMessage({
|
|
889
|
+
id: 1,
|
|
890
|
+
method: "initialize",
|
|
891
|
+
params: {
|
|
892
|
+
clientInfo: { name: "mono-agent", title: "mono-agent", version: "0" },
|
|
893
|
+
capabilities: { experimentalApi: true },
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
if (abortSignal?.aborted === true)
|
|
897
|
+
onAbort();
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
function normalizeCodexCatalog(rows) {
|
|
901
|
+
const result = [];
|
|
902
|
+
const seen = new Set();
|
|
903
|
+
for (const raw of rows.slice(0, 1_000)) {
|
|
904
|
+
if (!isRecord(raw))
|
|
905
|
+
continue;
|
|
906
|
+
const id = typeof raw.id === "string" ? raw.id.trim() : "";
|
|
907
|
+
if (id.length === 0 || id.length > 160 || seen.has(id))
|
|
908
|
+
continue;
|
|
909
|
+
const supportedRows = Array.isArray(raw.supportedReasoningEfforts)
|
|
910
|
+
? raw.supportedReasoningEfforts.map((entry) => isRecord(entry) ? entry.reasoningEffort : entry)
|
|
911
|
+
: [];
|
|
912
|
+
const supportedEfforts = normalizeEfforts(supportedRows);
|
|
913
|
+
const normalizedDefault = normalizeEffort(raw.defaultReasoningEffort);
|
|
914
|
+
seen.add(id);
|
|
915
|
+
result.push({
|
|
916
|
+
id,
|
|
917
|
+
displayName: boundedCatalogText(raw.displayName, 160) || id,
|
|
918
|
+
supportedEfforts,
|
|
919
|
+
...(normalizedDefault === undefined ? {} : { defaultEffort: normalizedDefault }),
|
|
920
|
+
...(typeof raw.isDefault === "boolean" ? { isDefault: raw.isDefault } : {}),
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
return result;
|
|
924
|
+
}
|
|
925
|
+
const SUPPORTED_EFFORTS = new Set([
|
|
926
|
+
"none",
|
|
927
|
+
"minimal",
|
|
928
|
+
"low",
|
|
929
|
+
"medium",
|
|
930
|
+
"high",
|
|
931
|
+
"xhigh",
|
|
932
|
+
"max",
|
|
933
|
+
"ultra",
|
|
934
|
+
]);
|
|
935
|
+
function normalizeEffort(value) {
|
|
936
|
+
const normalized = value === "off" ? "none" : typeof value === "string" ? value : "";
|
|
937
|
+
return SUPPORTED_EFFORTS.has(normalized) ? normalized : undefined;
|
|
938
|
+
}
|
|
939
|
+
function normalizeEfforts(values) {
|
|
940
|
+
return [...new Set(values.map(normalizeEffort).filter((value) => value !== undefined))];
|
|
941
|
+
}
|
|
942
|
+
function exactDefaultEffortWhenUnambiguous(supportedEfforts) {
|
|
943
|
+
return supportedEfforts.length === 1 ? supportedEfforts[0] : undefined;
|
|
944
|
+
}
|
|
945
|
+
function boundedCatalogText(value, limit) {
|
|
946
|
+
const normalized = typeof value === "string"
|
|
947
|
+
? value.replace(/[\u0000-\u001f\u007f]/gu, " ").replace(/\s+/gu, " ").trim()
|
|
948
|
+
: "";
|
|
949
|
+
return normalized.length <= limit ? normalized : `${normalized.slice(0, Math.max(0, limit - 1))}…`;
|
|
950
|
+
}
|
|
951
|
+
function parseCodexVersion(version) {
|
|
952
|
+
const match = /(?:^|\s)v?(\d+)\.(\d+)\.(\d+)(?:[-+\s]|$)/u.exec(version);
|
|
953
|
+
if (match === null)
|
|
954
|
+
return undefined;
|
|
955
|
+
const parts = match.slice(1, 4).map(Number);
|
|
956
|
+
if (parts.some((part) => !Number.isSafeInteger(part) || part < 0))
|
|
957
|
+
return undefined;
|
|
958
|
+
return [parts[0], parts[1], parts[2]];
|
|
959
|
+
}
|
|
960
|
+
function codexVersionMeetsMinimum(version, minimum) {
|
|
961
|
+
const parsed = parseCodexVersion(version);
|
|
962
|
+
if (parsed === undefined)
|
|
963
|
+
return false;
|
|
964
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
965
|
+
const installedPart = parsed[index] ?? 0;
|
|
966
|
+
const minimumPart = minimum[index] ?? 0;
|
|
967
|
+
if (installedPart !== minimumPart)
|
|
968
|
+
return installedPart > minimumPart;
|
|
969
|
+
}
|
|
970
|
+
return true;
|
|
971
|
+
}
|
|
972
|
+
function formatVersion(version) {
|
|
973
|
+
return version.join(".");
|
|
974
|
+
}
|
|
180
975
|
function parseOllamaList(stdout) {
|
|
181
976
|
const lines = stdout.trim().split(/\r?\n/u).filter(Boolean);
|
|
182
977
|
if (lines.length === 0) {
|
|
@@ -187,53 +982,83 @@ function parseOllamaList(stdout) {
|
|
|
187
982
|
.map((line) => line.trim().split(/\s+/u)[0])
|
|
188
983
|
.filter((name) => name !== undefined && name.length > 0);
|
|
189
984
|
}
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
for (const key of ["models", "data"]) {
|
|
198
|
-
const value = parsed[key];
|
|
199
|
-
if (Array.isArray(value)) {
|
|
200
|
-
return value.map(modelNameFromUnknown).filter(isNonEmptyString);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
// Fall through to tolerant line parsing.
|
|
207
|
-
}
|
|
208
|
-
return stdout.split(/\r?\n/u).map((line) => line.trim().split(/\s+/u)[0]).filter(isNonEmptyString);
|
|
985
|
+
function parseOpenCodeGoModels(stdout) {
|
|
986
|
+
const prefix = "opencode-go/";
|
|
987
|
+
return stdout
|
|
988
|
+
.split(/\r?\n/u)
|
|
989
|
+
.map((line) => line.trim())
|
|
990
|
+
.filter((line) => line.startsWith(prefix) && line.length > prefix.length)
|
|
991
|
+
.map((line) => line.slice(prefix.length));
|
|
209
992
|
}
|
|
210
|
-
function
|
|
993
|
+
function parseOpenAiModelEntriesBody(body) {
|
|
211
994
|
if (!isRecord(body) || !Array.isArray(body.data)) {
|
|
212
995
|
return [];
|
|
213
996
|
}
|
|
214
|
-
return body.data.map(
|
|
997
|
+
return body.data.map(modelEntryFromUnknown).filter(isModelEntry);
|
|
215
998
|
}
|
|
216
|
-
function
|
|
999
|
+
function modelEntryFromUnknown(value) {
|
|
217
1000
|
if (typeof value === "string") {
|
|
218
|
-
return value;
|
|
1001
|
+
return { id: value };
|
|
219
1002
|
}
|
|
220
1003
|
if (!isRecord(value)) {
|
|
221
1004
|
return undefined;
|
|
222
1005
|
}
|
|
223
1006
|
const raw = value.id ?? value.name ?? value.model;
|
|
224
|
-
|
|
1007
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
const reasoning = readReasoningCapability(value);
|
|
1011
|
+
return {
|
|
1012
|
+
id: raw,
|
|
1013
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
1014
|
+
};
|
|
225
1015
|
}
|
|
226
1016
|
function displayModelName(model) {
|
|
227
1017
|
return model.startsWith("pi:") ? model.split(":").slice(2).join(":") : model;
|
|
228
1018
|
}
|
|
229
1019
|
function parseJsonObject(value) {
|
|
230
1020
|
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
231
|
-
|
|
1021
|
+
if (!isRecord(parsed)) {
|
|
1022
|
+
throw new Error("Expected a JSON object.");
|
|
1023
|
+
}
|
|
1024
|
+
return parsed;
|
|
1025
|
+
}
|
|
1026
|
+
function firstOutputLine(value) {
|
|
1027
|
+
return value.split(/\r?\n/u).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 120) ?? "";
|
|
1028
|
+
}
|
|
1029
|
+
function readReasoningCapability(value) {
|
|
1030
|
+
for (const field of ["reasoning", "supportsReasoning", "supports_reasoning", "thinking", "supportsThinking", "supports_thinking"]) {
|
|
1031
|
+
const result = booleanCapability(value[field]);
|
|
1032
|
+
if (result !== undefined) {
|
|
1033
|
+
return result;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
for (const field of ["capabilities", "features"]) {
|
|
1037
|
+
const nested = value[field];
|
|
1038
|
+
const result = Array.isArray(nested) ? arrayCapability(nested) : isRecord(nested) ? readReasoningCapability(nested) : undefined;
|
|
1039
|
+
if (result !== undefined) {
|
|
1040
|
+
return result;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return undefined;
|
|
1044
|
+
}
|
|
1045
|
+
function booleanCapability(value) {
|
|
1046
|
+
return typeof value === "boolean" ? value : undefined;
|
|
1047
|
+
}
|
|
1048
|
+
function arrayCapability(value) {
|
|
1049
|
+
return value.some((entry) => typeof entry === "string" && /^(reasoning|thinking)$/iu.test(entry)) ? true : undefined;
|
|
1050
|
+
}
|
|
1051
|
+
function localModelDefaultEffort(model) {
|
|
1052
|
+
const normalized = model.toLowerCase();
|
|
1053
|
+
return ["gpt-oss", "qwen3", "qwq", "deepseek-r1", "reasoning", "thinking"].some((token) => normalized.includes(token))
|
|
1054
|
+
|| /(?:^|[-_:/.\s])o[1345](?:$|[-_:/.\s])/u.test(normalized)
|
|
1055
|
+
? "medium"
|
|
1056
|
+
: "none";
|
|
232
1057
|
}
|
|
233
1058
|
function isRecord(value) {
|
|
234
1059
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
235
1060
|
}
|
|
236
|
-
function
|
|
237
|
-
return value !== undefined
|
|
1061
|
+
function isModelEntry(value) {
|
|
1062
|
+
return value !== undefined;
|
|
238
1063
|
}
|
|
239
1064
|
//# sourceMappingURL=model-discovery.js.map
|