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:
@@ -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++).
|
||||
Reference in New Issue
Block a user