feat(providers): add Kimi provider - #7908
Conversation
Wraps the Kimi CLI over ACP mirroring the Grok driver stack, with a server-side "Sign in with Kimi" device flow that writes the CLI's own credential file. Model ids are translated to the kimi-code/ config.toml alias namespace that session/set_model requires. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Kimi shipped enabled by default while Cursor, Grok and OpenCode all default off with an explicit opt-in from Settings. Every install would probe for the Kimi CLI in the background and surface a spurious error for users without it. Align the default, tests and setup docs. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Kimi CLI executes shell commands through the ACP client's terminal capability, which T3 never advertised or implemented, so no Kimi turn could run a single command. Advertise terminal: true on adapter chat sessions (probes and text generation stay false) and implement the terminal/create, output, wait_for_exit, kill and release handlers with a per-session terminal manager over ChildProcessSpawner. The wait_for_exit response carries exitCode/signal at the top level, as Kimi requires; only terminal/output nests it as exitStatus. Co-authored-by: Cursor <cursoragent@cursor.com>
Pressing Stop while the Kimi agent was parked in terminal/wait_for_exit
(e.g. a foreground dev server it launched) deadlocked the turn forever:
Kimi cannot process session/cancel until its pending wait_for_exit
client request resolves, and the turn could not settle while awaiting
the prompt response.
KimiAcpTerminalManager gains killAll: it kills every live terminal's
process and settles its exit deferred with { exitCode: null, signal:
"SIGTERM" }, but keeps the terminal registered so the agent can still
call terminal/output and terminal/release on it (release stays the only
dispose path besides shutdown). interruptTurn now runs killAll right
before session/cancel, unblocking the agent's wait so it can honor the
cancel and answer the prompt with stopReason cancelled.
Done by Kimi Code (kimi-k2.5) via the T3 Code Kimi provider.
Four live-diagnosed bugs, one root cause: mode/approval state was one-way. - Approved ExitPlanMode never exited plan mode in T3, so the next turn forced Kimi straight back into plan. ExitPlanMode permission requests are now intercepted Claude-style: the plan markdown is captured into T3's proposed-plan flow and the native exit is cancelled, so Kimi stays in plan mode and T3's plan-approval UI owns the mode flip. - Full access kept asking for approvals after the plan phase because auto-approve required the tracked native mode to be yolo while it was stuck on plan. Full access now answers tool-gate permission requests in any native mode, with an explicit carve-out for user decisions (ExitPlanMode, AskUserQuestion), which are never auto-approved. - Full access + Auto mode asked for every command approval by hand for the same reason; fixed by the same predicate change. Kimi's per-mode behavior is unchanged — only who answers its permission requests. - Approval cards kept appearing after the turn was stopped because request_permission had no interrupted-turn gate. Requests for stopped or dead turns are now cancelled immediately without emitting request.opened. Also surfaces the real command text from the request payload in approval cards instead of just the tool title. Model: Kimi K3 (kimi-code) via T3 Code
The 6d15825 plan-mode interception answered every ExitPlanMode permission request with "cancelled" and emitted turn.proposed.completed, assuming the CLI would stay in plan mode and end the turn. Live forensics on thread f1999acf (native provider event log) showed the assumption was wrong: kimi-cli reads a cancelled plan decision as "dialog dismissed" — verbatim on the wire: "Plan approval dismissed. Plan mode remains active." — so it retried ExitPlanMode six times, escalated through AskUserQuestion, then gave up and printed the plan as plain text. The native mode flip never happened either: it was designed to ride a follow-up turn that never occurred (exactly one session/prompt in the log, one sendTurn span in the server trace), and the composer still showed Plan. ExitPlanMode now falls through to the regular approval card with the plan markdown as the card detail. Approving answers the native plan_approve option, so kimi-cli leaves plan mode and implements in the same turn: no retry loop, no mode-flip dependency. Declining selects plan_revise and the CLI re-presents the plan as a fresh card. Plan decisions remain excluded from full-access auto-approval. Known limitation, documented in docs/user/providers-kimi.md: after a native plan exit, a follow-up sent while the composer still shows Plan cannot re-enter native plan mode — the shared ACP runtime's tracked mode still says plan and short-circuits the identical set_mode. The next build turn pushes the runtime-derived mode and re-syncs tracking. The mock agent now mirrors the real CLI: a cancelled ExitPlanMode is retried (bounded) instead of ending the turn, and a selected plan_approve leaves plan mode silently. The plan-flow e2e asserts exactly one ExitPlanMode request per decision, so the cancelled-answer regression fails the suite. Model: Kimi K3 (kimi-code) via T3 Code
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
One finding on the Kimi settings wiring; the rest of the web changes (KimiIcon following the GrokIcon brand-fill pattern, the Button size="xs"/variant="outline" sign-in control, the settings-standard text-[13px] leading-[1.45] helper text, and the authAction slot on ProviderInstanceCard) match the existing shared-component and Tailwind conventions.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One convention issue found in the new Kimi sign-in error model: KimiAuthError carries no cause, so every wrap in KimiOAuth.ts erases the underlying HTTP/schema/filesystem failure and keeps only cause.message in detail. Everything else reviewed (driver/adapter/probe wiring, ACP support helpers, catchTags usage, schema predicates) follows the existing provider conventions.
Posted via Macroscope — Effect Service Conventions
ApprovabilityVerdict: Skipped Macroscope did not run approvability analysis for this PR. Macroscope could not determine whether this PR modifies its approvability configuration, so the PR was not approved automatically. A PR that may change the rules that govern approval is never approved automatically. Not approved because:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa151a064b
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Reviewed the web UI surface of this PR (KimiIcon, provider icon/driver registries, KimiSignInControl, and the ProviderInstanceCard/ProviderSettingsPanel authAction seam). Primitive usage is consistent: the new control composes Button with the existing size="xs" + spinner pattern already used in ProviderInstanceCard, KimiIcon mirrors GrokIcon's brand-fill/dark-variant shape, and Kimi is registered in every web-side provider map (providerIconUtils, providerDriverMeta, session-logic). The earlier instance-enabled resolution finding is resolved — the panel now uses resolveProviderInstanceEnabled.
One accessibility-consistency gap in the new async sign-in states is noted inline.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Reviewed the new Kimi provider code against the Effect service conventions. Two findings on the new KimiAuthError contract and its construction; the driver/adapter/ACP support modules follow the existing sibling provider patterns (namespace subpath imports, environment-acquired services, Effect.catchTags, structural catchIf on ACP error codes).
Posted via Macroscope — Effect Service Conventions
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A steer waiting on the prompt permit that lost its request fiber (client disconnect during Stop) skipped the runPrompt finalizer entirely, leaving promptsInFlight raised and the turn-activity tracker active, which deferred provider probes until the session stopped. The runPrompt finalizer now marks the slot settled on every path and a queued-interrupt compensation releases the slot, settling the merged turn with a cancelled completion when it was the last one. Found by Devin Review on the fork staging PR. Implemented by Claude via Cursor.
| startedAtMs + Math.min(expiresInSeconds * 1000, Duration.toMillis(MAX_SIGN_IN_DURATION)); | ||
| let intervalSeconds = Math.max(1, authorization.interval ?? DEFAULT_POLL_INTERVAL_SECONDS); | ||
|
|
||
| while ((yield* Clock.currentTimeMillis) < deadlineMs) { |
There was a problem hiding this comment.
🟡 Medium kimi/KimiOAuth.ts:328
A backward wall-clock correction can keep the sign-in stream polling past the 15-minute cap, potentially indefinitely, instead of emitting KimiAuthExpiredError. The deadline and loop checks use Clock.currentTimeMillis, so use Clock.monotonicTimeNanos for elapsed-time tracking and deadline checks.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/kimi/KimiOAuth.ts around line 328:
A backward wall-clock correction can keep the sign-in stream polling past the 15-minute cap, potentially indefinitely, instead of emitting `KimiAuthExpiredError`. The deadline and loop checks use `Clock.currentTimeMillis`, so use `Clock.monotonicTimeNanos` for elapsed-time tracking and deadline checks.
|
|
||
| const credentialsDir = path.join(resolveKimiCodeHome(homePath), CREDENTIALS_DIR_NAME); | ||
| const credentialsPath = path.join(credentialsDir, CREDENTIALS_FILE_NAME); | ||
| const temporaryPath = `${credentialsPath}.${process.pid}.${nowEpochMs}.tmp`; |
There was a problem hiding this comment.
🟠 High kimi/KimiOAuth.ts:248
Concurrent sign-ins for the same home can share temporaryPath when they run in the same millisecond, so one rename removes the other attempt's source and causes its write to fail or races the credential contents. Add a per-attempt random suffix to the temporary filename.
| const temporaryPath = `${credentialsPath}.${process.pid}.${nowEpochMs}.tmp`; | |
| const temporaryPath = `${credentialsPath}.${process.pid}.${nowEpochMs}.${Math.random().toString(36).slice(2)}.tmp`; |
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/kimi/KimiOAuth.ts around line 248:
Concurrent sign-ins for the same home can share `temporaryPath` when they run in the same millisecond, so one rename removes the other attempt's source and causes its write to fail or races the credential contents. Add a per-attempt random suffix to the temporary filename.
What Changed
Adds Kimi (Moonshot AI) as a first-class provider, wrapping
kimi acp. Structurally modeled on the Grok ACP stack, extended where the Kimi CLI advertises richer capabilities.~/.kimi-code/bininstall, then PATH; in-app OAuth device sign-in writes the official CLI credential file;KIMI_CODE_HOMEisolation per instanceplan, Supervised todefault, Auto and Auto-accept-edits toauto, Full access toyolo; full access auto-approves tool gates while plan decisions and Kimi's AskUserQuestion always surface as approval cardsplan_approveso the CLI exits plan mode and implements in the same turn; decline answersplan_reviseWhy
The Kimi Code CLI speaks ACP but T3 Code had no adapter for it. This adds bring-your-own-subscription Kimi support consistent with the existing provider set. Everything provider-shaped stays inside Kimi files; orchestration, contracts plumbing, and shared ACP machinery are unchanged except one additive optional flag already supported by the runtime.
Commits
Reviewable commit by commit; each is one concern:
Verification
Notes for reviewers
UI Changes
New surfaces: the Kimi provider card with Sign in with Kimi, the OAuth device confirmation, per-model Thinking options in the model picker, and plan approvals on the standard approval card.
Kimi detected and authenticated alongside the existing providers (note the CLI self-updated from 0.37.2 to 0.38.0 and detection kept working):
K3 mid-task with Thinking Max and Full access, running real commands through the ACP terminal:
A short demo video (plan, approve, same-turn implementation, mid-thread model switch) will follow in a comment.
Checklist
Built with Kimi K3 (Moonshot) driven inside T3 Code itself, with planning and independent review by Claude via Cursor.
Note
Add Kimi provider with OAuth device flow and ACP support
KimiDriver(probe/snapshot/maintenance),KimiAdapter(ACP session/steering/permissions), andKimiTextGeneration(commit messages, PR content, branch names, thread titles with 180s timeout).KimiOAuthwith atomic credential writes, pluskimiAuth.signIn/kimiAuth.signOutWS RPC handlers andKimiAuthErrorcontracts.KimiAcpTerminalManagerwith create/output/wait/kill/release, killAll, shutdown, UTF-8 boundary truncation at 4 MiB, and signal-based exit reporting.KimiIcon,KimiSignInControlin settings, andproviderDriverMetaentry with "Early Access" badge.KimiSettings.enableddefaults tofalse; provider probes defer while turns are active viarunKimiProbeWithActiveTurnDeferral;kimiAuthSignIn/kimiAuthSignOutRPCs requireAuthOrchestrationOperateScope.Macroscope summarized 21ca899.