MoonshotAI / MoonshotAI/kimi-cli
feat(vis): Add raw API request/response viewer with full prompt content
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 datetimeKIMI_WORK_DIR— working directory pathKIMI_WORK_DIR_LS— directory listingKIMI_AGENTS_MD— merged AGENTS.md content (from project root to work_dir)KIMI_SKILLS— formatted skills listKIMI_ADDITIONAL_DIRS_INFO— additional directory listingsKIMI_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:
src/kimi_cli/soul/agent.py:_load_system_prompt()builds system prompt from Jinja2 templatesrc/kimi_cli/soul/context.py:Contextmanagescontext.jsonl(messages, system prompt, usage, checkpoints)src/kimi_cli/soul/kimisoul.py:KimiSoulassembles system prompt + history + dynamic injections, then callskosong.step()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:
- Request metadata panel — model name, temperature, max_tokens, top_p in a card layout (follow
StateViewercard pattern) - System prompt section — full content with:
- Markdown rendering (reuse existing
Markdowncomponent from@/components/markdown.tsx) - Syntax-highlighted tool definitions block
- Token count estimation
- Collapsible sections matching the template structure
- Markdown rendering (reuse existing
- Messages array — each message rendered similar to existing
ContextViewerbut showing the EXACT JSON structure as sent to API:- Reuse existing component patterns:
UserMessage,AssistantMessage,ToolMessagefromcontext-viewer/ - Add JSON view toggle per message (reuse
ExpandedPayloadpattern fromwire-event-card.tsx)
- Reuse existing component patterns:
- Tool definitions — show the
toolsarray with each tool name, description, and input schema in a structured table - 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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