@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.
Files changed (73) hide show
  1. package/package.json +1 -1
  2. package/src/app.tsx +128 -47
  3. package/src/commands/handler.ts +5 -16
  4. package/src/config/buildInfo.ts +2 -2
  5. package/src/core/models/codexModelCapabilities.ts +57 -4
  6. package/src/core/models/codexModelsCacheSeed.ts +153 -0
  7. package/src/core/models/providerModelCache.ts +106 -0
  8. package/src/core/process/CommandRunner.ts +12 -1
  9. package/src/core/providerLauncher/registry.ts +37 -7
  10. package/src/core/providerLauncher/types.ts +2 -1
  11. package/src/core/providerRuntime/anthropic.ts +3 -5
  12. package/src/core/providerRuntime/antigravity.ts +246 -90
  13. package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -692
  14. package/src/core/providerRuntime/mistralVibe.ts +663 -0
  15. package/src/core/providerRuntime/models.ts +18 -4
  16. package/src/core/providerRuntime/reasoning.ts +9 -6
  17. package/src/core/providerRuntime/registry.ts +94 -49
  18. package/src/core/providerRuntime/types.ts +1 -0
  19. package/src/index.tsx +23 -9
  20. package/src/test/runtimeTestUtils.ts +90 -0
  21. package/src/ui/{ActivityBars.tsx → chrome/ActivityBars.tsx} +1 -1
  22. package/src/ui/{ActivityIndicator.tsx → chrome/ActivityIndicator.tsx} +3 -3
  23. package/src/ui/{AnimatedStatusText.tsx → chrome/AnimatedStatusText.tsx} +3 -3
  24. package/src/ui/{AppShell.tsx → chrome/AppShell.tsx} +11 -11
  25. package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +15 -15
  26. package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
  27. package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
  28. package/src/ui/{RuntimeStatusBar.tsx → chrome/RuntimeStatusBar.tsx} +3 -3
  29. package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
  30. package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +12 -12
  31. package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +3 -3
  32. package/src/ui/{commandNormalize.ts → input/commandNormalize.ts} +1 -1
  33. package/src/ui/{focus.ts → input/focus.ts} +1 -1
  34. package/src/ui/{inputBuffer.ts → input/inputBuffer.ts} +3 -3
  35. package/src/ui/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +1 -1
  36. package/src/ui/{AuthPanel.tsx → panels/AuthPanel.tsx} +5 -5
  37. package/src/ui/{BackendPicker.tsx → panels/BackendPicker.tsx} +2 -2
  38. package/src/ui/{ModePicker.tsx → panels/ModePicker.tsx} +2 -2
  39. package/src/ui/{ModelPicker.tsx → panels/ModelPicker.tsx} +2 -2
  40. package/src/ui/{ModelPickerScreen.tsx → panels/ModelPickerScreen.tsx} +11 -9
  41. package/src/ui/{ModelReasoningPicker.tsx → panels/ModelReasoningPicker.tsx} +5 -5
  42. package/src/ui/{Panel.tsx → panels/Panel.tsx} +1 -1
  43. package/src/ui/{PermissionsPanel.tsx → panels/PermissionsPanel.tsx} +3 -3
  44. package/src/ui/{PlanActionPicker.tsx → panels/PlanActionPicker.tsx} +2 -2
  45. package/src/ui/{PlanReviewPanel.tsx → panels/PlanReviewPanel.tsx} +5 -5
  46. package/src/ui/{ProviderPicker.tsx → panels/ProviderPicker.tsx} +9 -7
  47. package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
  48. package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +1 -1
  49. package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +3 -3
  50. package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +3 -3
  51. package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
  52. package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +2 -2
  53. package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
  54. package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
  55. package/src/ui/{modeDisplay.ts → render/modeDisplay.ts} +2 -2
  56. package/src/ui/{outputPipeline.ts → render/outputPipeline.ts} +2 -2
  57. package/src/ui/{runtimeDisplay.ts → render/runtimeDisplay.ts} +9 -8
  58. package/src/ui/{ActionRequiredBlock.tsx → timeline/ActionRequiredBlock.tsx} +3 -3
  59. package/src/ui/{AgentBlock.tsx → timeline/AgentBlock.tsx} +10 -10
  60. package/src/ui/{StaticIntroItem.tsx → timeline/StaticIntroItem.tsx} +2 -2
  61. package/src/ui/{ThinkingBlock.tsx → timeline/ThinkingBlock.tsx} +4 -4
  62. package/src/ui/{Timeline.tsx → timeline/Timeline.tsx} +8 -8
  63. package/src/ui/{TranscriptShell.tsx → timeline/TranscriptShell.tsx} +6 -6
  64. package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
  65. package/src/ui/{progressEntries.ts → timeline/progressEntries.ts} +3 -3
  66. package/src/ui/{runActivityView.ts → timeline/runActivityView.ts} +1 -1
  67. package/src/ui/{timelineMeasure.ts → timeline/timelineMeasure.ts} +14 -14
  68. /package/src/ui/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
  69. /package/src/ui/{slashCommands.ts → input/slashCommands.ts} +0 -0
  70. /package/src/ui/{logoVariants.ts → render/logoVariants.ts} +0 -0
  71. /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
  72. /package/src/ui/{textLayout.ts → render/textLayout.ts} +0 -0
  73. /package/src/ui/{layoutListWindow.ts → timeline/layoutListWindow.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { runCommand } from "../process/CommandRunner.js";
2
2
  import { sanitizeTerminalOutput } from "../terminal/terminalSanitize.js";
3
3
  import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
4
+ import { loadCachedProviderModels } from "../models/providerModelCache.js";
4
5
  import type { BackendRunHandlers } from "../providers/types.js";
5
6
  import type {
6
7
  ProviderBackendKind,
@@ -27,97 +28,144 @@ export const ANTIGRAVITY_DEFAULT_MODEL_ID = "gemini-3.5-flash";
27
28
  export const ANTIGRAVITY_DEFAULT_REASONING = "high";
28
29
 
29
30
  // ---------------------------------------------------------------------------
30
- // Effort levels for Antigravity Gemini models
31
+ // Model definitions
31
32
  // ---------------------------------------------------------------------------
32
33
 
33
- const AGY_LOW: ReasoningEffortCapability = { id: "low", label: "Low", description: null };
34
- const AGY_MEDIUM: ReasoningEffortCapability = { id: "medium", label: "Medium", description: null };
35
- const AGY_HIGH: ReasoningEffortCapability = { id: "high", label: "High", description: null };
34
+ interface AgySelectorMetadata {
35
+ provider: "antigravity";
36
+ selectors: Record<string, string>;
37
+ }
36
38
 
37
- // ---------------------------------------------------------------------------
38
- // Model definitions
39
- // ---------------------------------------------------------------------------
39
+ function normalizeAgyId(value: string): string {
40
+ return value
41
+ .trim()
42
+ .toLowerCase()
43
+ .replace(/[^a-z0-9.]+/g, "-")
44
+ .replace(/^-+|-+$/g, "");
45
+ }
40
46
 
41
- export const ANTIGRAVITY_MODELS: readonly ProviderModel[] = [
42
- {
43
- id: "gemini-3.5-flash",
44
- modelId: "gemini-3.5-flash",
45
- label: "Gemini 3.5 Flash",
46
- description: "Antigravity-routed Gemini 3.5 Flash. Select effort with ←/→.",
47
- defaultReasoningLevel: "high",
48
- supportedReasoningLevels: [AGY_LOW, AGY_MEDIUM, AGY_HIGH],
49
- source: "fallback",
50
- },
51
- {
52
- id: "gemini-3.1-pro",
53
- modelId: "gemini-3.1-pro",
54
- label: "Gemini 3.1 Pro",
55
- description: "Antigravity-routed Gemini 3.1 Pro. Select effort with ←/→.",
56
- defaultReasoningLevel: "high",
57
- supportedReasoningLevels: [AGY_LOW, AGY_HIGH],
58
- source: "fallback",
59
- },
60
- {
61
- id: "claude-sonnet-4-6-think",
62
- modelId: "claude-sonnet-4-6-think",
63
- label: "Claude Sonnet 4.6 (Thinking)",
64
- description: "Antigravity-routed Claude Sonnet 4.6 with extended thinking.",
65
- defaultReasoningLevel: null,
66
- supportedReasoningLevels: null,
67
- source: "fallback",
68
- },
69
- {
70
- id: "claude-opus-4-6-think",
71
- modelId: "claude-opus-4-6-think",
72
- label: "Claude Opus 4.6 (Thinking)",
73
- description: "Antigravity-routed Claude Opus 4.6 with extended thinking.",
74
- defaultReasoningLevel: null,
75
- supportedReasoningLevels: null,
76
- source: "fallback",
77
- },
78
- {
79
- id: "gpt-oss-120b",
80
- modelId: "gpt-oss-120b",
81
- label: "GPT-OSS 120B",
82
- description: "Antigravity-routed GPT-OSS 120B.",
83
- defaultReasoningLevel: null,
84
- supportedReasoningLevels: null,
85
- source: "fallback",
86
- },
87
- ];
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
+ }
88
54
 
89
- // ---------------------------------------------------------------------------
90
- // AGY_MODEL env mapping
91
- // ---------------------------------------------------------------------------
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
+ }
92
61
 
93
- /**
94
- * Maps Codexa model IDs to the AGY_MODEL env value passed to the agy subprocess.
95
- *
96
- * Verified:
97
- * gemini-3.5-flash → "gemini-3.5-flash" (confirmed via: AGY_MODEL=gemini-3.5-flash agy -p "say hello back")
98
- *
99
- * Unverified (same pattern, not independently smoke-tested):
100
- * gemini-3.1-pro "gemini-3.1-pro"
101
- */
102
- const AGY_MODEL_ENV_MAP: Readonly<Record<string, string>> = {
103
- "gemini-3.5-flash": "gemini-3.5-flash",
104
- "gemini-3.1-pro": "gemini-3.1-pro",
105
- };
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
+ }
106
107
 
107
- export function getAgyModelEnvValue(modelId: string): string | null {
108
- return AGY_MODEL_ENV_MAP[modelId] ?? null;
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;
109
149
  }
110
150
 
111
- export function buildAgyEnv(modelId: string, _reasoning?: string): NodeJS.ProcessEnv {
112
- // _reasoning is stored in route state and surfaced in the footer UI, but agy
113
- // has no verified CLI flag or env var for passing effort level to the subprocess.
114
- // TODO: wire _reasoning here once Antigravity exposes a stable mechanism.
115
- const envValue = getAgyModelEnvValue(modelId);
116
- return envValue ? { ...process.env, AGY_MODEL: envValue } : { ...process.env };
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;
117
165
  }
118
166
 
119
167
  export function getAntigravityModelLabel(modelId: string): string {
120
- return ANTIGRAVITY_MODELS.find((m) => m.id === modelId)?.label ?? modelId;
168
+ return getActiveAgyModels().find((m) => m.id === modelId)?.label ?? modelId;
121
169
  }
122
170
 
123
171
  // ---------------------------------------------------------------------------
@@ -138,7 +186,9 @@ export function migrateAntigravityLegacyModelId(modelId: string): { modelId: str
138
186
  "gemini-3.5-flash-low": { modelId: "gemini-3.5-flash", reasoning: "low" },
139
187
  "gemini-3.1-pro-high": { modelId: "gemini-3.1-pro", reasoning: "high" },
140
188
  "gemini-3.1-pro-low": { modelId: "gemini-3.1-pro", reasoning: "low" },
141
- "gpt-oss-120b-medium": { modelId: "gpt-oss-120b" },
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" },
142
192
  };
143
193
  return legacy[modelId] ?? { modelId };
144
194
  }
@@ -149,6 +199,52 @@ export function migrateAntigravityLegacyModelId(modelId: string): { modelId: str
149
199
 
150
200
  let agyRouteValidated = false;
151
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
+ }
152
248
 
153
249
  export function isAntigravityRouteConfigured(): boolean {
154
250
  return agyRouteValidated;
@@ -157,6 +253,7 @@ export function isAntigravityRouteConfigured(): boolean {
157
253
  export function resetAntigravityRouteValidationCacheForTests(): void {
158
254
  agyRouteValidated = false;
159
255
  resolvedAgyExecutable = "agy";
256
+ discoveredAgyModels = null;
160
257
  resetAgyExecutableCacheForTests();
161
258
  }
162
259
 
@@ -170,6 +267,24 @@ export async function validateAntigravityRoute(options: {
170
267
  runCommandImpl?: typeof runCommand;
171
268
  platform?: NodeJS.Platform;
172
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
+
173
288
  let resolved: string;
174
289
  try {
175
290
  resolved = await resolveAgyExecutable({
@@ -213,13 +328,23 @@ export async function validateAntigravityRoute(options: {
213
328
 
214
329
  resolvedAgyExecutable = resolved;
215
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
+ });
216
337
 
217
338
  return {
218
339
  status: "ready",
219
340
  providerId: "antigravity",
220
341
  backendKind: "antigravity-cli-auth",
221
342
  message: `Antigravity CLI found at: ${resolved}`,
222
- diagnostics: { resolvedCommand: resolved },
343
+ diagnostics: {
344
+ resolvedCommand: resolved,
345
+ modelSource: modelDiscovery.diagnostics?.modelSource ?? "unavailable",
346
+ discoveredModelCount: modelDiscovery.models.length,
347
+ },
223
348
  };
224
349
  }
225
350
 
@@ -233,16 +358,23 @@ export function runAntigravityWithRunner(
233
358
  runCommandImpl: typeof runCommand = runCommand,
234
359
  executable: string = resolvedAgyExecutable,
235
360
  platform: NodeJS.Platform = process.platform,
361
+ models: readonly ProviderModel[] = getActiveAgyModels(),
236
362
  ): () => void {
237
- const spawnSpec = buildSpawnSpec(executable, ["-p", request.prompt], platform);
238
- const env = buildAgyEnv(request.route.modelId, request.route.reasoning);
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);
239
371
 
240
372
  const runner = runCommandImpl(
241
373
  {
242
374
  executable: spawnSpec.executable,
243
375
  args: spawnSpec.args,
244
376
  cwd: request.workspaceRoot,
245
- env,
377
+ env: { ...process.env },
246
378
  timeoutMs: ANTIGRAVITY_TIMEOUT_MS,
247
379
  },
248
380
  );
@@ -288,12 +420,36 @@ export const antigravityRuntime: ProviderRuntime = {
288
420
  cwd: workspaceRoot,
289
421
  configuredPath: antigravityCommandPath ?? null,
290
422
  }),
291
- discoverModels: (): ProviderModelDiscoveryResult => ({
292
- status: "ready",
293
- providerId: "antigravity",
294
- backendKind: "antigravity-cli-auth",
295
- models: ANTIGRAVITY_MODELS,
296
- }),
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
+ },
297
453
  run: (request: ProviderChatRequest, handlers: BackendRunHandlers) => {
298
454
  handlers.onProgress?.({
299
455
  id: "antigravity-route",