@golba98/codexa 1.0.1 → 1.0.3
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 +391 -94
- package/package.json +14 -8
- package/src/app.tsx +196 -63
- package/src/commands/handler.ts +9 -4
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +10 -0
- package/src/config/layeredConfig.ts +0 -0
- package/src/config/persistence.ts +51 -0
- package/src/config/runtimeConfig.ts +4 -0
- package/src/config/settings.ts +6 -13
- package/src/config/toml-serialize.ts +0 -0
- package/src/config/trustStore.ts +0 -0
- package/src/config/updateCheckCache.ts +63 -0
- package/src/core/attachments.ts +0 -0
- package/src/core/auth/codexAuth.ts +0 -0
- package/src/core/channel.ts +23 -0
- package/src/core/cleanupFastFail.ts +0 -0
- package/src/core/clipboard.ts +0 -0
- package/src/core/codex.ts +0 -0
- package/src/core/codexExecArgs.ts +0 -0
- package/src/core/codexLaunch.ts +0 -0
- package/src/core/codexPrompt.ts +0 -0
- package/src/core/executables/claudeExecutable.ts +0 -0
- package/src/core/executables/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +61 -43
- package/src/core/executables/geminiExecutable.ts +0 -0
- package/src/core/githubDiagnostics.ts +0 -0
- package/src/core/hollowResponseFormat.ts +0 -0
- package/src/core/inputDebug.ts +0 -0
- package/src/core/launchContext.ts +33 -33
- package/src/core/models/codexCapabilities.ts +0 -0
- package/src/core/models/codexModelCapabilities.ts +0 -0
- package/src/core/models/modelSpecs.ts +0 -0
- package/src/core/perf/profiler.ts +0 -0
- package/src/core/perf/renderDebug.ts +0 -0
- package/src/core/planStorage.ts +0 -0
- package/src/core/process/CommandRunner.ts +0 -0
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/projectInstructions.ts +0 -0
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/providerLauncher/registry.ts +0 -0
- package/src/core/providerLauncher/types.ts +0 -0
- package/src/core/providerLauncher/workspaceConfig.ts +0 -0
- package/src/core/providerRuntime/anthropic.ts +0 -0
- package/src/core/providerRuntime/capabilityProfile.ts +0 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
- package/src/core/providerRuntime/contextMetadata.ts +0 -0
- package/src/core/providerRuntime/gemini.ts +0 -0
- package/src/core/providerRuntime/lmstudio.ts +0 -0
- package/src/core/providerRuntime/local.ts +0 -0
- package/src/core/providerRuntime/models.ts +0 -0
- package/src/core/providerRuntime/reasoning.ts +0 -0
- package/src/core/providerRuntime/registry.ts +0 -0
- package/src/core/providerRuntime/types.ts +0 -0
- package/src/core/providers/codexJsonStream.ts +0 -0
- package/src/core/providers/codexSubprocess.ts +0 -0
- package/src/core/providers/codexTranscript.ts +0 -0
- package/src/core/providers/openaiNative.ts +0 -0
- package/src/core/providers/registry.ts +0 -0
- package/src/core/providers/types.ts +0 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +0 -0
- package/src/core/terminal/terminalControl.ts +0 -0
- package/src/core/terminal/terminalSanitize.ts +0 -0
- package/src/core/terminal/terminalSelection.ts +0 -0
- package/src/core/terminal/terminalTitle.ts +0 -0
- package/src/core/updateCheck.ts +188 -0
- package/src/core/workspaceActivity.ts +0 -0
- package/src/core/workspaceGuard.ts +0 -0
- package/src/core/workspaceRoot.ts +0 -0
- package/src/exec.ts +0 -0
- package/src/headless/execArgs.ts +1 -0
- package/src/headless/execRunner.ts +0 -0
- package/src/index.tsx +13 -9
- package/src/session/appSession.ts +0 -0
- package/src/session/chatLifecycle.ts +0 -0
- package/src/session/liveRenderScheduler.ts +0 -0
- package/src/session/planFlow.ts +0 -0
- package/src/session/planTranscript.ts +0 -0
- package/src/session/promptRunSchedule.ts +0 -0
- package/src/session/types.ts +2 -1
- package/src/test/runtimeTestUtils.ts +0 -0
- package/src/types/react-dom.d.ts +0 -0
- package/src/ui/ActionRequiredBlock.tsx +5 -5
- package/src/ui/ActivityBars.tsx +3 -3
- package/src/ui/ActivityIndicator.tsx +6 -6
- package/src/ui/AgentBlock.tsx +6 -6
- package/src/ui/AnimatedStatusText.tsx +1 -1
- package/src/ui/AppShell.tsx +13 -5
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BackendPicker.tsx +0 -0
- package/src/ui/BottomComposer.tsx +117 -78
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModePicker.tsx +0 -0
- package/src/ui/ModelPicker.tsx +0 -0
- package/src/ui/ModelPickerScreen.tsx +10 -10
- package/src/ui/ModelReasoningPicker.tsx +14 -14
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PermissionsPanel.tsx +0 -0
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +8 -8
- package/src/ui/ProviderPicker.tsx +18 -18
- package/src/ui/ReasoningPicker.tsx +0 -0
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/SelectionPanel.tsx +4 -4
- package/src/ui/SettingsPanel.tsx +8 -8
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/StaticIntroItem.tsx +0 -0
- package/src/ui/StaticTranscriptItem.tsx +0 -0
- package/src/ui/TextEntryPanel.tsx +10 -10
- package/src/ui/ThemePicker.tsx +0 -0
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +21 -17
- package/src/ui/TopHeader.tsx +421 -257
- package/src/ui/TurnGroup.tsx +31 -31
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +203 -0
- package/src/ui/busyStatusAnimation.ts +0 -0
- package/src/ui/commandNormalize.ts +0 -0
- package/src/ui/diffRenderer.ts +0 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/inputBuffer.ts +0 -0
- package/src/ui/layout.ts +1 -1
- package/src/ui/logoVariants.ts +107 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/outputPipeline.ts +0 -0
- package/src/ui/progressEntries.ts +0 -0
- package/src/ui/runActivityView.ts +0 -0
- package/src/ui/runtimeDisplay.ts +100 -0
- package/src/ui/slashCommands.ts +0 -0
- package/src/ui/terminalAnswerFormat.ts +0 -0
- package/src/ui/textLayout.ts +0 -0
- package/src/ui/theme.tsx +274 -395
- package/src/ui/themeFlow.ts +0 -0
- package/src/ui/timelineMeasure.ts +29 -19
- package/src/ui/useThrottledValue.ts +0 -0
- package/scripts/audit-codexa-capabilities.mjs +0 -466
- package/scripts/smoke-terminal-bench.mjs +0 -35
- package/src/appRenderStability.test.ts +0 -131
- package/src/commands/handler.test.ts +0 -643
- package/src/config/launchArgs.test.ts +0 -158
- package/src/config/layeredConfig.test.ts +0 -143
- package/src/config/persistence.test.ts +0 -110
- package/src/config/runtimeConfig.test.ts +0 -218
- package/src/config/settings.test.ts +0 -155
- package/src/config/trustStore.test.ts +0 -29
- package/src/core/attachments.test.ts +0 -155
- package/src/core/auth/codexAuth.test.ts +0 -68
- package/src/core/cleanupFastFail.test.ts +0 -76
- package/src/core/codexExecArgs.test.ts +0 -195
- package/src/core/codexLaunch.test.ts +0 -205
- package/src/core/codexPrompt.test.ts +0 -252
- package/src/core/executables/codexExecutable.test.ts +0 -212
- package/src/core/executables/executableResolver.test.ts +0 -129
- package/src/core/executables/geminiExecutable.test.ts +0 -116
- package/src/core/executables/pathSanityScan.test.ts +0 -47
- package/src/core/githubDiagnostics.test.ts +0 -92
- package/src/core/hollowResponseFormat.test.ts +0 -58
- package/src/core/launchContext.test.ts +0 -157
- package/src/core/models/codexCapabilities.test.ts +0 -45
- package/src/core/models/codexModelCapabilities.test.ts +0 -246
- package/src/core/models/modelSpecs.test.ts +0 -283
- package/src/core/perf/renderDebug.test.ts +0 -230
- package/src/core/planStorage.test.ts +0 -143
- package/src/core/process/CommandRunner.test.ts +0 -105
- package/src/core/projectInstructions.test.ts +0 -50
- package/src/core/providerLauncher/launcher.test.ts +0 -238
- package/src/core/providerLauncher/registry.test.ts +0 -324
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
- package/src/core/providerRuntime/anthropic.test.ts +0 -1120
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
- package/src/core/providerRuntime/gemini.test.ts +0 -437
- package/src/core/providerRuntime/lmstudio.test.ts +0 -168
- package/src/core/providerRuntime/local.test.ts +0 -787
- package/src/core/providerRuntime/registry.test.ts +0 -233
- package/src/core/providers/codexJsonStream.test.ts +0 -148
- package/src/core/providers/codexSubprocess.test.ts +0 -68
- package/src/core/providers/codexTranscript.test.ts +0 -284
- package/src/core/terminal/terminalCapabilities.test.ts +0 -93
- package/src/core/terminal/terminalControl.test.ts +0 -75
- package/src/core/terminal/terminalSanitize.test.ts +0 -22
- package/src/core/terminal/terminalSelection.test.ts +0 -42
- package/src/core/terminal/terminalTitle.test.ts +0 -328
- package/src/core/workspaceActivity.test.ts +0 -163
- package/src/core/workspaceGuard.test.ts +0 -151
- package/src/core/workspaceRoot.test.ts +0 -23
- package/src/exec.test.ts +0 -13
- package/src/headless/execArgs.test.ts +0 -147
- package/src/headless/execRunner.test.ts +0 -434
- package/src/index.test.tsx +0 -618
- package/src/session/appSession.test.ts +0 -897
- package/src/session/chatLifecycle.test.ts +0 -64
- package/src/session/liveRenderScheduler.test.ts +0 -201
- package/src/session/planFlow.test.ts +0 -103
- package/src/session/planTranscript.test.ts +0 -65
- package/src/session/promptRunSchedule.test.ts +0 -36
- package/src/ui/ActivityIndicator.test.tsx +0 -58
- package/src/ui/AgentBlock.test.ts +0 -6
- package/src/ui/AnimatedStatusText.test.ts +0 -16
- package/src/ui/AppShell.test.tsx +0 -1739
- package/src/ui/AttachmentImportPanel.test.tsx +0 -204
- package/src/ui/BottomComposer.test.ts +0 -674
- package/src/ui/CodexLogo.tsx +0 -55
- package/src/ui/Markdown.test.ts +0 -157
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
- package/src/ui/ModelPickerScreen.test.tsx +0 -99
- package/src/ui/ModelPickerState.test.tsx +0 -151
- package/src/ui/ModelReasoningPicker.test.tsx +0 -447
- package/src/ui/PlanReviewPanel.test.tsx +0 -267
- package/src/ui/PromptCardBorder.test.tsx +0 -161
- package/src/ui/ProviderPicker.test.tsx +0 -289
- package/src/ui/ProviderShortcut.test.tsx +0 -143
- package/src/ui/SettingsPanel.test.tsx +0 -233
- package/src/ui/Timeline.test.ts +0 -2067
- package/src/ui/TimelineNavigation.test.tsx +0 -201
- package/src/ui/TopHeader.test.tsx +0 -239
- package/src/ui/TurnGroup.test.tsx +0 -365
- package/src/ui/busyStatusAnimation.test.ts +0 -30
- package/src/ui/commandNormalize.test.ts +0 -142
- package/src/ui/diffRenderer.test.ts +0 -102
- package/src/ui/focusFlow.test.tsx +0 -1098
- package/src/ui/inputBuffer.test.ts +0 -151
- package/src/ui/layout.test.ts +0 -145
- package/src/ui/modeDisplay.test.ts +0 -42
- package/src/ui/runActivityView.test.ts +0 -89
- package/src/ui/runLifecycleView.test.tsx +0 -237
- package/src/ui/statusRenderIsolation.test.tsx +0 -654
- package/src/ui/terminalAnswerFormat.test.ts +0 -19
- package/src/ui/textLayout.test.ts +0 -18
- package/src/ui/themeFlow.test.ts +0 -53
- package/src/ui/timelineMeasureCache.test.ts +0 -986
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
clearCodexModelCapabilityCache,
|
|
5
|
-
createFallbackModelCapabilities,
|
|
6
|
-
findModelCapability,
|
|
7
|
-
formatModelCapabilitiesList,
|
|
8
|
-
getCodexModelCapabilities,
|
|
9
|
-
getPreferredModelFromCapabilities,
|
|
10
|
-
normalizeCodexModelListResponses,
|
|
11
|
-
normalizeReasoningForModelCapabilities,
|
|
12
|
-
} from "./codexModelCapabilities.js";
|
|
13
|
-
|
|
14
|
-
const SAMPLE_RESPONSE = {
|
|
15
|
-
data: [
|
|
16
|
-
{
|
|
17
|
-
id: "gpt-5.4",
|
|
18
|
-
model: "gpt-5.4",
|
|
19
|
-
displayName: "gpt-5.4",
|
|
20
|
-
description: "Frontier model",
|
|
21
|
-
hidden: false,
|
|
22
|
-
isDefault: true,
|
|
23
|
-
defaultReasoningEffort: "medium",
|
|
24
|
-
supportedReasoningEfforts: [
|
|
25
|
-
{ reasoningEffort: "low", description: "Fast" },
|
|
26
|
-
{ reasoningEffort: "medium", description: "Balanced" },
|
|
27
|
-
{ reasoningEffort: "high", description: "Deep" },
|
|
28
|
-
{ reasoningEffort: "xhigh", description: "Deepest" },
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: "gpt-5.1-codex-mini",
|
|
33
|
-
model: "gpt-5.1-codex-mini",
|
|
34
|
-
displayName: "Codex Mini",
|
|
35
|
-
description: "Small model",
|
|
36
|
-
hidden: false,
|
|
37
|
-
isDefault: false,
|
|
38
|
-
defaultReasoningEffort: "medium",
|
|
39
|
-
supportedReasoningEfforts: [
|
|
40
|
-
{ reasoningEffort: "medium", description: "Dynamic" },
|
|
41
|
-
{ reasoningEffort: "high", description: "Deep" },
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
nextCursor: null,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
test("normalizes model/list responses with per-model reasoning metadata", () => {
|
|
49
|
-
const capabilities = normalizeCodexModelListResponses([SAMPLE_RESPONSE], {
|
|
50
|
-
discoveredAt: 123,
|
|
51
|
-
executable: "codex.cmd",
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
assert.equal(capabilities.status, "ready");
|
|
55
|
-
assert.equal(capabilities.source, "runtime");
|
|
56
|
-
assert.equal(capabilities.executable, "codex.cmd");
|
|
57
|
-
assert.equal(capabilities.models.length, 2);
|
|
58
|
-
assert.equal(capabilities.models[0]?.model, "gpt-5.4");
|
|
59
|
-
assert.equal(capabilities.models[0]?.reasoningLevelCount, 4);
|
|
60
|
-
assert.deepEqual(capabilities.models[0]?.supportedReasoningLevels?.map((item) => item.id), [
|
|
61
|
-
"low",
|
|
62
|
-
"medium",
|
|
63
|
-
"high",
|
|
64
|
-
"xhigh",
|
|
65
|
-
]);
|
|
66
|
-
assert.equal(capabilities.models[1]?.label, "Codex Mini");
|
|
67
|
-
assert.equal(capabilities.models[1]?.reasoningLevelCount, 2);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("normalization rejects empty or malformed discovery output", () => {
|
|
71
|
-
assert.throws(
|
|
72
|
-
() => normalizeCodexModelListResponses([{ data: [] }]),
|
|
73
|
-
/no usable models/i,
|
|
74
|
-
);
|
|
75
|
-
assert.throws(
|
|
76
|
-
() => normalizeCodexModelListResponses([{ data: [{ id: "", model: "" }] }]),
|
|
77
|
-
/no usable models/i,
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("fallback keeps models but does not invent reasoning support", () => {
|
|
82
|
-
const capabilities = createFallbackModelCapabilities(new Error("offline"), {
|
|
83
|
-
discoveredAt: 456,
|
|
84
|
-
executable: "codex.cmd",
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
assert.equal(capabilities.status, "fallback");
|
|
88
|
-
assert.equal(capabilities.error, "offline");
|
|
89
|
-
assert.ok(capabilities.models.length > 0);
|
|
90
|
-
assert.equal(capabilities.models[0]?.available, false);
|
|
91
|
-
assert.equal(capabilities.models[0]?.supportedReasoningLevels, null);
|
|
92
|
-
assert.equal(capabilities.models[0]?.reasoningLevelCount, null);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("normalizes reasoning by keeping valid values and clamping invalid ones", () => {
|
|
96
|
-
const capabilities = normalizeCodexModelListResponses([SAMPLE_RESPONSE]);
|
|
97
|
-
|
|
98
|
-
assert.equal(
|
|
99
|
-
normalizeReasoningForModelCapabilities("gpt-5.4", "high", capabilities),
|
|
100
|
-
"high",
|
|
101
|
-
);
|
|
102
|
-
assert.equal(
|
|
103
|
-
normalizeReasoningForModelCapabilities("gpt-5.1-codex-mini", "xhigh", capabilities),
|
|
104
|
-
"medium",
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("handles unavailable selected model with runtime default", () => {
|
|
109
|
-
const capabilities = normalizeCodexModelListResponses([SAMPLE_RESPONSE]);
|
|
110
|
-
|
|
111
|
-
assert.equal(getPreferredModelFromCapabilities(capabilities, "missing-model"), "gpt-5.4");
|
|
112
|
-
assert.equal(findModelCapability(capabilities, "Codex Mini"), null);
|
|
113
|
-
assert.equal(findModelCapability(capabilities, "gpt-5.1-codex-mini")?.model, "gpt-5.1-codex-mini");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("formats model list with dynamic reasoning counts", () => {
|
|
117
|
-
const capabilities = normalizeCodexModelListResponses([SAMPLE_RESPONSE]);
|
|
118
|
-
const message = formatModelCapabilitiesList(capabilities, "gpt-5.1-codex-mini");
|
|
119
|
-
|
|
120
|
-
assert.match(message, /Detected from Codex runtime/i);
|
|
121
|
-
assert.match(message, /gpt-5\.4.*4 reasoning levels/i);
|
|
122
|
-
assert.match(message, /gpt-5\.1-codex-mini.*2 reasoning levels.*\*/i);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("caches successful discovery and refreshes when forced", async () => {
|
|
126
|
-
clearCodexModelCapabilityCache();
|
|
127
|
-
let calls = 0;
|
|
128
|
-
|
|
129
|
-
const discover = async () => {
|
|
130
|
-
calls += 1;
|
|
131
|
-
return normalizeCodexModelListResponses([
|
|
132
|
-
{
|
|
133
|
-
data: [
|
|
134
|
-
{
|
|
135
|
-
id: `model-${calls}`,
|
|
136
|
-
model: `model-${calls}`,
|
|
137
|
-
displayName: `Model ${calls}`,
|
|
138
|
-
hidden: false,
|
|
139
|
-
isDefault: true,
|
|
140
|
-
defaultReasoningEffort: "medium",
|
|
141
|
-
supportedReasoningEfforts: [{ reasoningEffort: "medium", description: "Default" }],
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
|
-
], { discoveredAt: calls, executable: "codex" });
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const first = await getCodexModelCapabilities({
|
|
149
|
-
executable: "codex",
|
|
150
|
-
discover,
|
|
151
|
-
now: () => 100,
|
|
152
|
-
ttlMs: 1000,
|
|
153
|
-
});
|
|
154
|
-
const cached = await getCodexModelCapabilities({
|
|
155
|
-
executable: "codex",
|
|
156
|
-
discover,
|
|
157
|
-
now: () => 200,
|
|
158
|
-
ttlMs: 1000,
|
|
159
|
-
});
|
|
160
|
-
const refreshed = await getCodexModelCapabilities({
|
|
161
|
-
executable: "codex",
|
|
162
|
-
discover,
|
|
163
|
-
forceRefresh: true,
|
|
164
|
-
now: () => 300,
|
|
165
|
-
ttlMs: 1000,
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
assert.equal(calls, 2);
|
|
169
|
-
assert.equal(first.models[0]?.model, "model-1");
|
|
170
|
-
assert.equal(cached.models[0]?.model, "model-1");
|
|
171
|
-
assert.equal(refreshed.models[0]?.model, "model-2");
|
|
172
|
-
clearCodexModelCapabilityCache();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test("concurrent callers share a single in-flight discovery promise", async () => {
|
|
176
|
-
clearCodexModelCapabilityCache();
|
|
177
|
-
let calls = 0;
|
|
178
|
-
let resolveDiscover: (value: unknown) => void = () => {};
|
|
179
|
-
const gate = new Promise<unknown>((resolve) => {
|
|
180
|
-
resolveDiscover = resolve;
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const discover = async () => {
|
|
184
|
-
calls += 1;
|
|
185
|
-
await gate;
|
|
186
|
-
return normalizeCodexModelListResponses([
|
|
187
|
-
{
|
|
188
|
-
data: [
|
|
189
|
-
{
|
|
190
|
-
id: "model-a",
|
|
191
|
-
model: "model-a",
|
|
192
|
-
displayName: "Model A",
|
|
193
|
-
hidden: false,
|
|
194
|
-
isDefault: true,
|
|
195
|
-
defaultReasoningEffort: "medium",
|
|
196
|
-
supportedReasoningEfforts: [{ reasoningEffort: "medium", description: "Default" }],
|
|
197
|
-
},
|
|
198
|
-
],
|
|
199
|
-
},
|
|
200
|
-
], { discoveredAt: 1, executable: "codex" });
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
const firstPromise = getCodexModelCapabilities({
|
|
204
|
-
executable: "codex",
|
|
205
|
-
discover,
|
|
206
|
-
now: () => 100,
|
|
207
|
-
ttlMs: 1000,
|
|
208
|
-
});
|
|
209
|
-
const secondPromise = getCodexModelCapabilities({
|
|
210
|
-
executable: "codex",
|
|
211
|
-
discover,
|
|
212
|
-
now: () => 100,
|
|
213
|
-
ttlMs: 1000,
|
|
214
|
-
});
|
|
215
|
-
const thirdPromise = getCodexModelCapabilities({
|
|
216
|
-
executable: "codex",
|
|
217
|
-
discover,
|
|
218
|
-
now: () => 100,
|
|
219
|
-
ttlMs: 1000,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
resolveDiscover(undefined);
|
|
223
|
-
const [first, second, third] = await Promise.all([firstPromise, secondPromise, thirdPromise]);
|
|
224
|
-
|
|
225
|
-
assert.equal(calls, 1, "discovery should run exactly once when called concurrently");
|
|
226
|
-
assert.equal(first.models[0]?.model, "model-a");
|
|
227
|
-
assert.equal(second.models[0]?.model, "model-a");
|
|
228
|
-
assert.equal(third.models[0]?.model, "model-a");
|
|
229
|
-
clearCodexModelCapabilityCache();
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("falls back safely when discovery fails", async () => {
|
|
233
|
-
clearCodexModelCapabilityCache();
|
|
234
|
-
const capabilities = await getCodexModelCapabilities({
|
|
235
|
-
executable: "codex",
|
|
236
|
-
discover: async () => {
|
|
237
|
-
throw new Error("broken json");
|
|
238
|
-
},
|
|
239
|
-
now: () => 100,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
assert.equal(capabilities.status, "fallback");
|
|
243
|
-
assert.match(capabilities.error ?? "", /broken json/);
|
|
244
|
-
assert.equal(capabilities.models[0]?.supportedReasoningLevels, null);
|
|
245
|
-
clearCodexModelCapabilityCache();
|
|
246
|
-
});
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { mkdtempSync, readFileSync, rmSync } from "fs";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
import { tmpdir } from "os";
|
|
6
|
-
import {
|
|
7
|
-
areModelSpecsEqual,
|
|
8
|
-
createLoadingModelSpec,
|
|
9
|
-
createModelSpecService,
|
|
10
|
-
createUnknownModelSpec,
|
|
11
|
-
extractModelSpecFromDocText,
|
|
12
|
-
KNOWN_MODEL_SPECS,
|
|
13
|
-
loadModelSpecCache,
|
|
14
|
-
MODEL_SPEC_DOC_URLS,
|
|
15
|
-
parseTokenCount,
|
|
16
|
-
resolveModelSpec,
|
|
17
|
-
saveModelSpecCache,
|
|
18
|
-
stripHtmlToText,
|
|
19
|
-
type ModelSpec,
|
|
20
|
-
type VerifiedModelSpec,
|
|
21
|
-
} from "./modelSpecs.js";
|
|
22
|
-
|
|
23
|
-
test("parses token counts with commas and suffixes", () => {
|
|
24
|
-
assert.equal(parseTokenCount("1,050,000"), 1_050_000);
|
|
25
|
-
assert.equal(parseTokenCount("128,000"), 128_000);
|
|
26
|
-
assert.equal(parseTokenCount("1.05M"), 1_050_000);
|
|
27
|
-
assert.equal(parseTokenCount("400k"), 400_000);
|
|
28
|
-
assert.equal(parseTokenCount("nope"), null);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("extracts verified model specs from official-doc-like text", () => {
|
|
32
|
-
const spec = extractModelSpecFromDocText(
|
|
33
|
-
"gpt-5.4",
|
|
34
|
-
"GPT-5.4 1,050,000 context window 128,000 max output tokens",
|
|
35
|
-
123,
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
assert.deepEqual(spec, {
|
|
39
|
-
status: "verified",
|
|
40
|
-
contextWindow: 1_050_000,
|
|
41
|
-
maxOutputTokens: 128_000,
|
|
42
|
-
sourceUrl: MODEL_SPEC_DOC_URLS["gpt-5.4"],
|
|
43
|
-
verifiedAt: 123,
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("returns null for ambiguous or incomplete model spec text", () => {
|
|
48
|
-
assert.equal(
|
|
49
|
-
extractModelSpecFromDocText("gpt-5.4-mini", "GPT-5.4 mini supports coding and agents.", 123),
|
|
50
|
-
null,
|
|
51
|
-
);
|
|
52
|
-
assert.equal(
|
|
53
|
-
extractModelSpecFromDocText("gpt-5.4-mini", "400,000 context window but no output limit", 123),
|
|
54
|
-
null,
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("strips HTML before parsing model specs", () => {
|
|
59
|
-
const html = `
|
|
60
|
-
<html>
|
|
61
|
-
<body>
|
|
62
|
-
<h1>GPT-5.3-Codex</h1>
|
|
63
|
-
<div>400,000 <strong>context window</strong></div>
|
|
64
|
-
<div>128,000 <em>max output tokens</em></div>
|
|
65
|
-
</body>
|
|
66
|
-
</html>
|
|
67
|
-
`;
|
|
68
|
-
const text = stripHtmlToText(html);
|
|
69
|
-
assert.match(text, /400,000 context window/i);
|
|
70
|
-
assert.match(text, /128,000 max output tokens/i);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("cache round-trip preserves verified values", () => {
|
|
74
|
-
const dir = mkdtempSync(join(tmpdir(), "codexa-model-specs-"));
|
|
75
|
-
const cacheFile = join(dir, "model-specs.json");
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
const cache = {
|
|
79
|
-
"gpt-5.2": {
|
|
80
|
-
status: "verified" as const,
|
|
81
|
-
contextWindow: 400_000,
|
|
82
|
-
maxOutputTokens: 128_000,
|
|
83
|
-
sourceUrl: MODEL_SPEC_DOC_URLS["gpt-5.2"],
|
|
84
|
-
verifiedAt: 456,
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
saveModelSpecCache(cache, cacheFile);
|
|
88
|
-
|
|
89
|
-
const loaded = loadModelSpecCache(cacheFile);
|
|
90
|
-
assert.deepEqual(loaded, cache);
|
|
91
|
-
} finally {
|
|
92
|
-
rmSync(dir, { recursive: true, force: true });
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("background refresh updates specs and dedupes concurrent requests", async () => {
|
|
97
|
-
const dir = mkdtempSync(join(tmpdir(), "codexa-model-specs-"));
|
|
98
|
-
const cacheFile = join(dir, "model-specs.json");
|
|
99
|
-
let fetchCalls = 0;
|
|
100
|
-
const service = createModelSpecService({
|
|
101
|
-
cacheFile,
|
|
102
|
-
now: () => 789,
|
|
103
|
-
fetchImpl: async () => {
|
|
104
|
-
fetchCalls += 1;
|
|
105
|
-
return new Response("400,000 context window 128,000 max output tokens", { status: 200 });
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
const [left, right] = await Promise.all([
|
|
111
|
-
service.refreshSpec("gpt-5.2"),
|
|
112
|
-
service.refreshSpec("gpt-5.2"),
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
assert.equal(fetchCalls, 1);
|
|
116
|
-
assert.equal(left.status, "verified");
|
|
117
|
-
assert.equal(right.status, "verified");
|
|
118
|
-
assert.equal(left.contextWindow, 400_000);
|
|
119
|
-
assert.equal(left.maxOutputTokens, 128_000);
|
|
120
|
-
|
|
121
|
-
const persisted = JSON.parse(readFileSync(cacheFile, "utf-8")) as Record<string, ModelSpec>;
|
|
122
|
-
assert.equal(persisted["gpt-5.2"]?.verifiedAt, 789);
|
|
123
|
-
} finally {
|
|
124
|
-
rmSync(dir, { recursive: true, force: true });
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("refresh returns unknown when a fetch fails even if cache exists", async () => {
|
|
129
|
-
const dir = mkdtempSync(join(tmpdir(), "codexa-model-specs-"));
|
|
130
|
-
const cacheFile = join(dir, "model-specs.json");
|
|
131
|
-
saveModelSpecCache({
|
|
132
|
-
"gpt-5.3-codex": {
|
|
133
|
-
status: "verified",
|
|
134
|
-
contextWindow: 400_000,
|
|
135
|
-
maxOutputTokens: 128_000,
|
|
136
|
-
sourceUrl: MODEL_SPEC_DOC_URLS["gpt-5.3-codex"],
|
|
137
|
-
verifiedAt: 123,
|
|
138
|
-
},
|
|
139
|
-
}, cacheFile);
|
|
140
|
-
|
|
141
|
-
const service = createModelSpecService({
|
|
142
|
-
cacheFile,
|
|
143
|
-
fetchImpl: async () => new Response("broken page", { status: 200 }),
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
const spec = await service.refreshSpec("gpt-5.3-codex");
|
|
148
|
-
assert.equal(spec.status, "unknown");
|
|
149
|
-
assert.equal(spec.contextWindow, null);
|
|
150
|
-
assert.equal(spec.maxOutputTokens, null);
|
|
151
|
-
assert.equal(spec.error, "Unable to parse model spec for gpt-5.3-codex");
|
|
152
|
-
} finally {
|
|
153
|
-
rmSync(dir, { recursive: true, force: true });
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("refresh returns unknown when there is no cache and verification fails", async () => {
|
|
158
|
-
const dir = mkdtempSync(join(tmpdir(), "codexa-model-specs-"));
|
|
159
|
-
const cacheFile = join(dir, "model-specs.json");
|
|
160
|
-
const service = createModelSpecService({
|
|
161
|
-
cacheFile,
|
|
162
|
-
fetchImpl: async () => new Response("no token limits here", { status: 200 }),
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const spec = await service.refreshSpec("gpt-5.2");
|
|
167
|
-
assert.deepEqual(spec, createUnknownModelSpec("gpt-5.2", "Unable to parse model spec for gpt-5.2"));
|
|
168
|
-
} finally {
|
|
169
|
-
rmSync(dir, { recursive: true, force: true });
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("resolveModelSpec prefers runtime discovery metadata", () => {
|
|
174
|
-
const spec = resolveModelSpec("gpt-5.4", {
|
|
175
|
-
runtimeContextWindow: 2_000_000,
|
|
176
|
-
runtimeMaxOutputTokens: 256_000,
|
|
177
|
-
cache: {
|
|
178
|
-
"gpt-5.4": {
|
|
179
|
-
status: "verified",
|
|
180
|
-
contextWindow: 1_050_000,
|
|
181
|
-
maxOutputTokens: 128_000,
|
|
182
|
-
sourceUrl: MODEL_SPEC_DOC_URLS["gpt-5.4"]!,
|
|
183
|
-
verifiedAt: 1,
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
now: () => 999,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
assert.equal(spec.status, "verified");
|
|
190
|
-
assert.equal(spec.contextWindow, 2_000_000);
|
|
191
|
-
assert.equal(spec.maxOutputTokens, 256_000);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("resolveModelSpec falls back to persisted cache when runtime lacks context metadata", () => {
|
|
195
|
-
const spec = resolveModelSpec("gpt-5.4", {
|
|
196
|
-
cache: {
|
|
197
|
-
"gpt-5.4": {
|
|
198
|
-
status: "verified",
|
|
199
|
-
contextWindow: 1_050_000,
|
|
200
|
-
maxOutputTokens: 128_000,
|
|
201
|
-
sourceUrl: MODEL_SPEC_DOC_URLS["gpt-5.4"]!,
|
|
202
|
-
verifiedAt: 42,
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
assert.equal(spec.status, "verified");
|
|
208
|
-
assert.equal(spec.contextWindow, 1_050_000);
|
|
209
|
-
assert.equal(spec.verifiedAt, 42);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("resolveModelSpec does not use static registry values when cache is empty", () => {
|
|
213
|
-
const spec = resolveModelSpec("gpt-5.4-mini", { now: () => 11 });
|
|
214
|
-
assert.equal(spec.status, "unknown");
|
|
215
|
-
assert.equal(spec.contextWindow, null);
|
|
216
|
-
assert.deepEqual(KNOWN_MODEL_SPECS, {});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("resolveModelSpec returns loading while refresh is in-flight for unmapped models", () => {
|
|
220
|
-
const spec = resolveModelSpec("gpt-future-9", { refreshInFlight: true });
|
|
221
|
-
assert.equal(spec.status, "loading");
|
|
222
|
-
assert.equal(spec.contextWindow, null);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test("resolveModelSpec returns unknown only when no source can supply context metadata", () => {
|
|
226
|
-
const spec = resolveModelSpec("gpt-future-9");
|
|
227
|
-
assert.equal(spec.status, "unknown");
|
|
228
|
-
assert.equal(spec.contextWindow, null);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test("resolveModelSpec does not infer registry entries when switching between model names", () => {
|
|
232
|
-
const specFour = resolveModelSpec("gpt-5.4");
|
|
233
|
-
const specTwo = resolveModelSpec("gpt-5.2");
|
|
234
|
-
assert.equal(specFour.contextWindow, null);
|
|
235
|
-
assert.equal(specTwo.contextWindow, null);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("reports equality across verified and unknown specs", () => {
|
|
239
|
-
assert.equal(
|
|
240
|
-
areModelSpecsEqual(
|
|
241
|
-
createLoadingModelSpec("gpt-5.4"),
|
|
242
|
-
createLoadingModelSpec("gpt-5.4"),
|
|
243
|
-
),
|
|
244
|
-
true,
|
|
245
|
-
);
|
|
246
|
-
assert.equal(
|
|
247
|
-
areModelSpecsEqual(
|
|
248
|
-
createUnknownModelSpec("gpt-5.4", "a"),
|
|
249
|
-
createUnknownModelSpec("gpt-5.4", "b"),
|
|
250
|
-
),
|
|
251
|
-
false,
|
|
252
|
-
);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test("resolveModelSpec returns unknown for claude-sonnet-4-6 without runtime/config context", () => {
|
|
256
|
-
const spec = resolveModelSpec("claude-sonnet-4-6");
|
|
257
|
-
assert.equal(spec.status, "unknown");
|
|
258
|
-
assert.equal(spec.contextWindow, null);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test("resolveModelSpec returns unknown for anthropic short alias 'haiku'", () => {
|
|
262
|
-
const spec = resolveModelSpec("haiku");
|
|
263
|
-
assert.equal(spec.status, "unknown");
|
|
264
|
-
assert.equal(spec.contextWindow, null);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test("resolveModelSpec returns unknown for gemini-2.5-flash without provider context metadata", () => {
|
|
268
|
-
const spec = resolveModelSpec("gemini-2.5-flash");
|
|
269
|
-
assert.equal(spec.status, "unknown");
|
|
270
|
-
assert.equal(spec.contextWindow, null);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("resolveModelSpec returns null contextWindow (not 0) for an unrecognized model", () => {
|
|
274
|
-
const spec = resolveModelSpec("totally-unknown-model");
|
|
275
|
-
assert.equal(spec.status, "unknown");
|
|
276
|
-
assert.equal(spec.contextWindow, null);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test("resolveModelSpec does not estimate gemini-3-flash-preview", () => {
|
|
280
|
-
const spec = resolveModelSpec("gemini-3-flash-preview");
|
|
281
|
-
assert.equal(spec.status, "unknown");
|
|
282
|
-
assert.equal(spec.contextWindow, null);
|
|
283
|
-
});
|