~kris/9p

llm9p

ref: 35c20f3e6e008970ccdc9ea460682c578bd6d35a llm9p/internal/llm/client.go -rw-r--r-- 24.0 KiB
35c20f3e — pdfinn 6 months ago
fix(client): replace empty text content block with placeholder

When the LLM returns an end_turn with no text content after a tool
call, the session stores Message{Content:"", StructuredContent:""}.
On the next user turn, buildMessageParam() hit the plain-text branch
and called NewTextBlock(""), which the Anthropic API rejects with:
  400: "messages: text content blocks must be non-empty"

Replace empty Content with "..." before building the text block.
This preserves the alternating user/assistant message structure
required by the API without introducing invalid empty blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0afa911a — pdfinn 6 months ago
feat(llmfs): native Anthropic tool_use protocol support

Add structured tool_use protocol to llm9p, enabling Veltro to use
Claude's native JSON tool invocation instead of text-based parsing.

New types (backend.go):
- AskResponse: carries Response, StructuredJSON, and Tokens — replaces
  the old (string, int, error) return from AskWithRequest
- ToolDef: tool definition passed to Anthropic tools API
- ToolResult: tool execution result for submission to the LLM
- Backend.AskWithRequest() now returns (AskResponse, error)

client.go:
- Message.StructuredContent: stores JSON content blocks for correct
  history replay of tool_use and tool_result turns
- AskWithRequest(): when ToolDefs non-nil, passes tools to API and
  returns STOP:/TOOL: formatted response for Limbo parsing
  Format: "STOP:tool_use\nTOOL:<id>:<name>:<args>\n<text>" or
          "STOP:end_turn\n<text>" or plain text (no tools)
- AskWithToolResults(): submits tool results as a new user turn
- Helpers: buildMessageParam(), buildToolParams(), extractToolArgs(),
  jsonEscapeString()

session.go:
- Session.tools field + SetTools/Tools methods
- Session.AddStructuredMessage() for storing structured content blocks
- AskRequest extended with ToolDefs and ToolResults fields
- SessionManager.Ask(): includes tools, stores structured JSON in history
- SessionManager.AskWithToolResults(): new method for tool result turns
- Fix Compact() for new AskResponse return type
- Helpers: extractTextContent(), buildToolResultsJSON()

cli_client.go: update AskWithRequest() to return AskResponse (no tools
support; StructuredJSON always empty)

session_tools.go (new): /n/llm/{id}/tools write-only file
- Write JSON array of ToolDef to enable native tool_use protocol
- Empty write clears tools (returns session to text-only mode)

session_ask.go:
- Detect TOOL_RESULTS\n prefix in Write() → parseToolResults() → AskWithToolResults()
- TOOL_RESULTS format: "TOOL_RESULTS\n<id>\n<content>\n---\n..."

session_dir.go: add tools file to Children() and Lookup()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
a3dc06aa — pdfinn 7 months ago
feat(llm9p): Implement clone-based session architecture

Replace per-fid session model with Plan 9 clone pattern:
- Reading /n/llm/new creates a session and returns its ID
- Each session gets its own directory: /n/llm/<id>/
- Per-session files: ask, ctl, model, system, thinking, context, metrics
- AskWithRequest method for stateless CSP-style LLM calls
- Session settings (model, temperature, thinking) are per-session
- Remove old ask.go, context.go in favor of session-scoped files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ed43a61b — pdfinn 7 months ago
feat(llm9p): Add per-fid session isolation and prefill support

- Add SessionManager for per-fid conversation isolation
- Each 9P fid now gets its own conversation history
- Add FidAwareFile interface for files needing fid context
- Add /n/llm/prefill file for assistant response prefill
- Prefill helps keep model in character (e.g., "[Veltro]")
- Update ask, new, context files to use session manager
- Fix context contamination between parent and subagent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
fa382007 — pdfinn 7 months ago
feat(llm): Add extended thinking support and usage tracking

- Add thinking token control via /n/llm/thinking file (max/off/number)
- CLI backend sets MAX_THINKING_TOKENS env var for Claude CLI
- Default to max thinking (31999 tokens) for CLI backend
- Add /n/llm/usage file for token usage monitoring
- Add /n/llm/compact file for conversation summarization
- Extend Backend interface with ThinkingTokens, TotalTokens, ContextLimit, Compact
- Add true streaming support for CLI backend with line-by-line output
- Update example file with thinking documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
4e8ff274 — pdfinn 7 months ago
feat: Add system prompt file for persistent persona configuration

- Add system file (read/write) to set system prompt
- System prompt persists across conversation resets
- Add SystemPrompt() and SetSystemPrompt() to Backend interface
- Update both API and CLI clients to support dedicated system prompt
- Update documentation and examples

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
68199d2a — pdfinn 7 months ago
feat: Initial implementation of llm9p - LLM as 9P filesystem

Exposes Claude as a 9P filesystem, enabling interaction through
standard file operations:

- ask: write prompt, read response (shim pattern)
- model: read/write current model name
- temperature: read/write sampling temperature
- tokens: read-only token count from last response
- new: write to reset conversation
- context: read JSON history, write to add system message
- _example: usage documentation
- stream/chunk: blocking read for streaming responses

Includes:
- Full 9P2000 protocol implementation (stdlib only)
- Anthropic SDK integration with conversation state
- Streaming support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>