@jini-ai/agent-runtime 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-code-models.d.ts +63 -0
- package/dist/claude-code-models.js +252 -0
- package/dist/defs/aider.d.ts +1 -0
- package/dist/defs/aider.js +10 -1
- package/dist/defs/claude.d.ts +1 -1
- package/dist/defs/claude.js +25 -10
- package/dist/defs/opencode.d.ts +1 -0
- package/dist/defs/opencode.js +7 -2
- package/dist/defs/pi.d.ts +1 -0
- package/dist/defs/pi.js +12 -4
- package/dist/detection.d.ts +20 -0
- package/dist/detection.js +37 -3
- package/dist/providers/connection-test.js +1 -1
- package/dist/providers/model-catalog.js +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +5 -4
- package/dist/acp-model-probe.d.ts.map +0 -1
- package/dist/acp-model-probe.js.map +0 -1
- package/dist/agent-protocol/acp/account-failure.d.ts.map +0 -1
- package/dist/agent-protocol/acp/account-failure.js.map +0 -1
- package/dist/agent-protocol/acp/constants.d.ts.map +0 -1
- package/dist/agent-protocol/acp/constants.js.map +0 -1
- package/dist/agent-protocol/acp/index.d.ts.map +0 -1
- package/dist/agent-protocol/acp/index.js.map +0 -1
- package/dist/agent-protocol/acp/json.d.ts.map +0 -1
- package/dist/agent-protocol/acp/json.js.map +0 -1
- package/dist/agent-protocol/acp/models.d.ts.map +0 -1
- package/dist/agent-protocol/acp/models.js.map +0 -1
- package/dist/agent-protocol/acp/rpc.d.ts.map +0 -1
- package/dist/agent-protocol/acp/rpc.js.map +0 -1
- package/dist/agent-protocol/acp/session-params.d.ts.map +0 -1
- package/dist/agent-protocol/acp/session-params.js.map +0 -1
- package/dist/agent-protocol/acp/session.d.ts.map +0 -1
- package/dist/agent-protocol/acp/session.js.map +0 -1
- package/dist/agent-protocol/acp/text-suppression.d.ts.map +0 -1
- package/dist/agent-protocol/acp/text-suppression.js.map +0 -1
- package/dist/agent-protocol/acp/types.d.ts.map +0 -1
- package/dist/agent-protocol/acp/types.js.map +0 -1
- package/dist/agent-protocol/acp/updates.d.ts.map +0 -1
- package/dist/agent-protocol/acp/updates.js.map +0 -1
- package/dist/agent-protocol/core/index.d.ts.map +0 -1
- package/dist/agent-protocol/core/index.js.map +0 -1
- package/dist/agent-protocol/core/json-line-stream.d.ts.map +0 -1
- package/dist/agent-protocol/core/json-line-stream.js.map +0 -1
- package/dist/agent-protocol/index.d.ts.map +0 -1
- package/dist/agent-protocol/index.js.map +0 -1
- package/dist/agent-protocol/pi-rpc/events.d.ts.map +0 -1
- package/dist/agent-protocol/pi-rpc/events.js.map +0 -1
- package/dist/agent-protocol/pi-rpc/index.d.ts.map +0 -1
- package/dist/agent-protocol/pi-rpc/index.js.map +0 -1
- package/dist/agent-protocol/pi-rpc/internal.d.ts.map +0 -1
- package/dist/agent-protocol/pi-rpc/internal.js.map +0 -1
- package/dist/agent-protocol/pi-rpc/models.d.ts.map +0 -1
- package/dist/agent-protocol/pi-rpc/models.js.map +0 -1
- package/dist/agent-protocol/pi-rpc/session.d.ts.map +0 -1
- package/dist/agent-protocol/pi-rpc/session.js.map +0 -1
- package/dist/amr-model-cache.d.ts.map +0 -1
- package/dist/amr-model-cache.js.map +0 -1
- package/dist/amr-profile-resolver.d.ts.map +0 -1
- package/dist/amr-profile-resolver.js.map +0 -1
- package/dist/anthropic-live-models.d.ts.map +0 -1
- package/dist/anthropic-live-models.js.map +0 -1
- package/dist/artifact-taxonomy.d.ts.map +0 -1
- package/dist/artifact-taxonomy.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/capabilities.d.ts.map +0 -1
- package/dist/capabilities.js.map +0 -1
- package/dist/claude-stream.d.ts.map +0 -1
- package/dist/claude-stream.js.map +0 -1
- package/dist/copilot-stream.d.ts.map +0 -1
- package/dist/copilot-stream.js.map +0 -1
- package/dist/defs/aider.d.ts.map +0 -1
- package/dist/defs/aider.js.map +0 -1
- package/dist/defs/amp.d.ts.map +0 -1
- package/dist/defs/amp.js.map +0 -1
- package/dist/defs/amr.d.ts.map +0 -1
- package/dist/defs/amr.js.map +0 -1
- package/dist/defs/antigravity.d.ts.map +0 -1
- package/dist/defs/antigravity.js.map +0 -1
- package/dist/defs/claude.d.ts.map +0 -1
- package/dist/defs/claude.js.map +0 -1
- package/dist/defs/codebuddy.d.ts.map +0 -1
- package/dist/defs/codebuddy.js.map +0 -1
- package/dist/defs/codex.d.ts.map +0 -1
- package/dist/defs/codex.js.map +0 -1
- package/dist/defs/copilot.d.ts.map +0 -1
- package/dist/defs/copilot.js.map +0 -1
- package/dist/defs/cursor-agent.d.ts.map +0 -1
- package/dist/defs/cursor-agent.js.map +0 -1
- package/dist/defs/deepseek.d.ts.map +0 -1
- package/dist/defs/deepseek.js.map +0 -1
- package/dist/defs/devin.d.ts.map +0 -1
- package/dist/defs/devin.js.map +0 -1
- package/dist/defs/grok-build.d.ts.map +0 -1
- package/dist/defs/grok-build.js.map +0 -1
- package/dist/defs/hermes.d.ts.map +0 -1
- package/dist/defs/hermes.js.map +0 -1
- package/dist/defs/index.d.ts.map +0 -1
- package/dist/defs/index.js.map +0 -1
- package/dist/defs/kilo.d.ts.map +0 -1
- package/dist/defs/kilo.js.map +0 -1
- package/dist/defs/kimi.d.ts.map +0 -1
- package/dist/defs/kimi.js.map +0 -1
- package/dist/defs/kiro.d.ts.map +0 -1
- package/dist/defs/kiro.js.map +0 -1
- package/dist/defs/mimo.d.ts.map +0 -1
- package/dist/defs/mimo.js.map +0 -1
- package/dist/defs/opencode.d.ts.map +0 -1
- package/dist/defs/opencode.js.map +0 -1
- package/dist/defs/pi.d.ts.map +0 -1
- package/dist/defs/pi.js.map +0 -1
- package/dist/defs/qoder.d.ts.map +0 -1
- package/dist/defs/qoder.js.map +0 -1
- package/dist/defs/qwen.d.ts.map +0 -1
- package/dist/defs/qwen.js.map +0 -1
- package/dist/defs/reasonix.d.ts.map +0 -1
- package/dist/defs/reasonix.js.map +0 -1
- package/dist/defs/shared.d.ts.map +0 -1
- package/dist/defs/shared.js.map +0 -1
- package/dist/defs/trae-cli.d.ts.map +0 -1
- package/dist/defs/trae-cli.js.map +0 -1
- package/dist/defs/vibe.d.ts.map +0 -1
- package/dist/defs/vibe.js.map +0 -1
- package/dist/detection.d.ts.map +0 -1
- package/dist/detection.js.map +0 -1
- package/dist/diagnostics.d.ts.map +0 -1
- package/dist/diagnostics.js.map +0 -1
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js.map +0 -1
- package/dist/executables.d.ts.map +0 -1
- package/dist/executables.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation.d.ts.map +0 -1
- package/dist/invocation.js.map +0 -1
- package/dist/json-event-stream.d.ts.map +0 -1
- package/dist/json-event-stream.js.map +0 -1
- package/dist/launch.d.ts.map +0 -1
- package/dist/launch.js.map +0 -1
- package/dist/log-file.d.ts.map +0 -1
- package/dist/log-file.js.map +0 -1
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js.map +0 -1
- package/dist/metadata.d.ts.map +0 -1
- package/dist/metadata.js.map +0 -1
- package/dist/mmd-routes.d.ts.map +0 -1
- package/dist/mmd-routes.js.map +0 -1
- package/dist/model-registry.d.ts.map +0 -1
- package/dist/model-registry.js.map +0 -1
- package/dist/models.d.ts.map +0 -1
- package/dist/models.js.map +0 -1
- package/dist/opencode-log.d.ts.map +0 -1
- package/dist/opencode-log.js.map +0 -1
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/pi-models.d.ts.map +0 -1
- package/dist/pi-models.js.map +0 -1
- package/dist/prompt-augmenter.d.ts.map +0 -1
- package/dist/prompt-augmenter.js.map +0 -1
- package/dist/prompt-budget.d.ts.map +0 -1
- package/dist/prompt-budget.js.map +0 -1
- package/dist/prompt-file.d.ts.map +0 -1
- package/dist/prompt-file.js.map +0 -1
- package/dist/providers/aihubmix.d.ts.map +0 -1
- package/dist/providers/aihubmix.js.map +0 -1
- package/dist/providers/anthropic-messages.d.ts.map +0 -1
- package/dist/providers/anthropic-messages.js.map +0 -1
- package/dist/providers/azure-chat.d.ts.map +0 -1
- package/dist/providers/azure-chat.js.map +0 -1
- package/dist/providers/connection-guard.d.ts.map +0 -1
- package/dist/providers/connection-guard.js.map +0 -1
- package/dist/providers/connection-test.d.ts.map +0 -1
- package/dist/providers/connection-test.js.map +0 -1
- package/dist/providers/elevenlabs.d.ts.map +0 -1
- package/dist/providers/elevenlabs.js.map +0 -1
- package/dist/providers/google-messages.d.ts.map +0 -1
- package/dist/providers/google-messages.js.map +0 -1
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js.map +0 -1
- package/dist/providers/model-catalog.d.ts.map +0 -1
- package/dist/providers/model-catalog.js.map +0 -1
- package/dist/providers/oauth-callback-server.d.ts.map +0 -1
- package/dist/providers/oauth-callback-server.js.map +0 -1
- package/dist/providers/oauth-credentials.d.ts.map +0 -1
- package/dist/providers/oauth-credentials.js.map +0 -1
- package/dist/providers/oauth-provider.d.ts.map +0 -1
- package/dist/providers/oauth-provider.js.map +0 -1
- package/dist/providers/oauth-tokens.d.ts.map +0 -1
- package/dist/providers/oauth-tokens.js.map +0 -1
- package/dist/providers/ollama-chat.d.ts.map +0 -1
- package/dist/providers/ollama-chat.js.map +0 -1
- package/dist/providers/openai-chat.d.ts.map +0 -1
- package/dist/providers/openai-chat.js.map +0 -1
- package/dist/providers/pkce.d.ts.map +0 -1
- package/dist/providers/pkce.js.map +0 -1
- package/dist/providers/sse-decode.d.ts.map +0 -1
- package/dist/providers/sse-decode.js.map +0 -1
- package/dist/providers/token-params.d.ts.map +0 -1
- package/dist/providers/token-params.js.map +0 -1
- package/dist/providers/turn-end-guard.d.ts.map +0 -1
- package/dist/providers/turn-end-guard.js.map +0 -1
- package/dist/providers/types.d.ts.map +0 -1
- package/dist/providers/types.js.map +0 -1
- package/dist/qoder-stream.d.ts.map +0 -1
- package/dist/qoder-stream.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/resolution.d.ts.map +0 -1
- package/dist/resolution.js.map +0 -1
- package/dist/role-marker-guard.d.ts.map +0 -1
- package/dist/role-marker-guard.js.map +0 -1
- package/dist/telemetry-sink.d.ts.map +0 -1
- package/dist/telemetry-sink.js.map +0 -1
- package/dist/terminal-launch.d.ts.map +0 -1
- package/dist/terminal-launch.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { RuntimeEnv, RuntimeModelOption } from './types.js';
|
|
2
|
+
/** The single stdin line that asks the CLI for its initialize payload (which carries `models`). */
|
|
3
|
+
export declare const CLAUDE_CODE_INITIALIZE_REQUEST = "{\"type\":\"control_request\",\"request_id\":\"jini-model-probe\",\"request\":{\"subtype\":\"initialize\"}}\n";
|
|
4
|
+
/**
|
|
5
|
+
* argv for the initialize probe.
|
|
6
|
+
*
|
|
7
|
+
* `--no-session-persistence`: no transcript is written. `--strict-mcp-config` with no `--mcp-config`:
|
|
8
|
+
* none of the operator's MCP servers are started. `disableAllHooks`: the operator's SessionStart
|
|
9
|
+
* hooks do not fire for a metadata probe (verified: without it, two user hooks ran). User settings
|
|
10
|
+
* are otherwise kept, because they can change what the picker offers (e.g. `availableModels`).
|
|
11
|
+
*/
|
|
12
|
+
export declare const CLAUDE_CODE_INITIALIZE_ARGS: readonly string[];
|
|
13
|
+
/** Strips the bracketed context-window variant marker (`claude-fable-5-1[1m]`) —
|
|
14
|
+
* `models.ts#sanitizeCustomModel` rejects brackets, and `--model` accepts the bare name. */
|
|
15
|
+
export declare function normalizeClaudePickerModelId(raw: string): string;
|
|
16
|
+
/** Outcome of parsing `~/.claude.json`. The failure reasons are distinct so the freshness guard can
|
|
17
|
+
* say WHY it skipped; the runtime treats every failure as "nothing to add". */
|
|
18
|
+
export type ClaudeCodePickerCacheParse = {
|
|
19
|
+
readonly ok: true;
|
|
20
|
+
readonly ids: readonly string[];
|
|
21
|
+
} | {
|
|
22
|
+
readonly ok: false;
|
|
23
|
+
readonly reason: 'invalid-json' | 'no-cache' | 'no-usable-values';
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Parses the raw text of `~/.claude.json` down to the model ids in `additionalModelOptionsCache`.
|
|
27
|
+
*
|
|
28
|
+
* @returns Normalized ids (bracket suffix stripped), or a typed reason. Never throws.
|
|
29
|
+
* @complexity O(n) over the cache entries (plus the JSON parse of the whole file).
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseClaudeCodePickerCache(raw: string): ClaudeCodePickerCacheParse;
|
|
32
|
+
/**
|
|
33
|
+
* Parses the CLI's stream-json stdout for the initialize `control_response` and returns its model
|
|
34
|
+
* rows (aliases and concrete ids, bracket suffixes stripped, deduped, `default` excluded).
|
|
35
|
+
*
|
|
36
|
+
* @returns The rows, or `null` when no well-formed response with at least one usable model exists.
|
|
37
|
+
* @complexity O(s + n) over the stdout length and the n models listed.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseClaudeInitializeModels(stdout: string): RuntimeModelOption[] | null;
|
|
40
|
+
/** Where Claude Code keeps `.claude.json` for this env: `$CLAUDE_CONFIG_DIR` when set, else HOME. */
|
|
41
|
+
export declare function resolveClaudeConfigPath(env: RuntimeEnv): string;
|
|
42
|
+
/** The two I/O edges, injectable so tests never spawn a real CLI or read the real home dir. */
|
|
43
|
+
export interface ClaudeCodeModelIo {
|
|
44
|
+
/** Runs the initialize probe; resolves to whatever stdout was captured, or `null`. Never rejects. */
|
|
45
|
+
runInitialize(bin: string, env: RuntimeEnv): Promise<string | null>;
|
|
46
|
+
/** Reads a file as UTF-8, or `null` when missing/unreadable. Never rejects. */
|
|
47
|
+
readConfigFile(path: string): Promise<string | null>;
|
|
48
|
+
}
|
|
49
|
+
/** Test-only: install fake I/O (or `null` to restore the real one) and clear the process cache. */
|
|
50
|
+
export declare function setClaudeCodeModelIoForTesting(io: ClaudeCodeModelIo | null): void;
|
|
51
|
+
/**
|
|
52
|
+
* Returns the `claude` model list with the CLI's own picker catalog merged in, or `null` when there
|
|
53
|
+
* is nothing to merge. Needs no API key.
|
|
54
|
+
*
|
|
55
|
+
* @param bin - The resolved `claude` (or fork) binary detection found.
|
|
56
|
+
* @param env - The composed environment for the agent — what a spawn would actually see.
|
|
57
|
+
* @param fallbackModels - The def's static list; always fully present, in order, in a non-null result.
|
|
58
|
+
* @param now - Injectable clock for TTL tests.
|
|
59
|
+
* @complexity O(f + l) plus, at most once per {@link CLAUDE_CODE_MODEL_CACHE_TTL_MS} per cache key,
|
|
60
|
+
* one CLI spawn bounded by {@link CLAUDE_CODE_INIT_TIMEOUT_MS} and one file read.
|
|
61
|
+
*/
|
|
62
|
+
export declare function loadClaudeCodeModels(bin: string, env: RuntimeEnv, fallbackModels: readonly RuntimeModelOption[], now?: () => number): Promise<RuntimeModelOption[] | null>;
|
|
63
|
+
//# sourceMappingURL=claude-code-models.d.ts.map
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module claude-code-models
|
|
3
|
+
*
|
|
4
|
+
* Credential-free live model discovery for the `claude` runtime def — the `claude` analogue of
|
|
5
|
+
* `codex debug models`.
|
|
6
|
+
*
|
|
7
|
+
* **Why this exists.** `claude` has no list-models subcommand, so without an `ANTHROPIC_API_KEY` the
|
|
8
|
+
* picker used to render `defs/claude.ts`'s hand-written `CLAUDE_FALLBACK_MODELS` and nothing else. A
|
|
9
|
+
* subscription-only install therefore never saw a new model (e.g. `claude-opus-5-5`) until someone
|
|
10
|
+
* edited that array and republished.
|
|
11
|
+
*
|
|
12
|
+
* **The two sources, both needing no API key** (verified live against Claude Code 2.1.280):
|
|
13
|
+
*
|
|
14
|
+
* 1. The CLI's own `/model` picker list, via the stream-json control protocol. Writing ONE
|
|
15
|
+
* `{"type":"control_request","request":{"subtype":"initialize"}}` line to
|
|
16
|
+
* `claude -p --input-format stream-json --output-format stream-json --verbose` and closing stdin
|
|
17
|
+
* makes the CLI answer with a `control_response` whose `response.models` is exactly the rows its
|
|
18
|
+
* interactive `/model` picker renders — the binary's built-in models PLUS the server-fetched
|
|
19
|
+
* "additional" options — then exit 0. No user message is ever sent, so no model call is made
|
|
20
|
+
* and nothing is billed; it authenticates with whatever the CLI itself uses (subscription OAuth
|
|
21
|
+
* included). This is the same `initialize` handshake the Claude Agent SDK's `supportedModels()`
|
|
22
|
+
* rides on.
|
|
23
|
+
* 2. `~/.claude.json`'s `additionalModelOptionsCache` — the server-fetched "additional" picker rows
|
|
24
|
+
* Claude Code persists on every session start's bootstrap fetch (headless `-p` runs included;
|
|
25
|
+
* it only rewrites the file when the payload changed). Only the server's extras, NOT the
|
|
26
|
+
* built-in rows, so it is strictly weaker than (1); it is read only when (1) yields nothing — an
|
|
27
|
+
* older CLI without the control protocol, a fork bin (`openclaude`), a timeout.
|
|
28
|
+
*
|
|
29
|
+
* **Enrichment, never a gate.** Every failure resolves to `null` ("nothing to add"), and a non-null
|
|
30
|
+
* result is always `mergeLiveModels(fallback, live)` — the static list is fully present, in order.
|
|
31
|
+
*
|
|
32
|
+
* The cache-file parser ({@link parseClaudeCodePickerCache}) is shared with
|
|
33
|
+
* `scripts/check-model-fallback-freshness.ts` so the freshness guard and the runtime can never
|
|
34
|
+
* disagree about what that cache says.
|
|
35
|
+
*/
|
|
36
|
+
import { execFile } from 'node:child_process';
|
|
37
|
+
import { createHash } from 'node:crypto';
|
|
38
|
+
import { readFile } from 'node:fs/promises';
|
|
39
|
+
import os from 'node:os';
|
|
40
|
+
import { join } from 'node:path';
|
|
41
|
+
import { createCommandInvocation } from '@jini-ai/platform';
|
|
42
|
+
import { mergeLiveModels } from './anthropic-live-models.js';
|
|
43
|
+
import { sanitizeCustomModel } from './models.js';
|
|
44
|
+
/** A spawn costs ~2.5s and the picker list only moves when Claude Code updates or the server's
|
|
45
|
+
* "additional" options change, so one answer is trusted for this long per process. Failures are
|
|
46
|
+
* cached for the same window so a broken CLI is not re-spawned on every detection pass. */
|
|
47
|
+
const CLAUDE_CODE_MODEL_CACHE_TTL_MS = 30 * 60_000;
|
|
48
|
+
/** Measured ~2.5s on a warm machine (the CLI performs its bootstrap fetch before answering). */
|
|
49
|
+
const CLAUDE_CODE_INIT_TIMEOUT_MS = 8_000;
|
|
50
|
+
/** The single stdin line that asks the CLI for its initialize payload (which carries `models`). */
|
|
51
|
+
export const CLAUDE_CODE_INITIALIZE_REQUEST = '{"type":"control_request","request_id":"jini-model-probe","request":{"subtype":"initialize"}}\n';
|
|
52
|
+
/**
|
|
53
|
+
* argv for the initialize probe.
|
|
54
|
+
*
|
|
55
|
+
* `--no-session-persistence`: no transcript is written. `--strict-mcp-config` with no `--mcp-config`:
|
|
56
|
+
* none of the operator's MCP servers are started. `disableAllHooks`: the operator's SessionStart
|
|
57
|
+
* hooks do not fire for a metadata probe (verified: without it, two user hooks ran). User settings
|
|
58
|
+
* are otherwise kept, because they can change what the picker offers (e.g. `availableModels`).
|
|
59
|
+
*/
|
|
60
|
+
export const CLAUDE_CODE_INITIALIZE_ARGS = [
|
|
61
|
+
'-p',
|
|
62
|
+
'--input-format', 'stream-json',
|
|
63
|
+
'--output-format', 'stream-json',
|
|
64
|
+
'--verbose',
|
|
65
|
+
'--no-session-persistence',
|
|
66
|
+
'--strict-mcp-config',
|
|
67
|
+
'--settings', '{"disableAllHooks":true}',
|
|
68
|
+
];
|
|
69
|
+
/** Strips the bracketed context-window variant marker (`claude-fable-5-1[1m]`) —
|
|
70
|
+
* `models.ts#sanitizeCustomModel` rejects brackets, and `--model` accepts the bare name. */
|
|
71
|
+
export function normalizeClaudePickerModelId(raw) {
|
|
72
|
+
return raw.replace(/\[[^\]]*\]$/, '').trim();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parses the raw text of `~/.claude.json` down to the model ids in `additionalModelOptionsCache`.
|
|
76
|
+
*
|
|
77
|
+
* @returns Normalized ids (bracket suffix stripped), or a typed reason. Never throws.
|
|
78
|
+
* @complexity O(n) over the cache entries (plus the JSON parse of the whole file).
|
|
79
|
+
*/
|
|
80
|
+
export function parseClaudeCodePickerCache(raw) {
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(raw);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return { ok: false, reason: 'invalid-json' };
|
|
87
|
+
}
|
|
88
|
+
const cached = parsed && typeof parsed === 'object'
|
|
89
|
+
? parsed.additionalModelOptionsCache
|
|
90
|
+
: undefined;
|
|
91
|
+
if (!Array.isArray(cached) || cached.length === 0)
|
|
92
|
+
return { ok: false, reason: 'no-cache' };
|
|
93
|
+
const ids = cached
|
|
94
|
+
.map((entry) => (entry && typeof entry === 'object' ? entry.value : null))
|
|
95
|
+
.filter((value) => typeof value === 'string')
|
|
96
|
+
.map(normalizeClaudePickerModelId)
|
|
97
|
+
.filter((id) => id.length > 0);
|
|
98
|
+
return ids.length > 0 ? { ok: true, ids } : { ok: false, reason: 'no-usable-values' };
|
|
99
|
+
}
|
|
100
|
+
/** Pulls `response.response.models` out of the one `control_response` line, or `null`. */
|
|
101
|
+
function findInitializeModels(stdout) {
|
|
102
|
+
for (const line of stdout.split('\n')) {
|
|
103
|
+
if (!line.includes('control_response'))
|
|
104
|
+
continue;
|
|
105
|
+
try {
|
|
106
|
+
const msg = JSON.parse(line);
|
|
107
|
+
if (msg.type !== 'control_response' || msg.response?.subtype !== 'success')
|
|
108
|
+
continue;
|
|
109
|
+
const models = msg.response.response?.models;
|
|
110
|
+
if (Array.isArray(models))
|
|
111
|
+
return models;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Not a JSON line (or a truncated one on timeout) — keep scanning.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
/** The picker rows one initialize entry contributes: its selection value (an alias like `sonnet`,
|
|
120
|
+
* or a full id) and the concrete model it resolves to. `default` is the sentinel the fallback list
|
|
121
|
+
* already carries; anything `sanitizeCustomModel` would refuse is dropped rather than rendered. */
|
|
122
|
+
function initializeEntryRows(entry) {
|
|
123
|
+
const rows = [];
|
|
124
|
+
const displayName = typeof entry.displayName === 'string' ? entry.displayName : '';
|
|
125
|
+
for (const raw of [entry.value, entry.resolvedModel]) {
|
|
126
|
+
if (typeof raw !== 'string')
|
|
127
|
+
continue;
|
|
128
|
+
const id = normalizeClaudePickerModelId(raw);
|
|
129
|
+
if (id === 'default' || sanitizeCustomModel(id) !== id)
|
|
130
|
+
continue;
|
|
131
|
+
const isAlias = !id.startsWith('claude-');
|
|
132
|
+
rows.push({ id, label: isAlias && displayName ? `${displayName} (alias)` : id });
|
|
133
|
+
}
|
|
134
|
+
return rows;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Parses the CLI's stream-json stdout for the initialize `control_response` and returns its model
|
|
138
|
+
* rows (aliases and concrete ids, bracket suffixes stripped, deduped, `default` excluded).
|
|
139
|
+
*
|
|
140
|
+
* @returns The rows, or `null` when no well-formed response with at least one usable model exists.
|
|
141
|
+
* @complexity O(s + n) over the stdout length and the n models listed.
|
|
142
|
+
*/
|
|
143
|
+
export function parseClaudeInitializeModels(stdout) {
|
|
144
|
+
const models = findInitializeModels(String(stdout || ''));
|
|
145
|
+
if (!models)
|
|
146
|
+
return null;
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const raw of models) {
|
|
150
|
+
if (!raw || typeof raw !== 'object')
|
|
151
|
+
continue;
|
|
152
|
+
for (const row of initializeEntryRows(raw)) {
|
|
153
|
+
if (seen.has(row.id))
|
|
154
|
+
continue;
|
|
155
|
+
seen.add(row.id);
|
|
156
|
+
out.push(row);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return out.length > 0 ? out : null;
|
|
160
|
+
}
|
|
161
|
+
/** Where Claude Code keeps `.claude.json` for this env: `$CLAUDE_CONFIG_DIR` when set, else HOME. */
|
|
162
|
+
export function resolveClaudeConfigPath(env) {
|
|
163
|
+
const configDir = typeof env.CLAUDE_CONFIG_DIR === 'string' ? env.CLAUDE_CONFIG_DIR.trim() : '';
|
|
164
|
+
if (configDir)
|
|
165
|
+
return join(configDir, '.claude.json');
|
|
166
|
+
const home = typeof env.HOME === 'string' && env.HOME.trim() ? env.HOME.trim() : os.homedir();
|
|
167
|
+
return join(home, '.claude.json');
|
|
168
|
+
}
|
|
169
|
+
function runInitializeProbe(bin, env) {
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
try {
|
|
172
|
+
const invocation = createCommandInvocation({ command: bin, args: [...CLAUDE_CODE_INITIALIZE_ARGS], env });
|
|
173
|
+
const child = execFile(invocation.command, invocation.args, {
|
|
174
|
+
env: env,
|
|
175
|
+
// Neutral cwd, same reason as `invocation.ts#execAgentFile`: a metadata probe must never
|
|
176
|
+
// pick up (or write into) a project directory.
|
|
177
|
+
cwd: os.tmpdir(),
|
|
178
|
+
timeout: CLAUDE_CODE_INIT_TIMEOUT_MS,
|
|
179
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
180
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
181
|
+
},
|
|
182
|
+
// A timeout or non-zero exit still hands back what was captured; the parser decides.
|
|
183
|
+
(_err, stdout) => resolve(typeof stdout === 'string' ? stdout : String(stdout ?? '')));
|
|
184
|
+
child.stdin?.on('error', () => { });
|
|
185
|
+
child.stdin?.end(CLAUDE_CODE_INITIALIZE_REQUEST);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
resolve(null);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const defaultIo = {
|
|
193
|
+
runInitialize: runInitializeProbe,
|
|
194
|
+
readConfigFile: (path) => readFile(path, 'utf8').catch(() => null),
|
|
195
|
+
};
|
|
196
|
+
let activeIo = defaultIo;
|
|
197
|
+
const cache = new Map();
|
|
198
|
+
/** Test-only: install fake I/O (or `null` to restore the real one) and clear the process cache. */
|
|
199
|
+
export function setClaudeCodeModelIoForTesting(io) {
|
|
200
|
+
activeIo = io ?? defaultIo;
|
|
201
|
+
cache.clear();
|
|
202
|
+
}
|
|
203
|
+
/** One cache slot per binary + config location + auth-affecting env, fingerprinted so no secret is
|
|
204
|
+
* retained as a `Map` key. Two accounts on one host must not share a picker list. */
|
|
205
|
+
function cacheKeyFor(bin, env) {
|
|
206
|
+
const auth = ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'CLAUDE_CODE_OAUTH_TOKEN']
|
|
207
|
+
.map((name) => (typeof env[name] === 'string' ? env[name] : ''))
|
|
208
|
+
.join('\u0000');
|
|
209
|
+
const fingerprint = createHash('sha256').update(auth).digest('hex').slice(0, 16);
|
|
210
|
+
return `${bin}\u0000${resolveClaudeConfigPath(env)}\u0000${fingerprint}`;
|
|
211
|
+
}
|
|
212
|
+
/** Source (1), then source (2). Never rejects. */
|
|
213
|
+
async function discoverClaudeCodeModels(bin, env) {
|
|
214
|
+
try {
|
|
215
|
+
const stdout = await activeIo.runInitialize(bin, env);
|
|
216
|
+
const fromCli = stdout ? parseClaudeInitializeModels(stdout) : null;
|
|
217
|
+
if (fromCli)
|
|
218
|
+
return fromCli;
|
|
219
|
+
const raw = await activeIo.readConfigFile(resolveClaudeConfigPath(env));
|
|
220
|
+
const cached = raw === null ? null : parseClaudeCodePickerCache(raw);
|
|
221
|
+
if (!cached?.ok)
|
|
222
|
+
return null;
|
|
223
|
+
return cached.ids
|
|
224
|
+
.filter((id) => sanitizeCustomModel(id) === id)
|
|
225
|
+
.map((id) => ({ id, label: id }));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Returns the `claude` model list with the CLI's own picker catalog merged in, or `null` when there
|
|
233
|
+
* is nothing to merge. Needs no API key.
|
|
234
|
+
*
|
|
235
|
+
* @param bin - The resolved `claude` (or fork) binary detection found.
|
|
236
|
+
* @param env - The composed environment for the agent — what a spawn would actually see.
|
|
237
|
+
* @param fallbackModels - The def's static list; always fully present, in order, in a non-null result.
|
|
238
|
+
* @param now - Injectable clock for TTL tests.
|
|
239
|
+
* @complexity O(f + l) plus, at most once per {@link CLAUDE_CODE_MODEL_CACHE_TTL_MS} per cache key,
|
|
240
|
+
* one CLI spawn bounded by {@link CLAUDE_CODE_INIT_TIMEOUT_MS} and one file read.
|
|
241
|
+
*/
|
|
242
|
+
export async function loadClaudeCodeModels(bin, env, fallbackModels, now = Date.now) {
|
|
243
|
+
const key = cacheKeyFor(bin, env);
|
|
244
|
+
const cached = cache.get(key);
|
|
245
|
+
const pending = cached && cached.expiresAt > now() ? cached.value : null;
|
|
246
|
+
const value = pending ?? discoverClaudeCodeModels(bin, env);
|
|
247
|
+
if (!pending)
|
|
248
|
+
cache.set(key, { expiresAt: now() + CLAUDE_CODE_MODEL_CACHE_TTL_MS, value });
|
|
249
|
+
const live = await value;
|
|
250
|
+
return live && live.length > 0 ? mergeLiveModels(fallbackModels, live) : null;
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=claude-code-models.js.map
|
package/dist/defs/aider.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const aiderAgentDef: {
|
|
|
4
4
|
bin: string;
|
|
5
5
|
versionArgs: string[];
|
|
6
6
|
fallbackModels: import("../types.js").RuntimeModelOption[];
|
|
7
|
+
fallbackModelsAssertedAt: string;
|
|
7
8
|
buildArgs: (prompt: string, _imagePaths: string[], _extra: string[] | undefined, options?: import("../types.js").RuntimeBuildOptions | undefined) => string[];
|
|
8
9
|
maxPromptArgBytes: number;
|
|
9
10
|
streamFormat: string;
|
package/dist/defs/aider.js
CHANGED
|
@@ -14,10 +14,19 @@ export const aiderAgentDef = {
|
|
|
14
14
|
fallbackModels: [
|
|
15
15
|
DEFAULT_MODEL_OPTION,
|
|
16
16
|
{ id: 'sonnet', label: 'sonnet' },
|
|
17
|
-
|
|
17
|
+
// `gpt-4o` is stale — OpenAI's GPT-5.6 family replaced the old flagship/mini/nano naming
|
|
18
|
+
// with named tiers (sol/terra/luna); `sol` is the flagship `gpt-4o` mapped to, bare (no
|
|
19
|
+
// `openai/` prefix) to match this list's existing LiteLLM-provider-default spelling. See
|
|
20
|
+
// `packages/ui/src/features/execution/constants.ts`'s OpenAI preset and
|
|
21
|
+
// `scripts/check-model-fallback-freshness.ts`.
|
|
22
|
+
{ id: 'gpt-5.6-sol', label: 'gpt-5.6-sol' },
|
|
18
23
|
{ id: 'deepseek/deepseek-chat', label: 'deepseek/deepseek-chat' },
|
|
19
24
|
{ id: 'gemini/gemini-2.0-flash', label: 'gemini/gemini-2.0-flash' },
|
|
20
25
|
],
|
|
26
|
+
// Asserted against `packages/ui/src/features/execution/constants.ts`'s current OpenAI preset
|
|
27
|
+
// (gpt-5.6-sol/terra/luna). See `RuntimeAgentDef.fallbackModelsAssertedAt` and
|
|
28
|
+
// `scripts/check-model-fallback-freshness.ts`.
|
|
29
|
+
fallbackModelsAssertedAt: '2026-09-16',
|
|
21
30
|
// Aider's one-shot mode requires the prompt as `--message <text>` on
|
|
22
31
|
// argv; neither `--message` nor `--message-file` accept `-` as a stdin
|
|
23
32
|
// sentinel (it is treated as a literal filename), so we cannot pipe
|
package/dist/defs/claude.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare const claudeAgentDef: {
|
|
|
17
17
|
};
|
|
18
18
|
fallbackModels: import("../types.js").RuntimeModelOption[];
|
|
19
19
|
fallbackModelsAssertedAt: string;
|
|
20
|
-
fetchModels: (
|
|
20
|
+
fetchModels: (resolvedBin: string, env: import("../types.js").RuntimeEnv) => Promise<import("../types.js").RuntimeModelOption[] | null>;
|
|
21
21
|
reasoningOptions: {
|
|
22
22
|
id: string;
|
|
23
23
|
label: string;
|
package/dist/defs/claude.js
CHANGED
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
import { agentCapabilities } from '../capabilities.js';
|
|
31
31
|
import { buildClaudeMcpConfigArgs, DEFAULT_MODEL_OPTION } from './shared.js';
|
|
32
32
|
import { loadMmdRouteModels } from '../mmd-routes.js';
|
|
33
|
-
import { loadAnthropicLiveModels } from '../anthropic-live-models.js';
|
|
33
|
+
import { loadAnthropicLiveModels, mergeLiveModels } from '../anthropic-live-models.js';
|
|
34
|
+
import { loadClaudeCodeModels } from '../claude-code-models.js';
|
|
34
35
|
/**
|
|
35
36
|
* The levels `claude --effort` accepts, as the CLI itself reports them when
|
|
36
37
|
* given an unknown one. Anything outside this set is dropped rather than
|
|
@@ -43,8 +44,9 @@ const CLAUDE_EFFORT_LEVELS = new Set([
|
|
|
43
44
|
]);
|
|
44
45
|
/**
|
|
45
46
|
* The list a picker renders when nothing live is available — no `~/.config/mms/model-routes.json`,
|
|
46
|
-
*
|
|
47
|
-
* see `fetchModels` below.
|
|
47
|
+
* the CLI's own picker catalog unreadable (see `claude-code-models.ts`), and no `ANTHROPIC_API_KEY`
|
|
48
|
+
* in the agent's environment or a failed live call. Not the primary source: see `fetchModels` below.
|
|
49
|
+
* Every live source is unioned ON TOP of this list, so it is also the floor that never shrinks.
|
|
48
50
|
*
|
|
49
51
|
* Ordered current-first, then the still-active-but-superseded generations, which are KEPT rather
|
|
50
52
|
* than dropped — an installed CLI may be pinned to one via its own config, and removing a working
|
|
@@ -113,11 +115,14 @@ export const claudeAgentDef = {
|
|
|
113
115
|
// 1. Local mmd/MMS routes, when `~/.config/mms/model-routes.json` exists — an explicit local
|
|
114
116
|
// routing config is the operator's own authoritative statement of what this CLI can reach,
|
|
115
117
|
// including proxy-backed Claude-compatible models the vendor API knows nothing about.
|
|
116
|
-
// 2.
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
118
|
+
// 2. Otherwise, the UNION of two live sources, run in parallel:
|
|
119
|
+
// a. The CLI's own `/model` picker catalog — NO credential needed (subscription auth
|
|
120
|
+
// works). See `claude-code-models.ts`: the stream-json `initialize` control request,
|
|
121
|
+
// falling back to `~/.claude.json`'s `additionalModelOptionsCache`. Cached per process.
|
|
122
|
+
// b. The account's live Anthropic catalog, when `ANTHROPIC_API_KEY` is in this agent's
|
|
123
|
+
// environment (makes no network call at all without a key).
|
|
124
|
+
// Both are merged ON TOP of the static list, never in place of it; neither shadows the other.
|
|
125
|
+
// 3. The static list, when neither live source answers.
|
|
121
126
|
//
|
|
122
127
|
// Returning `null` from any step is "nothing to add", and `detection.ts#fetchModels` renders
|
|
123
128
|
// `fallbackModels` for it — so a live-discovery failure can never empty or shrink the picker.
|
|
@@ -126,8 +131,18 @@ export const claudeAgentDef = {
|
|
|
126
131
|
// `~/.claude.json`'s server-fetched `additionalModelOptionsCache`. See
|
|
127
132
|
// `RuntimeAgentDef.fallbackModelsAssertedAt` and `scripts/check-model-fallback-freshness.ts`.
|
|
128
133
|
fallbackModelsAssertedAt: '2026-09-05',
|
|
129
|
-
fetchModels: async (
|
|
130
|
-
|
|
134
|
+
fetchModels: async (resolvedBin, env) => {
|
|
135
|
+
const routed = await loadMmdRouteModels(env, CLAUDE_FALLBACK_MODELS);
|
|
136
|
+
if (routed)
|
|
137
|
+
return routed;
|
|
138
|
+
const [fromCli, fromApi] = await Promise.all([
|
|
139
|
+
loadClaudeCodeModels(resolvedBin, env, CLAUDE_FALLBACK_MODELS),
|
|
140
|
+
loadAnthropicLiveModels(env, CLAUDE_FALLBACK_MODELS),
|
|
141
|
+
]);
|
|
142
|
+
if (!fromCli)
|
|
143
|
+
return fromApi;
|
|
144
|
+
return fromApi ? mergeLiveModels(fromCli, fromApi) : fromCli;
|
|
145
|
+
},
|
|
131
146
|
// `claude --effort <level>`. The set differs from codex's on both ends — it
|
|
132
147
|
// has `max` and has no `none`/`minimal` — so it is spelled out rather than
|
|
133
148
|
// shared, and `CLAUDE_EFFORT_LEVELS` below is what `buildArgs` validates
|
package/dist/defs/opencode.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare const opencodeAgentDef: {
|
|
|
16
16
|
timeoutMs: number;
|
|
17
17
|
};
|
|
18
18
|
fallbackModels: import("../types.js").RuntimeModelOption[];
|
|
19
|
+
fallbackModelsAssertedAt: string;
|
|
19
20
|
buildArgs: (_prompt: string, _imagePaths: string[], _extra: string[] | undefined, options?: import("../types.js").RuntimeBuildOptions | undefined, runtimeContext?: import("../types.js").RuntimeContext | undefined) => string[];
|
|
20
21
|
promptViaStdin: true;
|
|
21
22
|
resumesSessionViaCli: true;
|
package/dist/defs/opencode.js
CHANGED
|
@@ -26,12 +26,17 @@ export const opencodeAgentDef = {
|
|
|
26
26
|
fallbackModels: [
|
|
27
27
|
DEFAULT_MODEL_OPTION,
|
|
28
28
|
{
|
|
29
|
-
id: 'anthropic/claude-sonnet-
|
|
30
|
-
label: 'anthropic/claude-sonnet-
|
|
29
|
+
id: 'anthropic/claude-sonnet-5',
|
|
30
|
+
label: 'anthropic/claude-sonnet-5',
|
|
31
31
|
},
|
|
32
32
|
{ id: 'openai/gpt-5', label: 'openai/gpt-5' },
|
|
33
33
|
{ id: 'google/gemini-2.5-pro', label: 'google/gemini-2.5-pro' },
|
|
34
34
|
],
|
|
35
|
+
// Checked against `packages/ui/src/features/execution/constants.ts`'s current OpenAI preset —
|
|
36
|
+
// `gpt-5` is a distinct, still-current generation from the newer gpt-5.6 sol/terra/luna tier
|
|
37
|
+
// names, so it is not stale. See `RuntimeAgentDef.fallbackModelsAssertedAt` and
|
|
38
|
+
// `scripts/check-model-fallback-freshness.ts`.
|
|
39
|
+
fallbackModelsAssertedAt: '2026-09-16',
|
|
35
40
|
// OpenCode's CLI help currently exposes model selection and session
|
|
36
41
|
// controls, but not an explicit per-run reasoning / effort flag. Keep
|
|
37
42
|
// `reasoningOptions` undefined and do not synthesize argv for
|
package/dist/defs/pi.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare const piAgentDef: {
|
|
|
6
6
|
versionProbeTimeoutMs: number;
|
|
7
7
|
fetchModels: (resolvedBin: string, env: import("../types.js").RuntimeEnv) => Promise<import("../types.js").RuntimeModelOption[] | null>;
|
|
8
8
|
fallbackModels: import("../types.js").RuntimeModelOption[];
|
|
9
|
+
fallbackModelsAssertedAt: string;
|
|
9
10
|
reasoningOptions: {
|
|
10
11
|
id: string;
|
|
11
12
|
label: string;
|
package/dist/defs/pi.js
CHANGED
|
@@ -34,15 +34,23 @@ export const piAgentDef = {
|
|
|
34
34
|
fallbackModels: [
|
|
35
35
|
DEFAULT_MODEL_OPTION,
|
|
36
36
|
{
|
|
37
|
-
id: 'anthropic/claude-sonnet-
|
|
38
|
-
label: 'Claude Sonnet
|
|
37
|
+
id: 'anthropic/claude-sonnet-5',
|
|
38
|
+
label: 'Claude Sonnet 5 (anthropic)',
|
|
39
39
|
},
|
|
40
|
-
{ id: 'anthropic/claude-opus-
|
|
40
|
+
{ id: 'anthropic/claude-opus-5', label: 'Claude Opus 5 (anthropic)' },
|
|
41
41
|
{ id: 'openai/gpt-5', label: 'GPT-5 (openai)' },
|
|
42
|
-
|
|
42
|
+
// `o4-mini` is stale — OpenAI's GPT-5.6 family replaced the old flagship/mini/nano naming
|
|
43
|
+
// with named tiers (sol/terra/luna); `terra` is the mid tier `o4-mini` mapped to. See
|
|
44
|
+
// `packages/ui/src/features/execution/constants.ts`'s OpenAI preset and
|
|
45
|
+
// `scripts/check-model-fallback-freshness.ts`.
|
|
46
|
+
{ id: 'openai/gpt-5.6-terra', label: 'GPT-5.6 Terra (openai)' },
|
|
43
47
|
{ id: 'google/gemini-2.5-pro', label: 'Gemini 2.5 Pro (google)' },
|
|
44
48
|
{ id: 'google/gemini-2.5-flash', label: 'Gemini 2.5 Flash (google)' },
|
|
45
49
|
],
|
|
50
|
+
// Asserted against `packages/ui/src/features/execution/constants.ts`'s current OpenAI preset
|
|
51
|
+
// (gpt-5.6-sol/terra/luna). See `RuntimeAgentDef.fallbackModelsAssertedAt` and
|
|
52
|
+
// `scripts/check-model-fallback-freshness.ts`.
|
|
53
|
+
fallbackModelsAssertedAt: '2026-09-16',
|
|
46
54
|
// Thinking level presets mapped to pi's --thinking flag.
|
|
47
55
|
reasoningOptions: [
|
|
48
56
|
{ id: 'default', label: 'Default' },
|
package/dist/detection.d.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { type AmrProfileResolver } from './amr-profile-resolver.js';
|
|
2
2
|
import type { AgentDiagnostic, DetectedAgent, RuntimeAgentDef, RuntimeModelSource, RuntimeModelOption } from './types.js';
|
|
3
|
+
type FetchedRuntimeModels = {
|
|
4
|
+
models: RuntimeModelOption[];
|
|
5
|
+
source: RuntimeModelSource;
|
|
6
|
+
};
|
|
7
|
+
/** Result of {@link probeAgentModels}: the models to surface and where they came from. */
|
|
8
|
+
export type ProbedAgentModels = FetchedRuntimeModels;
|
|
9
|
+
/**
|
|
10
|
+
* The model half of {@link detectAgents} for ONE def, without the `--version`/`--help`/auth probes:
|
|
11
|
+
* resolves the launch path, builds the same probe env, runs the def's `fetchModels`/`listModels`,
|
|
12
|
+
* and refreshes the live-model validation cache exactly like `detectAgents` does.
|
|
13
|
+
*
|
|
14
|
+
* For hosts that do their own cheaper availability check but still want the live model catalog —
|
|
15
|
+
* without this, such a host can only ever show `fallbackModels`.
|
|
16
|
+
*
|
|
17
|
+
* @returns `source: 'live'` when the CLI supplied a list; otherwise the def's `fallbackModels` with
|
|
18
|
+
* `source: 'fallback'` (including when the binary is not found). Never rejects.
|
|
19
|
+
* @complexity At most one model-listing spawn, bounded by the def's own timeout.
|
|
20
|
+
*/
|
|
21
|
+
export declare function probeAgentModels(def: RuntimeAgentDef, configuredEnv?: Record<string, string>, amrProfileResolver?: AmrProfileResolver): Promise<ProbedAgentModels>;
|
|
3
22
|
export declare function detectAgents(configuredEnvByAgent?: Record<string, Record<string, string>>, amrProfileResolver?: AmrProfileResolver): Promise<(Omit<RuntimeAgentDef, "buildArgs" | "listModels" | "fetchModels" | "deriveReasoningOptions" | "fallbackModels" | "fallbackModelsAssertedAt" | "helpArgs" | "capabilityFlags" | "fallbackBins" | "versionProbeTimeoutMs" | "maxPromptArgBytes" | "env" | "inactivityTimeoutMs" | "authProbe" | "needsAgentLogFile" | "stdoutPolicy" | "runtimeLock"> & {
|
|
4
23
|
models: RuntimeModelOption[];
|
|
5
24
|
modelsSource: RuntimeModelSource;
|
|
@@ -11,4 +30,5 @@ export declare function detectAgents(configuredEnvByAgent?: Record<string, Recor
|
|
|
11
30
|
diagnostics?: AgentDiagnostic[];
|
|
12
31
|
})[]>;
|
|
13
32
|
export declare function detectAgentsStream(configuredEnvByAgent?: Record<string, Record<string, string>>, amrProfileResolver?: AmrProfileResolver): AsyncGenerator<DetectedAgent>;
|
|
33
|
+
export {};
|
|
14
34
|
//# sourceMappingURL=detection.d.ts.map
|
package/dist/detection.js
CHANGED
|
@@ -169,6 +169,37 @@ function derivedReasoningOverride(def, models) {
|
|
|
169
169
|
const derived = def.deriveReasoningOptions?.(models);
|
|
170
170
|
return derived && derived.length > 0 ? { reasoningOptions: derived } : {};
|
|
171
171
|
}
|
|
172
|
+
/** The env every probe of one def spawns with — the same env a real run would see. */
|
|
173
|
+
function probeEnvForLaunch(def, configuredEnv, launch) {
|
|
174
|
+
return applyAgentLaunchEnv(spawnEnvForAgent(def.id, { ...process.env, ...(def.env || {}) }, configuredEnv, undefined, {}), launch);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The model half of {@link detectAgents} for ONE def, without the `--version`/`--help`/auth probes:
|
|
178
|
+
* resolves the launch path, builds the same probe env, runs the def's `fetchModels`/`listModels`,
|
|
179
|
+
* and refreshes the live-model validation cache exactly like `detectAgents` does.
|
|
180
|
+
*
|
|
181
|
+
* For hosts that do their own cheaper availability check but still want the live model catalog —
|
|
182
|
+
* without this, such a host can only ever show `fallbackModels`.
|
|
183
|
+
*
|
|
184
|
+
* @returns `source: 'live'` when the CLI supplied a list; otherwise the def's `fallbackModels` with
|
|
185
|
+
* `source: 'fallback'` (including when the binary is not found). Never rejects.
|
|
186
|
+
* @complexity At most one model-listing spawn, bounded by the def's own timeout.
|
|
187
|
+
*/
|
|
188
|
+
export async function probeAgentModels(def, configuredEnv = {}, amrProfileResolver = noopAmrProfileResolver) {
|
|
189
|
+
try {
|
|
190
|
+
const launch = resolveAgentLaunch(def, configuredEnv);
|
|
191
|
+
if (!launch.launchPath)
|
|
192
|
+
return { models: def.fallbackModels, source: 'fallback' };
|
|
193
|
+
const probeEnv = probeEnvForLaunch(def, configuredEnv, launch);
|
|
194
|
+
const fetched = await fetchModels(def, launch.launchPath, probeEnv);
|
|
195
|
+
const result = withRememberedAmrModels(def, probeEnv, fetched, amrProfileResolver);
|
|
196
|
+
rememberDetectedModelList(def, configuredEnv, result.models, amrProfileResolver);
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return { models: def.fallbackModels, source: 'fallback' };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
172
203
|
async function probe(def, configuredEnv = {}, amrProfileResolver) {
|
|
173
204
|
// Detection must probe the exact path the runtime will spawn, not just
|
|
174
205
|
// the PATH-visible shim. This is load-bearing under nvm/fnm/mise: the
|
|
@@ -182,7 +213,7 @@ async function probe(def, configuredEnv = {}, amrProfileResolver) {
|
|
|
182
213
|
if (!launch.selectedPath || !launch.launchPath) {
|
|
183
214
|
return unavailableAgent(def, [buildExecutableDiagnostic(def, configuredEnv)]);
|
|
184
215
|
}
|
|
185
|
-
const probeEnv =
|
|
216
|
+
const probeEnv = probeEnvForLaunch(def, configuredEnv, launch);
|
|
186
217
|
const outcome = await probeVersionAtPath(def, launch.launchPath, probeEnv);
|
|
187
218
|
if (outcome.kind === 'not-invocable') {
|
|
188
219
|
return unavailableAgent(def, [buildNotInvocableDiagnostic(def, launch, outcome.cause)]);
|
|
@@ -250,12 +281,15 @@ async function safeProbe(def, configuredEnv = {}, amrProfileResolver) {
|
|
|
250
281
|
}
|
|
251
282
|
}
|
|
252
283
|
function rememberDetectedLiveModels(def, configuredEnv, agent, amrProfileResolver) {
|
|
253
|
-
|
|
284
|
+
rememberDetectedModelList(def, configuredEnv, agent.models, amrProfileResolver);
|
|
285
|
+
}
|
|
286
|
+
function rememberDetectedModelList(def, configuredEnv, models, amrProfileResolver) {
|
|
287
|
+
if (def.id === 'amr' && models.length === 0)
|
|
254
288
|
return;
|
|
255
289
|
const scope = def.id === 'amr'
|
|
256
290
|
? amrModelScopeFromEnv({ ...process.env, ...(def.env || {}), ...configuredEnv }, amrProfileResolver)
|
|
257
291
|
: null;
|
|
258
|
-
rememberLiveModels(
|
|
292
|
+
rememberLiveModels(def.id, models, scope);
|
|
259
293
|
}
|
|
260
294
|
export async function detectAgents(configuredEnvByAgent = {}, amrProfileResolver = noopAmrProfileResolver) {
|
|
261
295
|
const results = await Promise.all(AGENT_DEFS.map((def) => safeProbe(def, configuredEnvByAgent?.[def.id] ?? {}, amrProfileResolver)));
|
|
@@ -287,7 +287,7 @@ export async function testProviderConnection(input) {
|
|
|
287
287
|
kind: 'auth_failed',
|
|
288
288
|
latencyMs: Date.now() - start,
|
|
289
289
|
model,
|
|
290
|
-
detail: 'No API key
|
|
290
|
+
detail: 'No API key saved. Save one to test the connection.',
|
|
291
291
|
};
|
|
292
292
|
}
|
|
293
293
|
const validated = await validateBaseUrlResolved(input.baseUrl, input.dnsLookup ?? defaultDnsLookup);
|
|
@@ -32,7 +32,7 @@ function missingApiKeyResponse(startedAt) {
|
|
|
32
32
|
ok: false,
|
|
33
33
|
kind: 'auth_failed',
|
|
34
34
|
latencyMs: Date.now() - startedAt,
|
|
35
|
-
detail: 'No API key
|
|
35
|
+
detail: 'No API key saved. Save one to load models.',
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
const BEDROCK_MODEL_OPTIONS = [
|
package/dist/types.d.ts
CHANGED
|
@@ -81,7 +81,7 @@ export type RuntimeBuildOptions = {
|
|
|
81
81
|
* expresses a deny-list — today, `claude`'s own `--disallowedTools` (verified against installed
|
|
82
82
|
* Claude Code 2.1.263: `--disallowedTools, --disallowed-tools <tools...>`, "Comma or
|
|
83
83
|
* space-separated list of tool names to deny"). This is a `@jini-ai/agent-runtime`-level
|
|
84
|
-
* *mechanism* only: no product's tool policy is baked in here (not
|
|
84
|
+
* *mechanism* only: no product's tool policy is baked in here (not one host's Bash-forbid opinion,
|
|
85
85
|
* not anyone else's) — the caller decides which names to pass. A def whose CLI has no equivalent
|
|
86
86
|
* flag simply ignores this field, same as `systemPromptOverlay` on a def with no overlay
|
|
87
87
|
* delivery. `undefined`/empty means no restriction — byte-identical to today's behavior (every
|