@golba98/codexa 1.0.2 → 1.0.4
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 +396 -100
- package/bin/codexa.js +62 -144
- package/package.json +14 -8
- package/src/app.tsx +596 -306
- package/src/commands/handler.ts +6 -6
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/persistence.ts +10 -0
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +8 -16
- package/src/config/trustStore.ts +1 -1
- package/src/config/updateCheckCache.ts +19 -1
- package/src/core/README.md +52 -0
- package/src/core/agent/loop.ts +282 -0
- package/src/core/agent/protocol.ts +211 -0
- package/src/core/agent/tools.ts +414 -0
- package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
- package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
- package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
- package/src/core/debug/modelStateDebug.ts +34 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +65 -43
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/providerLauncher/registry.ts +30 -14
- package/src/core/providerLauncher/types.ts +11 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +7 -1
- package/src/core/providerRuntime/antigravity.ts +305 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/contextMetadata.ts +12 -24
- package/src/core/providerRuntime/local.ts +129 -51
- package/src/core/providerRuntime/models.ts +22 -11
- package/src/core/providerRuntime/registry.ts +58 -31
- package/src/core/providerRuntime/types.ts +19 -14
- package/src/core/providers/codexSubprocess.ts +2 -2
- package/src/core/providers/types.ts +1 -1
- package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
- package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
- package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
- package/src/core/terminal/clearFrameBoundary.ts +814 -0
- package/src/core/terminal/frameLock.ts +109 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/terminalControl.ts +22 -0
- package/src/core/terminal/terminalTitle.ts +16 -102
- package/src/core/version/channel.ts +23 -0
- package/src/core/version/updateCheck.ts +193 -0
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +32 -39
- package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
- package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
- package/src/headless/execRunner.ts +2 -2
- package/src/index.tsx +43 -89
- package/src/session/appSession.ts +10 -7
- package/src/session/chatLifecycle.ts +1 -1
- package/src/session/liveRenderScheduler.ts +1 -1
- package/src/session/types.ts +3 -2
- 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 +670 -719
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BottomComposer.tsx +158 -118
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModelPickerScreen.tsx +222 -42
- package/src/ui/ModelReasoningPicker.tsx +15 -15
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +9 -9
- package/src/ui/ProviderPicker.tsx +735 -321
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/RuntimeStatusBar.tsx +108 -0
- package/src/ui/SelectionPanel.tsx +8 -4
- package/src/ui/SettingsPanel.tsx +9 -9
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +11 -11
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +1625 -1472
- package/src/ui/TopHeader.tsx +437 -293
- package/src/ui/TranscriptShell.tsx +322 -0
- package/src/ui/TurnGroup.tsx +33 -33
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +197 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/layout.ts +299 -25
- package/src/ui/layoutListWindow.ts +145 -0
- package/src/ui/logoVariants.ts +103 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/runtimeDisplay.ts +112 -0
- package/src/ui/textLayout.ts +15 -4
- package/src/ui/theme.tsx +274 -395
- package/src/ui/timelineMeasure.ts +218 -136
- package/scripts/audit-codexa-capabilities.mjs +0 -466
- package/scripts/gen-build-info.mjs +0 -33
- package/scripts/smoke-terminal-bench.mjs +0 -35
- package/src/appRenderStability.test.ts +0 -131
- package/src/commands/handler.test.ts +0 -655
- package/src/config/launchArgs.test.ts +0 -189
- package/src/config/layeredConfig.test.ts +0 -143
- package/src/config/persistence.test.ts +0 -114
- 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/codex.ts +0 -124
- 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/startupClear.test.ts +0 -55
- 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/updateCheck.test.ts +0 -194
- package/src/core/updateCheck.ts +0 -172
- 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 -436
- package/src/index.test.tsx +0 -620
- 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 -1776
- 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/StaticTranscriptItem.tsx +0 -56
- package/src/ui/Timeline.test.ts +0 -2067
- package/src/ui/TimelineNavigation.test.tsx +0 -201
- package/src/ui/TopHeader.test.tsx +0 -254
- 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 -146
- 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
- /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
- /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
- /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
- /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
- /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
- /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { sanitizeTerminalOutput } from "../terminal/terminalSanitize.js";
|
|
2
|
+
import { runAgentLoop, type AgentChatMessage, type AgentChatResponse } from "../agent/loop.js";
|
|
3
|
+
import { parseOpenAiToolCalls } from "../agent/protocol.js";
|
|
2
4
|
import type { BackendRunHandlers } from "../providers/types.js";
|
|
3
5
|
import type { ProviderWorkspaceOverride } from "../providerLauncher/types.js";
|
|
4
6
|
import type {
|
|
@@ -456,21 +458,110 @@ function getCachedSelectedModel(config: LocalProviderConfig, routeModel: string)
|
|
|
456
458
|
return selectFallbackLocalModel(config, discoveredIds) ?? routeModel;
|
|
457
459
|
}
|
|
458
460
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
461
|
+
interface LocalResponseDiagnostics {
|
|
462
|
+
choiceCount: number;
|
|
463
|
+
finishReasons: string[];
|
|
464
|
+
recognizedFields: string[];
|
|
465
|
+
topLevelKeys: string[];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
interface ExtractedLocalResponse extends AgentChatResponse {
|
|
469
|
+
diagnostics: LocalResponseDiagnostics;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function textFromContent(value: unknown): string {
|
|
473
|
+
if (typeof value === "string") return value;
|
|
474
|
+
if (Array.isArray(value)) return value.map(textFromContent).join("");
|
|
475
|
+
if (!isRecord(value)) return "";
|
|
476
|
+
|
|
477
|
+
// OpenAI-compatible servers commonly emit content as typed parts, such as
|
|
478
|
+
// { type: "text", text: "..." } or { type: "output_text", text: "..." }.
|
|
479
|
+
if (typeof value.text === "string") return value.text;
|
|
480
|
+
if (typeof value.content === "string" || Array.isArray(value.content)) {
|
|
481
|
+
return textFromContent(value.content);
|
|
482
|
+
}
|
|
483
|
+
if ((value.type === "text" || value.type === "output_text") && typeof value.value === "string") {
|
|
484
|
+
return value.value;
|
|
485
|
+
}
|
|
486
|
+
return "";
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function messageFieldText(message: Record<string, unknown>, fields: readonly string[], recognizedFields: Set<string>): string {
|
|
490
|
+
for (const field of fields) {
|
|
491
|
+
const text = textFromContent(message[field]);
|
|
492
|
+
if (text.trim()) {
|
|
493
|
+
recognizedFields.add(`message.${field}`);
|
|
494
|
+
return text;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return "";
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function extractNonStreamingResponse(body: unknown): ExtractedLocalResponse {
|
|
501
|
+
const topLevelKeys = isRecord(body) ? Object.keys(body).sort() : [];
|
|
502
|
+
const choices = isRecord(body) && Array.isArray(body.choices) ? body.choices : [];
|
|
503
|
+
const recognizedFields = new Set<string>();
|
|
504
|
+
const finishReasons: string[] = [];
|
|
505
|
+
const toolCalls = choices.flatMap((choice) => {
|
|
506
|
+
if (!isRecord(choice)) return [];
|
|
507
|
+
if (typeof choice.finish_reason === "string") finishReasons.push(choice.finish_reason);
|
|
508
|
+
const message = isRecord(choice.message) ? choice.message : null;
|
|
509
|
+
return message ? parseOpenAiToolCalls(message.tool_calls) : [];
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const content: string[] = [];
|
|
513
|
+
const reasoning: string[] = [];
|
|
514
|
+
for (const choice of choices) {
|
|
515
|
+
if (!isRecord(choice)) continue;
|
|
516
|
+
const message = isRecord(choice.message) ? choice.message : null;
|
|
517
|
+
const messageContent = message
|
|
518
|
+
? messageFieldText(message, ["content"], recognizedFields)
|
|
519
|
+
: "";
|
|
520
|
+
const legacyText = textFromContent(choice.text);
|
|
521
|
+
if (legacyText.trim()) recognizedFields.add("choice.text");
|
|
522
|
+
const answer = messageContent || legacyText;
|
|
523
|
+
if (answer.trim()) {
|
|
524
|
+
content.push(answer);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const reasoningText = message
|
|
529
|
+
? messageFieldText(message, ["reasoning_content", "reasoning", "analysis"], recognizedFields)
|
|
530
|
+
: "";
|
|
531
|
+
const choiceReasoning = !reasoningText
|
|
532
|
+
? messageFieldText(choice, ["reasoning_content", "reasoning", "analysis"], recognizedFields)
|
|
533
|
+
: "";
|
|
534
|
+
if ((reasoningText || choiceReasoning).trim()) {
|
|
535
|
+
reasoning.push(reasoningText || choiceReasoning);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
// Reasoning is a fallback only. A response with both fields should show its
|
|
541
|
+
// final answer, while reasoning-only servers still produce useful text.
|
|
542
|
+
text: (content.length > 0 ? content : reasoning).join("").trim(),
|
|
543
|
+
...(toolCalls.length > 0 ? { toolCalls } : {}),
|
|
544
|
+
diagnostics: {
|
|
545
|
+
choiceCount: choices.length,
|
|
546
|
+
finishReasons: [...new Set(finishReasons)],
|
|
547
|
+
recognizedFields: [...recognizedFields].sort(),
|
|
548
|
+
topLevelKeys,
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function emptyResponseError(config: LocalProviderConfig, model: string, diagnostics: LocalResponseDiagnostics): Error {
|
|
554
|
+
const finishReasons = diagnostics.finishReasons.length > 0 ? diagnostics.finishReasons.join(", ") : "none";
|
|
555
|
+
const fields = diagnostics.recognizedFields.length > 0 ? diagnostics.recognizedFields.join(", ") : "none";
|
|
556
|
+
const keys = diagnostics.topLevelKeys.length > 0 ? diagnostics.topLevelKeys.join(", ") : "none";
|
|
557
|
+
return new Error([
|
|
558
|
+
"Local OpenAI-compatible API returned no assistant text.",
|
|
559
|
+
`Endpoint: ${config.baseUrl}/chat/completions`,
|
|
560
|
+
`Model: ${model}`,
|
|
561
|
+
`Response details: choices=${diagnostics.choiceCount}; finish_reason=${finishReasons}; recognized_fields=${fields}; top_level_keys=${keys}.`,
|
|
562
|
+
"Check the local server response format or retry the prompt.",
|
|
563
|
+
].join("\n"));
|
|
564
|
+
}
|
|
474
565
|
|
|
475
566
|
function parseStreamDelta(line: string): string | null {
|
|
476
567
|
const trimmed = line.trim();
|
|
@@ -521,18 +612,19 @@ async function postLocalChatCompletion(options: {
|
|
|
521
612
|
request: ProviderChatRequest;
|
|
522
613
|
config: LocalProviderConfig;
|
|
523
614
|
stream: boolean;
|
|
615
|
+
messages?: readonly AgentChatMessage[];
|
|
524
616
|
fetchImpl: FetchImpl;
|
|
525
617
|
signal?: AbortSignal;
|
|
526
618
|
handlers: BackendRunHandlers;
|
|
527
619
|
capProfile: import("./capabilityProfile.js").ModelCapabilityProfile;
|
|
528
|
-
}): Promise<
|
|
620
|
+
}): Promise<AgentChatResponse> {
|
|
529
621
|
const model = getCachedSelectedModel(options.config, options.request.route.modelId);
|
|
530
622
|
const includeSystemPrompt = options.capProfile.supportsSystemPrompt !== false;
|
|
531
|
-
const messages = [
|
|
623
|
+
const messages: readonly AgentChatMessage[] = options.messages ?? [
|
|
532
624
|
...(includeSystemPrompt && options.request.projectInstructions?.content
|
|
533
|
-
? [{ role: "system", content: options.request.projectInstructions.content }]
|
|
625
|
+
? [{ role: "system" as const, content: options.request.projectInstructions.content }]
|
|
534
626
|
: []),
|
|
535
|
-
{ role: "user", content: options.request.prompt },
|
|
627
|
+
{ role: "user" as const, content: options.request.prompt },
|
|
536
628
|
];
|
|
537
629
|
const response = await options.fetchImpl(`${options.config.baseUrl}/chat/completions`, {
|
|
538
630
|
method: "POST",
|
|
@@ -560,11 +652,15 @@ async function postLocalChatCompletion(options: {
|
|
|
560
652
|
}
|
|
561
653
|
|
|
562
654
|
if (options.stream) {
|
|
563
|
-
return readStreamingResponse(response, options.handlers);
|
|
655
|
+
return { text: await readStreamingResponse(response, options.handlers) };
|
|
564
656
|
}
|
|
565
657
|
|
|
566
|
-
const parsed = JSON.parse(await response.text()) as unknown;
|
|
567
|
-
|
|
658
|
+
const parsed = JSON.parse(await response.text()) as unknown;
|
|
659
|
+
const extracted = extractNonStreamingResponse(parsed);
|
|
660
|
+
if (!extracted.text.trim() && (extracted.toolCalls?.length ?? 0) === 0) {
|
|
661
|
+
throw emptyResponseError(options.config, model, extracted.diagnostics);
|
|
662
|
+
}
|
|
663
|
+
return extracted;
|
|
568
664
|
}
|
|
569
665
|
|
|
570
666
|
function isModelNotLoadedError(status: number, body: string): boolean {
|
|
@@ -589,42 +685,24 @@ export async function runLocalOpenAiCompatible(
|
|
|
589
685
|
providerConfig: request.localConfig ?? configuredOverride,
|
|
590
686
|
rawMetadata: rawMeta,
|
|
591
687
|
});
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
688
|
+
const text = await runAgentLoop({
|
|
689
|
+
request,
|
|
690
|
+
handlers,
|
|
691
|
+
includeSystemPrompt: capProfile.supportsSystemPrompt !== false,
|
|
692
|
+
signal: options.signal,
|
|
693
|
+
sendMessages: async (messages) =>
|
|
694
|
+
postLocalChatCompletion({
|
|
597
695
|
request,
|
|
598
696
|
config,
|
|
599
|
-
|
|
697
|
+
messages,
|
|
698
|
+
stream: false,
|
|
600
699
|
fetchImpl,
|
|
601
700
|
signal: options.signal,
|
|
602
701
|
handlers,
|
|
603
702
|
capProfile,
|
|
604
|
-
})
|
|
605
|
-
if (streamed) return streamed;
|
|
606
|
-
} catch (error) {
|
|
607
|
-
if (options.signal?.aborted) throw error;
|
|
608
|
-
handlers.onProgress?.({
|
|
609
|
-
id: "local-stream-fallback",
|
|
610
|
-
source: "stderr",
|
|
611
|
-
text: "Local streaming request failed; retrying without streaming.",
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
const text = await postLocalChatCompletion({
|
|
617
|
-
request,
|
|
618
|
-
config,
|
|
619
|
-
stream: false,
|
|
620
|
-
fetchImpl,
|
|
621
|
-
signal: options.signal,
|
|
622
|
-
handlers,
|
|
623
|
-
capProfile,
|
|
703
|
+
}),
|
|
624
704
|
});
|
|
625
|
-
if (!text)
|
|
626
|
-
throw new Error("Local OpenAI-compatible API returned no assistant text.");
|
|
627
|
-
}
|
|
705
|
+
if (!text) throw new Error("Local OpenAI-compatible API returned no assistant text after tool execution.");
|
|
628
706
|
handlers.onAssistantDelta?.(text);
|
|
629
707
|
return text;
|
|
630
708
|
}
|
|
@@ -73,16 +73,20 @@ export const GEMINI_FALLBACK_MODELS: readonly ProviderModel[] = [
|
|
|
73
73
|
},
|
|
74
74
|
] as const;
|
|
75
75
|
|
|
76
|
+
function fallbackModelDescription(family: string): string {
|
|
77
|
+
return `Claude ${family} alias — version unknown because model discovery is unavailable`;
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
export const ANTHROPIC_FALLBACK_MODELS: readonly ProviderModel[] = [
|
|
77
81
|
{
|
|
78
82
|
id: "opus",
|
|
79
83
|
modelId: "opus",
|
|
80
|
-
label: "Opus
|
|
81
|
-
description: "
|
|
84
|
+
label: "Claude Opus (version unknown)",
|
|
85
|
+
description: fallbackModelDescription("Opus"),
|
|
82
86
|
defaultReasoningLevel: "xhigh",
|
|
83
87
|
supportedReasoningLevels: getClaudeCodeEffortLevels(["low", "medium", "high", "xhigh", "max"]),
|
|
84
88
|
source: "fallback",
|
|
85
|
-
canonicalId: "
|
|
89
|
+
canonicalId: "opus",
|
|
86
90
|
family: "opus",
|
|
87
91
|
effortSource: "fallback",
|
|
88
92
|
effortVerified: false,
|
|
@@ -90,12 +94,12 @@ export const ANTHROPIC_FALLBACK_MODELS: readonly ProviderModel[] = [
|
|
|
90
94
|
{
|
|
91
95
|
id: "sonnet",
|
|
92
96
|
modelId: "sonnet",
|
|
93
|
-
label: "Sonnet
|
|
94
|
-
description: "
|
|
97
|
+
label: "Claude Sonnet (version unknown)",
|
|
98
|
+
description: fallbackModelDescription("Sonnet"),
|
|
95
99
|
defaultReasoningLevel: "high",
|
|
96
100
|
supportedReasoningLevels: getClaudeCodeEffortLevels(["low", "medium", "high", "max"]),
|
|
97
101
|
source: "fallback",
|
|
98
|
-
canonicalId: "
|
|
102
|
+
canonicalId: "sonnet",
|
|
99
103
|
family: "sonnet",
|
|
100
104
|
effortSource: "fallback",
|
|
101
105
|
effortVerified: false,
|
|
@@ -103,20 +107,27 @@ export const ANTHROPIC_FALLBACK_MODELS: readonly ProviderModel[] = [
|
|
|
103
107
|
{
|
|
104
108
|
id: "haiku",
|
|
105
109
|
modelId: "haiku",
|
|
106
|
-
label: "Haiku
|
|
107
|
-
description: "
|
|
110
|
+
label: "Claude Haiku (version unknown)",
|
|
111
|
+
description: fallbackModelDescription("Haiku"),
|
|
108
112
|
defaultReasoningLevel: "medium",
|
|
109
113
|
supportedReasoningLevels: getClaudeCodeEffortLevels(["low", "medium", "high"]),
|
|
110
114
|
source: "fallback",
|
|
111
|
-
canonicalId: "
|
|
115
|
+
canonicalId: "haiku",
|
|
112
116
|
family: "haiku",
|
|
113
117
|
effortSource: "fallback",
|
|
114
118
|
effortVerified: false,
|
|
115
119
|
},
|
|
116
|
-
]
|
|
120
|
+
];
|
|
117
121
|
|
|
118
122
|
function isRuntimeSource(source: ProviderModel["source"]): boolean {
|
|
119
|
-
return source === "discovered"
|
|
123
|
+
return source === "discovered"
|
|
124
|
+
|| source === "claude-code"
|
|
125
|
+
|| source === "claude-code-command"
|
|
126
|
+
|| source === "claude-code-package"
|
|
127
|
+
|| source === "claude-code-cache"
|
|
128
|
+
|| source === "claude-code-config"
|
|
129
|
+
|| source === "settings"
|
|
130
|
+
|| source === "config";
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
export function providerModelsToCodexCapabilities(
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { codexSubprocessProvider } from "../providers/codexSubprocess.js";
|
|
2
2
|
import type { BackendRunHandlers } from "../providers/types.js";
|
|
3
3
|
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";
|
|
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";
|
|
7
8
|
import {
|
|
8
9
|
ANTHROPIC_FALLBACK_MODELS,
|
|
9
10
|
GEMINI_DEFAULT_MODEL_ID,
|
|
@@ -71,10 +72,11 @@ function unavailableRuntime(providerId: ProviderId, label: string): ProviderRunt
|
|
|
71
72
|
|
|
72
73
|
const PROVIDER_RUNTIMES: Record<ProviderId, ProviderRuntime> = {
|
|
73
74
|
openai: openAiRuntime,
|
|
74
|
-
anthropic: anthropicRuntime,
|
|
75
|
-
google: geminiRuntime,
|
|
76
|
-
local: localRuntime,
|
|
77
|
-
|
|
75
|
+
anthropic: anthropicRuntime,
|
|
76
|
+
google: geminiRuntime,
|
|
77
|
+
local: localRuntime,
|
|
78
|
+
antigravity: antigravityRuntime,
|
|
79
|
+
};
|
|
78
80
|
|
|
79
81
|
export function getProviderRuntime(providerId: ProviderId): ProviderRuntime {
|
|
80
82
|
return PROVIDER_RUNTIMES[providerId];
|
|
@@ -100,11 +102,12 @@ export function discoverProviderModels(providerId: ProviderId): ProviderModelDis
|
|
|
100
102
|
|
|
101
103
|
export async function validateProviderRouteActivation(options: {
|
|
102
104
|
route: ProviderRoute;
|
|
103
|
-
workspaceRoot: string;
|
|
104
|
-
geminiCommandPath?: string | null;
|
|
105
|
-
claudeCommandPath?: string | null;
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
workspaceRoot: string;
|
|
106
|
+
geminiCommandPath?: string | null;
|
|
107
|
+
claudeCommandPath?: string | null;
|
|
108
|
+
antigravityCommandPath?: string | null;
|
|
109
|
+
localConfig?: ProviderWorkspaceOverride | null;
|
|
110
|
+
}): Promise<ProviderRouteValidationResult> {
|
|
108
111
|
const runtime = getProviderRuntime(options.route.providerId);
|
|
109
112
|
if (!runtime.routeAvailable) {
|
|
110
113
|
return {
|
|
@@ -154,7 +157,7 @@ export function resolveActiveProviderRoute(options: {
|
|
|
154
157
|
currentReasoning: string;
|
|
155
158
|
}): ActiveProviderRoute {
|
|
156
159
|
const configuredRoute = options.workspaceConfigActiveRoute;
|
|
157
|
-
if (configuredRoute && isProviderRoutableInCodexa(configuredRoute.providerId)) {
|
|
160
|
+
if (configuredRoute && configuredRoute.providerId !== "google" && isProviderRoutableInCodexa(configuredRoute.providerId)) {
|
|
158
161
|
const route: ActiveProviderRoute = {
|
|
159
162
|
providerId: configuredRoute.providerId,
|
|
160
163
|
modelId: configuredRoute.modelId,
|
|
@@ -163,19 +166,36 @@ export function resolveActiveProviderRoute(options: {
|
|
|
163
166
|
...(configuredRoute.modelSelection ? { modelSelection: configuredRoute.modelSelection } : {}),
|
|
164
167
|
};
|
|
165
168
|
|
|
166
|
-
if (route.providerId === "google" && route.modelSelection) {
|
|
167
|
-
route.modelId = resolveGeminiModelId(route.modelSelection);
|
|
168
|
-
} else if (route.providerId === "google") {
|
|
169
|
-
route.modelId = normalizeGeminiModelId(route.modelId);
|
|
170
|
-
} else if (route.providerId === "
|
|
171
|
-
const discovery = discoverProviderModels("
|
|
172
|
-
const
|
|
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"
|
|
173
187
|
? discovery.diagnostics.selectedModel.trim()
|
|
174
188
|
: "";
|
|
175
|
-
if (discovery.status === "ready" && selectedModel) {
|
|
176
|
-
route.modelId = selectedModel;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
189
|
+
if (discovery.status === "ready" && selectedModel) {
|
|
190
|
+
route.modelId = selectedModel;
|
|
191
|
+
}
|
|
192
|
+
} else if (route.providerId === "antigravity") {
|
|
193
|
+
const migrated = migrateAntigravityLegacyModelId(route.modelId);
|
|
194
|
+
route.modelId = migrated.modelId;
|
|
195
|
+
if (!route.reasoning && migrated.reasoning) {
|
|
196
|
+
route.reasoning = migrated.reasoning;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
179
199
|
|
|
180
200
|
return route;
|
|
181
201
|
}
|
|
@@ -190,14 +210,21 @@ export function resolveActiveProviderRoute(options: {
|
|
|
190
210
|
|
|
191
211
|
export function getDefaultRouteModel(providerId: ProviderId, currentOpenAiModel: string): string {
|
|
192
212
|
if (providerId === "anthropic") {
|
|
193
|
-
|
|
213
|
+
const discovered = discoverProviderModels("anthropic");
|
|
214
|
+
if (discovered.status === "ready" && discovered.models.length > 0) {
|
|
215
|
+
return discovered.models[0].modelId;
|
|
216
|
+
}
|
|
217
|
+
return ANTHROPIC_FALLBACK_MODELS[0]?.modelId ?? "sonnet";
|
|
194
218
|
}
|
|
195
219
|
if (providerId === "google") {
|
|
196
220
|
return GEMINI_FALLBACK_MODELS[0]?.modelId ?? GEMINI_DEFAULT_MODEL_ID;
|
|
197
221
|
}
|
|
198
|
-
if (providerId === "local") {
|
|
199
|
-
const discovery = discoverProviderModels("local");
|
|
200
|
-
return discovery.models[0]?.modelId ?? "Local default";
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
222
|
+
if (providerId === "local") {
|
|
223
|
+
const discovery = discoverProviderModels("local");
|
|
224
|
+
return discovery.models[0]?.modelId ?? "Local default";
|
|
225
|
+
}
|
|
226
|
+
if (providerId === "antigravity") {
|
|
227
|
+
return ANTIGRAVITY_DEFAULT_MODEL_ID;
|
|
228
|
+
}
|
|
229
|
+
return currentOpenAiModel;
|
|
230
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
|
|
2
|
-
import type { ProjectInstructions } from "../projectInstructions.js";
|
|
2
|
+
import type { ProjectInstructions } from "../workspace/projectInstructions.js";
|
|
3
3
|
import type { BackendRunHandlers } from "../providers/types.js";
|
|
4
4
|
import type { ResolvedRuntimeConfig } from "../../config/runtimeConfig.js";
|
|
5
5
|
export type { ResolvedRuntimeConfig };
|
|
@@ -7,10 +7,11 @@ import type { ProviderId } from "../providerLauncher/types.js";
|
|
|
7
7
|
import type { ProviderWorkspaceOverride } from "../providerLauncher/types.js";
|
|
8
8
|
|
|
9
9
|
export type ProviderBackendKind =
|
|
10
|
-
| "codex-cli-auth"
|
|
11
|
-
| "gemini-cli-auth"
|
|
12
|
-
| "claude-code-auth"
|
|
13
|
-
| "
|
|
10
|
+
| "codex-cli-auth"
|
|
11
|
+
| "gemini-cli-auth"
|
|
12
|
+
| "claude-code-auth"
|
|
13
|
+
| "antigravity-cli-auth"
|
|
14
|
+
| "openai-api-key"
|
|
14
15
|
| "gemini-api-key"
|
|
15
16
|
| "anthropic-api-key"
|
|
16
17
|
| "local-openai-compatible"
|
|
@@ -23,10 +24,13 @@ export interface ProviderModel {
|
|
|
23
24
|
description: string | null;
|
|
24
25
|
defaultReasoningLevel: string | null;
|
|
25
26
|
supportedReasoningLevels: readonly ReasoningEffortCapability[] | null;
|
|
26
|
-
source?: "discovered" | "claude-code" | "settings" | "config" | "fallback";
|
|
27
|
-
canonicalId?: string;
|
|
28
|
-
family?: string;
|
|
29
|
-
|
|
27
|
+
source?: "discovered" | "claude-code" | "claude-code-command" | "claude-code-package" | "claude-code-cache" | "claude-code-config" | "settings" | "config" | "fallback";
|
|
28
|
+
canonicalId?: string;
|
|
29
|
+
family?: string;
|
|
30
|
+
version?: string;
|
|
31
|
+
isFallback?: boolean;
|
|
32
|
+
discoveryKind?: "models" | "aliases";
|
|
33
|
+
effortSource?: "claude-code" | "claude-code-command" | "claude-code-package" | "claude-code-cache" | "claude-code-config" | "settings" | "config" | "fallback";
|
|
30
34
|
effortVerified?: boolean;
|
|
31
35
|
raw?: unknown;
|
|
32
36
|
}
|
|
@@ -58,11 +62,12 @@ export type ActiveProviderRoute = ProviderRoute;
|
|
|
58
62
|
|
|
59
63
|
export interface ProviderRouteValidationRequest {
|
|
60
64
|
route: ProviderRoute;
|
|
61
|
-
workspaceRoot: string;
|
|
62
|
-
geminiCommandPath?: string | null;
|
|
63
|
-
claudeCommandPath?: string | null;
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
workspaceRoot: string;
|
|
66
|
+
geminiCommandPath?: string | null;
|
|
67
|
+
claudeCommandPath?: string | null;
|
|
68
|
+
antigravityCommandPath?: string | null;
|
|
69
|
+
localConfig?: ProviderWorkspaceOverride | null;
|
|
70
|
+
}
|
|
66
71
|
|
|
67
72
|
export interface ProviderRouteValidationResult {
|
|
68
73
|
status: "ready" | "not-configured";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { formatCodexLaunchError, spawnCodexProcess } from "../executables/codexExecutable.js";
|
|
3
|
-
import { prepareCodexExecLaunch } from "../codexLaunch.js";
|
|
3
|
+
import { prepareCodexExecLaunch } from "../codex/codexLaunch.js";
|
|
4
4
|
import * as perf from "../perf/profiler.js";
|
|
5
|
-
import { buildCodexPrompt } from "../codexPrompt.js";
|
|
5
|
+
import { buildCodexPrompt } from "../codex/codexPrompt.js";
|
|
6
6
|
import { createTerminalTitleSequenceStripper } from "../terminal/terminalTitle.js";
|
|
7
7
|
import { createCodexJsonStreamParser } from "./codexJsonStream.js";
|
|
8
8
|
import {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AvailableBackend } from "../../config/settings.js";
|
|
2
2
|
import type { ResolvedRuntimeConfig } from "../../config/runtimeConfig.js";
|
|
3
|
-
import type { ProjectInstructions } from "../projectInstructions.js";
|
|
3
|
+
import type { ProjectInstructions } from "../workspace/projectInstructions.js";
|
|
4
4
|
import type { RunProgressSource, RunToolActivity } from "../../session/types.js";
|
|
5
5
|
|
|
6
6
|
export interface BackendProgressUpdate {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { access, copyFile, mkdir } from "node:fs/promises";
|
|
1
|
+
import { access, copyFile, mkdir, stat } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
isSkippedExternalDependencyPath,
|
|
6
|
+
normalizeDiagnosticPath,
|
|
7
|
+
} from "../workspace/workspaceGuard.js";
|
|
3
8
|
|
|
4
9
|
export const IMAGE_EXTENSIONS = new Set([
|
|
5
10
|
".png",
|
|
@@ -44,10 +49,28 @@ export async function resolveAttachmentDestPath(
|
|
|
44
49
|
export async function importExternalFile(
|
|
45
50
|
srcPath: string,
|
|
46
51
|
attachmentsDir: string,
|
|
47
|
-
): Promise<string> {
|
|
52
|
+
): Promise<string | null> {
|
|
53
|
+
const normalized = normalizeDiagnosticPath(srcPath);
|
|
54
|
+
|
|
55
|
+
if (isSkippedExternalDependencyPath(normalized)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
if (!existsSync(normalized)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const s = await stat(normalized);
|
|
64
|
+
if (!s.isFile()) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
48
71
|
await mkdir(attachmentsDir, { recursive: true });
|
|
49
|
-
const destPath = await resolveAttachmentDestPath(
|
|
50
|
-
await copyFile(
|
|
72
|
+
const destPath = await resolveAttachmentDestPath(normalized, attachmentsDir);
|
|
73
|
+
await copyFile(normalized, destPath);
|
|
51
74
|
return destPath;
|
|
52
75
|
}
|
|
53
76
|
|