feat(registry): claude_stream + mcp_server_stdio para chat con tool-use

- claude_stream_go_core: lanza claude -p --output-format stream-json
  --verbose, decodifica NDJSON y emite eventos sinteticos (text_delta,
  tool_use, tool_result, result, error) por canal Go. 10 tests con fake
  claude bash.
- mcp_server_stdio_go_infra: scaffold de MCP server JSON-RPC 2.0 sobre
  stdio (initialize, tools/list, tools/call, ping). Usuario registra
  tool defs y handler unico. 9 tests.

Usadas por apps/kanban backend para reemplazar el chat HTTP one-shot
con XML actions por WebSocket streaming + tool-use nativa.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 14:54:56 +02:00
parent 98c4982707
commit 4881eeb7de
6 changed files with 1515 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
---
name: claude_stream
kind: function
lang: go
domain: core
version: "1.0.0"
purity: impure
signature: "func StreamClaude(ctx context.Context, opts ClaudeStreamOpts) (<-chan ClaudeEvent, error)"
description: "Lanza `claude -p --output-format stream-json --verbose` como subprocess y retorna un canal de eventos decodificados (text_delta, tool_use, tool_result, result, error). Expande automaticamente los bloques de contenido de los mensajes assistant/user en eventos sinteticos de grano fino."
tags: [claude, streaming, subprocess, agent, ndjson, tool-use]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports:
- bufio
- context
- encoding/json
- fmt
- io
- os
- os/exec
- strings
tested: true
tests:
- "system event"
- "text delta"
- "multiple text blocks"
- "tool use"
- "tool result string content"
- "tool result array content"
- "result event"
- "non-zero exit"
- "non-json line"
- "ctx cancel"
test_file_path: "functions/core/claude_stream_test.go"
file_path: "functions/core/claude_stream.go"
params:
- name: ctx
desc: "Contexto de cancelacion. Al cancelar, el subprocess recibe SIGTERM/SIGKILL y el canal se cierra."
- name: opts
desc: "Opciones de lanzamiento: Bin (path al binario claude, default 'claude'), Args (args extra sin -p ni --output-format ni --verbose), Stdin (prompt como io.Reader), Workdir (CWD del subprocess), Env (env extra mergeado con os.Environ()), Stderr (destino del stderr del subprocess)."
output: "Canal de ClaudeEvent cerrado cuando el subprocess termina. Cada evento tiene Type discriminador y campos especificos segun el tipo. Raw contiene siempre la linea NDJSON original. Retorna error solo si el spawn falla."
---
## Tipos exportados
**ClaudeEventType** — constantes de tipo de evento:
- `system` — evento de inicializacion con session_id y model
- `assistant` — mensaje raw del asistente (tambien genera text_delta y/o tool_use sinteticos)
- `user` — mensaje raw de usuario/tool_result (tambien genera tool_result sintetico)
- `result` — evento final con stop_reason, is_error, result
- `text_delta` — (sintetico) porcion de texto del asistente
- `tool_use` — (sintetico) llamada a herramienta con tool_use_id, tool_name, tool_input
- `tool_result` — (sintetico) resultado de herramienta con tool_result_id, content, is_error
- `error` — (sintetico) linea no-JSON o exit code != 0
**ClaudeStreamOpts** — configura el subprocess:
- `Bin string` — path al binario. Si vacio, usa `exec.LookPath("claude")`.
- `Args []string` — args extra. Se anteponen automaticamente `-p --output-format stream-json --verbose`.
- `Stdin io.Reader` — prompt user. Puede ser `strings.NewReader("prompt")` o nil.
- `Workdir string` — CWD del subprocess.
- `Env map[string]string` — variables extra mergeadas con `os.Environ()`.
- `Stderr io.Writer` — destino del stderr en vivo (ej. `os.Stderr` para debug). Si nil, se descarta.
## Ejemplo
```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
ch, err := core.StreamClaude(ctx, core.ClaudeStreamOpts{
Args: []string{"responde en una frase: que es Go"},
Stderr: os.Stderr,
})
if err != nil {
log.Fatal(err)
}
for ev := range ch {
switch ev.Type {
case core.ClaudeEventTextDelta:
fmt.Print(ev.Text)
case core.ClaudeEventToolUse:
fmt.Printf("\n[tool] %s(%s)\n", ev.ToolName, ev.ToolInput)
case core.ClaudeEventToolResult:
fmt.Printf("[result] %s\n", ev.ToolResultContent)
case core.ClaudeEventResult:
fmt.Printf("\n[done] stop_reason=%s\n", ev.StopReason)
case core.ClaudeEventError:
fmt.Fprintf(os.Stderr, "[error] %s\n", ev.Error)
}
}
```
## Notas
- El caller DEBE consumir el canal hasta que se cierre, o cancelar el ctx. No consumir bloquea la goroutine interna.
- El canal tiene buffer de 64 para absorber ráfagas sin bloquear la lectura de stdout.
- Los eventos `assistant` y `user` raw se emiten ademas de los sinteticos, para casos no contemplados.
- `tool_result.content` puede ser string o array `[{type:text,text:"..."}]` — la funcion concatena los bloques text en ambos casos.
- Los tests usan un fake claude bash; se skipean si bash no esta disponible en el PATH.
- Equivalente Go de `projects/osint_graph/apps/graph_explorer/chat.cpp` (C++).