feat(tools): stream shell_run output live into the TUI
This commit is contained in:
@@ -87,6 +87,86 @@ func TestAgentRunsToolLoop(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type streamToolProvider struct {
|
||||
calls int
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (p *streamToolProvider) ChatStream(ctx context.Context, req llm.ChatRequest, emit func(llm.StreamEvent) error) error {
|
||||
p.calls++
|
||||
switch p.calls {
|
||||
case 1:
|
||||
return emit(llm.StreamEvent{ToolCalls: []llm.ToolCallDelta{
|
||||
{Index: 0, ID: "call_1", Type: "function", Name: "test_stream", Arguments: `{}`},
|
||||
}})
|
||||
case 2:
|
||||
last := req.Messages[len(req.Messages)-1]
|
||||
if last.Role != llm.RoleTool || last.Content != "full result" {
|
||||
p.t.Fatalf("last message = %#v", last)
|
||||
}
|
||||
return emit(llm.StreamEvent{Content: "done"})
|
||||
default:
|
||||
p.t.Fatalf("unexpected call count %d", p.calls)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type streamEchoTool struct{}
|
||||
|
||||
func (streamEchoTool) Definition() llm.Tool {
|
||||
return llm.Tool{
|
||||
Type: "function",
|
||||
Function: llm.ToolFunction{
|
||||
Name: "test_stream",
|
||||
Description: "streaming test",
|
||||
Parameters: json.RawMessage(`{"type":"object"}`),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (streamEchoTool) Execute(ctx context.Context, raw json.RawMessage) (string, error) {
|
||||
return "full result", nil
|
||||
}
|
||||
|
||||
func (streamEchoTool) ExecuteStream(ctx context.Context, raw json.RawMessage, emit func(string) error) (string, error) {
|
||||
if err := emit("one\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := emit("two\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "full result", nil
|
||||
}
|
||||
|
||||
func TestAgentStreamsToolOutputToLogs(t *testing.T) {
|
||||
provider := &streamToolProvider{t: t}
|
||||
a := New(Options{
|
||||
Provider: provider,
|
||||
Model: "test",
|
||||
SystemPrompt: "system",
|
||||
ToolRegistry: tools.NewRegistry(streamEchoTool{}),
|
||||
})
|
||||
|
||||
var out strings.Builder
|
||||
var logs strings.Builder
|
||||
if err := a.RunTurn(context.Background(), "stream", &out, &logs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out.String() != "done" {
|
||||
t.Fatalf("out = %q", out.String())
|
||||
}
|
||||
logText := logs.String()
|
||||
if strings.Count(logText, "[output]") != 2 {
|
||||
t.Fatalf("logs = %q", logText)
|
||||
}
|
||||
if !strings.Contains(logText, "one\n") || !strings.Contains(logText, "two\n") {
|
||||
t.Fatalf("logs missing streamed chunks: %q", logText)
|
||||
}
|
||||
if strings.Contains(logText, "[output]\nfull result") {
|
||||
t.Fatalf("logs contain duplicate full output: %q", logText)
|
||||
}
|
||||
}
|
||||
|
||||
type contentProvider struct {
|
||||
called bool
|
||||
text string
|
||||
|
||||
Reference in New Issue
Block a user