@golba98/codexa 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +320 -0
- package/bin/codexa.js +445 -0
- package/package.json +45 -0
- package/scripts/audit-codexa-capabilities.mjs +466 -0
- package/scripts/smoke-terminal-bench.mjs +35 -0
- package/src/app.tsx +4561 -0
- package/src/appRenderStability.test.ts +131 -0
- package/src/commands/handler.test.ts +643 -0
- package/src/commands/handler.ts +875 -0
- package/src/config/launchArgs.test.ts +158 -0
- package/src/config/launchArgs.ts +186 -0
- package/src/config/layeredConfig.test.ts +143 -0
- package/src/config/layeredConfig.ts +836 -0
- package/src/config/persistence.test.ts +110 -0
- package/src/config/persistence.ts +311 -0
- package/src/config/runtimeConfig.test.ts +218 -0
- package/src/config/runtimeConfig.ts +554 -0
- package/src/config/settings.test.ts +155 -0
- package/src/config/settings.ts +401 -0
- package/src/config/toml-serialize.ts +98 -0
- package/src/config/trustStore.test.ts +29 -0
- package/src/config/trustStore.ts +68 -0
- package/src/core/attachments.test.ts +155 -0
- package/src/core/attachments.ts +71 -0
- package/src/core/auth/codexAuth.test.ts +68 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/cleanupFastFail.test.ts +76 -0
- package/src/core/cleanupFastFail.ts +67 -0
- package/src/core/clipboard.ts +24 -0
- package/src/core/codex.ts +124 -0
- package/src/core/codexExecArgs.test.ts +195 -0
- package/src/core/codexExecArgs.ts +152 -0
- package/src/core/codexLaunch.test.ts +205 -0
- package/src/core/codexLaunch.ts +162 -0
- package/src/core/codexPrompt.test.ts +252 -0
- package/src/core/codexPrompt.ts +428 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.test.ts +212 -0
- package/src/core/executables/codexExecutable.ts +159 -0
- package/src/core/executables/executableResolver.test.ts +129 -0
- package/src/core/executables/executableResolver.ts +138 -0
- package/src/core/executables/geminiExecutable.test.ts +116 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/executables/pathSanityScan.test.ts +47 -0
- package/src/core/githubDiagnostics.test.ts +92 -0
- package/src/core/githubDiagnostics.ts +222 -0
- package/src/core/hollowResponseFormat.test.ts +58 -0
- package/src/core/hollowResponseFormat.ts +39 -0
- package/src/core/inputDebug.ts +51 -0
- package/src/core/launchContext.test.ts +157 -0
- package/src/core/launchContext.ts +266 -0
- package/src/core/models/codexCapabilities.test.ts +45 -0
- package/src/core/models/codexCapabilities.ts +95 -0
- package/src/core/models/codexModelCapabilities.test.ts +246 -0
- package/src/core/models/codexModelCapabilities.ts +571 -0
- package/src/core/models/modelSpecs.test.ts +283 -0
- package/src/core/models/modelSpecs.ts +300 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.test.ts +230 -0
- package/src/core/perf/renderDebug.ts +373 -0
- package/src/core/planStorage.test.ts +143 -0
- package/src/core/planStorage.ts +141 -0
- package/src/core/process/CommandRunner.test.ts +105 -0
- package/src/core/process/CommandRunner.ts +269 -0
- package/src/core/process/processValidation.ts +101 -0
- package/src/core/projectInstructions.test.ts +50 -0
- package/src/core/projectInstructions.ts +54 -0
- package/src/core/providerLauncher/launcher.test.ts +238 -0
- package/src/core/providerLauncher/launcher.ts +203 -0
- package/src/core/providerLauncher/registry.test.ts +324 -0
- package/src/core/providerLauncher/registry.ts +253 -0
- package/src/core/providerLauncher/types.ts +84 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +638 -0
- package/src/core/providerLauncher/workspaceConfig.ts +407 -0
- package/src/core/providerRuntime/anthropic.test.ts +1120 -0
- package/src/core/providerRuntime/anthropic.ts +576 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +311 -0
- package/src/core/providerRuntime/capabilityProfile.ts +288 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +446 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +468 -0
- package/src/core/providerRuntime/contextMetadata.ts +409 -0
- package/src/core/providerRuntime/gemini.test.ts +437 -0
- package/src/core/providerRuntime/gemini.ts +784 -0
- package/src/core/providerRuntime/lmstudio.test.ts +168 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.test.ts +787 -0
- package/src/core/providerRuntime/local.ts +754 -0
- package/src/core/providerRuntime/models.ts +150 -0
- package/src/core/providerRuntime/reasoning.ts +17 -0
- package/src/core/providerRuntime/registry.test.ts +233 -0
- package/src/core/providerRuntime/registry.ts +203 -0
- package/src/core/providerRuntime/types.ts +103 -0
- package/src/core/providers/codexJsonStream.test.ts +148 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.test.ts +68 -0
- package/src/core/providers/codexSubprocess.ts +372 -0
- package/src/core/providers/codexTranscript.test.ts +284 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +59 -0
- package/src/core/terminal/terminalCapabilities.test.ts +93 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.test.ts +75 -0
- package/src/core/terminal/terminalControl.ts +147 -0
- package/src/core/terminal/terminalSanitize.test.ts +22 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalSelection.test.ts +42 -0
- package/src/core/terminal/terminalSelection.ts +66 -0
- package/src/core/terminal/terminalTitle.test.ts +328 -0
- package/src/core/terminal/terminalTitle.ts +483 -0
- package/src/core/workspaceActivity.test.ts +163 -0
- package/src/core/workspaceActivity.ts +380 -0
- package/src/core/workspaceGuard.test.ts +151 -0
- package/src/core/workspaceGuard.ts +288 -0
- package/src/core/workspaceRoot.test.ts +23 -0
- package/src/core/workspaceRoot.ts +47 -0
- package/src/exec.test.ts +13 -0
- package/src/exec.ts +72 -0
- package/src/headless/execArgs.test.ts +147 -0
- package/src/headless/execArgs.ts +294 -0
- package/src/headless/execRunner.test.ts +434 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.test.tsx +618 -0
- package/src/index.tsx +296 -0
- package/src/session/appSession.test.ts +897 -0
- package/src/session/appSession.ts +761 -0
- package/src/session/chatLifecycle.test.ts +64 -0
- package/src/session/chatLifecycle.ts +951 -0
- package/src/session/liveRenderScheduler.test.ts +201 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/planFlow.test.ts +103 -0
- package/src/session/planFlow.ts +149 -0
- package/src/session/planTranscript.test.ts +65 -0
- package/src/session/planTranscript.ts +15 -0
- package/src/session/promptRunSchedule.test.ts +36 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +228 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/ActionRequiredBlock.tsx +38 -0
- package/src/ui/ActivityBars.tsx +68 -0
- package/src/ui/ActivityIndicator.test.tsx +58 -0
- package/src/ui/ActivityIndicator.tsx +58 -0
- package/src/ui/AgentBlock.test.ts +6 -0
- package/src/ui/AgentBlock.tsx +130 -0
- package/src/ui/AnimatedStatusText.test.ts +16 -0
- package/src/ui/AnimatedStatusText.tsx +69 -0
- package/src/ui/AppShell.test.tsx +1739 -0
- package/src/ui/AppShell.tsx +698 -0
- package/src/ui/AttachmentImportPanel.test.tsx +204 -0
- package/src/ui/AttachmentImportPanel.tsx +98 -0
- package/src/ui/AuthPanel.tsx +113 -0
- package/src/ui/BackendPicker.tsx +28 -0
- package/src/ui/BottomComposer.test.ts +674 -0
- package/src/ui/BottomComposer.tsx +1028 -0
- package/src/ui/CodexLogo.tsx +55 -0
- package/src/ui/DashCard.tsx +82 -0
- package/src/ui/Markdown.test.ts +157 -0
- package/src/ui/Markdown.tsx +310 -0
- package/src/ui/ModePicker.tsx +27 -0
- package/src/ui/ModelPicker.tsx +31 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +411 -0
- package/src/ui/ModelPickerScreen.test.tsx +99 -0
- package/src/ui/ModelPickerScreen.tsx +416 -0
- package/src/ui/ModelPickerState.test.tsx +151 -0
- package/src/ui/ModelReasoningPicker.test.tsx +447 -0
- package/src/ui/ModelReasoningPicker.tsx +458 -0
- package/src/ui/Panel.tsx +51 -0
- package/src/ui/PermissionsPanel.tsx +78 -0
- package/src/ui/PlanActionPicker.tsx +119 -0
- package/src/ui/PlanReviewPanel.test.tsx +267 -0
- package/src/ui/PlanReviewPanel.tsx +212 -0
- package/src/ui/PromptCardBorder.test.tsx +161 -0
- package/src/ui/ProviderPicker.test.tsx +289 -0
- package/src/ui/ProviderPicker.tsx +321 -0
- package/src/ui/ProviderShortcut.test.tsx +143 -0
- package/src/ui/ReasoningPicker.tsx +46 -0
- package/src/ui/RunFooter.tsx +65 -0
- package/src/ui/SelectionPanel.tsx +67 -0
- package/src/ui/SettingsPanel.test.tsx +233 -0
- package/src/ui/SettingsPanel.tsx +156 -0
- package/src/ui/Spinner.tsx +25 -0
- package/src/ui/StaticIntroItem.tsx +54 -0
- package/src/ui/StaticTranscriptItem.tsx +56 -0
- package/src/ui/TextEntryPanel.tsx +139 -0
- package/src/ui/ThemePicker.tsx +31 -0
- package/src/ui/ThinkingBlock.tsx +100 -0
- package/src/ui/Timeline.test.ts +2067 -0
- package/src/ui/Timeline.tsx +1472 -0
- package/src/ui/TimelineNavigation.test.tsx +201 -0
- package/src/ui/TopHeader.test.tsx +239 -0
- package/src/ui/TopHeader.tsx +257 -0
- package/src/ui/TurnGroup.test.tsx +365 -0
- package/src/ui/TurnGroup.tsx +657 -0
- package/src/ui/busyStatusAnimation.test.ts +30 -0
- package/src/ui/busyStatusAnimation.ts +11 -0
- package/src/ui/commandNormalize.test.ts +142 -0
- package/src/ui/commandNormalize.ts +66 -0
- package/src/ui/diffRenderer.test.ts +102 -0
- package/src/ui/diffRenderer.ts +116 -0
- package/src/ui/focus.ts +61 -0
- package/src/ui/focusFlow.test.tsx +1098 -0
- package/src/ui/inputBuffer.test.ts +151 -0
- package/src/ui/inputBuffer.ts +203 -0
- package/src/ui/layout.test.ts +145 -0
- package/src/ui/layout.ts +287 -0
- package/src/ui/modeDisplay.test.ts +42 -0
- package/src/ui/modeDisplay.ts +52 -0
- package/src/ui/outputPipeline.ts +64 -0
- package/src/ui/progressEntries.ts +156 -0
- package/src/ui/runActivityView.test.ts +89 -0
- package/src/ui/runActivityView.ts +37 -0
- package/src/ui/runLifecycleView.test.tsx +237 -0
- package/src/ui/slashCommands.ts +41 -0
- package/src/ui/statusRenderIsolation.test.tsx +654 -0
- package/src/ui/terminalAnswerFormat.test.ts +19 -0
- package/src/ui/terminalAnswerFormat.ts +128 -0
- package/src/ui/textLayout.test.ts +18 -0
- package/src/ui/textLayout.ts +338 -0
- package/src/ui/theme.tsx +395 -0
- package/src/ui/themeFlow.test.ts +53 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timelineMeasure.ts +3088 -0
- package/src/ui/timelineMeasureCache.test.ts +986 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { buildClaudeSpawnSpec, resolveClaudeExecutable } from "../executables/claudeExecutable.js";
|
|
4
|
+
import { runCommand } from "../process/CommandRunner.js";
|
|
5
|
+
import type { CommandResult } from "../process/CommandRunner.js";
|
|
6
|
+
import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
|
|
7
|
+
import { ANTHROPIC_FALLBACK_MODELS } from "./models.js";
|
|
8
|
+
import { CLAUDE_CODE_EFFORT_IDS, getClaudeCodeEffortLevels } from "./reasoning.js";
|
|
9
|
+
import type { ProviderModel } from "./types.js";
|
|
10
|
+
|
|
11
|
+
type CommandRunner = typeof runCommand;
|
|
12
|
+
export type ClaudeCodeModelSource = "claude-code" | "config" | "settings" | "fallback";
|
|
13
|
+
|
|
14
|
+
export interface ClaudeCodeAuthInfo {
|
|
15
|
+
loggedIn: boolean;
|
|
16
|
+
authMethod?: string;
|
|
17
|
+
apiProvider?: string;
|
|
18
|
+
subscriptionType?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ClaudeCodeModel {
|
|
22
|
+
label: string;
|
|
23
|
+
family: string;
|
|
24
|
+
value: string;
|
|
25
|
+
canonicalId: string;
|
|
26
|
+
source: ClaudeCodeModelSource;
|
|
27
|
+
effortLevels: readonly string[];
|
|
28
|
+
defaultEffort: string;
|
|
29
|
+
effortSource: ClaudeCodeModelSource;
|
|
30
|
+
effortVerified: boolean;
|
|
31
|
+
description?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ClaudeCodeCapabilityDiscovery {
|
|
35
|
+
provider: "anthropic";
|
|
36
|
+
backend: "claude-code-cli";
|
|
37
|
+
resolvedCommand: string;
|
|
38
|
+
auth: ClaudeCodeAuthInfo;
|
|
39
|
+
models: ClaudeCodeModel[];
|
|
40
|
+
modelSource: ClaudeCodeModelSource;
|
|
41
|
+
discoveredAt: string;
|
|
42
|
+
settings?: {
|
|
43
|
+
path: string;
|
|
44
|
+
model?: string;
|
|
45
|
+
effortLevel?: string;
|
|
46
|
+
availableModels?: readonly string[];
|
|
47
|
+
};
|
|
48
|
+
diagnostics?: Record<string, string | number | boolean | null>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface DiscoverClaudeCodeCapabilitiesOptions {
|
|
52
|
+
cwd: string;
|
|
53
|
+
runCommandImpl?: CommandRunner;
|
|
54
|
+
configuredPath?: string | null;
|
|
55
|
+
settingsPath?: string | null;
|
|
56
|
+
now?: () => Date;
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface ClaudeSettingsInfo {
|
|
61
|
+
path: string;
|
|
62
|
+
model?: string;
|
|
63
|
+
effortLevel?: string;
|
|
64
|
+
availableModels?: readonly string[];
|
|
65
|
+
models?: readonly ClaudeCodeModel[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
69
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readString(value: unknown): string | undefined {
|
|
73
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readStringArray(value: unknown): string[] | undefined {
|
|
77
|
+
if (!Array.isArray(value)) return undefined;
|
|
78
|
+
const result = value.map(readString).filter((item): item is string => Boolean(item));
|
|
79
|
+
return result.length > 0 ? result : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function parseClaudeAuthStatus(stdout: string): ClaudeCodeAuthInfo | null {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(stdout.trim()) as unknown;
|
|
85
|
+
if (!isRecord(parsed)) return null;
|
|
86
|
+
return {
|
|
87
|
+
loggedIn: parsed["loggedIn"] === true,
|
|
88
|
+
authMethod: readString(parsed["authMethod"]),
|
|
89
|
+
apiProvider: readString(parsed["apiProvider"]),
|
|
90
|
+
subscriptionType: readString(parsed["subscriptionType"]),
|
|
91
|
+
};
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function modelFamilyFromValue(value: string): string {
|
|
98
|
+
const normalized = value.toLowerCase();
|
|
99
|
+
if (normalized.includes("opus")) return "opus";
|
|
100
|
+
if (normalized.includes("haiku")) return "haiku";
|
|
101
|
+
return "sonnet";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function fallbackModelByFamily(family: string): ProviderModel | undefined {
|
|
105
|
+
return ANTHROPIC_FALLBACK_MODELS.find((model) => model.family === family || model.modelId === family);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function fallbackEffortIds(family: string): string[] {
|
|
109
|
+
const fallback = fallbackModelByFamily(family);
|
|
110
|
+
return fallback?.supportedReasoningLevels?.map((level) => level.id) ?? ["low", "medium", "high"];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fallbackDefaultEffort(family: string): string {
|
|
114
|
+
return fallbackModelByFamily(family)?.defaultReasoningLevel ?? "medium";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function labelFromModelValue(value: string, fallbackLabel?: string): string {
|
|
118
|
+
if (fallbackLabel) return fallbackLabel;
|
|
119
|
+
const family = modelFamilyFromValue(value);
|
|
120
|
+
const fallback = fallbackModelByFamily(family);
|
|
121
|
+
return fallback?.label ?? value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeEffortIds(value: unknown, fallbackFamily: string): string[] {
|
|
125
|
+
const fromArray = readStringArray(value)
|
|
126
|
+
?.filter((item) => CLAUDE_CODE_EFFORT_IDS.has(item));
|
|
127
|
+
return fromArray && fromArray.length > 0 ? fromArray : fallbackEffortIds(fallbackFamily);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function normalizeClaudeCodeModel(raw: unknown, source: ClaudeCodeModelSource): ClaudeCodeModel | null {
|
|
131
|
+
if (typeof raw === "string") {
|
|
132
|
+
const value = raw.trim();
|
|
133
|
+
if (!value) return null;
|
|
134
|
+
const family = modelFamilyFromValue(value);
|
|
135
|
+
const fallback = fallbackModelByFamily(family);
|
|
136
|
+
return {
|
|
137
|
+
label: labelFromModelValue(value, fallback?.label),
|
|
138
|
+
family,
|
|
139
|
+
value: fallback?.modelId ?? value,
|
|
140
|
+
canonicalId: fallback?.canonicalId ?? value,
|
|
141
|
+
source,
|
|
142
|
+
effortLevels: fallbackEffortIds(family),
|
|
143
|
+
defaultEffort: fallbackDefaultEffort(family),
|
|
144
|
+
effortSource: "fallback",
|
|
145
|
+
effortVerified: false,
|
|
146
|
+
description: source === "settings" ? "Claude settings model" : undefined,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!isRecord(raw)) return null;
|
|
151
|
+
const value = readString(raw.value ?? raw.model ?? raw.modelId ?? raw.id);
|
|
152
|
+
if (!value) return null;
|
|
153
|
+
const family = readString(raw.family) ?? modelFamilyFromValue(value);
|
|
154
|
+
const fallback = fallbackModelByFamily(family);
|
|
155
|
+
// Accept both camelCase and snake_case effort field names for compatibility.
|
|
156
|
+
const rawEffortArray = raw.effortLevels ?? raw.effort_levels ?? raw.supportedEfforts ?? raw.supported_efforts;
|
|
157
|
+
const effortLevels = normalizeEffortIds(rawEffortArray, family);
|
|
158
|
+
const defaultEffort = readString(raw.defaultEffort ?? raw.default_effort ?? raw.effortLevel ?? raw.effort_level)
|
|
159
|
+
?? fallbackDefaultEffort(family);
|
|
160
|
+
const hasRawEffortArray = Array.isArray(rawEffortArray);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
label: readString(raw.label ?? raw.displayName ?? raw.display_name) ?? labelFromModelValue(value, fallback?.label),
|
|
164
|
+
family,
|
|
165
|
+
value: value === fallback?.canonicalId ? fallback.modelId : value,
|
|
166
|
+
canonicalId: readString(raw.canonicalId ?? raw.canonical_id) ?? fallback?.canonicalId ?? value,
|
|
167
|
+
source,
|
|
168
|
+
effortLevels,
|
|
169
|
+
defaultEffort: effortLevels.includes(defaultEffort) ? defaultEffort : effortLevels[0] ?? "medium",
|
|
170
|
+
effortSource: hasRawEffortArray ? source : "fallback",
|
|
171
|
+
effortVerified: source === "claude-code" && hasRawEffortArray,
|
|
172
|
+
description: readString(raw.description),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function dedupeModels(models: ClaudeCodeModel[]): ClaudeCodeModel[] {
|
|
177
|
+
const seen = new Set<string>();
|
|
178
|
+
const result: ClaudeCodeModel[] = [];
|
|
179
|
+
for (const model of models) {
|
|
180
|
+
const key = model.value.toLowerCase();
|
|
181
|
+
if (seen.has(key)) continue;
|
|
182
|
+
seen.add(key);
|
|
183
|
+
result.push(model);
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function fallbackClaudeModels(): ClaudeCodeModel[] {
|
|
189
|
+
return ANTHROPIC_FALLBACK_MODELS.map((model) => ({
|
|
190
|
+
label: model.label,
|
|
191
|
+
family: model.family ?? model.modelId,
|
|
192
|
+
value: model.modelId,
|
|
193
|
+
canonicalId: model.canonicalId ?? model.modelId,
|
|
194
|
+
source: "fallback",
|
|
195
|
+
effortLevels: model.supportedReasoningLevels?.map((level) => level.id) ?? [],
|
|
196
|
+
defaultEffort: model.defaultReasoningLevel ?? "medium",
|
|
197
|
+
effortSource: "fallback",
|
|
198
|
+
effortVerified: false,
|
|
199
|
+
description: model.description ?? undefined,
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function applyAvailableModelAllowlist(models: ClaudeCodeModel[], allowlist: readonly string[] | undefined): ClaudeCodeModel[] {
|
|
204
|
+
if (!allowlist || allowlist.length === 0) return models;
|
|
205
|
+
const allowed = new Set(allowlist.map((item) => item.toLowerCase()));
|
|
206
|
+
const filtered = models.filter((model) =>
|
|
207
|
+
allowed.has(model.value.toLowerCase()) ||
|
|
208
|
+
allowed.has(model.family.toLowerCase()) ||
|
|
209
|
+
allowed.has(model.canonicalId.toLowerCase()) ||
|
|
210
|
+
allowed.has(model.label.toLowerCase())
|
|
211
|
+
);
|
|
212
|
+
return filtered.length > 0 ? filtered : models;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function defaultClaudeSettingsPath(): string | null {
|
|
216
|
+
const home = process.env.USERPROFILE ?? process.env.HOME;
|
|
217
|
+
return home ? join(home, ".claude", "settings.json") : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function readClaudeSettings(settingsPath: string | null | undefined): ClaudeSettingsInfo | null {
|
|
221
|
+
const path = settingsPath === undefined ? defaultClaudeSettingsPath() : settingsPath;
|
|
222
|
+
if (!path || !existsSync(path)) return null;
|
|
223
|
+
try {
|
|
224
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
|
|
225
|
+
if (!isRecord(parsed)) return null;
|
|
226
|
+
// Accept both camelCase and snake_case field names — Claude settings.json has used both across versions.
|
|
227
|
+
const availableModels = readStringArray(parsed.availableModels ?? parsed.available_models);
|
|
228
|
+
const modelOverrides = parsed.modelOverrides ?? parsed.model_overrides;
|
|
229
|
+
const overrideModels = isRecord(modelOverrides)
|
|
230
|
+
? Object.entries(modelOverrides)
|
|
231
|
+
.map(([key, value]) => normalizeClaudeCodeModel(
|
|
232
|
+
isRecord(value) ? { id: key, ...value } : key,
|
|
233
|
+
"settings",
|
|
234
|
+
))
|
|
235
|
+
.filter((model): model is ClaudeCodeModel => Boolean(model))
|
|
236
|
+
: [];
|
|
237
|
+
return {
|
|
238
|
+
path,
|
|
239
|
+
model: readString(parsed.model ?? parsed.defaultModel ?? parsed.default_model),
|
|
240
|
+
effortLevel: readString(parsed.effortLevel ?? parsed.effort_level),
|
|
241
|
+
availableModels,
|
|
242
|
+
models: overrideModels,
|
|
243
|
+
};
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function runClaudeCommand(
|
|
250
|
+
executable: string,
|
|
251
|
+
args: string[],
|
|
252
|
+
cwd: string,
|
|
253
|
+
runCommandImpl: CommandRunner,
|
|
254
|
+
timeoutMs: number,
|
|
255
|
+
): Promise<CommandResult> {
|
|
256
|
+
const spawnSpec = buildClaudeSpawnSpec(executable, args);
|
|
257
|
+
return await runCommandImpl({
|
|
258
|
+
executable: spawnSpec.executable,
|
|
259
|
+
args: spawnSpec.args,
|
|
260
|
+
cwd,
|
|
261
|
+
timeoutMs,
|
|
262
|
+
}).result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function parseModelsFromJsonOutput(stdout: string): ClaudeCodeModel[] {
|
|
266
|
+
let parsed: unknown;
|
|
267
|
+
try {
|
|
268
|
+
parsed = JSON.parse(stdout.trim());
|
|
269
|
+
} catch {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
const rawModels = Array.isArray(parsed)
|
|
273
|
+
? parsed
|
|
274
|
+
: isRecord(parsed) && Array.isArray(parsed.models)
|
|
275
|
+
? parsed.models
|
|
276
|
+
: isRecord(parsed) && Array.isArray(parsed.data)
|
|
277
|
+
? parsed.data
|
|
278
|
+
: [];
|
|
279
|
+
return dedupeModels(
|
|
280
|
+
rawModels
|
|
281
|
+
.map((raw) => normalizeClaudeCodeModel(raw, "claude-code"))
|
|
282
|
+
.filter((model): model is ClaudeCodeModel => Boolean(model)),
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function candidateModelListArgs(helpOutput: string): string[][] {
|
|
287
|
+
const text = helpOutput.toLowerCase();
|
|
288
|
+
const candidates: string[][] = [];
|
|
289
|
+
if (/model\s+list/.test(text) && /--json/.test(text)) {
|
|
290
|
+
candidates.push(["model", "list", "--json"]);
|
|
291
|
+
}
|
|
292
|
+
if (/models\s+list/.test(text) && /--json/.test(text)) {
|
|
293
|
+
candidates.push(["models", "list", "--json"]);
|
|
294
|
+
}
|
|
295
|
+
if (/\bmodels\b/.test(text) && /--json/.test(text)) {
|
|
296
|
+
candidates.push(["models", "--json"]);
|
|
297
|
+
}
|
|
298
|
+
return candidates;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function discoverModelsFromClaudeHelp(
|
|
302
|
+
executable: string,
|
|
303
|
+
cwd: string,
|
|
304
|
+
runCommandImpl: CommandRunner,
|
|
305
|
+
timeoutMs: number,
|
|
306
|
+
): Promise<ClaudeCodeModel[]> {
|
|
307
|
+
const helpResults = await Promise.all([
|
|
308
|
+
runClaudeCommand(executable, ["--help"], cwd, runCommandImpl, timeoutMs),
|
|
309
|
+
runClaudeCommand(executable, ["model", "--help"], cwd, runCommandImpl, timeoutMs),
|
|
310
|
+
]);
|
|
311
|
+
const candidates = helpResults.flatMap((result) =>
|
|
312
|
+
result.status === "completed" && result.exitCode === 0
|
|
313
|
+
? candidateModelListArgs(`${result.stdout}\n${result.stderr}`)
|
|
314
|
+
: []
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
for (const args of candidates) {
|
|
318
|
+
const result = await runClaudeCommand(executable, args, cwd, runCommandImpl, timeoutMs);
|
|
319
|
+
if (result.status !== "completed" || result.exitCode !== 0) continue;
|
|
320
|
+
const models = parseModelsFromJsonOutput(result.stdout);
|
|
321
|
+
if (models.length > 0) return models;
|
|
322
|
+
}
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function claudeCodeModelsToProviderModels(models: readonly ClaudeCodeModel[]): readonly ProviderModel[] {
|
|
327
|
+
return models.map((model) => ({
|
|
328
|
+
id: model.value,
|
|
329
|
+
modelId: model.value,
|
|
330
|
+
label: model.label,
|
|
331
|
+
description: model.description ?? (
|
|
332
|
+
model.source === "fallback"
|
|
333
|
+
? `${model.label} - Fallback defaults${model.effortVerified ? "" : "; effort metadata unverified"}`
|
|
334
|
+
: `${model.label} - ${model.source === "claude-code" ? "Discovered from Claude Code" : "From Claude settings"}`
|
|
335
|
+
),
|
|
336
|
+
defaultReasoningLevel: model.defaultEffort,
|
|
337
|
+
supportedReasoningLevels: getClaudeCodeEffortLevels(model.effortLevels),
|
|
338
|
+
source: model.source,
|
|
339
|
+
canonicalId: model.canonicalId,
|
|
340
|
+
family: model.family,
|
|
341
|
+
effortSource: model.effortSource,
|
|
342
|
+
effortVerified: model.effortVerified,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export async function discoverClaudeCodeCapabilities(
|
|
347
|
+
options: DiscoverClaudeCodeCapabilitiesOptions,
|
|
348
|
+
): Promise<ClaudeCodeCapabilityDiscovery> {
|
|
349
|
+
const runCommandImpl = options.runCommandImpl ?? runCommand;
|
|
350
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
351
|
+
const resolvedCommand = await resolveClaudeExecutable({
|
|
352
|
+
runCommandImpl: options.runCommandImpl,
|
|
353
|
+
cwd: options.cwd,
|
|
354
|
+
configuredPath: options.configuredPath,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const authResult = await runClaudeCommand(resolvedCommand, ["auth", "status"], options.cwd, runCommandImpl, timeoutMs);
|
|
358
|
+
const authJson = authResult.status === "completed" && authResult.exitCode === 0
|
|
359
|
+
? parseClaudeAuthStatus(authResult.stdout)
|
|
360
|
+
: null;
|
|
361
|
+
const auth: ClaudeCodeAuthInfo = authJson?.loggedIn === true
|
|
362
|
+
? authJson
|
|
363
|
+
: { loggedIn: false };
|
|
364
|
+
|
|
365
|
+
const settings = readClaudeSettings(options.settingsPath);
|
|
366
|
+
const discoveredModels = await discoverModelsFromClaudeHelp(resolvedCommand, options.cwd, runCommandImpl, timeoutMs);
|
|
367
|
+
|
|
368
|
+
let modelSource: ClaudeCodeModelSource = "fallback";
|
|
369
|
+
let models: ClaudeCodeModel[] = fallbackClaudeModels();
|
|
370
|
+
|
|
371
|
+
if (discoveredModels.length > 0) {
|
|
372
|
+
modelSource = "claude-code";
|
|
373
|
+
models = discoveredModels;
|
|
374
|
+
} else if (settings?.models && settings.models.length > 0) {
|
|
375
|
+
modelSource = "settings";
|
|
376
|
+
models = [...settings.models];
|
|
377
|
+
} else if (settings?.availableModels && settings.availableModels.length > 0) {
|
|
378
|
+
modelSource = "settings";
|
|
379
|
+
models = settings.availableModels
|
|
380
|
+
.map((model) => normalizeClaudeCodeModel(model, "settings"))
|
|
381
|
+
.filter((model): model is ClaudeCodeModel => Boolean(model));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (settings?.model && !models.some((model) =>
|
|
385
|
+
model.value === settings.model || model.canonicalId === settings.model || model.family === settings.model
|
|
386
|
+
)) {
|
|
387
|
+
const settingsModel = normalizeClaudeCodeModel(settings.model, "settings");
|
|
388
|
+
if (settingsModel) {
|
|
389
|
+
models = [settingsModel, ...models];
|
|
390
|
+
if (modelSource === "fallback") modelSource = "settings";
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
models = applyAvailableModelAllowlist(dedupeModels(models), settings?.availableModels);
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
provider: "anthropic",
|
|
398
|
+
backend: "claude-code-cli",
|
|
399
|
+
resolvedCommand,
|
|
400
|
+
auth,
|
|
401
|
+
models,
|
|
402
|
+
modelSource,
|
|
403
|
+
discoveredAt: (options.now?.() ?? new Date()).toISOString(),
|
|
404
|
+
...(settings ? {
|
|
405
|
+
settings: {
|
|
406
|
+
path: settings.path,
|
|
407
|
+
...(settings.model ? { model: settings.model } : {}),
|
|
408
|
+
...(settings.effortLevel ? { effortLevel: settings.effortLevel } : {}),
|
|
409
|
+
...(settings.availableModels ? { availableModels: settings.availableModels } : {}),
|
|
410
|
+
},
|
|
411
|
+
} : {}),
|
|
412
|
+
diagnostics: {
|
|
413
|
+
authExitCode: authResult.exitCode,
|
|
414
|
+
authStatus: authResult.status,
|
|
415
|
+
authJsonParsed: authJson !== null,
|
|
416
|
+
loggedIn: auth.loggedIn,
|
|
417
|
+
modelSource,
|
|
418
|
+
settingsPath: settings?.path ?? null,
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function getClaudeModelDefaultEffort(modelId: string, models: readonly ProviderModel[]): string {
|
|
424
|
+
const normalized = modelId.toLowerCase();
|
|
425
|
+
const model = models.find((item) =>
|
|
426
|
+
item.modelId.toLowerCase() === normalized ||
|
|
427
|
+
item.id.toLowerCase() === normalized ||
|
|
428
|
+
item.family?.toLowerCase() === normalized ||
|
|
429
|
+
item.canonicalId?.toLowerCase() === normalized ||
|
|
430
|
+
(item.family ? normalized.includes(item.family.toLowerCase()) : false)
|
|
431
|
+
);
|
|
432
|
+
return model?.defaultReasoningLevel ?? "medium";
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function modelSupportsClaudeEffort(modelId: string, effort: string | null | undefined, models: readonly ProviderModel[]): boolean {
|
|
436
|
+
if (!effort) return false;
|
|
437
|
+
const normalized = modelId.toLowerCase();
|
|
438
|
+
const model = models.find((item) =>
|
|
439
|
+
item.modelId.toLowerCase() === normalized ||
|
|
440
|
+
item.id.toLowerCase() === normalized ||
|
|
441
|
+
item.family?.toLowerCase() === normalized ||
|
|
442
|
+
item.canonicalId?.toLowerCase() === normalized ||
|
|
443
|
+
(item.family ? normalized.includes(item.family.toLowerCase()) : false)
|
|
444
|
+
);
|
|
445
|
+
return Boolean(model?.supportedReasoningLevels?.some((level) => level.id === effort));
|
|
446
|
+
}
|