ubume 0.1.0
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 +88 -0
- package/bin/codexa-local-harness-bridge.js +3 -0
- package/bin/codexa.js +28 -0
- package/bin/ubume-local-harness-bridge.js +325 -0
- package/bin/ubume.js +398 -0
- package/package.json +66 -0
- package/src/app.tsx +5759 -0
- package/src/commands/handler.ts +889 -0
- package/src/config/appVersion.ts +69 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +196 -0
- package/src/config/layeredConfig.ts +853 -0
- package/src/config/legacyEnv.ts +16 -0
- package/src/config/persistence.ts +377 -0
- package/src/config/runtimeConfig.ts +558 -0
- package/src/config/settings.ts +405 -0
- package/src/config/toml-serialize.ts +109 -0
- package/src/config/trustStore.ts +84 -0
- package/src/config/updateCheckCache.ts +85 -0
- package/src/core/README.md +55 -0
- package/src/core/agent/loop.ts +464 -0
- package/src/core/agent/protocol.ts +345 -0
- package/src/core/agent/tools.ts +423 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/codex/codexExecArgs.ts +166 -0
- package/src/core/codex/codexLaunch.ts +163 -0
- package/src/core/codex/codexPrompt.ts +429 -0
- package/src/core/debug/inputDebug.ts +51 -0
- package/src/core/debug/localStreamDebug.ts +50 -0
- package/src/core/debug/modelStateDebug.ts +35 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.ts +160 -0
- package/src/core/executables/executableResolver.ts +164 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/models/codexCapabilities.ts +97 -0
- package/src/core/models/codexModelCapabilities.ts +624 -0
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/modelSpecs.ts +303 -0
- package/src/core/models/providerModelCache.ts +94 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.ts +398 -0
- package/src/core/process/CommandRunner.ts +280 -0
- package/src/core/process/processValidation.ts +111 -0
- package/src/core/providerLauncher/launcher.ts +220 -0
- package/src/core/providerLauncher/registry.ts +354 -0
- package/src/core/providerLauncher/types.ts +95 -0
- package/src/core/providerLauncher/workspaceConfig.ts +487 -0
- package/src/core/providerRuntime/anthropic.ts +580 -0
- package/src/core/providerRuntime/antigravity.ts +500 -0
- package/src/core/providerRuntime/capabilityProfile.ts +383 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/codexaCupy.ts +97 -0
- package/src/core/providerRuntime/codexaNative.ts +425 -0
- package/src/core/providerRuntime/contextMetadata.ts +397 -0
- package/src/core/providerRuntime/gemini.ts +789 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.ts +770 -0
- package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
- package/src/core/providerRuntime/localOutputBudget.ts +17 -0
- package/src/core/providerRuntime/mistralVibe.ts +667 -0
- package/src/core/providerRuntime/models.ts +175 -0
- package/src/core/providerRuntime/reasoning.ts +20 -0
- package/src/core/providerRuntime/registry.ts +286 -0
- package/src/core/providerRuntime/types.ts +142 -0
- package/src/core/providerRuntime/unsloth.ts +216 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.ts +378 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +91 -0
- package/src/core/shared/attachments.ts +101 -0
- package/src/core/shared/cleanupFastFail.ts +67 -0
- package/src/core/shared/clipboard.ts +24 -0
- package/src/core/shared/clipboardImage.ts +111 -0
- package/src/core/shared/githubDiagnostics.ts +222 -0
- package/src/core/shared/hollowResponseFormat.ts +39 -0
- package/src/core/terminal/clearFrameBoundary.ts +852 -0
- package/src/core/terminal/frameLock.ts +110 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.ts +169 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalTitle.ts +400 -0
- package/src/core/version/channel.ts +27 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +203 -0
- package/src/core/workspace/appData.ts +107 -0
- package/src/core/workspace/conversationStore.ts +335 -0
- package/src/core/workspace/launchContext.ts +259 -0
- package/src/core/workspace/planStorage.ts +135 -0
- package/src/core/workspace/projectInstructions.ts +54 -0
- package/src/core/workspace/scratchDir.ts +64 -0
- package/src/core/workspace/workspaceActivity.ts +384 -0
- package/src/core/workspace/workspaceGuard.ts +377 -0
- package/src/core/workspace/workspaceRoot.ts +47 -0
- package/src/exec.ts +73 -0
- package/src/headless/execArgs.ts +296 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.tsx +270 -0
- package/src/legacyEnvBootstrap.ts +5 -0
- package/src/session/appSession.ts +771 -0
- package/src/session/chatLifecycle.ts +994 -0
- package/src/session/conversation.ts +107 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/persistedResponse.ts +93 -0
- package/src/session/planFlow.ts +159 -0
- package/src/session/planTranscript.ts +19 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +234 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/chrome/ActivityBars.tsx +68 -0
- package/src/ui/chrome/ActivityIndicator.tsx +58 -0
- package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
- package/src/ui/chrome/AppShell.tsx +474 -0
- package/src/ui/chrome/BottomComposer.tsx +1135 -0
- package/src/ui/chrome/DashCard.tsx +82 -0
- package/src/ui/chrome/RunFooter.tsx +65 -0
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/chrome/Spinner.tsx +25 -0
- package/src/ui/chrome/TopHeader.tsx +439 -0
- package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
- package/src/ui/chrome/busyStatusAnimation.ts +11 -0
- package/src/ui/input/commandNormalize.ts +66 -0
- package/src/ui/input/focus.ts +73 -0
- package/src/ui/input/imageAttachments.ts +18 -0
- package/src/ui/input/inputBuffer.ts +203 -0
- package/src/ui/input/pastedContent.ts +75 -0
- package/src/ui/input/rawArrowKeys.ts +28 -0
- package/src/ui/input/slashCommands.ts +43 -0
- package/src/ui/input/useStdinRawModeLease.ts +23 -0
- package/src/ui/layout.ts +560 -0
- package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
- package/src/ui/panels/AuthPanel.tsx +149 -0
- package/src/ui/panels/BackendPicker.tsx +28 -0
- package/src/ui/panels/ModePicker.tsx +31 -0
- package/src/ui/panels/ModelPicker.tsx +31 -0
- package/src/ui/panels/ModelPickerScreen.tsx +761 -0
- package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
- package/src/ui/panels/Panel.tsx +51 -0
- package/src/ui/panels/PermissionsPanel.tsx +78 -0
- package/src/ui/panels/PlanActionPicker.tsx +187 -0
- package/src/ui/panels/ProviderPicker.tsx +753 -0
- package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
- package/src/ui/panels/ReasoningPicker.tsx +46 -0
- package/src/ui/panels/ResumePicker.tsx +90 -0
- package/src/ui/panels/SelectionPanel.tsx +138 -0
- package/src/ui/panels/SettingsPanel.tsx +156 -0
- package/src/ui/panels/TextEntryPanel.tsx +139 -0
- package/src/ui/panels/ThemePicker.tsx +32 -0
- package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
- package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
- package/src/ui/panels/responsivePickerViewport.ts +64 -0
- package/src/ui/render/Markdown.tsx +331 -0
- package/src/ui/render/diffRenderer.ts +116 -0
- package/src/ui/render/logoVariants.ts +113 -0
- package/src/ui/render/modeDisplay.ts +52 -0
- package/src/ui/render/outputPipeline.ts +64 -0
- package/src/ui/render/runtimeDisplay.ts +128 -0
- package/src/ui/render/terminalAnswerFormat.ts +128 -0
- package/src/ui/render/textLayout.ts +392 -0
- package/src/ui/theme.tsx +274 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
- package/src/ui/timeline/AgentBlock.tsx +130 -0
- package/src/ui/timeline/StaticIntroItem.tsx +54 -0
- package/src/ui/timeline/ThinkingBlock.tsx +100 -0
- package/src/ui/timeline/Timeline.tsx +1410 -0
- package/src/ui/timeline/TranscriptShell.tsx +302 -0
- package/src/ui/timeline/TurnGroup.tsx +673 -0
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/timeline/liveViewportWindow.ts +68 -0
- package/src/ui/timeline/progressEntries.ts +156 -0
- package/src/ui/timeline/runActivityView.ts +37 -0
- package/src/ui/timeline/staticTranscriptCache.ts +174 -0
- package/src/ui/timeline/streamCoalesce.ts +53 -0
- package/src/ui/timeline/timelineMeasure.ts +3273 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type ProviderId = "openai" | "anthropic" | "google" | "mistral" | "local" | "codexa-native" | "codexa-cupy" | "antigravity";
|
|
2
|
+
|
|
3
|
+
export type LocalBackendId = "lm-studio" | "unsloth";
|
|
4
|
+
|
|
5
|
+
export type ProviderBackendType =
|
|
6
|
+
| "codex-cli-auth"
|
|
7
|
+
| "gemini-cli-auth"
|
|
8
|
+
| "claude-code-auth"
|
|
9
|
+
| "mistral-vibe-cli-auth"
|
|
10
|
+
| "antigravity-cli-auth"
|
|
11
|
+
| "openai-api-key"
|
|
12
|
+
| "gemini-api-key"
|
|
13
|
+
| "anthropic-api-key"
|
|
14
|
+
| "local-openai-compatible"
|
|
15
|
+
| "codexa-native-pytorch"
|
|
16
|
+
| "codexa-cupy"
|
|
17
|
+
| "unavailable";
|
|
18
|
+
|
|
19
|
+
export type ProviderLaunchAction = "launch" | "set-default" | "cancel";
|
|
20
|
+
export type ProviderRouteAction = "use-in-ubume" | "select-model" | "refresh-models" | "run-diagnostics";
|
|
21
|
+
export type ProviderPickerAction = ProviderLaunchAction | ProviderRouteAction;
|
|
22
|
+
export type ProviderRouteMode = "in-ubume" | "launch-only";
|
|
23
|
+
|
|
24
|
+
export interface ProviderLaunchCommand {
|
|
25
|
+
executable: string;
|
|
26
|
+
args: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ProviderConfig {
|
|
30
|
+
id: ProviderId;
|
|
31
|
+
displayName: string;
|
|
32
|
+
currentModel: string;
|
|
33
|
+
contextLengthLabel?: string;
|
|
34
|
+
contextLengthSource?: string;
|
|
35
|
+
capabilityProfile?: import("../providerRuntime/capabilityProfile.js").ModelCapabilityProfile;
|
|
36
|
+
backendType: ProviderBackendType;
|
|
37
|
+
routeMode: ProviderRouteMode;
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
statusLabel: string;
|
|
40
|
+
launchCommand: ProviderLaunchCommand | null;
|
|
41
|
+
isDefault: boolean;
|
|
42
|
+
isActiveRoute: boolean;
|
|
43
|
+
routeUnavailableReason: string | null;
|
|
44
|
+
routeDiagnostics?: Record<string, string | number | boolean | null>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProviderWorkspaceConfig {
|
|
48
|
+
workspaceDefaultProviderId?: ProviderId;
|
|
49
|
+
activeRoute?: ProviderActiveRoute;
|
|
50
|
+
providers?: Partial<Record<ProviderId, ProviderWorkspaceOverride>>;
|
|
51
|
+
migrationNotice?: ProviderWorkspaceMigrationNotice;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ProviderWorkspaceMigrationNotice {
|
|
55
|
+
deprecatedProviderId: string;
|
|
56
|
+
revertedProviderId: ProviderId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ProviderActiveRoute {
|
|
60
|
+
providerId: ProviderId;
|
|
61
|
+
modelId: string;
|
|
62
|
+
backendKind?: import("../providerRuntime/types.js").ProviderBackendKind;
|
|
63
|
+
reasoning?: string;
|
|
64
|
+
modelSelection?: import("../providerRuntime/types.js").GeminiModelSelection;
|
|
65
|
+
localBackend?: LocalBackendId;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ProviderWorkspaceOverride {
|
|
69
|
+
currentModel?: string;
|
|
70
|
+
currentReasoning?: string;
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
type?: "openai-compatible";
|
|
73
|
+
baseUrl?: string;
|
|
74
|
+
apiKey?: string;
|
|
75
|
+
pinnedModel?: string;
|
|
76
|
+
defaultModel?: string;
|
|
77
|
+
localBackend?: LocalBackendId;
|
|
78
|
+
models?: Record<string, ProviderModelWorkspaceOverride>;
|
|
79
|
+
command?: string | ProviderLaunchCommand | null;
|
|
80
|
+
claudeCommandPath?: string;
|
|
81
|
+
geminiCommandPath?: string;
|
|
82
|
+
codexCommandPath?: string;
|
|
83
|
+
antigravityCommandPath?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ProviderModelWorkspaceOverride {
|
|
87
|
+
contextLength?: number;
|
|
88
|
+
maxOutputTokens?: number;
|
|
89
|
+
supportsStreaming?: boolean;
|
|
90
|
+
supportsToolCalls?: boolean;
|
|
91
|
+
supportsSystemPrompt?: boolean;
|
|
92
|
+
supportsVision?: boolean;
|
|
93
|
+
/** Opt in to sending the active reasoning level as OpenAI `reasoning_effort` (Local harness). */
|
|
94
|
+
supportsReasoningEffort?: boolean;
|
|
95
|
+
}
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { DEFAULT_MODEL } from "../../config/settings.js";
|
|
4
|
+
import { resolveUbumeWorkspaceDataDir } from "../workspace/appData.js";
|
|
5
|
+
import { normalizeWorkspaceRoot } from "../workspace/workspaceRoot.js";
|
|
6
|
+
import { isKnownProviderId } from "./registry.js";
|
|
7
|
+
import { getDefaultRouteModel, getProviderRuntime, isProviderRouteConfigured, isProviderRoutableInUbume } from "../providerRuntime/registry.js";
|
|
8
|
+
import { normalizeGeminiModelId } from "../providerRuntime/models.js";
|
|
9
|
+
import type {
|
|
10
|
+
ProviderActiveRoute,
|
|
11
|
+
ProviderId,
|
|
12
|
+
ProviderLaunchCommand,
|
|
13
|
+
ProviderWorkspaceConfig,
|
|
14
|
+
ProviderWorkspaceOverride,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
const DEPRECATED_ANTIGRAVITY_PROVIDER_ID = "antigravity";
|
|
18
|
+
const DEPRECATED_ANTIGRAVITY_BACKENDS = new Set(["antigravity-cli-auth", "agy"]);
|
|
19
|
+
const DEPRECATED_GOOGLE_PROVIDER_ID = "google";
|
|
20
|
+
|
|
21
|
+
export function getProviderWorkspaceConfigFile(workspaceRoot: string): string {
|
|
22
|
+
return join(resolveUbumeWorkspaceDataDir(normalizeWorkspaceRoot(workspaceRoot)), "providers.json");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getLegacyProviderWorkspaceConfigFile(workspaceRoot: string): string {
|
|
26
|
+
return join(normalizeWorkspaceRoot(workspaceRoot), ".codexa", "providers.json");
|
|
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 isDeprecatedAntigravityProviderId(value: unknown): boolean {
|
|
34
|
+
return value === DEPRECATED_ANTIGRAVITY_PROVIDER_ID;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isDeprecatedAntigravityRoute(value: unknown): boolean {
|
|
38
|
+
if (!isRecord(value)) return false;
|
|
39
|
+
return isDeprecatedAntigravityProviderId(value.providerId ?? value.provider_id)
|
|
40
|
+
|| DEPRECATED_ANTIGRAVITY_BACKENDS.has(String(value.backendKind ?? value.backend_kind));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isDeprecatedGoogleRoute(value: unknown): boolean {
|
|
44
|
+
return isRecord(value) && (value.providerId ?? value.provider_id) === DEPRECATED_GOOGLE_PROVIDER_ID;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveDeprecatedProviderFallback(
|
|
48
|
+
providers: Partial<Record<ProviderId, ProviderWorkspaceOverride>>,
|
|
49
|
+
): ProviderId {
|
|
50
|
+
const candidates: readonly ProviderId[] = ["openai", "anthropic", "local"];
|
|
51
|
+
return candidates.find((providerId) =>
|
|
52
|
+
isProviderRoutableInUbume(providerId)
|
|
53
|
+
&& (providerId === "openai" || providers[providerId] !== undefined || isProviderRouteConfigured(providerId))
|
|
54
|
+
) ?? "openai";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createFallbackActiveRoute(
|
|
58
|
+
providerId: ProviderId,
|
|
59
|
+
providers: Partial<Record<ProviderId, ProviderWorkspaceOverride>>,
|
|
60
|
+
): ProviderActiveRoute {
|
|
61
|
+
const override = providers[providerId];
|
|
62
|
+
const modelId = providerId === "openai"
|
|
63
|
+
? override?.currentModel ?? DEFAULT_MODEL
|
|
64
|
+
: providerId === "google"
|
|
65
|
+
? normalizeGeminiModelId(override?.currentModel ?? getDefaultRouteModel(providerId, DEFAULT_MODEL))
|
|
66
|
+
: override?.currentModel ?? getDefaultRouteModel(providerId, DEFAULT_MODEL);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
providerId,
|
|
70
|
+
modelId,
|
|
71
|
+
backendKind: getProviderRuntime(providerId).backendKind,
|
|
72
|
+
...(override?.currentReasoning ? { reasoning: override.currentReasoning } : {}),
|
|
73
|
+
...(providerId === "google" ? { modelSelection: { kind: "manual" as const, modelId } } : {}),
|
|
74
|
+
...(providerId === "local" ? { localBackend: override?.localBackend ?? "lm-studio" } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseLaunchCommand(value: unknown): ProviderWorkspaceOverride["command"] | undefined {
|
|
79
|
+
if (value === null) return null;
|
|
80
|
+
if (typeof value === "string") return value;
|
|
81
|
+
if (!isRecord(value)) return undefined;
|
|
82
|
+
if (typeof value.executable !== "string") return undefined;
|
|
83
|
+
return {
|
|
84
|
+
executable: value.executable,
|
|
85
|
+
args: Array.isArray(value.args)
|
|
86
|
+
? value.args.filter((arg): arg is string => typeof arg === "string")
|
|
87
|
+
: [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseProviderOverride(value: unknown): ProviderWorkspaceOverride | undefined {
|
|
92
|
+
if (!isRecord(value)) return undefined;
|
|
93
|
+
const override: ProviderWorkspaceOverride = {};
|
|
94
|
+
|
|
95
|
+
// Accept both camelCase and snake_case field names for compatibility with
|
|
96
|
+
// config files written by different tool versions.
|
|
97
|
+
if (typeof value.currentModel === "string") {
|
|
98
|
+
override.currentModel = value.currentModel;
|
|
99
|
+
} else if (typeof value.current_model === "string") {
|
|
100
|
+
override.currentModel = value.current_model;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof value.currentReasoning === "string") {
|
|
104
|
+
override.currentReasoning = value.currentReasoning;
|
|
105
|
+
} else if (typeof value.current_reasoning === "string") {
|
|
106
|
+
override.currentReasoning = value.current_reasoning;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (typeof value.enabled === "boolean") {
|
|
110
|
+
override.enabled = value.enabled;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const providerType = value.type;
|
|
114
|
+
if (providerType === "openai-compatible") {
|
|
115
|
+
override.type = providerType;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const baseUrl = value.baseUrl ?? value.base_url;
|
|
119
|
+
if (typeof baseUrl === "string" && baseUrl.trim()) {
|
|
120
|
+
override.baseUrl = baseUrl.trim();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const apiKey = value.apiKey ?? value.api_key;
|
|
124
|
+
if (typeof apiKey === "string" && apiKey.trim()) {
|
|
125
|
+
override.apiKey = apiKey.trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const pinnedModel = value.pinnedModel ?? value.pinned_model;
|
|
129
|
+
if (typeof pinnedModel === "string" && pinnedModel.trim()) {
|
|
130
|
+
override.pinnedModel = pinnedModel.trim();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const defaultModel = value.defaultModel ?? value.default_model;
|
|
134
|
+
if (typeof defaultModel === "string" && defaultModel.trim()) {
|
|
135
|
+
override.defaultModel = defaultModel.trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const localBackend = value.localBackend ?? value.local_backend;
|
|
139
|
+
if (localBackend === "lm-studio" || localBackend === "unsloth") {
|
|
140
|
+
override.localBackend = localBackend;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (isRecord(value.models)) {
|
|
144
|
+
const models: Record<string, import("./types.js").ProviderModelWorkspaceOverride> = {};
|
|
145
|
+
for (const [modelId, modelValue] of Object.entries(value.models)) {
|
|
146
|
+
if (!modelId.trim() || !isRecord(modelValue)) continue;
|
|
147
|
+
const entry: import("./types.js").ProviderModelWorkspaceOverride = {};
|
|
148
|
+
|
|
149
|
+
const rawContextLength = modelValue.contextLength ?? modelValue.context_length;
|
|
150
|
+
if (typeof rawContextLength === "number" && Number.isInteger(rawContextLength) && rawContextLength > 0) {
|
|
151
|
+
entry.contextLength = rawContextLength;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const rawMaxOutput = modelValue.maxOutputTokens ?? modelValue.max_output_tokens;
|
|
155
|
+
if (typeof rawMaxOutput === "number" && Number.isInteger(rawMaxOutput) && rawMaxOutput > 0) {
|
|
156
|
+
entry.maxOutputTokens = rawMaxOutput;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const [camelKey, snakeKey] of [
|
|
160
|
+
["supportsReasoningEffort", "supports_reasoning_effort"],
|
|
161
|
+
["supportsStreaming", "supports_streaming"],
|
|
162
|
+
["supportsToolCalls", "supports_tool_calls"],
|
|
163
|
+
["supportsSystemPrompt", "supports_system_prompt"],
|
|
164
|
+
["supportsVision", "supports_vision"],
|
|
165
|
+
] as const) {
|
|
166
|
+
const raw = modelValue[camelKey] ?? modelValue[snakeKey];
|
|
167
|
+
if (typeof raw === "boolean") {
|
|
168
|
+
(entry as Record<string, unknown>)[camelKey] = raw;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (Object.keys(entry).length > 0) {
|
|
173
|
+
models[modelId] = entry;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (Object.keys(models).length > 0) {
|
|
177
|
+
override.models = models;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const command = parseLaunchCommand(value.command);
|
|
182
|
+
if (command !== undefined) {
|
|
183
|
+
override.command = command;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const claudeCommandPath = value.claudeCommandPath ?? value.claude_command_path;
|
|
187
|
+
if (typeof claudeCommandPath === "string" && claudeCommandPath.trim()) {
|
|
188
|
+
override.claudeCommandPath = claudeCommandPath.trim();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const geminiCommandPath = value.geminiCommandPath ?? value.gemini_command_path;
|
|
192
|
+
if (typeof geminiCommandPath === "string" && geminiCommandPath.trim()) {
|
|
193
|
+
override.geminiCommandPath = geminiCommandPath.trim();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const codexCommandPath = value.codexCommandPath ?? value.codex_command_path;
|
|
197
|
+
if (typeof codexCommandPath === "string" && codexCommandPath.trim()) {
|
|
198
|
+
override.codexCommandPath = codexCommandPath.trim();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return override;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseActiveRoute(value: unknown): ProviderActiveRoute | undefined {
|
|
205
|
+
if (!isRecord(value)) return undefined;
|
|
206
|
+
const providerId = value.providerId ?? value.provider_id;
|
|
207
|
+
const modelId = value.modelId ?? value.model_id;
|
|
208
|
+
const backendKind = value.backendKind ?? value.backend_kind;
|
|
209
|
+
const reasoning = value.reasoning;
|
|
210
|
+
const modelSelection = value.modelSelection ?? value.model_selection;
|
|
211
|
+
const localBackend = value.localBackend ?? value.local_backend;
|
|
212
|
+
|
|
213
|
+
if (typeof providerId !== "string" || !isKnownProviderId(providerId) || !isProviderRoutableInUbume(providerId)) return undefined;
|
|
214
|
+
if (typeof modelId !== "string" || !modelId.trim()) return undefined;
|
|
215
|
+
|
|
216
|
+
const normalizedModelId = providerId === "google" ? normalizeGeminiModelId(modelId.trim()) : modelId.trim();
|
|
217
|
+
const normalizedModelSelection = providerId === "google" && isRecord(modelSelection)
|
|
218
|
+
? modelSelection.kind === "manual"
|
|
219
|
+
? { kind: "manual" as const, modelId: normalizeGeminiModelId(typeof modelSelection.modelId === "string" ? modelSelection.modelId : null) }
|
|
220
|
+
: { kind: "auto" as const, family: modelSelection.family === "gemini-2.5" ? "gemini-2.5" as const : "gemini-3" as const }
|
|
221
|
+
: undefined;
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
providerId,
|
|
225
|
+
modelId: normalizedModelId,
|
|
226
|
+
backendKind: getProviderRuntime(providerId).backendKind,
|
|
227
|
+
...(typeof reasoning === "string" && reasoning.trim() ? { reasoning: reasoning.trim() } : {}),
|
|
228
|
+
...(normalizedModelSelection ? { modelSelection: normalizedModelSelection } : {}),
|
|
229
|
+
...(providerId === "local"
|
|
230
|
+
? { localBackend: localBackend === "unsloth" ? "unsloth" as const : "lm-studio" as const }
|
|
231
|
+
: {}),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function parseProviderWorkspaceConfig(data: unknown): ProviderWorkspaceConfig {
|
|
236
|
+
if (!isRecord(data)) return {};
|
|
237
|
+
|
|
238
|
+
const config: ProviderWorkspaceConfig = {};
|
|
239
|
+
const providers: Partial<Record<ProviderId, ProviderWorkspaceOverride>> = {};
|
|
240
|
+
let foundDeprecatedAntigravity = false;
|
|
241
|
+
let foundDeprecatedGoogle = false;
|
|
242
|
+
|
|
243
|
+
if (isRecord(data.providers)) {
|
|
244
|
+
for (const [id, value] of Object.entries(data.providers)) {
|
|
245
|
+
if (isDeprecatedAntigravityProviderId(id)) {
|
|
246
|
+
foundDeprecatedAntigravity = true;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (id === DEPRECATED_GOOGLE_PROVIDER_ID) {
|
|
250
|
+
foundDeprecatedGoogle = true;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (!isKnownProviderId(id)) continue;
|
|
254
|
+
const override = parseProviderOverride(value);
|
|
255
|
+
if (override) providers[id] = override;
|
|
256
|
+
}
|
|
257
|
+
config.providers = providers;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const defaultProvider = data.workspaceDefaultProviderId
|
|
261
|
+
?? data.workspace_default_provider_id
|
|
262
|
+
?? data.defaultProviderId
|
|
263
|
+
?? data.default_provider_id;
|
|
264
|
+
if (defaultProvider === DEPRECATED_GOOGLE_PROVIDER_ID) {
|
|
265
|
+
foundDeprecatedGoogle = true;
|
|
266
|
+
config.workspaceDefaultProviderId = resolveDeprecatedProviderFallback(providers);
|
|
267
|
+
} else if (isDeprecatedAntigravityProviderId(defaultProvider)) {
|
|
268
|
+
foundDeprecatedAntigravity = true;
|
|
269
|
+
config.workspaceDefaultProviderId = resolveDeprecatedProviderFallback(providers);
|
|
270
|
+
} else if (typeof defaultProvider === "string" && isKnownProviderId(defaultProvider)) {
|
|
271
|
+
config.workspaceDefaultProviderId = defaultProvider;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const rawActiveRoute = data.activeRoute ?? data.active_route;
|
|
275
|
+
if (isDeprecatedGoogleRoute(rawActiveRoute)) {
|
|
276
|
+
foundDeprecatedGoogle = true;
|
|
277
|
+
const fallbackProviderId = resolveDeprecatedProviderFallback(providers);
|
|
278
|
+
config.activeRoute = createFallbackActiveRoute(fallbackProviderId, providers);
|
|
279
|
+
config.workspaceDefaultProviderId ??= fallbackProviderId;
|
|
280
|
+
} else if (isDeprecatedAntigravityRoute(rawActiveRoute)) {
|
|
281
|
+
foundDeprecatedAntigravity = true;
|
|
282
|
+
const fallbackProviderId = resolveDeprecatedProviderFallback(providers);
|
|
283
|
+
config.activeRoute = createFallbackActiveRoute(fallbackProviderId, providers);
|
|
284
|
+
config.workspaceDefaultProviderId ??= fallbackProviderId;
|
|
285
|
+
} else {
|
|
286
|
+
const activeRoute = parseActiveRoute(rawActiveRoute);
|
|
287
|
+
if (activeRoute) {
|
|
288
|
+
config.activeRoute = activeRoute;
|
|
289
|
+
if (activeRoute.providerId === "local") {
|
|
290
|
+
providers.local = {
|
|
291
|
+
...providers.local,
|
|
292
|
+
localBackend: activeRoute.localBackend ?? "lm-studio",
|
|
293
|
+
};
|
|
294
|
+
config.providers = providers;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (foundDeprecatedGoogle || foundDeprecatedAntigravity) {
|
|
300
|
+
const revertedProviderId = config.activeRoute?.providerId
|
|
301
|
+
?? config.workspaceDefaultProviderId
|
|
302
|
+
?? resolveDeprecatedProviderFallback(providers);
|
|
303
|
+
config.migrationNotice = {
|
|
304
|
+
deprecatedProviderId: foundDeprecatedGoogle ? DEPRECATED_GOOGLE_PROVIDER_ID : DEPRECATED_ANTIGRAVITY_PROVIDER_ID,
|
|
305
|
+
revertedProviderId,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return config;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function serializeLaunchCommand(command: string | ProviderLaunchCommand | null | undefined): unknown {
|
|
313
|
+
if (command === undefined || command === null || typeof command === "string") {
|
|
314
|
+
return command;
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
executable: command.executable,
|
|
318
|
+
args: command.args,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function serializeProviderWorkspaceConfig(config: ProviderWorkspaceConfig): Record<string, unknown> {
|
|
323
|
+
const providers = Object.fromEntries(
|
|
324
|
+
Object.entries(config.providers ?? {}).map(([id, override]) => [
|
|
325
|
+
id,
|
|
326
|
+
{
|
|
327
|
+
...(override.currentModel !== undefined ? { current_model: override.currentModel } : {}),
|
|
328
|
+
...(override.currentReasoning !== undefined ? { current_reasoning: override.currentReasoning } : {}),
|
|
329
|
+
...(override.enabled !== undefined ? { enabled: override.enabled } : {}),
|
|
330
|
+
...(override.type !== undefined ? { type: override.type } : {}),
|
|
331
|
+
...(override.baseUrl !== undefined ? { base_url: override.baseUrl } : {}),
|
|
332
|
+
...(override.apiKey !== undefined ? { api_key: override.apiKey } : {}),
|
|
333
|
+
...(override.pinnedModel !== undefined ? { pinned_model: override.pinnedModel } : {}),
|
|
334
|
+
...(override.defaultModel !== undefined ? { default_model: override.defaultModel } : {}),
|
|
335
|
+
...(override.localBackend !== undefined ? { local_backend: override.localBackend } : {}),
|
|
336
|
+
...(override.models !== undefined ? { models: Object.fromEntries(
|
|
337
|
+
Object.entries(override.models).map(([modelId, model]) => [
|
|
338
|
+
modelId,
|
|
339
|
+
{
|
|
340
|
+
...(model.contextLength !== undefined ? { contextLength: model.contextLength } : {}),
|
|
341
|
+
...(model.maxOutputTokens !== undefined ? { maxOutputTokens: model.maxOutputTokens } : {}),
|
|
342
|
+
...(model.supportsStreaming !== undefined ? { supportsStreaming: model.supportsStreaming } : {}),
|
|
343
|
+
...(model.supportsToolCalls !== undefined ? { supportsToolCalls: model.supportsToolCalls } : {}),
|
|
344
|
+
...(model.supportsSystemPrompt !== undefined ? { supportsSystemPrompt: model.supportsSystemPrompt } : {}),
|
|
345
|
+
...(model.supportsVision !== undefined ? { supportsVision: model.supportsVision } : {}),
|
|
346
|
+
...(model.supportsReasoningEffort !== undefined ? { supportsReasoningEffort: model.supportsReasoningEffort } : {}),
|
|
347
|
+
},
|
|
348
|
+
]),
|
|
349
|
+
) } : {}),
|
|
350
|
+
...(override.command !== undefined ? { command: serializeLaunchCommand(override.command) } : {}),
|
|
351
|
+
...(override.claudeCommandPath !== undefined ? { claude_command_path: override.claudeCommandPath } : {}),
|
|
352
|
+
...(override.geminiCommandPath !== undefined ? { gemini_command_path: override.geminiCommandPath } : {}),
|
|
353
|
+
...(override.codexCommandPath !== undefined ? { codex_command_path: override.codexCommandPath } : {}),
|
|
354
|
+
},
|
|
355
|
+
]),
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
...(config.workspaceDefaultProviderId ? { workspaceDefaultProviderId: config.workspaceDefaultProviderId } : {}),
|
|
360
|
+
...(config.activeRoute ? {
|
|
361
|
+
activeRoute: {
|
|
362
|
+
providerId: config.activeRoute.providerId,
|
|
363
|
+
modelId: config.activeRoute.modelId,
|
|
364
|
+
backendKind: config.activeRoute.backendKind ?? getProviderRuntime(config.activeRoute.providerId).backendKind,
|
|
365
|
+
...(config.activeRoute.reasoning ? { reasoning: config.activeRoute.reasoning } : {}),
|
|
366
|
+
...(config.activeRoute.modelSelection ? { modelSelection: config.activeRoute.modelSelection } : {}),
|
|
367
|
+
...(config.activeRoute.providerId === "local"
|
|
368
|
+
? { localBackend: config.activeRoute.localBackend ?? config.providers?.local?.localBackend ?? "lm-studio" }
|
|
369
|
+
: {}),
|
|
370
|
+
},
|
|
371
|
+
} : {}),
|
|
372
|
+
...(Object.keys(providers).length > 0 ? { providers } : {}),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function loadProviderWorkspaceConfig(workspaceRoot: string): ProviderWorkspaceConfig {
|
|
377
|
+
const filePath = getProviderWorkspaceConfigFile(workspaceRoot);
|
|
378
|
+
if (existsSync(filePath)) {
|
|
379
|
+
try {
|
|
380
|
+
return parseProviderWorkspaceConfig(JSON.parse(readFileSync(filePath, "utf-8")));
|
|
381
|
+
} catch {
|
|
382
|
+
return {};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const legacyFilePath = getLegacyProviderWorkspaceConfigFile(workspaceRoot);
|
|
387
|
+
if (!existsSync(legacyFilePath)) return {};
|
|
388
|
+
try {
|
|
389
|
+
return parseProviderWorkspaceConfig(JSON.parse(readFileSync(legacyFilePath, "utf-8")));
|
|
390
|
+
} catch {
|
|
391
|
+
return {};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function saveProviderWorkspaceConfig(workspaceRoot: string, config: ProviderWorkspaceConfig): void {
|
|
396
|
+
const filePath = getProviderWorkspaceConfigFile(workspaceRoot);
|
|
397
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
398
|
+
const tmpFile = `${filePath}.tmp`;
|
|
399
|
+
writeFileSync(tmpFile, JSON.stringify(serializeProviderWorkspaceConfig(config), null, 2), "utf-8");
|
|
400
|
+
renameSync(tmpFile, filePath);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function setProviderWorkspaceDefault(
|
|
404
|
+
config: ProviderWorkspaceConfig,
|
|
405
|
+
providerId: ProviderId,
|
|
406
|
+
): ProviderWorkspaceConfig {
|
|
407
|
+
return {
|
|
408
|
+
...config,
|
|
409
|
+
workspaceDefaultProviderId: providerId === DEPRECATED_GOOGLE_PROVIDER_ID ? "openai" : providerId,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function setProviderDefaultModel(
|
|
414
|
+
config: ProviderWorkspaceConfig,
|
|
415
|
+
providerId: ProviderId,
|
|
416
|
+
modelId: string,
|
|
417
|
+
): ProviderWorkspaceConfig {
|
|
418
|
+
return {
|
|
419
|
+
...config,
|
|
420
|
+
providers: {
|
|
421
|
+
...config.providers,
|
|
422
|
+
[providerId]: {
|
|
423
|
+
...config.providers?.[providerId],
|
|
424
|
+
currentModel: modelId,
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function setProviderDefaultReasoning(
|
|
431
|
+
config: ProviderWorkspaceConfig,
|
|
432
|
+
providerId: ProviderId,
|
|
433
|
+
reasoning: string,
|
|
434
|
+
): ProviderWorkspaceConfig {
|
|
435
|
+
return {
|
|
436
|
+
...config,
|
|
437
|
+
providers: {
|
|
438
|
+
...config.providers,
|
|
439
|
+
[providerId]: {
|
|
440
|
+
...config.providers?.[providerId],
|
|
441
|
+
currentReasoning: reasoning,
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function setProviderActiveRoute(
|
|
448
|
+
config: ProviderWorkspaceConfig,
|
|
449
|
+
activeRoute: ProviderActiveRoute,
|
|
450
|
+
): ProviderWorkspaceConfig {
|
|
451
|
+
if (activeRoute.providerId === DEPRECATED_GOOGLE_PROVIDER_ID || !isProviderRoutableInUbume(activeRoute.providerId) || !isProviderRouteConfigured(activeRoute.providerId)) {
|
|
452
|
+
return config;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (activeRoute.providerId !== "local") {
|
|
456
|
+
return { ...config, activeRoute };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const localBackend = activeRoute.localBackend ?? config.providers?.local?.localBackend ?? "lm-studio";
|
|
460
|
+
return {
|
|
461
|
+
...config,
|
|
462
|
+
activeRoute: { ...activeRoute, localBackend },
|
|
463
|
+
providers: {
|
|
464
|
+
...config.providers,
|
|
465
|
+
local: {
|
|
466
|
+
...config.providers?.local,
|
|
467
|
+
localBackend,
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function setLocalBackendPreference(
|
|
474
|
+
config: ProviderWorkspaceConfig,
|
|
475
|
+
localBackend: import("./types.js").LocalBackendId,
|
|
476
|
+
): ProviderWorkspaceConfig {
|
|
477
|
+
return {
|
|
478
|
+
...config,
|
|
479
|
+
providers: {
|
|
480
|
+
...config.providers,
|
|
481
|
+
local: {
|
|
482
|
+
...config.providers?.local,
|
|
483
|
+
localBackend,
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
}
|