Skip to content

feat(cli): expand print mode to cover kimi-cli stream-json capabilities#1199

Closed
RealKai42 wants to merge 8 commits into
mainfrom
kaiyi/json-output
Closed

feat(cli): expand print mode to cover kimi-cli stream-json capabilities#1199
RealKai42 wants to merge 8 commits into
mainfrom
kaiyi/json-output

Conversation

@RealKai42

Copy link
Copy Markdown
Collaborator

Summary

Brings kimi -p (print) mode up to — and slightly beyond — the
non-interactive stream-json capabilities of kimi-cli, so other programs can
drive Kimi Code and consume its output as pure JSON.

What's included

  • Thinking output — model reasoning is emitted as its own
    {"role":"assistant","type":"thinking"} JSONL line, before the answer it produced.
  • --input-format text|stream-json — read prompts from stdin instead of
    --prompt; stream-json reads one JSON user message per line and runs each as
    a turn (multi-turn).
  • --final-message-only and the --quiet shorthand
    (--output-format text --final-message-only).
  • Notifications — background-task and cron events as
    {"type":"notification", ...} lines.
  • Errors as JSON + exit codes — a failed turn is reported as
    {"type":"error","code","message","retryable"} on stdout in stream-json
    (stderr in text), and mapped to exit code 75 for retryable provider errors
    (connection/timeout/rate-limit/5xx) or 1 otherwise. stdout stays entirely JSON.
  • Background-task drain on exit — prompt mode waits for still-running
    background tasks before closing, gated by background.keepAliveOnExit and
    bounded by background.printWaitCeilingS.
  • TUI-level activity on stdouttool_progress (live tool output, moved off
    stderr in stream-json), subagent, warning, skill_activated,
    mcp_server_status, compaction, goal_update, agent_status and
    tool_list_updated, so the stream is both complete and entirely JSON. text
    mode is unchanged (tool progress on stderr, no activity events).

Parity with kimi-cli

stream-json now covers every output type kimi-cli's JsonPrinter emits
(assistant / tool / notification); plan-display is the only one not covered and
is unreachable since plan mode is disabled in print mode. kimi-code additionally
emits the activity layer above, plus error, a session.resume_hint meta line
and a goal summary.

Tests & docs

  • CLI option parsing/validation tests, prompt-runner behaviour tests for every
    new path (input/output formats, final-only, quiet, notifications, errors +
    exit codes, background-task drain, activity events).
  • Updated the kimi command reference docs (en/zh).
  • Full suite green (vitest run), typecheck and lint clean.

- emit model thinking as its own `{"role":"assistant","type":"thinking"}` JSONL line
- add `--input-format text|stream-json` (multi-turn JSON prompts over stdin)
- add `--final-message-only` and the `--quiet` shorthand
- surface background-task/cron events as `{"type":"notification",...}` lines
- report turn failures as `{"type":"error",...}` JSON and map retryable
  provider errors to exit code 75 (keeps stream-json stdout entirely JSON)
- wait for background tasks before exit, gated by background.keepAliveOnExit,
  bounded by background.printWaitCeilingS

Adds CLI option/validation tests, prompt-runner behaviour tests, and updates
the kimi-command reference docs (en/zh).
Surface the activity layer the TUI renders on stdout as JSON so the
stream-json output is both complete and entirely JSON:

- live tool output as `{"type":"tool_progress",...}` (moved off stderr in
  stream-json mode; text mode keeps the stderr chunk)
- subagent lifecycle as `{"type":"subagent","event":...}`
- `{"type":"warning"|"skill_activated"|"mcp_server_status"|"compaction"|
  "goal_update"|"agent_status"|"tool_list_updated",...}`

Activity events flush the pending assistant message first to preserve
ordering, and are emitted only in stream-json mode. Adds prompt-runner
tests and updates the kimi-command reference docs.
@changeset-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ff95ddd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@ff95ddd
npx https://pkg.pr.new/@moonshot-ai/kimi-code@ff95ddd

commit: ff95ddd

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d61fc2d628

ℹ️ 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".

Comment thread apps/kimi-code/src/cli/options.ts
Comment thread apps/kimi-code/src/cli/run-prompt.ts Outdated
…JSON on failure

- `--quiet` with neither `--prompt` nor `--input-format` now fails validation
  ("Quiet mode requires --prompt or --input-format.") instead of entering print
  mode and crashing on an undefined prompt.
- `PromptFinalJsonWriter.finish()` skips emitting when there is no final text,
  matching the text writer — a failed `--final-message-only` turn no longer
  prints a spurious `{"role":"assistant","content":""}` before its error line.

Adds a changeset for the print-mode stream-json work.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3bea6f9fa2

ℹ️ 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".

const lines = io.stdin ?? defaultStdinLines(process.stdin);
if (inputFormat === 'stream-json') {
for await (const command of readUserCommands(lines, stderr)) {
await runPromptTurn(session, command, outputFormat, finalOnly, stdout, stderr);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep stream-json notifications alive between stdin turns

When --input-format stream-json is driven interactively, this loop unsubscribes at the end of each runPromptTurn and then waits for the next stdin line. If a background task or cron fires in that idle window, no listener writes the notification, and the exit drain won't recover already-terminated tasks because it lists only activeOnly tasks; the stream-json consumer silently misses events that this mode promises to surface. Keep a run-level notification listener across the whole stdin loop or replay terminal tasks to cover the gap.

Useful? React with 👍 / 👎.

Comment thread apps/kimi-code/src/cli/run-prompt.ts Outdated
…ge-only mode

The exit drain still waits for running background tasks, but no longer emits
their `background.task.terminated` JSON in `--output-format stream-json
--final-message-only`, preserving the single-final-assistant-message contract.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46dc04d982

ℹ️ 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".

Comment thread apps/kimi-code/src/cli/run-prompt.ts
Comment thread apps/kimi-code/src/cli/run-prompt.ts
… for goal summaries

- `tool_progress` JSON now carries `custom_kind`/`custom_data`, so stream-json
  clients keep structured `kind: "custom"` payloads (e.g. the MCP OAuth
  authorization URL) instead of an opaque `{"kind":"custom"}`.
- The headless `/goal` summary is suppressed on stdout under
  `--final-message-only`, matching the single-final-assistant-message contract
  (the goal status is still conveyed by the exit code).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98f4cfd83d

ℹ️ 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".

Comment on lines +579 to +580
if (outputFormat === 'stream-json') {
outputWriter.writeActivity(toolProgressMessage(event));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Forward subagent tool progress events

When a subagent runs a tool, its tool.progress event is emitted with the child agent id (the TUI handles these in subagent-event-handler.ts), so the main-agent filter above returns before this branch is reached. In --output-format stream-json, live output and custom progress payloads from subagent tools therefore never reach stdout, despite the new activity stream advertising TUI-level live tool output; route child-agent progress here as well or handle it before the main-turn filter.

Useful? React with 👍 / 👎.

Add a `Record<Event['type'], StreamJsonRoute>` coverage table that classifies
every protocol event by how print mode's stream-json output treats it. Because
it is keyed by the full event union, adding a new event type fails `typecheck`
until it is explicitly classified, so print mode can never silently drop a new
event. A companion test keeps the notification/activity/tool_progress rows in
sync with the mappers in run-prompt.ts (now exported for testing).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fef49beb9d

ℹ️ 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".

Comment thread apps/kimi-code/src/cli/run-prompt.ts Outdated
Comment on lines 581 to 584
} else if (event.update.text !== undefined && event.update.text.length > 0) {
stderr.write(
event.update.text.endsWith('\n') ? event.update.text : `${event.update.text}\n`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate text tool progress in final-only mode

When --final-message-only or --quiet is used with text output and a tool emits tool.progress text (for example live shell output), this branch still writes every chunk to stderr because it bypasses the final-only writer. That violates the advertised “only the final assistant message”/quiet contract and leaves scripts with progress noise on stderr; suppress this path when finalOnly is true or route it through the writer.

Useful? React with 👍 / 👎.

- text-mode `tool.progress` output is suppressed when `--final-message-only` /
  `--quiet` is set, matching the minimal-output contract (it bypassed the
  final-only writer and wrote every chunk to stderr).
- add kitchen-sink final-only invariant tests (stream-json and quiet) that feed
  every in-turn event kind and assert stdout is exactly the final message with a
  silent stderr — one guard that catches any direct-write leak at once.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff95ddd82d

ℹ️ 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".

Comment on lines +1467 to +1470
(part as { type?: unknown }).type === 'text' &&
typeof (part as { text?: unknown }).text === 'string'
) {
texts.push((part as { text: string }).text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve media parts in stream-json input

When --input-format stream-json receives a user message whose content array includes image_url or video_url parts, this filter keeps only text parts, so mixed prompts silently lose their media and image-only prompts become empty and are skipped. The SDK prompt input already supports media parts, so programmatic multimodal callers using JSONL get a successful run with the wrong prompt instead of the intended content.

Useful? React with 👍 / 👎.

@RealKai42 RealKai42 closed this Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant