@golba98/codexa 1.0.4 → 1.0.5
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/package.json +1 -1
- package/src/app.tsx +128 -47
- package/src/commands/handler.ts +5 -16
- package/src/config/buildInfo.ts +2 -2
- package/src/core/models/codexModelCapabilities.ts +57 -4
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/providerModelCache.ts +106 -0
- package/src/core/process/CommandRunner.ts +12 -1
- package/src/core/providerLauncher/registry.ts +37 -7
- package/src/core/providerLauncher/types.ts +2 -1
- package/src/core/providerRuntime/anthropic.ts +3 -5
- package/src/core/providerRuntime/antigravity.ts +246 -90
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -692
- package/src/core/providerRuntime/mistralVibe.ts +663 -0
- package/src/core/providerRuntime/models.ts +18 -4
- package/src/core/providerRuntime/reasoning.ts +9 -6
- package/src/core/providerRuntime/registry.ts +94 -49
- package/src/core/providerRuntime/types.ts +1 -0
- package/src/index.tsx +23 -9
- package/src/test/runtimeTestUtils.ts +90 -0
- package/src/ui/{ActivityBars.tsx → chrome/ActivityBars.tsx} +1 -1
- package/src/ui/{ActivityIndicator.tsx → chrome/ActivityIndicator.tsx} +3 -3
- package/src/ui/{AnimatedStatusText.tsx → chrome/AnimatedStatusText.tsx} +3 -3
- package/src/ui/{AppShell.tsx → chrome/AppShell.tsx} +11 -11
- package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +15 -15
- package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
- package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
- package/src/ui/{RuntimeStatusBar.tsx → chrome/RuntimeStatusBar.tsx} +3 -3
- package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
- package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +12 -12
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +3 -3
- package/src/ui/{commandNormalize.ts → input/commandNormalize.ts} +1 -1
- package/src/ui/{focus.ts → input/focus.ts} +1 -1
- package/src/ui/{inputBuffer.ts → input/inputBuffer.ts} +3 -3
- package/src/ui/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +1 -1
- package/src/ui/{AuthPanel.tsx → panels/AuthPanel.tsx} +5 -5
- package/src/ui/{BackendPicker.tsx → panels/BackendPicker.tsx} +2 -2
- package/src/ui/{ModePicker.tsx → panels/ModePicker.tsx} +2 -2
- package/src/ui/{ModelPicker.tsx → panels/ModelPicker.tsx} +2 -2
- package/src/ui/{ModelPickerScreen.tsx → panels/ModelPickerScreen.tsx} +11 -9
- package/src/ui/{ModelReasoningPicker.tsx → panels/ModelReasoningPicker.tsx} +5 -5
- package/src/ui/{Panel.tsx → panels/Panel.tsx} +1 -1
- package/src/ui/{PermissionsPanel.tsx → panels/PermissionsPanel.tsx} +3 -3
- package/src/ui/{PlanActionPicker.tsx → panels/PlanActionPicker.tsx} +2 -2
- package/src/ui/{PlanReviewPanel.tsx → panels/PlanReviewPanel.tsx} +5 -5
- package/src/ui/{ProviderPicker.tsx → panels/ProviderPicker.tsx} +9 -7
- package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
- package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +1 -1
- package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +3 -3
- package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +3 -3
- package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
- package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +2 -2
- package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
- package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
- package/src/ui/{modeDisplay.ts → render/modeDisplay.ts} +2 -2
- package/src/ui/{outputPipeline.ts → render/outputPipeline.ts} +2 -2
- package/src/ui/{runtimeDisplay.ts → render/runtimeDisplay.ts} +9 -8
- package/src/ui/{ActionRequiredBlock.tsx → timeline/ActionRequiredBlock.tsx} +3 -3
- package/src/ui/{AgentBlock.tsx → timeline/AgentBlock.tsx} +10 -10
- package/src/ui/{StaticIntroItem.tsx → timeline/StaticIntroItem.tsx} +2 -2
- package/src/ui/{ThinkingBlock.tsx → timeline/ThinkingBlock.tsx} +4 -4
- package/src/ui/{Timeline.tsx → timeline/Timeline.tsx} +8 -8
- package/src/ui/{TranscriptShell.tsx → timeline/TranscriptShell.tsx} +6 -6
- package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
- package/src/ui/{progressEntries.ts → timeline/progressEntries.ts} +3 -3
- package/src/ui/{runActivityView.ts → timeline/runActivityView.ts} +1 -1
- package/src/ui/{timelineMeasure.ts → timeline/timelineMeasure.ts} +14 -14
- /package/src/ui/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
- /package/src/ui/{slashCommands.ts → input/slashCommands.ts} +0 -0
- /package/src/ui/{logoVariants.ts → render/logoVariants.ts} +0 -0
- /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
- /package/src/ui/{textLayout.ts → render/textLayout.ts} +0 -0
- /package/src/ui/{layoutListWindow.ts → timeline/layoutListWindow.ts} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
|
|
1
|
+
import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
|
|
2
|
+
import { formatReasoningLabel } from "../../config/settings.js";
|
|
2
3
|
|
|
3
4
|
export const CLAUDE_CODE_EFFORT_LEVELS: readonly ReasoningEffortCapability[] = [
|
|
4
5
|
{ id: "low", label: "Low", description: "Claude Code low effort." },
|
|
@@ -10,8 +11,10 @@ export const CLAUDE_CODE_EFFORT_LEVELS: readonly ReasoningEffortCapability[] = [
|
|
|
10
11
|
|
|
11
12
|
export const CLAUDE_CODE_EFFORT_IDS = new Set(CLAUDE_CODE_EFFORT_LEVELS.map((level) => level.id));
|
|
12
13
|
|
|
13
|
-
export function getClaudeCodeEffortLevels(ids: readonly string[]): readonly ReasoningEffortCapability[] {
|
|
14
|
-
return ids
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
export function getClaudeCodeEffortLevels(ids: readonly string[]): readonly ReasoningEffortCapability[] {
|
|
15
|
+
return ids.map((id) => CLAUDE_CODE_EFFORT_LEVELS.find((level) => level.id === id) ?? {
|
|
16
|
+
id,
|
|
17
|
+
label: formatReasoningLabel(id),
|
|
18
|
+
description: null,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { codexSubprocessProvider } from "../providers/codexSubprocess.js";
|
|
2
|
+
import { loadSeededOpenAiModels } from "../models/codexModelsCacheSeed.js";
|
|
3
|
+
import { loadCachedProviderModels, saveCachedProviderModels } from "../models/providerModelCache.js";
|
|
2
4
|
import type { BackendRunHandlers } from "../providers/types.js";
|
|
3
5
|
import type { ProviderId, ProviderActiveRoute, ProviderWorkspaceOverride } from "../providerLauncher/types.js";
|
|
4
|
-
import { anthropicRuntime } from "./anthropic.js";
|
|
5
|
-
import { geminiRuntime } from "./gemini.js";
|
|
6
|
-
import { localRuntime } from "./local.js";
|
|
7
|
-
import { antigravityRuntime, ANTIGRAVITY_DEFAULT_MODEL_ID, migrateAntigravityLegacyModelId } from "./antigravity.js";
|
|
6
|
+
import { anthropicRuntime } from "./anthropic.js";
|
|
7
|
+
import { geminiRuntime } from "./gemini.js";
|
|
8
|
+
import { localRuntime } from "./local.js";
|
|
9
|
+
import { antigravityRuntime, ANTIGRAVITY_DEFAULT_MODEL_ID, migrateAntigravityLegacyModelId } from "./antigravity.js";
|
|
10
|
+
import { mistralVibeRuntime } from "./mistralVibe.js";
|
|
8
11
|
import {
|
|
9
12
|
ANTHROPIC_FALLBACK_MODELS,
|
|
10
13
|
GEMINI_DEFAULT_MODEL_ID,
|
|
@@ -28,11 +31,14 @@ const openAiRuntime: ProviderRuntime = {
|
|
|
28
31
|
routeAvailable: true,
|
|
29
32
|
routeStatus: "Uses the configured Codex/OpenAI backend inside Codexa.",
|
|
30
33
|
launchAvailable: true,
|
|
34
|
+
// Seeded from local caches (codex's own models_cache.json or Codexa's
|
|
35
|
+
// last-good discovery); live app-server discovery refreshes them via
|
|
36
|
+
// getCodexModelCapabilities in the app run loop.
|
|
31
37
|
discoverModels: () => ({
|
|
32
38
|
status: "ready",
|
|
33
39
|
providerId: "openai",
|
|
34
40
|
backendKind: "codex-cli-auth",
|
|
35
|
-
models: [],
|
|
41
|
+
models: loadSeededOpenAiModels()?.models ?? [],
|
|
36
42
|
}),
|
|
37
43
|
run: (request: ProviderChatRequest, handlers: BackendRunHandlers) => {
|
|
38
44
|
handlers.onProgress?.({
|
|
@@ -72,11 +78,12 @@ function unavailableRuntime(providerId: ProviderId, label: string): ProviderRunt
|
|
|
72
78
|
|
|
73
79
|
const PROVIDER_RUNTIMES: Record<ProviderId, ProviderRuntime> = {
|
|
74
80
|
openai: openAiRuntime,
|
|
75
|
-
anthropic: anthropicRuntime,
|
|
76
|
-
google: geminiRuntime,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
anthropic: anthropicRuntime,
|
|
82
|
+
google: geminiRuntime,
|
|
83
|
+
mistral: mistralVibeRuntime,
|
|
84
|
+
local: localRuntime,
|
|
85
|
+
antigravity: antigravityRuntime,
|
|
86
|
+
};
|
|
80
87
|
|
|
81
88
|
export function getProviderRuntime(providerId: ProviderId): ProviderRuntime {
|
|
82
89
|
return PROVIDER_RUNTIMES[providerId];
|
|
@@ -97,17 +104,39 @@ export function getProviderRouteSetupMessage(providerId: ProviderId): string {
|
|
|
97
104
|
}
|
|
98
105
|
|
|
99
106
|
export function discoverProviderModels(providerId: ProviderId): ProviderModelDiscoveryResult {
|
|
100
|
-
|
|
107
|
+
const result = getProviderRuntime(providerId).discoverModels();
|
|
108
|
+
// When the live/synchronous probe only produced fallback entries (or none),
|
|
109
|
+
// prefer the last-good discovery persisted from a previous session.
|
|
110
|
+
const hasRuntimeModels = result.models.some((model) => model.source && model.source !== "fallback");
|
|
111
|
+
if (result.status === "ready" && !hasRuntimeModels) {
|
|
112
|
+
const cached = loadCachedProviderModels(providerId);
|
|
113
|
+
if (cached) {
|
|
114
|
+
return { ...result, models: cached.models };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Persist a ready discovery so the next session starts from last-good models.
|
|
121
|
+
export function persistProviderDiscovery(discovery: ProviderModelDiscoveryResult): void {
|
|
122
|
+
const runtimeModels = discovery.models.filter((model) => model.source && model.source !== "fallback");
|
|
123
|
+
if (discovery.status !== "ready" || runtimeModels.length === 0) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
saveCachedProviderModels(discovery.providerId, {
|
|
127
|
+
discoveredAt: Date.now(),
|
|
128
|
+
models: runtimeModels,
|
|
129
|
+
});
|
|
101
130
|
}
|
|
102
131
|
|
|
103
132
|
export async function validateProviderRouteActivation(options: {
|
|
104
133
|
route: ProviderRoute;
|
|
105
|
-
workspaceRoot: string;
|
|
106
|
-
geminiCommandPath?: string | null;
|
|
107
|
-
claudeCommandPath?: string | null;
|
|
108
|
-
antigravityCommandPath?: string | null;
|
|
109
|
-
localConfig?: ProviderWorkspaceOverride | null;
|
|
110
|
-
}): Promise<ProviderRouteValidationResult> {
|
|
134
|
+
workspaceRoot: string;
|
|
135
|
+
geminiCommandPath?: string | null;
|
|
136
|
+
claudeCommandPath?: string | null;
|
|
137
|
+
antigravityCommandPath?: string | null;
|
|
138
|
+
localConfig?: ProviderWorkspaceOverride | null;
|
|
139
|
+
}): Promise<ProviderRouteValidationResult> {
|
|
111
140
|
const runtime = getProviderRuntime(options.route.providerId);
|
|
112
141
|
if (!runtime.routeAvailable) {
|
|
113
142
|
return {
|
|
@@ -157,7 +186,7 @@ export function resolveActiveProviderRoute(options: {
|
|
|
157
186
|
currentReasoning: string;
|
|
158
187
|
}): ActiveProviderRoute {
|
|
159
188
|
const configuredRoute = options.workspaceConfigActiveRoute;
|
|
160
|
-
if (configuredRoute && configuredRoute.providerId !== "google" && isProviderRoutableInCodexa(configuredRoute.providerId)) {
|
|
189
|
+
if (configuredRoute && configuredRoute.providerId !== "google" && isProviderRoutableInCodexa(configuredRoute.providerId)) {
|
|
161
190
|
const route: ActiveProviderRoute = {
|
|
162
191
|
providerId: configuredRoute.providerId,
|
|
163
192
|
modelId: configuredRoute.modelId,
|
|
@@ -166,36 +195,48 @@ export function resolveActiveProviderRoute(options: {
|
|
|
166
195
|
...(configuredRoute.modelSelection ? { modelSelection: configuredRoute.modelSelection } : {}),
|
|
167
196
|
};
|
|
168
197
|
|
|
169
|
-
if (route.providerId === "google" && route.modelSelection) {
|
|
170
|
-
route.modelId = resolveGeminiModelId(route.modelSelection);
|
|
171
|
-
} else if (route.providerId === "google") {
|
|
172
|
-
route.modelId = normalizeGeminiModelId(route.modelId);
|
|
173
|
-
} else if (route.providerId === "anthropic") {
|
|
174
|
-
const discovery = discoverProviderModels("anthropic");
|
|
175
|
-
const stillAvailable = discovery.models.some((model) =>
|
|
176
|
-
model.modelId === route.modelId ||
|
|
177
|
-
model.id === route.modelId ||
|
|
178
|
-
model.canonicalId === route.modelId
|
|
179
|
-
);
|
|
180
|
-
const hasNonFallbackModels = discovery.models.some((model) => model.source !== "fallback");
|
|
181
|
-
if (discovery.status === "ready" && hasNonFallbackModels && discovery.models.length > 0 && !stillAvailable) {
|
|
182
|
-
route.modelId = discovery.models[0]!.modelId;
|
|
183
|
-
}
|
|
184
|
-
} else if (route.providerId === "local") {
|
|
185
|
-
const discovery = discoverProviderModels("local");
|
|
186
|
-
const selectedModel = typeof discovery.diagnostics?.selectedModel === "string"
|
|
198
|
+
if (route.providerId === "google" && route.modelSelection) {
|
|
199
|
+
route.modelId = resolveGeminiModelId(route.modelSelection);
|
|
200
|
+
} else if (route.providerId === "google") {
|
|
201
|
+
route.modelId = normalizeGeminiModelId(route.modelId);
|
|
202
|
+
} else if (route.providerId === "anthropic") {
|
|
203
|
+
const discovery = discoverProviderModels("anthropic");
|
|
204
|
+
const stillAvailable = discovery.models.some((model) =>
|
|
205
|
+
model.modelId === route.modelId ||
|
|
206
|
+
model.id === route.modelId ||
|
|
207
|
+
model.canonicalId === route.modelId
|
|
208
|
+
);
|
|
209
|
+
const hasNonFallbackModels = discovery.models.some((model) => model.source !== "fallback");
|
|
210
|
+
if (discovery.status === "ready" && hasNonFallbackModels && discovery.models.length > 0 && !stillAvailable) {
|
|
211
|
+
route.modelId = discovery.models[0]!.modelId;
|
|
212
|
+
}
|
|
213
|
+
} else if (route.providerId === "local") {
|
|
214
|
+
const discovery = discoverProviderModels("local");
|
|
215
|
+
const selectedModel = typeof discovery.diagnostics?.selectedModel === "string"
|
|
187
216
|
? discovery.diagnostics.selectedModel.trim()
|
|
188
217
|
: "";
|
|
189
|
-
if (discovery.status === "ready" && selectedModel) {
|
|
190
|
-
route.modelId = selectedModel;
|
|
191
|
-
}
|
|
218
|
+
if (discovery.status === "ready" && selectedModel) {
|
|
219
|
+
route.modelId = selectedModel;
|
|
220
|
+
}
|
|
192
221
|
} else if (route.providerId === "antigravity") {
|
|
193
222
|
const migrated = migrateAntigravityLegacyModelId(route.modelId);
|
|
194
223
|
route.modelId = migrated.modelId;
|
|
195
224
|
if (!route.reasoning && migrated.reasoning) {
|
|
196
225
|
route.reasoning = migrated.reasoning;
|
|
197
226
|
}
|
|
198
|
-
|
|
227
|
+
const discovery = discoverProviderModels("antigravity");
|
|
228
|
+
if (discovery.status === "ready" && discovery.models.length > 0) {
|
|
229
|
+
let model = discovery.models.find((item) => item.modelId === route.modelId || item.id === route.modelId);
|
|
230
|
+
if (!model) {
|
|
231
|
+
model = discovery.models[0];
|
|
232
|
+
route.modelId = model.modelId;
|
|
233
|
+
}
|
|
234
|
+
const levels = model.supportedReasoningLevels;
|
|
235
|
+
if (levels?.length && (!route.reasoning || !levels.some((level) => level.id === route.reasoning))) {
|
|
236
|
+
route.reasoning = model.defaultReasoningLevel ?? levels[0]?.id;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
199
240
|
|
|
200
241
|
return route;
|
|
201
242
|
}
|
|
@@ -214,17 +255,21 @@ export function getDefaultRouteModel(providerId: ProviderId, currentOpenAiModel:
|
|
|
214
255
|
if (discovered.status === "ready" && discovered.models.length > 0) {
|
|
215
256
|
return discovered.models[0].modelId;
|
|
216
257
|
}
|
|
217
|
-
return ANTHROPIC_FALLBACK_MODELS[0]?.modelId ?? "sonnet";
|
|
258
|
+
return ANTHROPIC_FALLBACK_MODELS[0]?.modelId ?? "sonnet";
|
|
218
259
|
}
|
|
219
260
|
if (providerId === "google") {
|
|
220
261
|
return GEMINI_FALLBACK_MODELS[0]?.modelId ?? GEMINI_DEFAULT_MODEL_ID;
|
|
221
262
|
}
|
|
222
|
-
if (providerId === "local") {
|
|
223
|
-
const discovery = discoverProviderModels("local");
|
|
224
|
-
return discovery.models[0]?.modelId ?? "Local default";
|
|
225
|
-
}
|
|
263
|
+
if (providerId === "local") {
|
|
264
|
+
const discovery = discoverProviderModels("local");
|
|
265
|
+
return discovery.models[0]?.modelId ?? "Local default";
|
|
266
|
+
}
|
|
267
|
+
if (providerId === "mistral") {
|
|
268
|
+
const discovery = discoverProviderModels("mistral");
|
|
269
|
+
return discovery.models[0]?.modelId ?? "Vibe default";
|
|
270
|
+
}
|
|
226
271
|
if (providerId === "antigravity") {
|
|
227
|
-
return ANTIGRAVITY_DEFAULT_MODEL_ID;
|
|
228
|
-
}
|
|
229
|
-
return currentOpenAiModel;
|
|
230
|
-
}
|
|
272
|
+
return discoverProviderModels("antigravity").models[0]?.modelId ?? ANTIGRAVITY_DEFAULT_MODEL_ID;
|
|
273
|
+
}
|
|
274
|
+
return currentOpenAiModel;
|
|
275
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -18,11 +18,24 @@ import {
|
|
|
18
18
|
import { resolveInkRenderInstance, type InkRenderInstance } from "./core/terminal/inkRenderReset.js";
|
|
19
19
|
import { resetFrameLockForResize, wrapStdoutWithFrameLock } from "./core/terminal/frameLock.js";
|
|
20
20
|
|
|
21
|
-
type RenderHandle = Pick<Instance, "clear" | "cleanup" | "waitUntilExit">;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
type RenderHandle = Pick<Instance, "clear" | "cleanup" | "waitUntilExit">;
|
|
22
|
+
|
|
23
|
+
export function resolveKittyKeyboardOptions(
|
|
24
|
+
env: Record<string, string | undefined>,
|
|
25
|
+
): RenderOptions["kittyKeyboard"] | undefined {
|
|
26
|
+
// Ink's auto mode probes with CSI ? u. Kitty's response can race Ink's
|
|
27
|
+
// regular input listeners and leak into both the first frame and composer.
|
|
28
|
+
// A direct Kitty session is already known to support the protocol, so enable
|
|
29
|
+
// it without probing. Avoid protocol negotiation in all other terminals.
|
|
30
|
+
if ((env.TERM ?? "").toLowerCase() !== "xterm-kitty") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
mode: "enabled",
|
|
36
|
+
flags: ["disambiguateEscapeCodes"],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
26
39
|
|
|
27
40
|
export interface AppStdout {
|
|
28
41
|
isTTY: boolean;
|
|
@@ -222,10 +235,11 @@ export function startApp({
|
|
|
222
235
|
process.on("uncaughtException", handleFatal);
|
|
223
236
|
process.on("unhandledRejection", handleFatal);
|
|
224
237
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
238
|
+
const kittyKeyboard = resolveKittyKeyboardOptions(env);
|
|
239
|
+
renderHandle = renderApp(<App launchArgs={launchArgs} />, {
|
|
240
|
+
...(kittyKeyboard ? { kittyKeyboard } : {}),
|
|
241
|
+
stdout: wrappedStdout as any,
|
|
242
|
+
});
|
|
229
243
|
|
|
230
244
|
// Resolve the real Ink class instance to get access to lastOutput,
|
|
231
245
|
// onRender, calculateLayout, etc. Gracefully degrades to null in tests.
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { resolveRuntimeConfig, type RuntimeConfig, DEFAULT_RUNTIME_CONFIG, type ResolvedRuntimeConfig } from "../config/runtimeConfig.js";
|
|
5
|
+
import type { ProviderId } from "../core/providerLauncher/types.js";
|
|
6
|
+
import { resetAnthropicRouteValidationCacheForTests } from "../core/providerRuntime/anthropic.js";
|
|
7
|
+
import { resetAntigravityRouteValidationCacheForTests } from "../core/providerRuntime/antigravity.js";
|
|
8
|
+
import type { ProviderModel } from "../core/providerRuntime/types.js";
|
|
2
9
|
|
|
3
10
|
export function makeResolvedRuntime(overrides: Partial<RuntimeConfig> = {}): ResolvedRuntimeConfig {
|
|
4
11
|
return resolveRuntimeConfig({
|
|
@@ -12,3 +19,86 @@ export function makeResolvedRuntime(overrides: Partial<RuntimeConfig> = {}): Res
|
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
export const TEST_RUNTIME = makeResolvedRuntime();
|
|
22
|
+
|
|
23
|
+
function agyModel(id: string, label: string): ProviderModel {
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
modelId: id,
|
|
27
|
+
label,
|
|
28
|
+
description: null,
|
|
29
|
+
defaultReasoningLevel: null,
|
|
30
|
+
supportedReasoningLevels: null,
|
|
31
|
+
source: "discovered",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The catalog `agy models` reports on a configured machine. Antigravity resolves
|
|
37
|
+
* its status, backend kind, and model labels from whatever discovery last found,
|
|
38
|
+
* so tests that assert on any of those need a catalog seeded rather than
|
|
39
|
+
* whatever happens to be installed.
|
|
40
|
+
*/
|
|
41
|
+
export const ANTIGRAVITY_FIXTURE_MODELS: readonly ProviderModel[] = [
|
|
42
|
+
agyModel("gemini-3.5-flash", "Gemini 3.5 Flash"),
|
|
43
|
+
agyModel("gemini-3.1-pro", "Gemini 3.1 Pro"),
|
|
44
|
+
agyModel("claude-sonnet-4-6", "Claude Sonnet 4.6"),
|
|
45
|
+
agyModel("claude-sonnet-4-6-think", "Claude Sonnet 4.6 (Thinking)"),
|
|
46
|
+
agyModel("gpt-oss-120b", "GPT-OSS 120B"),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Drops every provider's in-memory discovery result.
|
|
51
|
+
*
|
|
52
|
+
* Bun shares the module registry across test files, so a catalog discovered by
|
|
53
|
+
* one test (some probe the real `claude` / `agy` CLIs) otherwise survives into
|
|
54
|
+
* the next and takes precedence over both the cache and the fallback list —
|
|
55
|
+
* making assertions depend on file order and on what is installed locally.
|
|
56
|
+
*/
|
|
57
|
+
export function resetProviderDiscoveryState(): void {
|
|
58
|
+
resetAntigravityRouteValidationCacheForTests();
|
|
59
|
+
resetAnthropicRouteValidationCacheForTests();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Runs `fn` with HOME pointed at a throwaway directory holding only the provider
|
|
64
|
+
* model cache built from `seed`.
|
|
65
|
+
*
|
|
66
|
+
* Provider discovery reads `~/.codexa-model-cache.json`, so a test that calls it
|
|
67
|
+
* without this is asserting against the developer's own machine — it passes with
|
|
68
|
+
* a warm cache and fails on a cold one (or in CI). HOME is read per call (see
|
|
69
|
+
* `getProviderModelCacheFile`), so redirecting the env var here is enough.
|
|
70
|
+
*
|
|
71
|
+
* Antigravity's in-memory discovery is reset around the body as well: Bun shares
|
|
72
|
+
* the module registry across test files, so a catalog discovered by one file
|
|
73
|
+
* otherwise leaks into the next and takes precedence over the seeded cache,
|
|
74
|
+
* making results depend on file order. Seed exactly what the assertions need.
|
|
75
|
+
*/
|
|
76
|
+
export function withSeededModelCache<T>(
|
|
77
|
+
seed: Partial<Record<ProviderId, readonly ProviderModel[]>>,
|
|
78
|
+
fn: () => T,
|
|
79
|
+
): T {
|
|
80
|
+
const tempHome = mkdtempSync(join(tmpdir(), "codexa-test-home-"));
|
|
81
|
+
const previousHome = process.env.HOME;
|
|
82
|
+
const previousUserProfile = process.env.USERPROFILE;
|
|
83
|
+
try {
|
|
84
|
+
process.env.HOME = tempHome;
|
|
85
|
+
delete process.env.USERPROFILE;
|
|
86
|
+
const providers = Object.fromEntries(
|
|
87
|
+
Object.entries(seed).map(([providerId, models]) => [providerId, { discoveredAt: 1, models }]),
|
|
88
|
+
);
|
|
89
|
+
writeFileSync(
|
|
90
|
+
join(tempHome, ".codexa-model-cache.json"),
|
|
91
|
+
`${JSON.stringify({ version: 1, providers }, null, 2)}\n`,
|
|
92
|
+
"utf8",
|
|
93
|
+
);
|
|
94
|
+
resetProviderDiscoveryState();
|
|
95
|
+
return fn();
|
|
96
|
+
} finally {
|
|
97
|
+
if (previousHome === undefined) delete process.env.HOME;
|
|
98
|
+
else process.env.HOME = previousHome;
|
|
99
|
+
if (previousUserProfile === undefined) delete process.env.USERPROFILE;
|
|
100
|
+
else process.env.USERPROFILE = previousUserProfile;
|
|
101
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
102
|
+
resetProviderDiscoveryState();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { useTheme } from "
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
4
4
|
|
|
5
5
|
// ─── WaveBar ─────────────────────────────────────────────────────────────────
|
|
6
6
|
// Audio-visualizer style wave. Renders as a single <Text> node to minimise
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Text } from "ink";
|
|
3
|
-
import type { UIState, ExternalCliStatus } from "
|
|
4
|
-
import { useTheme } from "
|
|
3
|
+
import type { UIState, ExternalCliStatus } from "../../session/types.js";
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
5
5
|
|
|
6
6
|
const SPINNER_FRAMES = ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?"];
|
|
7
7
|
const STREAMING_FRAMES = ["?", "?", "?", "?"];
|
|
@@ -36,7 +36,7 @@ export function ActivityIndicator({ uiState, externalCliStatus = "idle" }: Activ
|
|
|
36
36
|
let bold = false;
|
|
37
37
|
|
|
38
38
|
if (isError) {
|
|
39
|
-
glyph = "�";
|
|
39
|
+
glyph = "�";
|
|
40
40
|
color = theme.error;
|
|
41
41
|
bold = true;
|
|
42
42
|
} else if (isAction) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { Text } from "ink";
|
|
3
|
-
import * as renderDebug from "
|
|
4
|
-
import { useTheme } from "
|
|
5
|
-
import { sanitizeTerminalOutput } from "
|
|
3
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
5
|
+
import { sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
|
|
6
6
|
import { BUSY_STATUS_FRAME_MS, BUSY_STATUS_FRAMES, getBusyStatusFrame } from "./busyStatusAnimation.js";
|
|
7
7
|
|
|
8
8
|
interface AnimatedStatusTextProps {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useStdin } from "ink";
|
|
3
|
-
import { useTheme } from "
|
|
4
|
-
import type { RuntimeSummary } from "
|
|
5
|
-
import type { CodexAuthState } from "
|
|
6
|
-
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "
|
|
7
|
-
import * as renderDebug from "
|
|
8
|
-
import type { Screen, TimelineEvent, UIState } from "
|
|
9
|
-
import { isBusy } from "
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
4
|
+
import type { RuntimeSummary } from "../../config/runtimeConfig.js";
|
|
5
|
+
import type { CodexAuthState } from "../../core/auth/codexAuth.js";
|
|
6
|
+
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../../config/settings.js";
|
|
7
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
8
|
+
import type { Screen, TimelineEvent, UIState } from "../../session/types.js";
|
|
9
|
+
import { isBusy } from "../../session/types.js";
|
|
10
10
|
import {
|
|
11
11
|
ActivePanelLayoutContext,
|
|
12
12
|
type ActivePanelLayout,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
MIN_TERMINAL_COLS,
|
|
25
25
|
MIN_TERMINAL_ROWS,
|
|
26
26
|
type TerminalViewport,
|
|
27
|
-
} from "
|
|
27
|
+
} from "../layout.js";
|
|
28
28
|
import {
|
|
29
29
|
buildActiveRenderItems,
|
|
30
30
|
buildStaticRenderItems,
|
|
@@ -33,9 +33,9 @@ import {
|
|
|
33
33
|
Timeline,
|
|
34
34
|
TimelineRowView,
|
|
35
35
|
type TimelineItem,
|
|
36
|
-
} from "
|
|
37
|
-
import { buildNativeTranscriptParts, type NativeTranscriptRowItem, type TimelineRow } from "
|
|
38
|
-
import type { TerminalSelectionProfile } from "
|
|
36
|
+
} from "../timeline/Timeline.js";
|
|
37
|
+
import { buildNativeTranscriptParts, type NativeTranscriptRowItem, type TimelineRow } from "../timeline/timelineMeasure.js";
|
|
38
|
+
import type { TerminalSelectionProfile } from "../../core/terminal/terminalSelection.js";
|
|
39
39
|
import { MemoizedTopHeader, measureTopHeaderRows, type UpdateAvailableInfo } from "./TopHeader.js";
|
|
40
40
|
|
|
41
41
|
const COMPACT_HEADER_TO_COMPOSER_GAP_ROWS = 1;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useFocus, useInput, useStdin } from "ink";
|
|
3
|
-
import { formatContextCompact } from "
|
|
4
|
-
import type { ModelSpec } from "
|
|
5
|
-
import type { ExternalCliStatus, UIState } from "
|
|
6
|
-
import { FOCUS_IDS } from "
|
|
3
|
+
import { formatContextCompact } from "../../core/providerRuntime/contextMetadata.js";
|
|
4
|
+
import type { ModelSpec } from "../../core/models/modelSpecs.js";
|
|
5
|
+
import type { ExternalCliStatus, UIState } from "../../session/types.js";
|
|
6
|
+
import { FOCUS_IDS } from "../input/focus.js";
|
|
7
7
|
import {
|
|
8
8
|
createInputViewport,
|
|
9
9
|
deleteInputBackward,
|
|
@@ -14,22 +14,22 @@ import {
|
|
|
14
14
|
moveCursorRight,
|
|
15
15
|
normalizeInputText,
|
|
16
16
|
normalizeCursorOffset,
|
|
17
|
-
} from "
|
|
18
|
-
import { getModeDisplaySpec } from "
|
|
17
|
+
} from "../input/inputBuffer.js";
|
|
18
|
+
import { getModeDisplaySpec } from "../render/modeDisplay.js";
|
|
19
19
|
import { ActivityIndicator } from "./ActivityIndicator.js";
|
|
20
20
|
import { measureRunFooterRows, MemoizedRunFooter } from "./RunFooter.js";
|
|
21
|
-
import { useTheme } from "
|
|
22
|
-
import { clampVisualText, getShellWidth, type Layout } from "
|
|
23
|
-
import { getTextWidth, splitTextAtColumn } from "
|
|
24
|
-
import { useThrottledValue } from "
|
|
25
|
-
import { sanitizeTerminalOutput } from "
|
|
26
|
-
import { getStdinDebugState, traceInputDebug } from "
|
|
27
|
-
import * as renderDebug from "
|
|
21
|
+
import { useTheme } from "../theme.js";
|
|
22
|
+
import { clampVisualText, getShellWidth, type Layout } from "../layout.js";
|
|
23
|
+
import { getTextWidth, splitTextAtColumn } from "../render/textLayout.js";
|
|
24
|
+
import { useThrottledValue } from "../useThrottledValue.js";
|
|
25
|
+
import { sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
|
|
26
|
+
import { getStdinDebugState, traceInputDebug } from "../../core/debug/inputDebug.js";
|
|
27
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
28
28
|
import { AnimatedStatusText } from "./AnimatedStatusText.js";
|
|
29
29
|
import { isAnimatedBusyState } from "./busyStatusAnimation.js";
|
|
30
30
|
import { Spinner } from "./Spinner.js";
|
|
31
|
-
import type { TerminalSelectionProfile } from "
|
|
32
|
-
import { getSlashCommandSuggestions, type CommandSuggestion } from "
|
|
31
|
+
import type { TerminalSelectionProfile } from "../../core/terminal/terminalSelection.js";
|
|
32
|
+
import { getSlashCommandSuggestions, type CommandSuggestion } from "../input/slashCommands.js";
|
|
33
33
|
|
|
34
34
|
// ─── Types & constants ────────────────────────────────────────────────────────
|
|
35
35
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { Box, Text } from "ink";
|
|
4
|
-
import { useTheme } from "
|
|
5
|
-
import { getVisualWidth } from "
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
5
|
+
import { getVisualWidth } from "../layout.js";
|
|
6
6
|
|
|
7
7
|
interface DashCardProps {
|
|
8
8
|
cols: number;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { memo } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import type { UIState } from "
|
|
4
|
-
import * as renderDebug from "
|
|
5
|
-
import { useTheme } from "
|
|
3
|
+
import type { UIState } from "../../session/types.js";
|
|
4
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
5
|
+
import { useTheme } from "../theme.js";
|
|
6
6
|
import { AnimatedStatusText } from "./AnimatedStatusText.js";
|
|
7
7
|
import { isAnimatedBusyState } from "./busyStatusAnimation.js";
|
|
8
8
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { useTheme } from "
|
|
4
|
-
import type { Layout } from "
|
|
5
|
-
import * as renderDebug from "
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
4
|
+
import type { Layout } from "../layout.js";
|
|
5
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
6
6
|
|
|
7
7
|
export type RuntimeAvailability = "available" | "checking" | "reconnecting" | "unavailable" | "unknown";
|
|
8
8
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import React, { memo } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "
|
|
4
|
-
import { formatCodexaBrandLabel } from "
|
|
5
|
-
import { formatVersionLabel } from "
|
|
6
|
-
import type { RuntimeSummary } from "
|
|
7
|
-
import type { CodexAuthState } from "
|
|
8
|
-
import { getAuthStateLabel } from "
|
|
9
|
-
import * as renderDebug from "
|
|
10
|
-
import { useTheme } from "
|
|
11
|
-
import { clampVisualText, isDecorativeLayoutMode, type Layout, useAppLayoutBudget } from "
|
|
12
|
-
import { getTextWidth } from "
|
|
3
|
+
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../../config/settings.js";
|
|
4
|
+
import { formatCodexaBrandLabel } from "../../core/version/channel.js";
|
|
5
|
+
import { formatVersionLabel } from "../../core/version/updateCheck.js";
|
|
6
|
+
import type { RuntimeSummary } from "../../config/runtimeConfig.js";
|
|
7
|
+
import type { CodexAuthState } from "../../core/auth/codexAuth.js";
|
|
8
|
+
import { getAuthStateLabel } from "../../core/auth/codexAuth.js";
|
|
9
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
10
|
+
import { useTheme } from "../theme.js";
|
|
11
|
+
import { clampVisualText, isDecorativeLayoutMode, type Layout, useAppLayoutBudget } from "../layout.js";
|
|
12
|
+
import { getTextWidth } from "../render/textLayout.js";
|
|
13
13
|
import { UPDATE_CARD_ROWS, UpdateAvailableCard } from "./UpdateAvailableCard.js";
|
|
14
14
|
import {
|
|
15
15
|
LOGO_LARGE,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
LOGO_LARGE_MIN_COLS,
|
|
19
19
|
LOGO_LARGE_MIN_ROWS,
|
|
20
20
|
getLogoWidth,
|
|
21
|
-
} from "
|
|
21
|
+
} from "../render/logoVariants.js";
|
|
22
22
|
|
|
23
23
|
// Re-exported for backward compatibility with existing tests.
|
|
24
24
|
export const HEADER_WORDMARK_LINES = LOGO_LARGE;
|
|
@@ -434,4 +434,4 @@ export const MemoizedTopHeader = memo(TopHeader, (prev, next) => {
|
|
|
434
434
|
export const MIN_LOGO_TERMINAL_WIDTH = LOGO_COMPACT_MIN_COLS;
|
|
435
435
|
|
|
436
436
|
// Re-export for consumers that reference these constants directly.
|
|
437
|
-
export { LOGO_LARGE_MIN_COLS, LOGO_MEDIUM_MIN_COLS, LOGO_COMPACT_MIN_COLS } from "
|
|
437
|
+
export { LOGO_LARGE_MIN_COLS, LOGO_MEDIUM_MIN_COLS, LOGO_COMPACT_MIN_COLS } from "../render/logoVariants.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { CODEXA_UPDATE_COMMAND, formatVersionLabel } from "
|
|
4
|
-
import { clampVisualText } from "
|
|
5
|
-
import { useTheme } from "
|
|
3
|
+
import { CODEXA_UPDATE_COMMAND, formatVersionLabel } from "../../core/version/updateCheck.js";
|
|
4
|
+
import { clampVisualText } from "../layout.js";
|
|
5
|
+
import { useTheme } from "../theme.js";
|
|
6
6
|
|
|
7
7
|
export const UPDATE_CARD_CONTENT_ROWS = 4; // title + available + using + command
|
|
8
8
|
export const UPDATE_CARD_ROWS = UPDATE_CARD_CONTENT_ROWS + 2; // +2 for top/bottom border rows
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatTerminalAnswerInline } from "
|
|
1
|
+
import { formatTerminalAnswerInline } from "../render/terminalAnswerFormat.js";
|
|
2
2
|
|
|
3
3
|
function stripOuterQuotes(s: string): string {
|
|
4
4
|
if ((s.startsWith("'") && s.endsWith("'")) || (s.startsWith('"') && s.endsWith('"'))) {
|