refactor: rewrite from Python to Bun + TypeScript + React Ink#1707
refactor: rewrite from Python to Bun + TypeScript + React Ink#1707Yuandiaodiaodiao wants to merge 12 commits intoMoonshotAI:mainfrom
Conversation
…act Ink Complete rewrite of kimi-cli from Python (Typer + Rich + prompt-toolkit) to Bun + TypeScript + React Ink TUI framework. Removes all web UI code. Key changes: - Runtime: Python → Bun (TypeScript) - CLI framework: Typer → Commander.js - TUI: Rich + prompt-toolkit → React Ink - Validation: Pydantic → Zod v4 - LLM SDK: kosong → @anthropic-ai/sdk, openai, @google/genai - File I/O: KaosPath → Bun.file / Bun.write - Shell exec: kaos.exec → Bun.spawn Architecture preserved with matching directory structure: - soul/ — Core agent loop (kimisoul, agent, context, toolset, compaction) - tools/ — All tool implementations (file, shell, web, think, plan, etc.) - wire/ — Wire protocol for UI↔Agent communication - ui/shell/ — React Ink TUI components - cli/ — CLI entry point and routing - config, session, hooks, auth, notifications, approval_runtime 67 TypeScript files, 0 type errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
751b51b to
963d58d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 751b51bc86
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/kimi_cli/llm.ts
Outdated
| const stubProvider: LLMProvider = { | ||
| modelName: model.model, | ||
| async *chat(messages: Message[], chatOpts?: ChatOptions) { | ||
| throw new Error( | ||
| `LLM provider "${provider.type}" is not yet implemented in TypeScript. ` + |
There was a problem hiding this comment.
Replace stubbed chat provider with real provider dispatch
createLLM always returns stubProvider, and chat() immediately throws for every configured provider type, so every turn fails as soon as the model is invoked. This makes the CLI unable to generate any assistant response even with valid provider/model config, which is a full functional regression.
Useful? React with 👍 / 👎.
| React.createElement(Shell, { | ||
| modelName: app.soul.modelName, | ||
| onSubmit: (input: string) => { | ||
| app.soul.run(input); | ||
| }, |
There was a problem hiding this comment.
Wire soul events into shell so the interactive UI can update
The shell is rendered with only onSubmit, which calls app.soul.run(...) directly, but no onWireReady/callback bridge is provided to feed useWire with turn/text/tool/approval events. As a result the message list does not reflect agent output, and approval-required tool calls can stall because the UI never receives/returns approval events.
Useful? React with 👍 / 👎.
| contentParts.push({ | ||
| type: "tool_use", | ||
| id: tc.id, | ||
| name: tc.name, | ||
| input: JSON.parse(tc.arguments || "{}"), |
There was a problem hiding this comment.
Handle malformed tool-call JSON without aborting the turn
Tool call arguments are parsed with JSON.parse while assembling assistant context content, and parsing errors are not caught. If a model emits invalid JSON arguments (a common transient/tool-calling failure mode), the whole turn throws before tool execution reaches the existing argument-parse error handling in toolset.handle, causing avoidable turn termination.
Useful? React with 👍 / 👎.
Tests (35 files, 376 pass): - tests/core/ — config, context, session, toolset, compaction, approval, slash commands, wire messages, llm, agentspec - tests/tools/ — read, write, replace, glob, grep, shell, fetch, think, ask_user, todo, tool schemas - tests/hooks/ — hook engine - tests/utils/ — path, async, logging, result builder - tests/ui/ — theme, slash commands, console, events, keyboard, print mode - tests/e2e/ — CLI entry, basic e2e - tests/background/ — background manager CI: - .github/workflows/ci-kimi-cli-ts.yml — typecheck, test, build binaries for linux-x64/arm64, darwin-x64/arm64, windows-x64 - Auto release on "release:" commit messages Other: - package.json build scripts for cross-platform binary compilation - Restore docs/, examples/, scripts/, .github/, .agents/ from main - Remove old Python CI workflows Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace stub provider with real OpenAI-compatible streaming implementation - Supports Kimi API, OpenAI API, and any compatible endpoint - SSE stream parsing with text, thinking/reasoning, and tool_call support - Message format conversion (ContentPart → OpenAI messages) - Fallback LLM creation from env vars: KIMI_BASE_URL, KIMI_API_KEY, KIMI_MODEL_NAME - Default text output callbacks for print mode - Tested with kimi-k2.5 via api.moonshot.cn Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e67aa7e2f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/kimi_cli/cli/index.ts
Outdated
| if (options.print && prompt) { | ||
| // Non-interactive print mode | ||
| await app.runPrint(prompt); | ||
| } else { |
There was a problem hiding this comment.
Honor --print mode without requiring positional prompt
The --print flag is documented as non-interactive, but this branch only enters print mode when prompt is also present; kimi --print (or any future stdin-driven print flow) falls through to the interactive Ink shell instead. In automation/non-TTY contexts this causes unexpected hangs or UI startup failures instead of producing print output.
Useful? React with 👍 / 👎.
| const shellPath = process.env.SHELL || "/bin/bash"; | ||
|
|
||
| const proc = Bun.spawn([shellPath, "-c", params.command], { | ||
| stdout: "pipe", |
There was a problem hiding this comment.
Use a Windows-compatible shell fallback
When SHELL is unset, the tool hardcodes /bin/bash as the executable. On Windows, SHELL is commonly absent and /bin/bash usually does not exist, so Shell tool calls fail before running the requested command. Because this commit adds Windows binaries in CI, this breaks core tool functionality on that target.
Useful? React with 👍 / 👎.
| const dirFile = Bun.file(sessionsDir); | ||
| if (!(await dirFile.exists())) return []; |
There was a problem hiding this comment.
Check session directory existence with dir-aware APIs
This uses Bun.file(sessionsDir).exists() to test whether the sessions directory exists, but Bun.file(...).exists() returns false for directories, so Session.list() returns [] even when sessions are present. That makes session discovery/listing logic silently fail for normal repositories.
Useful? React with 👍 / 👎.
- Add WelcomeBox with logo, directory, session, model, tip - Rewrite StatusBar as separator line + status text (not box) - Update Prompt with ✨ sparkles emoji - Switch TOML parser from @iarna/toml to smol-toml (fixes key parsing) - Pass workDir, sessionId, thinking to Shell component Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3732f9cdd7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| switch (provider.type) { | ||
| case "kimi": | ||
| case "openai_legacy": | ||
| case "openai_responses": | ||
| llmProvider = new OpenAICompatibleProvider({ |
There was a problem hiding this comment.
Normalize OpenAI provider type in LLM dispatch
The provider switch only handles "openai_legacy"/"openai_responses", but config parsing allows "openai" and app.ts passes that value through. In that common configuration, createLLM falls into the default branch and returns a provider that throws on the first chat() call, so OpenAI-backed sessions fail at runtime despite valid config.
Useful? React with 👍 / 👎.
| async function registerBuiltinTools(toolset: KimiToolset): Promise<void> { | ||
| // Import and register all built-in tools | ||
| const toolModules = [ | ||
| () => import("../tools/file/read.ts"), | ||
| () => import("../tools/file/write.ts"), |
There was a problem hiding this comment.
Load tools from agent spec instead of hardcoded list
Agent construction always registers a fixed module list here, so the tool set defined in agents/*/agent.yaml (including allow/exclude controls and declared tools like background/task/plan/Agent) is never applied. This disables configured agent behaviors and subagent/background workflows even though the specs exist in the repository.
Useful? React with 👍 / 👎.
| if (!dirPath.startsWith("/")) { | ||
| return ToolError( | ||
| `\`${params.directory}\` is not an absolute path. You must provide an absolute path to search.`, |
There was a problem hiding this comment.
Accept Windows absolute paths in Glob validation
The absolute-path check only accepts paths starting with /, so Windows drive-letter paths (for example C:\repo) are rejected as non-absolute. On Windows, ctx.workingDir itself uses that format, which makes normal Glob calls fail with the absolute-path error and effectively breaks the tool on that platform.
Useful? React with 👍 / 👎.
| const filePath = configFile ?? getConfigFile(); | ||
| const dir = filePath.substring(0, filePath.lastIndexOf("/")); | ||
| await Bun.$`mkdir -p ${dir}`.quiet(); |
There was a problem hiding this comment.
Use path-aware parent dir handling in saveConfig
Parent directory extraction uses lastIndexOf("/"), which returns -1 for Windows paths with backslashes. When config is first created, this can produce an empty directory string and cause the mkdir -p step to fail before config.toml is written, breaking first-run setup on Windows.
Useful? React with 👍 / 👎.
- Interactive mode callbacks now push WireUIEvents to Shell component instead of writing directly to process.stdout - Print mode (--print) still uses direct stdout for non-TUI output - Fix chat history layout: messages display from top, not flex-end - Remove default stdout fallback from app.ts soul callbacks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show filtered command list when user types '/' - Menu renders below input, pushes up from bottom - Up/Down arrows navigate, Enter selects, Tab auto-completes - Hide status bar when slash menu is open - Deduplicate commands (shell + soul) - Fuzzy filter by command name and aliases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove nested flex-grow Box that separated messages from input. Now messages + input are in one flex column (flex-grow), content flows from top with natural empty space below — matching Python layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38cf997b7c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }); | ||
|
|
||
| try { | ||
| const [response, feedback] = await this._runtime.waitForResponse(requestId); |
There was a problem hiding this comment.
Resolve approval requests before waiting for response
This call can block forever because there is no wiring in this commit that resolves user approvals back into ApprovalRuntime for normal tool flows. I checked the tree for ApprovalRuntime.resolve usage and only found the internal auto-approve branch in approval.ts, so with default yolo=false any tool that requests approval can stall the turn indefinitely while awaiting waitForResponse.
Useful? React with 👍 / 👎.
src/kimi_cli/session.ts
Outdated
| // ── WorkDir Metadata ──────────────────────────────────── | ||
|
|
||
| function getSessionsBaseDir(workDir: string): string { | ||
| return join(getShareDir(), "sessions", workDir.replace(/\//g, "_").replace(/^_/, "")); |
There was a problem hiding this comment.
Normalize Windows paths when building sessions base dir
getSessionsBaseDir only rewrites /, so Windows workdirs like C:\repo\proj retain backslashes and the drive-colon in the path segment. That produces invalid/unstable session directory paths on Windows, which breaks Session.create/find/list for those workdirs.
Useful? React with 👍 / 👎.
| // Background mode - stub for now | ||
| return builder.error( | ||
| "Background tasks are not yet implemented in this version.", | ||
| ); |
There was a problem hiding this comment.
Implement or disable run_in_background in Shell tool
The tool schema and description explicitly promote run_in_background=true for long-running commands, but this branch returns an immediate error for every such call. That creates deterministic failures for long tasks the model is guided to run in background, instead of executing or scheduling them.
Useful? React with 👍 / 👎.
- ChatList: height = content (no flexGrow), grows as messages added - InputBox: flexGrow=1, fills remaining space, text starts from top - When no messages, input occupies entire middle area - Top (WelcomeBox) and Bottom (StatusBar) stay fixed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- InputBox: flexGrow=1 + flexShrink=1 + minHeight=6 - Fills remaining space, shrinks as ChatList grows, min 6 lines - ChatList: flexShrink=0 — never compressed, grows with content - Root: minHeight instead of height — allows overflow + scroll when ChatList + InputBox exceed screen, WelcomeBox scrolls off top - Input always rendered (even during streaming), shows 🔄 + steer hint - Remove disabled input state — always interactive Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e7cae1919
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| yolo: options.yolo, | ||
| sessionId: options.session, | ||
| maxStepsPerTurn: options.maxStepsPerTurn, | ||
| callbacks, | ||
| }); |
There was a problem hiding this comment.
Propagate
--continue into interactive app creation
In interactive mode, KimiCLI.create(...) is called without continueSession, so kimi -C is silently ignored unless --print is also used. This breaks session resume behavior for the default shell workflow and causes users to start a fresh session unexpectedly even when they requested continuation.
Useful? React with 👍 / 👎.
| onSubmit: (input: string) => { | ||
| app.soul.run(input); | ||
| }, |
There was a problem hiding this comment.
Route streaming input to steer instead of dropping it
The shell prompt explicitly allows input while streaming, but onSubmit always calls app.soul.run(input). KimiSoul.run returns early when a turn is already running, so any mid-turn user guidance is discarded instead of being queued as steer input. In practice this makes in-flight correction/steering impossible during long tool runs.
Useful? React with 👍 / 👎.
| const approvalState = new ApprovalState({ | ||
| yolo: | ||
| opts.config.default_yolo || opts.session.state.approval.yolo, | ||
| autoApproveActions: new Set( | ||
| opts.session.state.approval.auto_approve_actions, |
There was a problem hiding this comment.
Persist approval state changes back into session state
Approval settings are loaded from session.state.approval into a separate ApprovalState, but no change hook writes updates back to session.state. As a result, /yolo toggles and approve_for_session decisions are lost on shutdown/resume because Session.saveState() persists stale approval data.
Useful? React with 👍 / 👎.
…nhancements Add new modules (auth platforms, subagents, background tasks, wire jsonrpc/root_hub), enhance existing tools (plan mode, hooks, compaction, slash commands), and improve shell UI components (approval/debug/question/replay/usage panels, task browser).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 302656023c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const callbacks: SoulCallbacks = { | ||
| onTextDelta: (text) => process.stdout.write(text), | ||
| onThinkDelta: (text) => process.stderr.write(chalk.dim(text)), | ||
| onError: (err) => | ||
| process.stderr.write(chalk.red(`[ERROR] ${err.message}\n`)), |
There was a problem hiding this comment.
Honor print mode formatting flags
In the --print branch, output is hardwired to raw text streaming callbacks, so --output-format stream-json and --final-message-only are never applied. This causes automation that relies on JSON lines or final-only output to receive the wrong format even when those flags are explicitly set.
Useful? React with 👍 / 👎.
| // Determine config source: --config-file takes precedence over legacy --config as path | ||
| const configFile = options.configFile ?? undefined; | ||
|
|
There was a problem hiding this comment.
Parse inline --config payload instead of ignoring it
The CLI accepts --config <string>, but this code only forwards options.configFile and never consumes options.config (nor calls loadConfigFromString). As a result, users providing inline TOML/JSON config get silently ignored settings.
Useful? React with 👍 / 👎.
| if (opts.maxStepsPerTurn) { | ||
| config.loop_control.max_steps_per_turn = opts.maxStepsPerTurn; | ||
| } |
There was a problem hiding this comment.
Apply max-retries override in app creation
CLI call sites pass maxRetriesPerStep, but KimiCLI.create only applies max_steps_per_turn and never updates loop_control.max_retries_per_step. This makes --max-retries-per-step a no-op, so retry behavior stays at config defaults regardless of user input.
Useful? React with 👍 / 👎.
| const file = Bun.file(path); | ||
| // Synchronous existence check is not available — use a simple approach | ||
| return file.size > 0 ? null : null; // Will be refined when plan tools exist |
There was a problem hiding this comment.
Bring back all non-kimi_cli files (CI workflows, packages, tests, web, vis, klips, configs, etc.) that were removed during the bun-ts refactor.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 94b7b71d07
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const planTarget = inspectPlanEditTarget(resolvedPath, { | ||
| planModeChecker: this._planModeChecker ?? ctx.getPlanMode, | ||
| planFilePathGetter: this._planFilePathGetter, | ||
| }); |
There was a problem hiding this comment.
Pass current plan path into WriteFile plan-mode guard
When plan mode is active, inspectPlanEditTarget requires a plan file path, but this call only passes this._planFilePathGetter and does not fall back to runtime context. Because tool registration never calls bindPlanMode, that getter stays unset, so WriteFile returns the “plan file is unavailable” error for every plan-mode write and blocks the core plan workflow from progressing.
Useful? React with 👍 / 👎.
| }); | ||
|
|
||
| // 6. Load agent | ||
| const agent = await loadAgent({ runtime }); |
There was a problem hiding this comment.
Honor CLI agent selection when constructing the runtime agent
Agent loading is hardcoded to loadAgent({ runtime }), so runtime always uses the default agent spec. Since --agent and --agent-file are parsed in cli/index.ts but KimiCLI.create has no corresponding inputs to forward, those user-facing flags are effectively no-ops across print/wire/interactive modes.
Useful? React with 👍 / 👎.
| const gitFile = Bun.file(`${current}/.git`); | ||
| if (await gitFile.exists()) return current; | ||
| const parent = dirname(current); |
There was a problem hiding this comment.
Detect git roots with directory-aware filesystem checks
This root-detection logic relies on Bun.file(...).exists() for .git, but Bun reports false for directories, so standard repos with a .git/ directory are not recognized as project roots. The walk then falls back to workDir as root, which skips parent-scope AGENTS.md files and breaks the intended root-to-leaf instruction merge.
Useful? React with 👍 / 👎.
kimicli用python是彻底的失败 立刻重构为ts
Summary
Key Changes
Architecture Preserved
Stats
bun run src/kimi_cli/index.ts --helpworksTest Plan
bun installsucceedstsc --noEmit --skipLibCheckpasses with 0 errorsbun run src/kimi_cli/index.ts --helpshows CLI help~/.kimi/config.tomland test interactive chat🤖 Generated with Claude Code