MoonshotAI / MoonshotAI/kimi-cli

feat(vis): Add raw API request/response viewer with full prompt content

Open
#2,339 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
11.4k
Forks
1.3k
Avg merge
9h 47m
Merged PRs (30d)
2

Description

Problem

The vis module currently provides excellent visualization of wire events and context messages, but it lacks the ability to view the complete raw API request sent to the LLM provider. This is a critical gap for debugging and understanding agent behavior.


What's missing (with specific code references)

1. Full System Prompt Content — only shown as raw text, no structured rendering

The system prompt IS captured in context.jsonl as {"role": "_system_prompt", "content": "..."} (written by src/kimi_cli/soul/context.py:write_system_prompt()), but the vis frontend only renders it in a plain <pre> tag.

File: vis/src/features/context-viewer/context-viewer.tsx — the SystemPromptRow component

The system prompt is built via Jinja2 template in src/kimi_cli/soul/agent.py:_load_system_prompt() with BuiltinSystemPromptArgs which injects:

  • KIMI_NOW — current datetime
  • KIMI_WORK_DIR — working directory path
  • KIMI_WORK_DIR_LS — directory listing
  • KIMI_AGENTS_MD — merged AGENTS.md content (from project root to work_dir)
  • KIMI_SKILLS — formatted skills list
  • KIMI_ADDITIONAL_DIRS_INFO — additional directory listings
  • KIMI_OS / KIMI_SHELL — environment info

The vis frontend does NOT show:

  • Which template variables were injected and their resolved values
  • The tool definitions section (part of the system prompt but rendered as raw text without syntax highlighting)
  • A structured breakdown of system prompt sections (instructions, tool definitions, environment context, AGENTS.md, skills, etc.)
2. Raw API Request Body — not captured at all

The wire protocol event types (defined in src/kimi_cli/wire/types.py) include individual events like TextPart, ThinkPart, ToolCall, ToolCallPart, ToolResult, StatusUpdate, etc. But no event type captures the assembled API request body sent to the LLM.

The LLM call happens in src/kimi_cli/soul/kimisoul.py via kosong (packages/kosong/), which is the unified chat provider abstraction. The kosong.ChatProvider.step() method takes a Conversation object and returns a StepResult, but neither the request nor the response is emitted as a wire event.

What should be visible but is not:

// The actual API request body (Anthropic/OpenAI format) — NOT captured anywhere
{
  "model": "claude-sonnet-4-20250514",  // NOT captured
  "system": "...full system prompt...",  // captured in context.jsonl but not shown as part of request
  "messages": [                          // captured as individual context.jsonl lines but not as assembled array
    {"role": "user", "content": "..."},
    {"role": "assistant", "content": "...", "tool_calls": [...]},
    {"role": "tool", "content": "...", "tool_call_id": "..."},
  ],
  "tools": [...],                        // tool definitions — NOT captured in wire events
  "temperature": 1.0,                    // NOT captured
  "max_tokens": 16384,                   // NOT captured
  "top_p": 0.95                          // NOT captured
}
3. API Response Metadata — partially captured

The StatusUpdate wire event (src/kimi_cli/wire/types.py) captures:

  • token_usage: TokenUsage — input/output/cache tokens (YES)
  • context_usage: float — context window percentage (YES)
  • context_tokens: int / max_context_tokens: int (YES)
  • message_id: str (YES)

But does NOT capture:

  • Model name used for this request
  • Stop reason (stop, tool_use, max_tokens, end_turn)
  • Finish details / refusal reason
  • Response headers (rate limits, retry-after)
  • API latency per request
4. Prompt Assembly View — no way to see how context becomes an API request

The data flow is:

  1. src/kimi_cli/soul/agent.py:_load_system_prompt() builds system prompt from Jinja2 template
  2. src/kimi_cli/soul/context.py:Context manages context.jsonl (messages, system prompt, usage, checkpoints)
  3. src/kimi_cli/soul/kimisoul.py:KimiSoul assembles system prompt + history + dynamic injections, then calls kosong.step()
  4. packages/kosong/ converts to provider-specific API format and makes HTTP call

There is no visualization for step 3 to 4, i.e., how the internal message history becomes the actual API request.


Why this matters

  • Prompt engineering: Users need to see the exact system prompt to understand agent behavior and customize it effectively.
  • Token cost debugging: Without seeing the full request, it is impossible to understand why certain sessions consume more tokens than expected.
  • Reproducibility: Being able to export the raw API request would allow replaying conversations against different models or providers.
  • Learning: New users trying to understand how CLI agents work internally would benefit from seeing the actual API conversation.

Proposed solution

Backend changes (src/kimi_cli/)
A. Add new wire event types for API request/response capture

In src/kimi_cli/wire/types.py, add:

class APIRequest(BaseModel):
    """The full API request sent to the LLM provider."""
    model: str
    system: str | list[dict]  # system prompt as sent
    messages: list[dict]       # assembled messages array
    tools: list[dict] | None = None  # tool definitions
    temperature: float | None = None
    max_tokens: int | None = None
    top_p: float | None = None

class APIResponse(BaseModel):
    """The LLM API response metadata."""
    model: str
    stop_reason: str | None = None
    usage: dict  # token counts from response
    latency_ms: float
    headers: dict[str, str] | None = None  # rate limit headers

Add them to the Event union type.

B. Emit events in KimiSoul step loop

In src/kimi_cli/soul/kimisoul.py, before calling provider.step(), emit APIRequest; after receiving response, emit APIResponse.

C. Backend API — no changes needed

The existing vis/api/sessions.py:get_wire_events() already reads all wire events from wire.jsonl and returns them with {index, timestamp, type, payload}. New event types will be automatically included.

Frontend changes (vis/src/)
D. Add "Raw Request" tab

In vis/src/App.tsx, add a new tab alongside existing "Wire Events", "Context Messages", "State", "Dual", "Agents":

type Tab = "wire" | "context" | "state" | "dual" | "agents" | "raw";
E. Create RawRequestViewer component

New file: vis/src/features/raw-request/raw-request-viewer.tsx

Should render:

  1. Request metadata panel — model name, temperature, max_tokens, top_p in a card layout (follow StateViewer card pattern)
  2. System prompt section — full content with:
    • Markdown rendering (reuse existing Markdown component from @/components/markdown.tsx)
    • Syntax-highlighted tool definitions block
    • Token count estimation
    • Collapsible sections matching the template structure
  3. Messages array — each message rendered similar to existing ContextViewer but showing the EXACT JSON structure as sent to API:
    • Reuse existing component patterns: UserMessage, AssistantMessage, ToolMessage from context-viewer/
    • Add JSON view toggle per message (reuse ExpandedPayload pattern from wire-event-card.tsx)
  4. Tool definitions — show the tools array with each tool name, description, and input schema in a structured table
  5. Token breakdown — per-section token estimates (system prompt vs. messages vs. tools)
F. Styling patterns to follow (existing codebase conventions)

From existing components, these patterns should be reused:

Type badge colors (from wire-event-card.tsx:TYPE_COLORS):

// For event/message type badges
"bg-blue-500/15 text-blue-700 dark:text-blue-300"      // user/system
"bg-green-500/15 text-green-700 dark:text-green-300"    // assistant
"bg-purple-500/15 text-purple-700 dark:text-purple-300" // tool calls
"bg-amber-500/15 text-amber-700 dark:text-amber-300"    // metadata

Collapsible sections (pattern used everywhere):

const [expanded, setExpanded] = useState(false);
// ChevronDown when expanded, ChevronRight when collapsed
// Use border-dashed for metadata sections, solid for content

JSON payload viewer (from wire-event-card.tsx:ExpandedPayload):

<div className="mt-2 ml-6 mb-2 rounded-md border bg-card relative group/payload">
  <pre className="overflow-auto text-xs font-mono text-card-foreground max-h-[500px] p-3 whitespace-pre-wrap">
    {JSON.stringify(data, null, 2)}
  </pre>
</div>

Message layout (from context-viewer/assistant-message.tsx):

<div className="my-2 flex gap-3">
  <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-secondary">
    <Bot size={14} />
  </div>
  <div className="flex-1 min-w-0">...</div>
</div>

System prompt card (enhance existing SystemPromptRow in context-viewer.tsx):

<div className="my-2 rounded-md border border-blue-500/30 bg-blue-500/5 px-3 py-2">
  // FileText icon, blue color scheme, estimated tokens display
</div>

Related files

File Role
src/kimi_cli/wire/types.py Wire event type definitions — add APIRequest/APIResponse here
src/kimi_cli/soul/kimisoul.py Main agent loop — emit new events before/after provider.step()
src/kimi_cli/soul/agent.py System prompt template loading — BuiltinSystemPromptArgs / _load_system_prompt()
src/kimi_cli/soul/context.py Context file management — write_system_prompt(), context.jsonl format
src/kimi_cli/vis/api/sessions.py Vis backend API — already reads wire.jsonl, no changes needed
vis/src/App.tsx Main app with tab navigation — add "Raw Request" tab
vis/src/features/context-viewer/context-viewer.tsx Context viewer — has SystemPromptRow to enhance
vis/src/features/wire-viewer/wire-event-card.tsx Wire event rendering — reuse TYPE_COLORS, ExpandedPayload
vis/src/lib/api.ts Frontend API types — add APIRequestEvent/APIResponseEvent types
packages/kosong/ LLM abstraction layer — where actual API calls happen

Environment

  • kimi-cli version: latest (main branch)
  • Component: vis/ (React visualization app) + src/kimi_cli/wire/ + src/kimi_cli/soul/

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading src/kimi_cli/wire/types.py and the provider.step() flow in src/kimi_cli/soul/kimisoul.py, then trace the existing event API and frontend tabs in vis/src/App.tsx. Review context-viewer.tsx and wire-event-card.tsx for reusable rendering patterns. Done means request and response data are emitted to wire.jsonl and the new Raw Request tab displays the specified metadata, prompts, messages, tools, and token details.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, react, typescript
Domain
backend, cli, devtools, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.