@golba98/codexa 1.0.3 → 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/README.md +17 -18
- package/bin/codexa.js +62 -144
- package/package.json +4 -3
- package/src/app.tsx +642 -303
- package/src/commands/handler.ts +7 -18
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +1 -1
- package/src/config/trustStore.ts +1 -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/executableResolver.ts +5 -1
- 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/perf/renderDebug.ts +10 -6
- package/src/core/process/CommandRunner.ts +12 -1
- package/src/core/providerLauncher/registry.ts +64 -18
- package/src/core/providerLauncher/types.ts +12 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +10 -6
- package/src/core/providerRuntime/antigravity.ts +461 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -446
- 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/mistralVibe.ts +663 -0
- package/src/core/providerRuntime/models.ts +40 -15
- package/src/core/providerRuntime/reasoning.ts +9 -6
- package/src/core/providerRuntime/registry.ts +76 -4
- package/src/core/providerRuntime/types.ts +20 -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/{channel.ts → version/channel.ts} +1 -1
- package/src/core/{updateCheck.ts → version/updateCheck.ts} +10 -5
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +9 -16
- 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 +66 -98
- 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 +1 -1
- 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/chrome/AppShell.tsx +672 -0
- package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +159 -158
- package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
- package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
- package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +54 -38
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +5 -5
- 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/layout.ts +298 -24
- package/src/ui/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +3 -3
- 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} +224 -42
- 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/panels/ProviderPicker.tsx +737 -0
- package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
- package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +5 -1
- package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +4 -4
- package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +4 -4
- package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
- package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +17 -23
- package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
- package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
- package/src/ui/{logoVariants.ts → render/logoVariants.ts} +4 -8
- 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} +23 -10
- package/src/ui/{textLayout.ts → render/textLayout.ts} +15 -4
- 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} +1620 -1471
- package/src/ui/timeline/TranscriptShell.tsx +322 -0
- package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- 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} +206 -134
- package/src/core/codex.ts +0 -124
- package/src/ui/AppShell.tsx +0 -706
- package/src/ui/ProviderPicker.tsx +0 -321
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- /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
- /package/src/ui/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
- /package/src/ui/{slashCommands.ts → input/slashCommands.ts} +0 -0
- /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
|
@@ -4,7 +4,6 @@ import type { BackendRunHandlers } from "../providers/types.js";
|
|
|
4
4
|
import { ANTHROPIC_FALLBACK_MODELS } from "./models.js";
|
|
5
5
|
import type { ProviderBackendKind, ProviderChatRequest, ProviderModel, ProviderModelDiscoveryResult, ProviderRouteValidationResult, ProviderRuntime } from "./types.js";
|
|
6
6
|
import { buildClaudeSpawnSpec, resetClaudeExecutableCacheForTests } from "../executables/claudeExecutable.js";
|
|
7
|
-
import { CLAUDE_CODE_EFFORT_IDS } from "./reasoning.js";
|
|
8
7
|
import {
|
|
9
8
|
claudeCodeModelsToProviderModels,
|
|
10
9
|
discoverClaudeCodeCapabilities,
|
|
@@ -19,6 +18,7 @@ const ANTHROPIC_MAX_TOKENS = 1024;
|
|
|
19
18
|
const ANTHROPIC_TIMEOUT_MS = 120_000;
|
|
20
19
|
const ANTHROPIC_AUTH_CHECK_TIMEOUT_MS = 10_000;
|
|
21
20
|
const ANTHROPIC_ROUTE_VALIDATION_TIMEOUT_MS = 15_000;
|
|
21
|
+
const DISCOVERY_FAILURE_MESSAGE = "Claude Code model version discovery failed; using fallback aliases with unknown versions.";
|
|
22
22
|
export const ANTHROPIC_ROUTE_SETUP_MESSAGE = "Anthropic/Claude is not configured for in-Codexa routing.\nSign in with Claude Code or set ANTHROPIC_API_KEY.";
|
|
23
23
|
export { parseClaudeAuthStatus } from "./claudeCodeDiscovery.js";
|
|
24
24
|
|
|
@@ -55,10 +55,9 @@ export function mapModelIdToClaudeArg(modelId: string): string {
|
|
|
55
55
|
return modelId;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export function mapReasoningToEffort(reasoning: string | null | undefined): string | null {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
58
|
+
export function mapReasoningToEffort(reasoning: string | null | undefined): string | null {
|
|
59
|
+
return reasoning?.trim() || null;
|
|
60
|
+
}
|
|
62
61
|
|
|
63
62
|
function buildClaudeCodeBaseArgs(request: ProviderChatRequest): string[] {
|
|
64
63
|
const effort = mapReasoningToEffort(request.route.reasoning ?? null);
|
|
@@ -498,6 +497,9 @@ export const anthropicRuntime: ProviderRuntime = {
|
|
|
498
497
|
providerId: "anthropic",
|
|
499
498
|
backendKind: getAnthropicRuntimeBackendKind(),
|
|
500
499
|
models: getActiveAnthropicModels(),
|
|
500
|
+
message: (claudeCapabilityDiscovery?.modelSource ?? "fallback") === "fallback"
|
|
501
|
+
? DISCOVERY_FAILURE_MESSAGE
|
|
502
|
+
: undefined,
|
|
501
503
|
diagnostics: claudeCapabilityDiscovery ? {
|
|
502
504
|
resolvedCommand: claudeCapabilityDiscovery.resolvedCommand,
|
|
503
505
|
modelSource: claudeCapabilityDiscovery.modelSource,
|
|
@@ -534,7 +536,9 @@ export const anthropicRuntime: ProviderRuntime = {
|
|
|
534
536
|
providerId: "anthropic",
|
|
535
537
|
backendKind: getAnthropicRuntimeBackendKind(),
|
|
536
538
|
models: discoveredAnthropicModels,
|
|
537
|
-
message:
|
|
539
|
+
message: discovery.modelSource === "fallback"
|
|
540
|
+
? DISCOVERY_FAILURE_MESSAGE
|
|
541
|
+
: `Refreshed Claude capabilities (${discovery.modelSource}).`,
|
|
538
542
|
diagnostics: {
|
|
539
543
|
resolvedCommand: discovery.resolvedCommand,
|
|
540
544
|
modelSource: discovery.modelSource,
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { runCommand } from "../process/CommandRunner.js";
|
|
2
|
+
import { sanitizeTerminalOutput } from "../terminal/terminalSanitize.js";
|
|
3
|
+
import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
|
|
4
|
+
import { loadCachedProviderModels } from "../models/providerModelCache.js";
|
|
5
|
+
import type { BackendRunHandlers } from "../providers/types.js";
|
|
6
|
+
import type {
|
|
7
|
+
ProviderBackendKind,
|
|
8
|
+
ProviderChatRequest,
|
|
9
|
+
ProviderModel,
|
|
10
|
+
ProviderModelDiscoveryResult,
|
|
11
|
+
ProviderRouteValidationResult,
|
|
12
|
+
ProviderRuntime,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
import {
|
|
15
|
+
resolveAgyExecutable,
|
|
16
|
+
resetAgyExecutableCacheForTests,
|
|
17
|
+
} from "../executables/antigravityExecutable.js";
|
|
18
|
+
import { buildSpawnSpec } from "../executables/executableResolver.js";
|
|
19
|
+
|
|
20
|
+
export { resetAgyExecutableCacheForTests };
|
|
21
|
+
|
|
22
|
+
const ANTIGRAVITY_TIMEOUT_MS = 120_000;
|
|
23
|
+
const ANTIGRAVITY_VALIDATION_TIMEOUT_MS = 10_000;
|
|
24
|
+
const ANTIGRAVITY_ROUTE_SETUP_MESSAGE =
|
|
25
|
+
"`agy` command not found. Install Antigravity CLI or set AGY_EXECUTABLE to the full path.";
|
|
26
|
+
|
|
27
|
+
export const ANTIGRAVITY_DEFAULT_MODEL_ID = "gemini-3.5-flash";
|
|
28
|
+
export const ANTIGRAVITY_DEFAULT_REASONING = "high";
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Model definitions
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
interface AgySelectorMetadata {
|
|
35
|
+
provider: "antigravity";
|
|
36
|
+
selectors: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeAgyId(value: string): string {
|
|
40
|
+
return value
|
|
41
|
+
.trim()
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.replace(/[^a-z0-9.]+/g, "-")
|
|
44
|
+
.replace(/^-+|-+$/g, "");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatAgyVariantLabel(value: string): string {
|
|
48
|
+
return value
|
|
49
|
+
.split(/[-_\s]+/)
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1).toLowerCase()}`)
|
|
52
|
+
.join(" ") || value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readAgySelectorMetadata(model: ProviderModel): AgySelectorMetadata | null {
|
|
56
|
+
if (!model.raw || typeof model.raw !== "object" || Array.isArray(model.raw)) return null;
|
|
57
|
+
const raw = model.raw as Partial<AgySelectorMetadata>;
|
|
58
|
+
if (raw.provider !== "antigravity" || !raw.selectors || typeof raw.selectors !== "object") return null;
|
|
59
|
+
return { provider: "antigravity", selectors: raw.selectors };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function preferredAgyDefault(modelId: string, efforts: readonly string[]): string {
|
|
63
|
+
if ((modelId === "gemini-3.5-flash" || modelId === "gemini-3.1-pro") && efforts.includes(ANTIGRAVITY_DEFAULT_REASONING)) {
|
|
64
|
+
return ANTIGRAVITY_DEFAULT_REASONING;
|
|
65
|
+
}
|
|
66
|
+
return efforts[0] ?? ANTIGRAVITY_DEFAULT_REASONING;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const AGY_REASONING_DISPLAY_ORDER = ["low", "medium", "high", "xhigh", "max"] as const;
|
|
70
|
+
|
|
71
|
+
function sortAgyReasoningLevels(levels: readonly ReasoningEffortCapability[]): ReasoningEffortCapability[] {
|
|
72
|
+
const rank = new Map<string, number>(AGY_REASONING_DISPLAY_ORDER.map((id, index) => [id, index]));
|
|
73
|
+
return levels
|
|
74
|
+
.map((level, index) => ({ level, index }))
|
|
75
|
+
.sort((left, right) => (rank.get(left.level.id) ?? AGY_REASONING_DISPLAY_ORDER.length + left.index)
|
|
76
|
+
- (rank.get(right.level.id) ?? AGY_REASONING_DISPLAY_ORDER.length + right.index))
|
|
77
|
+
.map(({ level }) => level);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function parseAgyModelsOutput(stdout: string): ProviderModel[] {
|
|
81
|
+
const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
82
|
+
const parsed = lines.map((selector) => {
|
|
83
|
+
const match = selector.match(/^(.*?)\s+\(([^()]+)\)$/);
|
|
84
|
+
return { selector, base: match?.[1]?.trim() ?? selector, variant: match?.[2]?.trim() ?? null };
|
|
85
|
+
});
|
|
86
|
+
const baseCounts = new Map<string, number>();
|
|
87
|
+
for (const item of parsed) baseCounts.set(item.base, (baseCounts.get(item.base) ?? 0) + 1);
|
|
88
|
+
|
|
89
|
+
const models: ProviderModel[] = [];
|
|
90
|
+
const grouped = new Map<string, ProviderModel>();
|
|
91
|
+
for (const item of parsed) {
|
|
92
|
+
const isVariantGroup = item.variant !== null && (baseCounts.get(item.base) ?? 0) > 1;
|
|
93
|
+
if (!isVariantGroup) {
|
|
94
|
+
const modelId = normalizeAgyId(item.selector);
|
|
95
|
+
models.push({
|
|
96
|
+
id: modelId,
|
|
97
|
+
modelId,
|
|
98
|
+
label: item.selector,
|
|
99
|
+
description: `Discovered from agy models: ${item.selector}`,
|
|
100
|
+
defaultReasoningLevel: null,
|
|
101
|
+
supportedReasoningLevels: null,
|
|
102
|
+
source: "discovered",
|
|
103
|
+
raw: { provider: "antigravity", selectors: { "": item.selector } } satisfies AgySelectorMetadata,
|
|
104
|
+
});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const modelId = normalizeAgyId(item.base);
|
|
109
|
+
const effortId = normalizeAgyId(item.variant ?? "");
|
|
110
|
+
const existing = grouped.get(item.base);
|
|
111
|
+
if (existing) {
|
|
112
|
+
const metadata = readAgySelectorMetadata(existing);
|
|
113
|
+
const levels = sortAgyReasoningLevels([...(existing.supportedReasoningLevels ?? []), {
|
|
114
|
+
id: effortId,
|
|
115
|
+
label: formatAgyVariantLabel(item.variant ?? effortId),
|
|
116
|
+
description: null,
|
|
117
|
+
}]);
|
|
118
|
+
const selectors = { ...(metadata?.selectors ?? {}), [effortId]: item.selector };
|
|
119
|
+
const updated = {
|
|
120
|
+
...existing,
|
|
121
|
+
defaultReasoningLevel: preferredAgyDefault(modelId, levels.map((level) => level.id)),
|
|
122
|
+
supportedReasoningLevels: levels,
|
|
123
|
+
raw: { provider: "antigravity", selectors } satisfies AgySelectorMetadata,
|
|
124
|
+
};
|
|
125
|
+
grouped.set(item.base, updated);
|
|
126
|
+
models[models.indexOf(existing)] = updated;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const level: ReasoningEffortCapability = {
|
|
131
|
+
id: effortId,
|
|
132
|
+
label: formatAgyVariantLabel(item.variant ?? effortId),
|
|
133
|
+
description: null,
|
|
134
|
+
};
|
|
135
|
+
const model: ProviderModel = {
|
|
136
|
+
id: modelId,
|
|
137
|
+
modelId,
|
|
138
|
+
label: item.base,
|
|
139
|
+
description: `Discovered from agy models. Select an advertised variant with ←/→.`,
|
|
140
|
+
defaultReasoningLevel: preferredAgyDefault(modelId, [effortId]),
|
|
141
|
+
supportedReasoningLevels: [level],
|
|
142
|
+
source: "discovered",
|
|
143
|
+
raw: { provider: "antigravity", selectors: { [effortId]: item.selector } } satisfies AgySelectorMetadata,
|
|
144
|
+
};
|
|
145
|
+
grouped.set(item.base, model);
|
|
146
|
+
models.push(model);
|
|
147
|
+
}
|
|
148
|
+
return models;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Resolve persisted model/reasoning state to the exact selector advertised by `agy models`.
|
|
152
|
+
export function getAgyModelSelector(
|
|
153
|
+
modelId: string,
|
|
154
|
+
reasoning: string | null | undefined,
|
|
155
|
+
models: readonly ProviderModel[] = getActiveAgyModels(),
|
|
156
|
+
): string | null {
|
|
157
|
+
const model = models.find((item) => item.modelId === modelId || item.id === modelId);
|
|
158
|
+
if (!model) return null;
|
|
159
|
+
const metadata = readAgySelectorMetadata(model);
|
|
160
|
+
if (!metadata) return null;
|
|
161
|
+
if (!model.supportedReasoningLevels?.length) return metadata.selectors[""] ?? null;
|
|
162
|
+
if (reasoning) return metadata.selectors[reasoning] ?? null;
|
|
163
|
+
const effort = model.defaultReasoningLevel;
|
|
164
|
+
return effort ? metadata.selectors[effort] ?? null : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function getAntigravityModelLabel(modelId: string): string {
|
|
168
|
+
return getActiveAgyModels().find((m) => m.id === modelId)?.label ?? modelId;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Legacy model ID migration
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Migrates legacy compound Antigravity model IDs (from feat/antigravity-cli-provider)
|
|
177
|
+
* to the new family + reasoning format.
|
|
178
|
+
*
|
|
179
|
+
* Old IDs encoded effort in the model ID (e.g., "gemini-3.5-flash-high").
|
|
180
|
+
* New IDs use the base family ("gemini-3.5-flash") with reasoning stored separately.
|
|
181
|
+
*/
|
|
182
|
+
export function migrateAntigravityLegacyModelId(modelId: string): { modelId: string; reasoning?: string } {
|
|
183
|
+
const legacy: Record<string, { modelId: string; reasoning?: string }> = {
|
|
184
|
+
"gemini-3.5-flash-high": { modelId: "gemini-3.5-flash", reasoning: "high" },
|
|
185
|
+
"gemini-3.5-flash-medium": { modelId: "gemini-3.5-flash", reasoning: "medium" },
|
|
186
|
+
"gemini-3.5-flash-low": { modelId: "gemini-3.5-flash", reasoning: "low" },
|
|
187
|
+
"gemini-3.1-pro-high": { modelId: "gemini-3.1-pro", reasoning: "high" },
|
|
188
|
+
"gemini-3.1-pro-low": { modelId: "gemini-3.1-pro", reasoning: "low" },
|
|
189
|
+
"claude-sonnet-4-6-think": { modelId: "claude-sonnet-4.6-thinking" },
|
|
190
|
+
"claude-opus-4-6-think": { modelId: "claude-opus-4.6-thinking" },
|
|
191
|
+
"gpt-oss-120b": { modelId: "gpt-oss-120b-medium" },
|
|
192
|
+
};
|
|
193
|
+
return legacy[modelId] ?? { modelId };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Module-level state
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
let agyRouteValidated = false;
|
|
201
|
+
let resolvedAgyExecutable: string = "agy";
|
|
202
|
+
let discoveredAgyModels: readonly ProviderModel[] | null = null;
|
|
203
|
+
|
|
204
|
+
function getActiveAgyModels(): readonly ProviderModel[] {
|
|
205
|
+
if (discoveredAgyModels?.length) return discoveredAgyModels;
|
|
206
|
+
return loadCachedProviderModels("antigravity")?.models ?? [];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function discoverAgyModels(options: {
|
|
210
|
+
executable: string;
|
|
211
|
+
cwd: string;
|
|
212
|
+
runCommandImpl: typeof runCommand;
|
|
213
|
+
platform: NodeJS.Platform;
|
|
214
|
+
}): Promise<ProviderModelDiscoveryResult> {
|
|
215
|
+
const spawnSpec = buildSpawnSpec(options.executable, ["models"], options.platform);
|
|
216
|
+
const result = await options.runCommandImpl({
|
|
217
|
+
executable: spawnSpec.executable,
|
|
218
|
+
args: spawnSpec.args,
|
|
219
|
+
cwd: options.cwd,
|
|
220
|
+
timeoutMs: ANTIGRAVITY_VALIDATION_TIMEOUT_MS,
|
|
221
|
+
}).result;
|
|
222
|
+
const models = result.status === "completed" && result.exitCode === 0
|
|
223
|
+
? parseAgyModelsOutput(result.stdout)
|
|
224
|
+
: [];
|
|
225
|
+
if (models.length > 0) {
|
|
226
|
+
discoveredAgyModels = models;
|
|
227
|
+
return {
|
|
228
|
+
status: "ready",
|
|
229
|
+
providerId: "antigravity",
|
|
230
|
+
backendKind: "antigravity-cli-auth",
|
|
231
|
+
models,
|
|
232
|
+
message: `Loaded ${models.length} models from agy models.`,
|
|
233
|
+
diagnostics: { modelSource: "agy-models-command", modelsExitCode: result.exitCode, modelsStatus: result.status },
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const cached = loadCachedProviderModels("antigravity")?.models ?? [];
|
|
237
|
+
return {
|
|
238
|
+
status: cached.length > 0 ? "ready" : "not-configured",
|
|
239
|
+
providerId: "antigravity",
|
|
240
|
+
backendKind: cached.length > 0 ? "antigravity-cli-auth" : "unavailable",
|
|
241
|
+
models: cached,
|
|
242
|
+
message: cached.length > 0
|
|
243
|
+
? "Live agy model metadata is unavailable; using the last successful discovery."
|
|
244
|
+
: "Antigravity model metadata is unavailable. Run Refresh models after checking `agy models`.",
|
|
245
|
+
diagnostics: { modelSource: cached.length > 0 ? "cache" : "unavailable", modelsExitCode: result.exitCode, modelsStatus: result.status },
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function isAntigravityRouteConfigured(): boolean {
|
|
250
|
+
return agyRouteValidated;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function resetAntigravityRouteValidationCacheForTests(): void {
|
|
254
|
+
agyRouteValidated = false;
|
|
255
|
+
resolvedAgyExecutable = "agy";
|
|
256
|
+
discoveredAgyModels = null;
|
|
257
|
+
resetAgyExecutableCacheForTests();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Route validation
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
export async function validateAntigravityRoute(options: {
|
|
265
|
+
cwd?: string;
|
|
266
|
+
configuredPath?: string | null;
|
|
267
|
+
runCommandImpl?: typeof runCommand;
|
|
268
|
+
platform?: NodeJS.Platform;
|
|
269
|
+
}): Promise<ProviderRouteValidationResult> {
|
|
270
|
+
// Already validated this session: skip the executable probe and model
|
|
271
|
+
// re-discovery so re-activating antigravity is instant. "Refresh models"
|
|
272
|
+
// bypasses this via runtime.refreshModels, so a stale catalog stays
|
|
273
|
+
// user-recoverable.
|
|
274
|
+
if (agyRouteValidated && discoveredAgyModels?.length) {
|
|
275
|
+
return {
|
|
276
|
+
status: "ready",
|
|
277
|
+
providerId: "antigravity",
|
|
278
|
+
backendKind: "antigravity-cli-auth",
|
|
279
|
+
message: `Antigravity CLI found at: ${resolvedAgyExecutable}`,
|
|
280
|
+
diagnostics: {
|
|
281
|
+
resolvedCommand: resolvedAgyExecutable,
|
|
282
|
+
modelSource: "session-cache",
|
|
283
|
+
discoveredModelCount: discoveredAgyModels.length,
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let resolved: string;
|
|
289
|
+
try {
|
|
290
|
+
resolved = await resolveAgyExecutable({
|
|
291
|
+
cwd: options.cwd,
|
|
292
|
+
configuredPath: options.configuredPath,
|
|
293
|
+
runCommandImpl: options.runCommandImpl,
|
|
294
|
+
});
|
|
295
|
+
} catch {
|
|
296
|
+
return {
|
|
297
|
+
status: "not-configured",
|
|
298
|
+
providerId: "antigravity",
|
|
299
|
+
backendKind: "unavailable",
|
|
300
|
+
message: ANTIGRAVITY_ROUTE_SETUP_MESSAGE,
|
|
301
|
+
diagnostics: { resolvedCommand: null },
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Probe the binary to confirm it's actually installed. Running --help has no
|
|
306
|
+
// auth side effects and exits 0 when agy is present. buildSpawnSpec wraps
|
|
307
|
+
// .cmd/.bat shims in `cmd.exe /d /s /c call` on Windows (no-op elsewhere) so
|
|
308
|
+
// the probe can actually launch the resolved executable.
|
|
309
|
+
const runCommandImpl = options.runCommandImpl ?? runCommand;
|
|
310
|
+
const probeSpec = buildSpawnSpec(resolved, ["--help"], options.platform ?? process.platform);
|
|
311
|
+
const probe = runCommandImpl({
|
|
312
|
+
executable: probeSpec.executable,
|
|
313
|
+
args: probeSpec.args,
|
|
314
|
+
cwd: options.cwd ?? process.cwd(),
|
|
315
|
+
timeoutMs: ANTIGRAVITY_VALIDATION_TIMEOUT_MS,
|
|
316
|
+
});
|
|
317
|
+
const probeResult = await probe.result;
|
|
318
|
+
|
|
319
|
+
if (probeResult.status === "spawn_error") {
|
|
320
|
+
return {
|
|
321
|
+
status: "not-configured",
|
|
322
|
+
providerId: "antigravity",
|
|
323
|
+
backendKind: "unavailable",
|
|
324
|
+
message: ANTIGRAVITY_ROUTE_SETUP_MESSAGE,
|
|
325
|
+
diagnostics: { resolvedCommand: resolved },
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
resolvedAgyExecutable = resolved;
|
|
330
|
+
agyRouteValidated = true;
|
|
331
|
+
const modelDiscovery = await discoverAgyModels({
|
|
332
|
+
executable: resolved,
|
|
333
|
+
cwd: options.cwd ?? process.cwd(),
|
|
334
|
+
runCommandImpl,
|
|
335
|
+
platform: options.platform ?? process.platform,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
status: "ready",
|
|
340
|
+
providerId: "antigravity",
|
|
341
|
+
backendKind: "antigravity-cli-auth",
|
|
342
|
+
message: `Antigravity CLI found at: ${resolved}`,
|
|
343
|
+
diagnostics: {
|
|
344
|
+
resolvedCommand: resolved,
|
|
345
|
+
modelSource: modelDiscovery.diagnostics?.modelSource ?? "unavailable",
|
|
346
|
+
discoveredModelCount: modelDiscovery.models.length,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
// run()
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
export function runAntigravityWithRunner(
|
|
356
|
+
request: ProviderChatRequest,
|
|
357
|
+
handlers: BackendRunHandlers,
|
|
358
|
+
runCommandImpl: typeof runCommand = runCommand,
|
|
359
|
+
executable: string = resolvedAgyExecutable,
|
|
360
|
+
platform: NodeJS.Platform = process.platform,
|
|
361
|
+
models: readonly ProviderModel[] = getActiveAgyModels(),
|
|
362
|
+
): () => void {
|
|
363
|
+
const selector = getAgyModelSelector(request.route.modelId, request.route.reasoning, models);
|
|
364
|
+
if (!selector) {
|
|
365
|
+
handlers.onError(
|
|
366
|
+
`Antigravity has no verified selector for ${request.route.modelId}${request.route.reasoning ? ` / ${request.route.reasoning}` : ""}. Refresh models and try again.`,
|
|
367
|
+
);
|
|
368
|
+
return () => undefined;
|
|
369
|
+
}
|
|
370
|
+
const spawnSpec = buildSpawnSpec(executable, ["--model", selector, "-p", request.prompt], platform);
|
|
371
|
+
|
|
372
|
+
const runner = runCommandImpl(
|
|
373
|
+
{
|
|
374
|
+
executable: spawnSpec.executable,
|
|
375
|
+
args: spawnSpec.args,
|
|
376
|
+
cwd: request.workspaceRoot,
|
|
377
|
+
env: { ...process.env },
|
|
378
|
+
timeoutMs: ANTIGRAVITY_TIMEOUT_MS,
|
|
379
|
+
},
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
runner.result.then((result) => {
|
|
383
|
+
if (result.status === "canceled") return;
|
|
384
|
+
|
|
385
|
+
if (result.status !== "completed" || result.exitCode !== 0) {
|
|
386
|
+
const message = result.userMessage || result.stderr || "Antigravity CLI execution failed.";
|
|
387
|
+
handlers.onError(message, `agy command: ${JSON.stringify([spawnSpec.executable, ...spawnSpec.args])}`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const text = sanitizeTerminalOutput(result.stdout).trim();
|
|
392
|
+
if (text) {
|
|
393
|
+
handlers.onAssistantDelta?.(text);
|
|
394
|
+
}
|
|
395
|
+
handlers.onFinalAnswerObserved?.(text);
|
|
396
|
+
handlers.onResponse(text);
|
|
397
|
+
}).catch((error) => {
|
|
398
|
+
const message = error instanceof Error ? error.message : "Antigravity CLI execution failed.";
|
|
399
|
+
handlers.onError(message);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
return runner.cancel;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
// Runtime
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
|
|
409
|
+
export const antigravityRuntime: ProviderRuntime = {
|
|
410
|
+
providerId: "antigravity",
|
|
411
|
+
label: "Antigravity CLI",
|
|
412
|
+
modelPickerLabel: "Antigravity",
|
|
413
|
+
backendKind: "antigravity-cli-auth",
|
|
414
|
+
routeAvailable: true,
|
|
415
|
+
routeStatus: "Routes through the Antigravity CLI (`agy`) when installed.",
|
|
416
|
+
routeSetupMessage: ANTIGRAVITY_ROUTE_SETUP_MESSAGE,
|
|
417
|
+
launchAvailable: true,
|
|
418
|
+
isRouteConfigured: isAntigravityRouteConfigured,
|
|
419
|
+
validateRoute: async ({ workspaceRoot, antigravityCommandPath }) => validateAntigravityRoute({
|
|
420
|
+
cwd: workspaceRoot,
|
|
421
|
+
configuredPath: antigravityCommandPath ?? null,
|
|
422
|
+
}),
|
|
423
|
+
discoverModels: (): ProviderModelDiscoveryResult => {
|
|
424
|
+
const models = getActiveAgyModels();
|
|
425
|
+
return {
|
|
426
|
+
status: models.length > 0 ? "ready" : "not-configured",
|
|
427
|
+
providerId: "antigravity",
|
|
428
|
+
backendKind: models.length > 0 ? "antigravity-cli-auth" : "unavailable",
|
|
429
|
+
models,
|
|
430
|
+
...(models.length === 0 ? { message: "Antigravity model metadata is unavailable. Run Refresh models." } : {}),
|
|
431
|
+
};
|
|
432
|
+
},
|
|
433
|
+
refreshModels: async ({ cwd }): Promise<ProviderModelDiscoveryResult> => {
|
|
434
|
+
let executable = resolvedAgyExecutable;
|
|
435
|
+
try {
|
|
436
|
+
executable = await resolveAgyExecutable({ cwd });
|
|
437
|
+
resolvedAgyExecutable = executable;
|
|
438
|
+
} catch {
|
|
439
|
+
const cached = loadCachedProviderModels("antigravity")?.models ?? [];
|
|
440
|
+
return {
|
|
441
|
+
status: cached.length > 0 ? "ready" : "not-configured",
|
|
442
|
+
providerId: "antigravity",
|
|
443
|
+
backendKind: cached.length > 0 ? "antigravity-cli-auth" : "unavailable",
|
|
444
|
+
models: cached,
|
|
445
|
+
message: cached.length > 0
|
|
446
|
+
? "Antigravity CLI is unavailable; using the last successful model discovery."
|
|
447
|
+
: ANTIGRAVITY_ROUTE_SETUP_MESSAGE,
|
|
448
|
+
diagnostics: { modelSource: cached.length > 0 ? "cache" : "unavailable", resolvedCommand: null },
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return discoverAgyModels({ executable, cwd, runCommandImpl: runCommand, platform: process.platform });
|
|
452
|
+
},
|
|
453
|
+
run: (request: ProviderChatRequest, handlers: BackendRunHandlers) => {
|
|
454
|
+
handlers.onProgress?.({
|
|
455
|
+
id: "antigravity-route",
|
|
456
|
+
source: "stdout",
|
|
457
|
+
text: "Starting Antigravity CLI",
|
|
458
|
+
});
|
|
459
|
+
return runAntigravityWithRunner(request, handlers);
|
|
460
|
+
},
|
|
461
|
+
};
|