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.
Files changed (182) hide show
  1. package/README.md +88 -0
  2. package/bin/codexa-local-harness-bridge.js +3 -0
  3. package/bin/codexa.js +28 -0
  4. package/bin/ubume-local-harness-bridge.js +325 -0
  5. package/bin/ubume.js +398 -0
  6. package/package.json +66 -0
  7. package/src/app.tsx +5759 -0
  8. package/src/commands/handler.ts +889 -0
  9. package/src/config/appVersion.ts +69 -0
  10. package/src/config/buildInfo.ts +3 -0
  11. package/src/config/launchArgs.ts +196 -0
  12. package/src/config/layeredConfig.ts +853 -0
  13. package/src/config/legacyEnv.ts +16 -0
  14. package/src/config/persistence.ts +377 -0
  15. package/src/config/runtimeConfig.ts +558 -0
  16. package/src/config/settings.ts +405 -0
  17. package/src/config/toml-serialize.ts +109 -0
  18. package/src/config/trustStore.ts +84 -0
  19. package/src/config/updateCheckCache.ts +85 -0
  20. package/src/core/README.md +55 -0
  21. package/src/core/agent/loop.ts +464 -0
  22. package/src/core/agent/protocol.ts +345 -0
  23. package/src/core/agent/tools.ts +423 -0
  24. package/src/core/auth/codexAuth.ts +359 -0
  25. package/src/core/codex/codexExecArgs.ts +166 -0
  26. package/src/core/codex/codexLaunch.ts +163 -0
  27. package/src/core/codex/codexPrompt.ts +429 -0
  28. package/src/core/debug/inputDebug.ts +51 -0
  29. package/src/core/debug/localStreamDebug.ts +50 -0
  30. package/src/core/debug/modelStateDebug.ts +35 -0
  31. package/src/core/executables/antigravityExecutable.ts +48 -0
  32. package/src/core/executables/claudeExecutable.ts +63 -0
  33. package/src/core/executables/codexExecutable.ts +160 -0
  34. package/src/core/executables/executableResolver.ts +164 -0
  35. package/src/core/executables/geminiExecutable.ts +78 -0
  36. package/src/core/models/codexCapabilities.ts +97 -0
  37. package/src/core/models/codexModelCapabilities.ts +624 -0
  38. package/src/core/models/codexModelsCacheSeed.ts +153 -0
  39. package/src/core/models/modelSpecs.ts +303 -0
  40. package/src/core/models/providerModelCache.ts +94 -0
  41. package/src/core/perf/profiler.ts +125 -0
  42. package/src/core/perf/renderDebug.ts +398 -0
  43. package/src/core/process/CommandRunner.ts +280 -0
  44. package/src/core/process/processValidation.ts +111 -0
  45. package/src/core/providerLauncher/launcher.ts +220 -0
  46. package/src/core/providerLauncher/registry.ts +354 -0
  47. package/src/core/providerLauncher/types.ts +95 -0
  48. package/src/core/providerLauncher/workspaceConfig.ts +487 -0
  49. package/src/core/providerRuntime/anthropic.ts +580 -0
  50. package/src/core/providerRuntime/antigravity.ts +500 -0
  51. package/src/core/providerRuntime/capabilityProfile.ts +383 -0
  52. package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
  53. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  54. package/src/core/providerRuntime/codexaCupy.ts +97 -0
  55. package/src/core/providerRuntime/codexaNative.ts +425 -0
  56. package/src/core/providerRuntime/contextMetadata.ts +397 -0
  57. package/src/core/providerRuntime/gemini.ts +789 -0
  58. package/src/core/providerRuntime/lmstudio.ts +118 -0
  59. package/src/core/providerRuntime/local.ts +770 -0
  60. package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
  61. package/src/core/providerRuntime/localOutputBudget.ts +17 -0
  62. package/src/core/providerRuntime/mistralVibe.ts +667 -0
  63. package/src/core/providerRuntime/models.ts +175 -0
  64. package/src/core/providerRuntime/reasoning.ts +20 -0
  65. package/src/core/providerRuntime/registry.ts +286 -0
  66. package/src/core/providerRuntime/types.ts +142 -0
  67. package/src/core/providerRuntime/unsloth.ts +216 -0
  68. package/src/core/providers/codexJsonStream.ts +305 -0
  69. package/src/core/providers/codexSubprocess.ts +378 -0
  70. package/src/core/providers/codexTranscript.ts +695 -0
  71. package/src/core/providers/openaiNative.ts +13 -0
  72. package/src/core/providers/registry.ts +21 -0
  73. package/src/core/providers/types.ts +91 -0
  74. package/src/core/shared/attachments.ts +101 -0
  75. package/src/core/shared/cleanupFastFail.ts +67 -0
  76. package/src/core/shared/clipboard.ts +24 -0
  77. package/src/core/shared/clipboardImage.ts +111 -0
  78. package/src/core/shared/githubDiagnostics.ts +222 -0
  79. package/src/core/shared/hollowResponseFormat.ts +39 -0
  80. package/src/core/terminal/clearFrameBoundary.ts +852 -0
  81. package/src/core/terminal/frameLock.ts +110 -0
  82. package/src/core/terminal/inkRenderReset.ts +123 -0
  83. package/src/core/terminal/startupClear.ts +20 -0
  84. package/src/core/terminal/terminalCapabilities.ts +100 -0
  85. package/src/core/terminal/terminalControl.ts +169 -0
  86. package/src/core/terminal/terminalSanitize.ts +147 -0
  87. package/src/core/terminal/terminalTitle.ts +400 -0
  88. package/src/core/version/channel.ts +27 -0
  89. package/src/core/version/packageManager.ts +119 -0
  90. package/src/core/version/updateCheck.ts +203 -0
  91. package/src/core/workspace/appData.ts +107 -0
  92. package/src/core/workspace/conversationStore.ts +335 -0
  93. package/src/core/workspace/launchContext.ts +259 -0
  94. package/src/core/workspace/planStorage.ts +135 -0
  95. package/src/core/workspace/projectInstructions.ts +54 -0
  96. package/src/core/workspace/scratchDir.ts +64 -0
  97. package/src/core/workspace/workspaceActivity.ts +384 -0
  98. package/src/core/workspace/workspaceGuard.ts +377 -0
  99. package/src/core/workspace/workspaceRoot.ts +47 -0
  100. package/src/exec.ts +73 -0
  101. package/src/headless/execArgs.ts +296 -0
  102. package/src/headless/execRunner.ts +304 -0
  103. package/src/index.tsx +270 -0
  104. package/src/legacyEnvBootstrap.ts +5 -0
  105. package/src/session/appSession.ts +771 -0
  106. package/src/session/chatLifecycle.ts +994 -0
  107. package/src/session/conversation.ts +107 -0
  108. package/src/session/liveRenderScheduler.ts +214 -0
  109. package/src/session/persistedResponse.ts +93 -0
  110. package/src/session/planFlow.ts +159 -0
  111. package/src/session/planTranscript.ts +19 -0
  112. package/src/session/promptRunSchedule.ts +26 -0
  113. package/src/session/types.ts +234 -0
  114. package/src/test/runtimeTestUtils.ts +14 -0
  115. package/src/types/react-dom.d.ts +3 -0
  116. package/src/ui/chrome/ActivityBars.tsx +68 -0
  117. package/src/ui/chrome/ActivityIndicator.tsx +58 -0
  118. package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
  119. package/src/ui/chrome/AppShell.tsx +474 -0
  120. package/src/ui/chrome/BottomComposer.tsx +1135 -0
  121. package/src/ui/chrome/DashCard.tsx +82 -0
  122. package/src/ui/chrome/RunFooter.tsx +65 -0
  123. package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
  124. package/src/ui/chrome/Spinner.tsx +25 -0
  125. package/src/ui/chrome/TopHeader.tsx +439 -0
  126. package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
  127. package/src/ui/chrome/busyStatusAnimation.ts +11 -0
  128. package/src/ui/input/commandNormalize.ts +66 -0
  129. package/src/ui/input/focus.ts +73 -0
  130. package/src/ui/input/imageAttachments.ts +18 -0
  131. package/src/ui/input/inputBuffer.ts +203 -0
  132. package/src/ui/input/pastedContent.ts +75 -0
  133. package/src/ui/input/rawArrowKeys.ts +28 -0
  134. package/src/ui/input/slashCommands.ts +43 -0
  135. package/src/ui/input/useStdinRawModeLease.ts +23 -0
  136. package/src/ui/layout.ts +560 -0
  137. package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
  138. package/src/ui/panels/AuthPanel.tsx +149 -0
  139. package/src/ui/panels/BackendPicker.tsx +28 -0
  140. package/src/ui/panels/ModePicker.tsx +31 -0
  141. package/src/ui/panels/ModelPicker.tsx +31 -0
  142. package/src/ui/panels/ModelPickerScreen.tsx +761 -0
  143. package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
  144. package/src/ui/panels/Panel.tsx +51 -0
  145. package/src/ui/panels/PermissionsPanel.tsx +78 -0
  146. package/src/ui/panels/PlanActionPicker.tsx +187 -0
  147. package/src/ui/panels/ProviderPicker.tsx +753 -0
  148. package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
  149. package/src/ui/panels/ReasoningPicker.tsx +46 -0
  150. package/src/ui/panels/ResumePicker.tsx +90 -0
  151. package/src/ui/panels/SelectionPanel.tsx +138 -0
  152. package/src/ui/panels/SettingsPanel.tsx +156 -0
  153. package/src/ui/panels/TextEntryPanel.tsx +139 -0
  154. package/src/ui/panels/ThemePicker.tsx +32 -0
  155. package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
  156. package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
  157. package/src/ui/panels/responsivePickerViewport.ts +64 -0
  158. package/src/ui/render/Markdown.tsx +331 -0
  159. package/src/ui/render/diffRenderer.ts +116 -0
  160. package/src/ui/render/logoVariants.ts +113 -0
  161. package/src/ui/render/modeDisplay.ts +52 -0
  162. package/src/ui/render/outputPipeline.ts +64 -0
  163. package/src/ui/render/runtimeDisplay.ts +128 -0
  164. package/src/ui/render/terminalAnswerFormat.ts +128 -0
  165. package/src/ui/render/textLayout.ts +392 -0
  166. package/src/ui/theme.tsx +274 -0
  167. package/src/ui/themeFlow.ts +41 -0
  168. package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
  169. package/src/ui/timeline/AgentBlock.tsx +130 -0
  170. package/src/ui/timeline/StaticIntroItem.tsx +54 -0
  171. package/src/ui/timeline/ThinkingBlock.tsx +100 -0
  172. package/src/ui/timeline/Timeline.tsx +1410 -0
  173. package/src/ui/timeline/TranscriptShell.tsx +302 -0
  174. package/src/ui/timeline/TurnGroup.tsx +673 -0
  175. package/src/ui/timeline/layoutListWindow.ts +145 -0
  176. package/src/ui/timeline/liveViewportWindow.ts +68 -0
  177. package/src/ui/timeline/progressEntries.ts +156 -0
  178. package/src/ui/timeline/runActivityView.ts +37 -0
  179. package/src/ui/timeline/staticTranscriptCache.ts +174 -0
  180. package/src/ui/timeline/streamCoalesce.ts +53 -0
  181. package/src/ui/timeline/timelineMeasure.ts +3273 -0
  182. package/src/ui/useThrottledValue.ts +31 -0
@@ -0,0 +1,55 @@
1
+ import { resolveClaudeExecutable } from "../executables/claudeExecutable.js";
2
+ import { runCommand } from "../process/CommandRunner.js";
3
+ import {
4
+ claudeCodeModelsToProviderModels,
5
+ discoverClaudeCodeCapabilities,
6
+ discoverModelsFromClaudePackageMetadata,
7
+ } from "./claudeCodeDiscovery.js";
8
+
9
+ async function main(): Promise<void> {
10
+ const cwd = process.cwd();
11
+ const resolvedCommand = await resolveClaudeExecutable({ cwd });
12
+ const versionResult = await runCommand({
13
+ executable: resolvedCommand,
14
+ args: ["--version"],
15
+ cwd,
16
+ timeoutMs: 5_000,
17
+ }).result;
18
+ const packageMetadata = discoverModelsFromClaudePackageMetadata(resolvedCommand);
19
+ const discovery = await discoverClaudeCodeCapabilities({ cwd });
20
+ const normalized = claudeCodeModelsToProviderModels(discovery.models);
21
+
22
+ const report = {
23
+ claudeCommand: resolvedCommand,
24
+ claudeBinaryPath: packageMetadata?.sourcePath ?? resolvedCommand,
25
+ claudeVersion: versionResult.status === "completed" && versionResult.exitCode === 0
26
+ ? versionResult.stdout.trim()
27
+ : null,
28
+ discoverySourceUsed: discovery.modelSource,
29
+ packageMetadataSource: packageMetadata?.sourcePath ?? null,
30
+ rawDiscoveredModelEntries: packageMetadata?.rawModelIds ?? [],
31
+ normalizedModelEntries: normalized.map((model) => ({
32
+ id: model.modelId,
33
+ label: model.label,
34
+ family: model.family,
35
+ version: model.version,
36
+ canonicalId: model.canonicalId,
37
+ source: model.source,
38
+ isFallback: model.isFallback,
39
+ discoveryKind: model.discoveryKind,
40
+ })),
41
+ fallbackReason: discovery.modelSource === "fallback"
42
+ ? discovery.diagnostics ?? { reason: "No Claude Code command, package metadata, settings, or config model source returned versioned models." }
43
+ : null,
44
+ };
45
+
46
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
47
+ }
48
+
49
+ try {
50
+ await main();
51
+ } catch (err) {
52
+ const message = err instanceof Error ? err.message : String(err);
53
+ process.stderr.write(`debug:claude-models failed: ${message}\n`);
54
+ process.exit(1);
55
+ }
@@ -0,0 +1,97 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { createInterface } from "node:readline";
6
+
7
+ import { isLocalDevChannel } from "../version/channel.js";
8
+ import type { BackendRunHandlers } from "../providers/types.js";
9
+ import type { ProviderChatRequest, ProviderModelDiscoveryResult, ProviderRouteValidationResult, ProviderRuntime } from "./types.js";
10
+ import { formatConversationHistory } from "../../session/conversation.js";
11
+
12
+ export const CODEXA_CUPY_MODEL_ID = "codexa-250m-cupy";
13
+
14
+ interface Config { modelRoot: string; python: string; bridgeScript: string; checkpoint: string; tokenizer: string; device: string; }
15
+ interface Response { type: string; id?: string; text?: string; message?: string; device?: string; context_length?: number; }
16
+ interface Bridge { child: ChildProcessWithoutNullStreams; ready: Promise<Response>; pending: Map<string, { resolve: (text: string) => void; reject: (error: Error) => void }>; }
17
+
18
+ let bridge: Bridge | null = null;
19
+ let sequence = 0;
20
+
21
+ export function resolveCodexaCupyConfig(env: NodeJS.ProcessEnv = process.env): Config {
22
+ const modelRoot = env.CODEXA_CUPY_MODEL_ROOT?.trim() || join(homedir(), "Development", "2-Python", "32-LLM (NumPy)");
23
+ return {
24
+ modelRoot,
25
+ python: env.CODEXA_CUPY_PYTHON?.trim() || "python3",
26
+ bridgeScript: join(modelRoot, "scripts", "codexa_cupy_bridge.py"),
27
+ checkpoint: env.CODEXA_CUPY_CHECKPOINT?.trim() || join(modelRoot, "runs", "pretraining", "pretrain_250m_fineweb_followup_10m", "target_final.npz"),
28
+ tokenizer: env.CODEXA_CUPY_TOKENIZER?.trim() || join(modelRoot, "data", "tokenized", "general-fineweb-10m-v1", "tokenizer.json"),
29
+ device: env.CODEXA_CUPY_DEVICE?.trim() || "cuda",
30
+ };
31
+ }
32
+
33
+ function missing(config: Config): string[] {
34
+ const executableMissing = config.python.includes("/") && !existsSync(config.python);
35
+ return [config.bridgeScript, config.checkpoint, config.tokenizer]
36
+ .filter((path) => !existsSync(path))
37
+ .concat(executableMissing ? [config.python] : []);
38
+ }
39
+
40
+ export function discoverCodexaCupyModels(config = resolveCodexaCupyConfig(), env: NodeJS.ProcessEnv = process.env): ProviderModelDiscoveryResult {
41
+ if (!isLocalDevChannel(env)) return { status: "not-configured", providerId: "codexa-cupy", backendKind: "unavailable", models: [], message: "CuPy is only available on ubume-dev." };
42
+ const missingPaths = missing(config);
43
+ if (missingPaths.length) return { status: "not-configured", providerId: "codexa-cupy", backendKind: "unavailable", models: [], message: `CuPy is missing required files:\n${missingPaths.join("\n")}`, diagnostics: { modelRoot: config.modelRoot, missingPaths: missingPaths.join(", ") } };
44
+ return {
45
+ status: "ready", providerId: "codexa-cupy", backendKind: "codexa-cupy",
46
+ models: [{ id: CODEXA_CUPY_MODEL_ID, modelId: CODEXA_CUPY_MODEL_ID, label: "CuPy 250M", description: "NumPy model with CuPy/CUDA inference.", defaultReasoningLevel: null, supportedReasoningLevels: null, source: "config", raw: { context_length: 128, supportsStreaming: false, supportsToolCalls: false, supportsSystemPrompt: true, supportsVision: false } }],
47
+ diagnostics: { modelRoot: config.modelRoot, checkpoint: config.checkpoint, tokenizer: config.tokenizer, device: config.device, runtime: bridge ? "loaded" : "not-loaded" },
48
+ };
49
+ }
50
+
51
+ function stopBridge(message = "CuPy process stopped."): void {
52
+ const active = bridge; bridge = null; if (!active) return;
53
+ for (const pending of active.pending.values()) pending.reject(new Error(message));
54
+ active.pending.clear();
55
+ if (!active.child.killed) active.child.kill("SIGTERM");
56
+ }
57
+
58
+ function startBridge(config: Config, handlers: BackendRunHandlers): Bridge {
59
+ if (bridge && !bridge.child.killed && bridge.child.exitCode === null) return bridge;
60
+ const child = spawn(config.python, [config.bridgeScript, "--checkpoint", config.checkpoint, "--tokenizer", config.tokenizer, "--device", config.device], { cwd: config.modelRoot, stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, PYTHONUNBUFFERED: "1" } });
61
+ const pending = new Map<string, { resolve: (text: string) => void; reject: (error: Error) => void }>();
62
+ let readyResolve: (response: Response) => void = () => {};
63
+ let readyReject: (error: Error) => void = () => {};
64
+ const ready = new Promise<Response>((resolve, reject) => { readyResolve = resolve; readyReject = reject; });
65
+ const active: Bridge = { child, ready, pending }; bridge = active;
66
+ const timeout = setTimeout(() => { readyReject(new Error("CuPy timed out while loading the checkpoint.")); stopBridge(); }, 60_000);
67
+ createInterface({ input: child.stdout }).on("line", (line) => {
68
+ let response: Response; try { response = JSON.parse(line) as Response; } catch { return; }
69
+ if (response.type === "ready") { clearTimeout(timeout); readyResolve(response); return; }
70
+ if (!response.id) return;
71
+ const request = pending.get(response.id); if (!request) return; pending.delete(response.id);
72
+ if (response.type === "response" && typeof response.text === "string") request.resolve(response.text);
73
+ else request.reject(new Error(response.message || "CuPy returned an invalid response."));
74
+ });
75
+ child.stderr.on("data", (chunk: Buffer) => { const text = chunk.toString("utf8").trim(); if (text) handlers.onProgress?.({ id: "codexa-cupy-load", source: "stderr", text }); });
76
+ child.on("error", (error) => { clearTimeout(timeout); readyReject(error); stopBridge(error.message); });
77
+ child.on("close", (code, signal) => { clearTimeout(timeout); const message = `CuPy process exited (${signal ?? code ?? "unknown"}).`; readyReject(new Error(message)); stopBridge(message); });
78
+ return active;
79
+ }
80
+
81
+ async function sendPrompt(prompt: string, handlers: BackendRunHandlers): Promise<string> {
82
+ const config = resolveCodexaCupyConfig(); const missingPaths = missing(config); if (missingPaths.length) throw new Error(`CuPy is missing required files:\n${missingPaths.join("\n")}`);
83
+ const active = startBridge(config, handlers); const ready = await active.ready;
84
+ handlers.onProgress?.({ id: "codexa-cupy-ready", source: "stdout", text: `CuPy loaded on ${ready.device ?? config.device}` });
85
+ const id = `cupy-${Date.now()}-${++sequence}`;
86
+ const response = new Promise<string>((resolve, reject) => active.pending.set(id, { resolve, reject }));
87
+ active.child.stdin.write(`${JSON.stringify({ type: "chat", id, prompt })}\n`);
88
+ return response;
89
+ }
90
+
91
+ export const codexaCupyRuntime: ProviderRuntime = {
92
+ providerId: "codexa-cupy", label: "CuPy", modelPickerLabel: "CuPy", backendKind: "codexa-cupy", routeAvailable: true, routeStatus: "Runs the NumPy checkpoint through CuPy/CUDA.", routeSetupMessage: "CuPy model files are unavailable.", launchAvailable: false,
93
+ isRouteConfigured: () => isLocalDevChannel() && discoverCodexaCupyModels().status === "ready",
94
+ discoverModels: discoverCodexaCupyModels, refreshModels: async () => discoverCodexaCupyModels(),
95
+ validateRoute: async (): Promise<ProviderRouteValidationResult> => { const discovery = discoverCodexaCupyModels(); return { status: discovery.status, providerId: "codexa-cupy", backendKind: discovery.backendKind, message: discovery.message, diagnostics: discovery.diagnostics }; },
96
+ run: (request, handlers) => { let canceled = false; handlers.onProgress?.({ id: "codexa-cupy-route", source: "stdout", text: bridge ? "Using loaded CuPy model" : "Loading CuPy checkpoint" }); const prompt = request.conversationHistory?.length ? `Previous conversation:\n${formatConversationHistory(request.conversationHistory)}\n\nCurrent request:\n${request.prompt}` : request.prompt; sendPrompt(prompt, handlers).then((text) => { if (canceled) return; handlers.onAssistantDelta?.(text); handlers.onFinalAnswerObserved?.(text); handlers.onResponse(text); }).catch((error) => { if (!canceled) handlers.onError(error instanceof Error ? error.message : "CuPy failed."); }); return () => { canceled = true; stopBridge("CuPy request canceled."); }; },
97
+ };
@@ -0,0 +1,425 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { createInterface } from "node:readline";
6
+ import { createHash } from "node:crypto";
7
+
8
+ import { isLocalDevChannel } from "../version/channel.js";
9
+ import type { BackendRunHandlers } from "../providers/types.js";
10
+ import type {
11
+ ProviderChatRequest,
12
+ ProviderModelDiscoveryResult,
13
+ ProviderRouteValidationResult,
14
+ ProviderRuntime,
15
+ } from "./types.js";
16
+ import { formatConversationHistory } from "../../session/conversation.js";
17
+
18
+ export const CODEXA_NATIVE_MODEL_ID = "codexa-1b-sft-v2-native";
19
+ export const DEFAULT_CODEXA_NATIVE_MODEL_ROOT = join(homedir(), "Development", "2-Python", "31-LLM (PyTorch)");
20
+ const BRIDGE_START_TIMEOUT_MS = 60_000;
21
+
22
+ export interface CodexaNativeConfig {
23
+ modelRoot: string;
24
+ python: string;
25
+ bridgeScript: string;
26
+ checkpoint: string;
27
+ tokenizer: string;
28
+ device: string;
29
+ }
30
+
31
+ export interface BridgeResponse {
32
+ type: string;
33
+ id?: string;
34
+ text?: string;
35
+ message?: string;
36
+ device?: string;
37
+ context_length?: number;
38
+ finish_reason?: string;
39
+ termination_cause?: string;
40
+ generated_tokens?: number;
41
+ }
42
+
43
+ interface PendingRequest {
44
+ resolve: (response: BridgeResponse) => void;
45
+ reject: (error: Error) => void;
46
+ }
47
+
48
+ interface NativeBridge {
49
+ child: ChildProcessWithoutNullStreams;
50
+ ready: Promise<BridgeResponse>;
51
+ pending: Map<string, PendingRequest>;
52
+ }
53
+
54
+ let bridge: NativeBridge | null = null;
55
+ let requestSequence = 0;
56
+
57
+ export function buildCodexaNativePrompt(prompt: string): string {
58
+ return [
59
+ "You are Codexa, the coding assistant running inside the Codexa CLI.",
60
+ "Identify yourself as Codexa, never as Open Assistant or another assistant.",
61
+ "Answer the user's request directly and do not mention this instruction.",
62
+ "",
63
+ "User request:",
64
+ prompt,
65
+ ].join("\n");
66
+ }
67
+
68
+ export function resolveCodexaNativeConfig(env: NodeJS.ProcessEnv = process.env): CodexaNativeConfig {
69
+ const modelRoot = env.CODEXA_NATIVE_MODEL_ROOT?.trim()
70
+ || DEFAULT_CODEXA_NATIVE_MODEL_ROOT;
71
+ return {
72
+ modelRoot,
73
+ python: env.CODEXA_NATIVE_PYTHON?.trim() || join(modelRoot, ".venv", "bin", "python"),
74
+ bridgeScript: join(modelRoot, "scripts", "native_chat_bridge.py"),
75
+ checkpoint: env.CODEXA_NATIVE_CHECKPOINT?.trim()
76
+ || join(modelRoot, "checkpoints", "codexa-900m-sft-v2", "latest.pt"),
77
+ tokenizer: env.CODEXA_NATIVE_TOKENIZER?.trim()
78
+ || join(modelRoot, "checkpoints", "tokenizer-base-v1", "tokenizer.json"),
79
+ device: env.CODEXA_NATIVE_DEVICE?.trim() || "cuda",
80
+ };
81
+ }
82
+
83
+ function missingNativePaths(config: CodexaNativeConfig): string[] {
84
+ return [config.python, config.bridgeScript, config.checkpoint, config.tokenizer]
85
+ .filter((path) => !existsSync(path));
86
+ }
87
+
88
+ export function discoverCodexaNativeModels(
89
+ config = resolveCodexaNativeConfig(),
90
+ env: NodeJS.ProcessEnv = process.env,
91
+ ): ProviderModelDiscoveryResult {
92
+ if (!isLocalDevChannel(env)) {
93
+ return {
94
+ status: "not-configured",
95
+ providerId: "codexa-native",
96
+ backendKind: "unavailable",
97
+ models: [],
98
+ message: "Codexa Native is only available on ubume-dev.",
99
+ };
100
+ }
101
+ const missing = missingNativePaths(config);
102
+ if (missing.length > 0) {
103
+ return {
104
+ status: "not-configured",
105
+ providerId: "codexa-native",
106
+ backendKind: "unavailable",
107
+ models: [],
108
+ message: `Codexa Native is missing required files:\n${missing.join("\n")}`,
109
+ diagnostics: {
110
+ modelRoot: config.modelRoot,
111
+ missingPaths: missing.join(", "),
112
+ },
113
+ };
114
+ }
115
+ return {
116
+ status: "ready",
117
+ providerId: "codexa-native",
118
+ backendKind: "codexa-native-pytorch",
119
+ models: [{
120
+ id: CODEXA_NATIVE_MODEL_ID,
121
+ modelId: CODEXA_NATIVE_MODEL_ID,
122
+ label: "Codexa 1B SFT v2 (Native)",
123
+ description: "Direct PyTorch checkpoint inference; no LM Studio or GGUF.",
124
+ defaultReasoningLevel: null,
125
+ supportedReasoningLevels: null,
126
+ source: "config",
127
+ raw: {
128
+ context_length: 2048,
129
+ supportsStreaming: false,
130
+ supportsToolCalls: false,
131
+ supportsSystemPrompt: true,
132
+ supportsVision: false,
133
+ },
134
+ }],
135
+ diagnostics: {
136
+ modelRoot: config.modelRoot,
137
+ checkpoint: config.checkpoint,
138
+ tokenizer: config.tokenizer,
139
+ device: config.device,
140
+ runtime: bridge ? "loaded" : "not-loaded",
141
+ },
142
+ };
143
+ }
144
+
145
+ function rejectPending(nativeBridge: NativeBridge, message: string): void {
146
+ for (const pending of nativeBridge.pending.values()) {
147
+ pending.reject(new Error(message));
148
+ }
149
+ nativeBridge.pending.clear();
150
+ }
151
+
152
+ function stopBridge(message = "Codexa Native process stopped."): void {
153
+ const active = bridge;
154
+ bridge = null;
155
+ if (!active) return;
156
+ rejectPending(active, message);
157
+ if (!active.child.killed) active.child.kill("SIGTERM");
158
+ }
159
+
160
+ function startBridge(config: CodexaNativeConfig, handlers: BackendRunHandlers): NativeBridge {
161
+ if (bridge && !bridge.child.killed && bridge.child.exitCode === null) return bridge;
162
+
163
+ const child = spawn(config.python, [
164
+ config.bridgeScript,
165
+ "--checkpoint", config.checkpoint,
166
+ "--tokenizer", config.tokenizer,
167
+ "--device", config.device,
168
+ ], {
169
+ cwd: config.modelRoot,
170
+ stdio: ["pipe", "pipe", "pipe"],
171
+ env: { ...process.env, PYTHONUNBUFFERED: "1" },
172
+ });
173
+ const pending = new Map<string, PendingRequest>();
174
+ let readyResolve: (response: BridgeResponse) => void = () => {};
175
+ let readyReject: (error: Error) => void = () => {};
176
+ const ready = new Promise<BridgeResponse>((resolve, reject) => {
177
+ readyResolve = resolve;
178
+ readyReject = reject;
179
+ });
180
+ const nativeBridge: NativeBridge = { child, ready, pending };
181
+ bridge = nativeBridge;
182
+
183
+ const timeout = setTimeout(() => {
184
+ readyReject(new Error("Codexa Native timed out while loading the checkpoint."));
185
+ stopBridge("Codexa Native timed out while loading the checkpoint.");
186
+ }, BRIDGE_START_TIMEOUT_MS);
187
+
188
+ createInterface({ input: child.stdout }).on("line", (line) => {
189
+ let response: BridgeResponse;
190
+ try {
191
+ response = JSON.parse(line) as BridgeResponse;
192
+ } catch {
193
+ return;
194
+ }
195
+ if (response.type === "ready") {
196
+ clearTimeout(timeout);
197
+ readyResolve(response);
198
+ return;
199
+ }
200
+ if (!response.id) return;
201
+ const request = pending.get(response.id);
202
+ if (!request) return;
203
+ pending.delete(response.id);
204
+ if (response.type === "response" && typeof response.text === "string") {
205
+ request.resolve(response);
206
+ } else {
207
+ request.reject(new Error(response.message || "Codexa Native returned an invalid response."));
208
+ }
209
+ });
210
+
211
+ child.stderr.on("data", (chunk: Buffer) => {
212
+ const text = chunk.toString("utf8").trim();
213
+ if (text) handlers.onProgress?.({ id: "codexa-native-load", source: "stderr", text });
214
+ });
215
+ child.on("error", (error) => {
216
+ clearTimeout(timeout);
217
+ readyReject(error);
218
+ rejectPending(nativeBridge, error.message);
219
+ if (bridge === nativeBridge) bridge = null;
220
+ });
221
+ child.on("close", (code, signal) => {
222
+ clearTimeout(timeout);
223
+ const message = `Codexa Native process exited (${signal ?? code ?? "unknown"}).`;
224
+ readyReject(new Error(message));
225
+ rejectPending(nativeBridge, message);
226
+ if (bridge === nativeBridge) bridge = null;
227
+ });
228
+ return nativeBridge;
229
+ }
230
+
231
+ async function sendPrompt(
232
+ prompt: string,
233
+ handlers: BackendRunHandlers,
234
+ announceReady = true,
235
+ ): Promise<BridgeResponse> {
236
+ const config = resolveCodexaNativeConfig();
237
+ const missing = missingNativePaths(config);
238
+ if (missing.length > 0) throw new Error(`Codexa Native is missing required files:\n${missing.join("\n")}`);
239
+ const nativeBridge = startBridge(config, handlers);
240
+ const ready = await nativeBridge.ready;
241
+ if (announceReady) {
242
+ handlers.onProgress?.({
243
+ id: "codexa-native-ready",
244
+ source: "stdout",
245
+ text: `Codexa Native loaded on ${ready.device ?? config.device}`,
246
+ });
247
+ }
248
+ const id = `native-${Date.now()}-${++requestSequence}`;
249
+ const response = new Promise<BridgeResponse>((resolve, reject) => {
250
+ nativeBridge.pending.set(id, { resolve, reject });
251
+ });
252
+ nativeBridge.child.stdin.write(`${JSON.stringify({ type: "chat", id, prompt: buildCodexaNativePrompt(prompt) })}\n`);
253
+ return response;
254
+ }
255
+
256
+ const NATIVE_CONTEXT_LENGTH = 2048;
257
+ const NATIVE_TRANSCRIPT_BUDGET_CHARS = 5_800;
258
+ const NATIVE_EXACT_TAIL_CHARS = 1_200;
259
+
260
+ function hashNativeTranscript(messages: readonly { role: string; content: string }[]): string {
261
+ return createHash("sha256").update(JSON.stringify(messages)).digest("hex");
262
+ }
263
+
264
+ export function stitchNativeContinuation(accumulated: string, next: string): string {
265
+ if (!accumulated) return next;
266
+ if (!next) return accumulated;
267
+ const maximum = Math.min(accumulated.length, next.length);
268
+ for (let overlap = maximum; overlap >= 4; overlap -= 1) {
269
+ if (accumulated.endsWith(next.slice(0, overlap))) return accumulated + next.slice(overlap);
270
+ }
271
+ return accumulated + next;
272
+ }
273
+
274
+ function nativeCheckpointPrompt(source: string): string {
275
+ return [
276
+ "Create a compact continuation checkpoint for another model window.",
277
+ "Preserve the user's goal, decisions, constraints, files, commands, results, errors, and unfinished work.",
278
+ "Do not answer the user. Return only a concise factual checkpoint.",
279
+ "",
280
+ source.slice(-NATIVE_TRANSCRIPT_BUDGET_CHARS),
281
+ ].join("\n");
282
+ }
283
+
284
+ function nativeContinuationPrompt(checkpoint: string, exactTail: string): string {
285
+ return [
286
+ "Continue the same assistant answer seamlessly in a fresh context window.",
287
+ "Do not mention context limits, checkpoints, continuation, or restate completed text.",
288
+ "",
289
+ "Conversation checkpoint:",
290
+ checkpoint,
291
+ "",
292
+ "Exact end of the answer already shown to the user:",
293
+ exactTail,
294
+ "",
295
+ "Continue immediately after that exact text:",
296
+ ].join("\n");
297
+ }
298
+
299
+ export async function runCodexaNativeRollover(options: {
300
+ request: ProviderChatRequest;
301
+ handlers: BackendRunHandlers;
302
+ send: (prompt: string, announceReady: boolean) => Promise<BridgeResponse>;
303
+ }): Promise<string> {
304
+ const history = options.request.conversationHistory?.length
305
+ ? formatConversationHistory(options.request.conversationHistory)
306
+ : "";
307
+ const fullPrompt = history
308
+ ? `Previous conversation:\n${history}\n\nCurrent request:\n${options.request.prompt}`
309
+ : options.request.prompt;
310
+ const conversationHistory = options.request.conversationHistory ?? [];
311
+ const coveredMessages = [...conversationHistory, { role: "user", content: options.request.prompt }];
312
+ const transcriptHash = hashNativeTranscript(coveredMessages);
313
+ let checkpoint = options.request.localContextCheckpoint?.modelId === CODEXA_NATIVE_MODEL_ID
314
+ && options.request.localContextCheckpoint.throughMessageCount <= conversationHistory.length
315
+ && hashNativeTranscript(conversationHistory.slice(0, options.request.localContextCheckpoint.throughMessageCount))
316
+ === options.request.localContextCheckpoint.transcriptHash
317
+ ? options.request.localContextCheckpoint.summary
318
+ : "";
319
+ let prompt = fullPrompt;
320
+
321
+ if (fullPrompt.length > NATIVE_TRANSCRIPT_BUDGET_CHARS) {
322
+ if (!checkpoint) {
323
+ const summaryResponse = await options.send(nativeCheckpointPrompt(fullPrompt), false);
324
+ checkpoint = summaryResponse.text?.trim() || fullPrompt.slice(-NATIVE_EXACT_TAIL_CHARS);
325
+ }
326
+ options.handlers.onLocalContextCheckpoint?.({
327
+ version: 1,
328
+ modelId: CODEXA_NATIVE_MODEL_ID,
329
+ contextLength: NATIVE_CONTEXT_LENGTH,
330
+ throughMessageCount: coveredMessages.length,
331
+ transcriptHash,
332
+ summary: checkpoint,
333
+ activeWindowChars: checkpoint.length + Math.min(NATIVE_EXACT_TAIL_CHARS, fullPrompt.length),
334
+ responseCharsCovered: 0,
335
+ updatedAt: new Date().toISOString(),
336
+ });
337
+ prompt = nativeContinuationPrompt(checkpoint, fullPrompt.slice(-NATIVE_EXACT_TAIL_CHARS));
338
+ }
339
+
340
+ let accumulated = "";
341
+ let announceReady = true;
342
+ let emptyWindows = 0;
343
+ while (true) {
344
+ const response = await options.send(prompt, announceReady);
345
+ announceReady = false;
346
+ const text = response.text ?? "";
347
+ const previousLength = accumulated.length;
348
+ accumulated = stitchNativeContinuation(accumulated, text);
349
+ emptyWindows = accumulated.length === previousLength ? emptyWindows + 1 : 0;
350
+ if (response.finish_reason !== "length") return accumulated;
351
+ if (emptyWindows >= 2) return accumulated;
352
+
353
+ const summarySource = [checkpoint, fullPrompt, accumulated].filter(Boolean).join("\n\n");
354
+ const summaryResponse = await options.send(nativeCheckpointPrompt(summarySource), false);
355
+ checkpoint = summaryResponse.text?.trim() || summarySource.slice(-NATIVE_EXACT_TAIL_CHARS);
356
+ options.handlers.onLocalContextCheckpoint?.({
357
+ version: 1,
358
+ modelId: CODEXA_NATIVE_MODEL_ID,
359
+ contextLength: NATIVE_CONTEXT_LENGTH,
360
+ throughMessageCount: coveredMessages.length,
361
+ transcriptHash,
362
+ summary: checkpoint,
363
+ activeWindowChars: checkpoint.length + Math.min(NATIVE_EXACT_TAIL_CHARS, accumulated.length),
364
+ responseCharsCovered: accumulated.length,
365
+ updatedAt: new Date().toISOString(),
366
+ });
367
+ prompt = nativeContinuationPrompt(checkpoint, accumulated.slice(-NATIVE_EXACT_TAIL_CHARS));
368
+ }
369
+ }
370
+
371
+ export function resetCodexaNativeRuntimeForTests(): void {
372
+ stopBridge("Codexa Native test reset.");
373
+ requestSequence = 0;
374
+ }
375
+
376
+ export const codexaNativeRuntime: ProviderRuntime = {
377
+ providerId: "codexa-native",
378
+ label: "Codexa Native",
379
+ modelPickerLabel: "Codexa Native",
380
+ backendKind: "codexa-native-pytorch",
381
+ routeAvailable: true,
382
+ routeStatus: "Runs the Codexa 1B SFT v2 checkpoint directly through PyTorch.",
383
+ routeSetupMessage: "Codexa Native model files are unavailable.",
384
+ launchAvailable: false,
385
+ isRouteConfigured: () => isLocalDevChannel() && discoverCodexaNativeModels().status === "ready",
386
+ validateRoute: async (): Promise<ProviderRouteValidationResult> => {
387
+ const discovery = discoverCodexaNativeModels();
388
+ return {
389
+ status: discovery.status,
390
+ providerId: "codexa-native",
391
+ backendKind: discovery.backendKind,
392
+ message: discovery.message,
393
+ diagnostics: discovery.diagnostics,
394
+ };
395
+ },
396
+ discoverModels: discoverCodexaNativeModels,
397
+ refreshModels: async () => discoverCodexaNativeModels(),
398
+ run: (request: ProviderChatRequest, handlers: BackendRunHandlers) => {
399
+ let canceled = false;
400
+ handlers.onProgress?.({
401
+ id: "codexa-native-route",
402
+ source: "stdout",
403
+ text: bridge ? "Using loaded Codexa Native model" : "Loading Codexa Native checkpoint",
404
+ });
405
+ runCodexaNativeRollover({
406
+ request,
407
+ handlers,
408
+ send: (prompt, announceReady) => sendPrompt(prompt, handlers, announceReady),
409
+ })
410
+ .then((text) => {
411
+ if (canceled) return;
412
+ handlers.onAssistantDelta?.(text);
413
+ handlers.onFinalAnswerObserved?.(text);
414
+ handlers.onResponse(text);
415
+ })
416
+ .catch((error) => {
417
+ if (canceled) return;
418
+ handlers.onError(error instanceof Error ? error.message : "Codexa Native failed.");
419
+ });
420
+ return () => {
421
+ canceled = true;
422
+ stopBridge("Codexa Native request canceled.");
423
+ };
424
+ },
425
+ };