// Command mockapi is a fake Messages API for exercising the TUI by hand,
// without an API key and without spending anything.
//
// It lives under testdata so it is invisible to ./... — it is a manual test
// fixture, not part of the build.
//
// go run ./internal/tui/testdata/mockapi &
// ./harness -C /tmp/demo -base-url http://127.0.0.1:8899 -api-key mock
//
// The script is fixed: the first turn streams a sentence and asks to run a
// shell command, which makes the TUI raise an approval prompt; once a
// tool_result comes back it streams a closing sentence and stops. That covers
// streaming, tool rendering, approve, deny, and interrupt.
//
// Flags: -addr to move it, -fail to make the command exit non-zero, -slow to
// stretch the stream out for watching the spinner and ctrl-c.
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"strings"
"time"
)
var (
addr = flag.String("addr", "127.0.0.1:8899", "listen address")
slow = flag.Duration("slow", 0, "pause between streamed words, e.g. 150ms")
cmd = flag.String("cmd", "git status --short && go build ./...", "command the model asks to run")
)
func main() {
flag.Parse()
http.HandleFunc("/v1/messages", handle)
log.Printf("mock Messages API on http://%s", *addr)
log.Fatal(http.ListenAndServe(*addr, nil))
}
func handle(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
// A tool_result in the history means the harness ran (or refused) our
// command and fed the outcome back, so the next turn is the last one.
final := strings.Contains(string(body), "tool_result")
w.Header().Set("Content-Type", "text/event-stream")
w.WriteHeader(http.StatusOK)
send(w, "message_start", map[string]any{
"type": "message_start",
"message": map[string]any{
"id": "msg_mock", "model": "claude-opus-5",
"usage": map[string]any{"input_tokens": 1200, "cache_read_input_tokens": 8000},
},
})
text := "Checking the repository state first."
if final {
text = "That is everything I can tell from here."
}
send(w, "content_block_start", map[string]any{
"type": "content_block_start", "index": 0,
"content_block": map[string]any{"type": "text", "text": ""},
})
for _, word := range strings.SplitAfter(text, " ") {
send(w, "content_block_delta", map[string]any{
"type": "content_block_delta", "index": 0,
"delta": map[string]any{"type": "text_delta", "text": word},
})
time.Sleep(*slow)
}
send(w, "content_block_stop", map[string]any{"type": "content_block_stop", "index": 0})
stop := "end_turn"
if !final {
stop = "tool_use"
send(w, "content_block_start", map[string]any{
"type": "content_block_start", "index": 1,
"content_block": map[string]any{"type": "tool_use", "id": "toolu_1", "name": "bash"},
})
args, _ := json.Marshal(map[string]string{"command": *cmd})
send(w, "content_block_delta", map[string]any{
"type": "content_block_delta", "index": 1,
"delta": map[string]any{"type": "input_json_delta", "partial_json": string(args)},
})
send(w, "content_block_stop", map[string]any{"type": "content_block_stop", "index": 1})
}
send(w, "message_delta", map[string]any{
"type": "message_delta",
"delta": map[string]any{"stop_reason": stop},
"usage": map[string]any{"output_tokens": 95},
})
send(w, "message_stop", map[string]any{"type": "message_stop"})
}
func send(w http.ResponseWriter, event string, data any) {
b, _ := json.Marshal(data)
fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event, b)
w.(http.Flusher).Flush()
}