Skip to content

feat(kimi): support kimi-code (Moonshot's new Node CLI, successor of kimi-cli) #1561

Description

@whyihaveyou

Context

Moonshot has released kimi-code, a ground-up Node.js rewrite of the Python kimi-cli, and it is the designated successor going forward:

  • MoonshotAI/kimi-code repo created 2026-05-22, first release 0.2.0 on 2026-05-26, currently at 0.26.0 (2026-07-16)
  • The Python kimi-cli README now states: "Kimi CLI is evolving into Kimi Code CLI … This project will be gradually wound down." (it is still being released in the meantime — 1.49.0 on 2026-07-16)
  • cc-connect's kimi agent (added in feat(agent): add Kimi CLI support #616) targets the Python kimi-cli dialect. Verified: latest kimi-cli 1.49.0 still supports --print / --work-dir / --quiet / --resume / --output-format, so the current agent and current kimi-cli still match.

When users install kimi-code (which the official installer now does by default), the agent breaks in three ways. Related: #1476, #806, #1253, #1059, PR #1483.

Incompatibilities (all reproduced against kimi-code 0.26.0)

1. Unsupported flags (covers #1476, #806/#1253 for kimi-code)

cc-connect builds: kimi --print --output-format stream-json [--quiet] [--plan] [--resume SID] [--model M] --work-dir DIR --prompt ...

kimi-code 0.26.0 rejects / lacks:

cc-connect flag kimi-code status what works instead
--print error: unknown option '--print' drop it — --prompt alone enters non-interactive print mode
--work-dir DIR ❌ unknown option drop it — setting the process cwd is sufficient (cc-connect already sets cmd.Dir)
--quiet ❌ unknown option drop it
--resume SID ❌ unknown option -r SID
--output-format stream-json, --model, --plan, --prompt ✅ supported

2. stream-json schema change: assistant content is a plain string (the #1059 symptom, on kimi-code)

kimi-code emits:

{"role":"assistant","content":"17 × 23 = 391"}

but handleAssistant only handles content as []any of {type:"text",...} blocks, so every reply arrives empty. Rewriting the line to content:[{"type":"text","text":"..."}] fixes rendering.

3. Permission-mode flags conflict (worth knowing before "fixing" with --yolo)

--yolo / --auto are mutually exclusive with --prompt in kimi-code (error: Cannot combine --prompt with --yolo). Bare --prompt mode auto-approves tool calls (implicit-yolo semantics), which matches the agent's documented "default" mode expectation — so no extra flag is needed.

What already works unchanged: tool_calls / tool role events match the parser, and the session-resume hint ("To resume this session: kimi -r session_...") matches extractResumeSessionID as-is.

Working workaround (in production on my setup)

A small shim between cc-connect and kimi-code, selected via the existing cmd option — no cc-connect changes needed:

[projects.agent.options]
cmd = "/path/to/kimi-cc-shim"
model = "kimi-code/k3"
#!/usr/bin/env python3
"""kimi-cc-shim: translate cc-connect's kimi-cli dialect to kimi-code v0.26+.
Args: drop --print/--quiet/--work-dir; --resume SID -> -r SID; pass the rest.
Stdout: rewrite assistant string content -> [{"type":"text","text":...}].
stderr / exit code / signals pass through."""
import json, os, signal, subprocess, sys

REAL_KIMI = os.path.join(os.path.dirname(os.path.abspath(__file__)), "kimi")

def translate_args(argv):
    out, i = [], 0
    while i < len(argv):
        a = argv[i]
        if a in ("--print", "--quiet"):
            i += 1
        elif a == "--work-dir":
            i += 2
        elif a.startswith("--work-dir="):
            i += 1
        elif a == "--resume":
            out += ["-r", argv[i + 1]]; i += 2
        elif a.startswith("--resume="):
            out += ["-r", a.split("=", 1)[1]]; i += 1
        else:
            out.append(a); i += 1
    return out

def main():
    child = subprocess.Popen([REAL_KIMI] + translate_args(sys.argv[1:]),
                             stdout=subprocess.PIPE, stderr=None, text=True, bufsize=1)
    def forward(sig, _):
        try: child.send_signal(sig)
        except Exception: pass
    signal.signal(signal.SIGTERM, forward); signal.signal(signal.SIGINT, forward)
    for line in child.stdout:
        out_line = line
        try:
            msg = json.loads(line)
            if isinstance(msg, dict) and msg.get("role") == "assistant" \
                    and isinstance(msg.get("content"), str):
                msg["content"] = [{"type": "text", "text": msg["content"]}]
                out_line = json.dumps(msg, ensure_ascii=False) + "\n"
        except (json.JSONDecodeError, ValueError):
            pass
        sys.stdout.write(out_line); sys.stdout.flush()
    child.stdout.close()
    sys.exit(child.wait())

if __name__ == "__main__":
    main()

Verified end-to-end via cc-connect → Feishu: prompts, tool calls, session resume all work.

Suggestion for native support

Probe the CLI flavor once at agent init (e.g. parse kimi --help for --print, or kimi --version output format) and branch both the flag set and the content parser. PR #1483's probe-gating approach for --work-dir generalizes naturally to this. Happy to test any PR against kimi-code 0.26.x on Feishu.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions