@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
@@ -0,0 +1,153 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { formatReasoningLabel } from "../../config/settings.js";
5
+ import type { ProviderModel } from "../providerRuntime/types.js";
6
+ import type {
7
+ CodexModelCapabilities,
8
+ CodexModelCapability,
9
+ ReasoningEffortCapability,
10
+ } from "./codexModelCapabilities.js";
11
+ import { loadCachedProviderModels, type CachedProviderModels } from "./providerModelCache.js";
12
+
13
+ // The codex CLI maintains its own model catalog cache with slugs, labels and
14
+ // reasoning levels. Reading it seeds Codexa's OpenAI model list instantly —
15
+ // no subprocess — and stays current because the codex CLI refreshes the file
16
+ // on its own runs.
17
+ // Resolved per call from env (matching claudeCodeDiscovery) so HOME
18
+ // redirection in tests holds — Bun's homedir() ignores runtime HOME changes.
19
+ export function getCodexModelsCacheFile(): string {
20
+ const home = process.env.USERPROFILE ?? process.env.HOME ?? homedir();
21
+ return join(home, ".codex", "models_cache.json");
22
+ }
23
+
24
+ interface CodexSeed {
25
+ fetchedAt: number;
26
+ models: readonly ProviderModel[];
27
+ }
28
+
29
+ function isRecord(value: unknown): value is Record<string, unknown> {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+
33
+ function asString(value: unknown): string | null {
34
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
35
+ }
36
+
37
+ function parseReasoningLevels(raw: unknown): readonly ReasoningEffortCapability[] | null {
38
+ if (!Array.isArray(raw)) {
39
+ return null;
40
+ }
41
+ const levels = raw
42
+ .map((entry): ReasoningEffortCapability | null => {
43
+ if (!isRecord(entry)) {
44
+ return null;
45
+ }
46
+ const id = asString(entry.effort);
47
+ if (!id) {
48
+ return null;
49
+ }
50
+ return { id, label: formatReasoningLabel(id), description: asString(entry.description) };
51
+ })
52
+ .filter((entry): entry is ReasoningEffortCapability => entry !== null);
53
+ return levels.length > 0 ? levels : null;
54
+ }
55
+
56
+ function parseSeedModel(raw: unknown): ProviderModel | null {
57
+ if (!isRecord(raw)) {
58
+ return null;
59
+ }
60
+ // Models with visibility "hide" are internal codex routes (e.g. auto-review).
61
+ if (asString(raw.visibility) === "hide") {
62
+ return null;
63
+ }
64
+ const modelId = asString(raw.slug);
65
+ if (!modelId) {
66
+ return null;
67
+ }
68
+ return {
69
+ id: modelId,
70
+ modelId,
71
+ label: asString(raw.display_name) ?? modelId,
72
+ description: asString(raw.description),
73
+ defaultReasoningLevel: asString(raw.default_reasoning_level),
74
+ supportedReasoningLevels: parseReasoningLevels(raw.supported_reasoning_levels),
75
+ source: "discovered",
76
+ };
77
+ }
78
+
79
+ export function loadCodexSeedModels(cacheFile = getCodexModelsCacheFile()): CodexSeed | null {
80
+ try {
81
+ if (!existsSync(cacheFile)) {
82
+ return null;
83
+ }
84
+ const parsed: unknown = JSON.parse(readFileSync(cacheFile, "utf8"));
85
+ if (!isRecord(parsed) || !Array.isArray(parsed.models)) {
86
+ return null;
87
+ }
88
+ const models = parsed.models
89
+ .map(parseSeedModel)
90
+ .filter((model): model is ProviderModel => model !== null);
91
+ if (models.length === 0) {
92
+ return null;
93
+ }
94
+ const fetchedAtRaw = asString(parsed.fetched_at);
95
+ const fetchedAt = fetchedAtRaw ? Date.parse(fetchedAtRaw) : Number.NaN;
96
+ return { fetchedAt: Number.isFinite(fetchedAt) ? fetchedAt : 0, models };
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ // Freshest locally known OpenAI models without spawning a subprocess:
103
+ // codex's own cache file vs Codexa's persisted last-good discovery.
104
+ export function loadSeededOpenAiModels(options: {
105
+ codexCacheFile?: string;
106
+ providerCacheFile?: string;
107
+ } = {}): CachedProviderModels | null {
108
+ const seed = loadCodexSeedModels(options.codexCacheFile);
109
+ const persisted = options.providerCacheFile === undefined
110
+ ? loadCachedProviderModels("openai")
111
+ : loadCachedProviderModels("openai", options.providerCacheFile);
112
+ if (seed && (!persisted || seed.fetchedAt >= persisted.discoveredAt)) {
113
+ return { discoveredAt: seed.fetchedAt, models: seed.models };
114
+ }
115
+ return persisted;
116
+ }
117
+
118
+ function toCapability(model: ProviderModel, index: number): CodexModelCapability {
119
+ return {
120
+ id: model.id,
121
+ model: model.modelId,
122
+ label: model.label,
123
+ description: model.description,
124
+ available: true,
125
+ hidden: false,
126
+ isDefault: index === 0,
127
+ defaultReasoningLevel: model.defaultReasoningLevel,
128
+ supportedReasoningLevels: model.supportedReasoningLevels,
129
+ reasoningLevelCount: model.supportedReasoningLevels ? model.supportedReasoningLevels.length : null,
130
+ source: "runtime",
131
+ raw: model,
132
+ };
133
+ }
134
+
135
+ // Capabilities for the OpenAI picker sourced purely from local caches.
136
+ // Returns null when nothing is cached yet (first ever launch).
137
+ export function loadSeededCodexCapabilities(options: {
138
+ codexCacheFile?: string;
139
+ providerCacheFile?: string;
140
+ } = {}): CodexModelCapabilities | null {
141
+ const seeded = loadSeededOpenAiModels(options);
142
+ if (!seeded) {
143
+ return null;
144
+ }
145
+ return {
146
+ status: "ready",
147
+ source: "runtime",
148
+ models: seeded.models.map(toCapability),
149
+ discoveredAt: seeded.discoveredAt,
150
+ executable: null,
151
+ error: null,
152
+ };
153
+ }
@@ -0,0 +1,106 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import type { ProviderId } from "../providerLauncher/types.js";
5
+ import type { ProviderModel } from "../providerRuntime/types.js";
6
+
7
+ // Persistent last-good model discovery results, one entry per provider.
8
+ // Lets pickers open instantly with the previous session's discovered models
9
+ // while a background refresh runs. Corrupt or missing cache degrades to null.
10
+ // Resolved per call from env (matching claudeCodeDiscovery) so HOME
11
+ // redirection in tests holds — Bun's homedir() ignores runtime HOME changes.
12
+ export function getProviderModelCacheFile(): string {
13
+ const home = process.env.USERPROFILE ?? process.env.HOME ?? homedir();
14
+ return join(home, ".codexa-model-cache.json");
15
+ }
16
+
17
+ const CACHE_VERSION = 1;
18
+
19
+ export interface CachedProviderModels {
20
+ discoveredAt: number;
21
+ models: readonly ProviderModel[];
22
+ }
23
+
24
+ interface ProviderModelCacheFile {
25
+ version: number;
26
+ providers: Partial<Record<ProviderId, CachedProviderModels>>;
27
+ }
28
+
29
+ function readCacheFile(cacheFile: string): ProviderModelCacheFile | null {
30
+ try {
31
+ if (!existsSync(cacheFile)) {
32
+ return null;
33
+ }
34
+ const parsed: unknown = JSON.parse(readFileSync(cacheFile, "utf8"));
35
+ if (
36
+ typeof parsed !== "object" || parsed === null || Array.isArray(parsed)
37
+ || (parsed as ProviderModelCacheFile).version !== CACHE_VERSION
38
+ || typeof (parsed as ProviderModelCacheFile).providers !== "object"
39
+ || (parsed as ProviderModelCacheFile).providers === null
40
+ ) {
41
+ return null;
42
+ }
43
+ return parsed as ProviderModelCacheFile;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ // A model id is passed straight to a provider CLI, so it must be a bare token.
50
+ // Discovery scrapes CLI output, and a buggy build can bake an ANSI escape into
51
+ // an id (e.g. "claude-fable-5\x1b[1m"). Reject rather than repair: a cached id
52
+ // we cannot vouch for is dropped so discovery re-probes for a clean one.
53
+ const VALID_MODEL_ID = /^[A-Za-z0-9._:\/-]+$/;
54
+
55
+ function isUsableModel(model: unknown): model is ProviderModel {
56
+ if (typeof model !== "object" || model === null) {
57
+ return false;
58
+ }
59
+ const candidate = model as ProviderModel;
60
+ return typeof candidate.id === "string"
61
+ && typeof candidate.modelId === "string"
62
+ && typeof candidate.label === "string"
63
+ && VALID_MODEL_ID.test(candidate.id)
64
+ && VALID_MODEL_ID.test(candidate.modelId);
65
+ }
66
+
67
+ function isValidEntry(entry: unknown): entry is CachedProviderModels {
68
+ if (typeof entry !== "object" || entry === null) {
69
+ return false;
70
+ }
71
+ const candidate = entry as CachedProviderModels;
72
+ return typeof candidate.discoveredAt === "number" && Array.isArray(candidate.models);
73
+ }
74
+
75
+ export function loadCachedProviderModels(
76
+ providerId: ProviderId,
77
+ cacheFile = getProviderModelCacheFile(),
78
+ ): CachedProviderModels | null {
79
+ const cache = readCacheFile(cacheFile);
80
+ const entry = cache?.providers?.[providerId];
81
+ if (!entry || !isValidEntry(entry)) {
82
+ return null;
83
+ }
84
+ const models = entry.models.filter(isUsableModel);
85
+ if (models.length === 0) {
86
+ return null;
87
+ }
88
+ return { discoveredAt: entry.discoveredAt, models };
89
+ }
90
+
91
+ export function saveCachedProviderModels(
92
+ providerId: ProviderId,
93
+ entry: CachedProviderModels,
94
+ cacheFile = getProviderModelCacheFile(),
95
+ ): void {
96
+ if (entry.models.length === 0) {
97
+ return;
98
+ }
99
+ try {
100
+ const cache = readCacheFile(cacheFile) ?? { version: CACHE_VERSION, providers: {} };
101
+ cache.providers[providerId] = entry;
102
+ writeFileSync(cacheFile, `${JSON.stringify(cache, null, 2)}\n`, "utf8");
103
+ } catch {
104
+ // Persistence is best-effort; discovery still works without it.
105
+ }
106
+ }
@@ -9,6 +9,7 @@ export interface CommandSpec {
9
9
  cwd: string;
10
10
  env?: NodeJS.ProcessEnv;
11
11
  timeoutMs?: number;
12
+ stdinData?: string;
12
13
  }
13
14
 
14
15
  export interface CommandResult {
@@ -177,10 +178,20 @@ function runProcess(
177
178
  cwd: spec.cwd,
178
179
  env: spec.env,
179
180
  shell: false,
180
- stdio: ["ignore", "pipe", "pipe"],
181
+ stdio: [spec.stdinData !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
181
182
  });
182
183
  handlers.onProcessLifecycle?.("spawned");
183
184
 
185
+ if (spec.stdinData !== undefined) {
186
+ try {
187
+ child.stdin?.on("error", () => { /* EPIPE when the process exits before reading stdin */ });
188
+ child.stdin?.write(spec.stdinData);
189
+ child.stdin?.end();
190
+ } catch {
191
+ // stdin already closed; the close/error handlers report the outcome
192
+ }
193
+ }
194
+
184
195
  const result = new Promise<CommandResult>((resolve) => {
185
196
  const finish = (partial: Omit<CommandResult, "stdout" | "stderr" | "startedAt" | "endedAt" | "durationMs" | "userMessage"> & { endedAt?: number }) => {
186
197
  if (timeoutHandle) clearTimeout(timeoutHandle);
@@ -16,14 +16,15 @@ import {
16
16
  } from "../providerRuntime/registry.js";
17
17
  import { normalizeGeminiModelId } from "../providerRuntime/models.js";
18
18
  import { ANTIGRAVITY_DEFAULT_MODEL_ID } from "../providerRuntime/antigravity.js";
19
+ import { discoverMistralVibeModels } from "../providerRuntime/mistralVibe.js";
19
20
  import { setLocalProviderConfig } from "../providerRuntime/local.js";
20
21
  import { formatContextLength, resolveModelContextLengthCached } from "../providerRuntime/contextMetadata.js";
21
22
  import { resolveModelCapabilityProfileCached } from "../providerRuntime/capabilityProfile.js";
22
23
 
23
24
  // Google/Gemini remains a recognized legacy config value so existing workspace
24
25
  // files can be migrated, but it is no longer a selectable Codexa provider.
25
- const PROVIDER_ORDER: readonly ProviderId[] = ["openai", "anthropic", "local", "antigravity"];
26
- const KNOWN_PROVIDER_IDS: readonly ProviderId[] = ["openai", "anthropic", "google", "local", "antigravity"];
26
+ const PROVIDER_ORDER: readonly ProviderId[] = ["openai", "anthropic", "mistral", "local", "antigravity"];
27
+ const KNOWN_PROVIDER_IDS: readonly ProviderId[] = ["openai", "anthropic", "google", "mistral", "local", "antigravity"];
27
28
 
28
29
  const DEFAULT_PROVIDER_ID: ProviderId = "openai";
29
30
 
@@ -78,6 +79,17 @@ const DEFAULT_PROVIDERS: Record<ProviderId, ProviderDefault> = {
78
79
  isActiveRoute: false,
79
80
  routeUnavailableReason: "Local provider unavailable. Start LM Studio, load a model, and enable the local server.",
80
81
  },
82
+ mistral: {
83
+ id: "mistral",
84
+ displayName: "Mistral Vibe CLI",
85
+ currentModel: () => "Vibe default",
86
+ backendType: "mistral-vibe-cli-auth",
87
+ routeMode: "in-codexa",
88
+ enabled: true,
89
+ launchCommand: { executable: "vibe", args: [] },
90
+ isActiveRoute: false,
91
+ routeUnavailableReason: null,
92
+ },
81
93
  antigravity: {
82
94
  id: "antigravity",
83
95
  displayName: "Antigravity",
@@ -137,9 +149,11 @@ function applyOverride(
137
149
  : provider.currentModel,
138
150
  enabled: nextEnabled,
139
151
  launchCommand: nextCommand,
140
- statusLabel: nextEnabled
141
- ? (provider.routeUnavailableReason ? "Needs config" : "Enabled")
142
- : "Disabled",
152
+ statusLabel: !nextEnabled
153
+ ? "Disabled"
154
+ : provider.routeMode === "launch-only"
155
+ ? provider.statusLabel
156
+ : (provider.routeUnavailableReason ? "Needs config" : "Enabled"),
143
157
  };
144
158
  }
145
159
 
@@ -157,6 +171,7 @@ export function getActiveRouteProviderId(config: ProviderWorkspaceConfig | null
157
171
 
158
172
  export function buildProviderRegistry(options: {
159
173
  activeModel: string;
174
+ workspaceRoot?: string;
160
175
  workspaceConfig?: ProviderWorkspaceConfig | null;
161
176
  diagnostics?: Record<string, Record<string, string | number | boolean | null>>;
162
177
  routeErrors?: Record<string, string>;
@@ -170,7 +185,9 @@ export function buildProviderRegistry(options: {
170
185
  }
171
186
  const defaults = DEFAULT_PROVIDERS[id];
172
187
  const runtime = getProviderRuntime(id);
173
- const discovery = runtime.discoverModels();
188
+ const discovery = id === "mistral"
189
+ ? discoverMistralVibeModels(options.workspaceRoot ?? process.cwd())
190
+ : runtime.discoverModels();
174
191
 
175
192
  const activeRoute = options.workspaceConfig?.activeRoute;
176
193
  const isThisActive = activeRoute?.providerId === id;
@@ -205,6 +222,10 @@ export function buildProviderRegistry(options: {
205
222
  }
206
223
  }
207
224
 
225
+ if (id === "mistral") {
226
+ currentModelLabel = discovery.models[0]?.modelId ?? "Vibe default";
227
+ }
228
+
208
229
  const rawMetadataForModel = discovery.models.find((model) => model.modelId === currentModelLabel)?.raw;
209
230
  const contextMetadata = resolveModelContextLengthCached({
210
231
  providerId: id,
@@ -230,7 +251,16 @@ export function buildProviderRegistry(options: {
230
251
 
231
252
  const enabled = id === "local" ? discovery.status === "ready" : defaults.enabled;
232
253
 
233
- const statusLabel = id === "local"
254
+ const availabilityStatus = options.diagnostics?.[id]?.availabilityStatus;
255
+ const statusLabel = id === "mistral"
256
+ ? availabilityStatus === "checking"
257
+ ? "Checking"
258
+ : availabilityStatus === "unavailable"
259
+ ? "Missing"
260
+ : routeUnavailableReason
261
+ ? "Needs config"
262
+ : "Enabled"
263
+ : id === "local"
234
264
  ? (discovery.status === "ready" ? "Enabled" : "Disabled")
235
265
  : !defaults.enabled
236
266
  ? "Disabled"
@@ -1,9 +1,10 @@
1
- export type ProviderId = "openai" | "anthropic" | "google" | "local" | "antigravity";
1
+ export type ProviderId = "openai" | "anthropic" | "google" | "mistral" | "local" | "antigravity";
2
2
 
3
3
  export type ProviderBackendType =
4
4
  | "codex-cli-auth"
5
5
  | "gemini-cli-auth"
6
6
  | "claude-code-auth"
7
+ | "mistral-vibe-cli-auth"
7
8
  | "antigravity-cli-auth"
8
9
  | "openai-api-key"
9
10
  | "gemini-api-key"
@@ -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,
@@ -56,10 +55,9 @@ export function mapModelIdToClaudeArg(modelId: string): string {
56
55
  return modelId;
57
56
  }
58
57
 
59
- export function mapReasoningToEffort(reasoning: string | null | undefined): string | null {
60
- if (reasoning && CLAUDE_CODE_EFFORT_IDS.has(reasoning)) return reasoning;
61
- return null;
62
- }
58
+ export function mapReasoningToEffort(reasoning: string | null | undefined): string | null {
59
+ return reasoning?.trim() || null;
60
+ }
63
61
 
64
62
  function buildClaudeCodeBaseArgs(request: ProviderChatRequest): string[] {
65
63
  const effort = mapReasoningToEffort(request.route.reasoning ?? null);