package main import ( "context" "fmt" ) type ToolExec func(ctx context.Context, name, argsJSON string) string const maxAgentIterations = 50 // Agent is a stateless driver: given a message slice and tools, it advances // the conversation until the assistant replies with no tool calls. type Agent struct { Name string Client *Client SystemPrompt string Tools []Tool ToolExec ToolExec OnToolCall func(agent string, tc ToolCall, result string) } // Run advances the given messages, returning the final assistant text and // the updated message slice (including tool calls + tool results). func (a *Agent) Run(ctx context.Context, messages []Message) (string, []Message, error) { for i := 0; i < maxAgentIterations; i++ { msg, err := a.Client.Chat(ctx, messages, a.Tools) if err != nil { return "", messages, err } messages = append(messages, msg) if len(msg.ToolCalls) == 0 { return msg.Content, messages, nil } for _, tc := range msg.ToolCalls { result := a.ToolExec(ctx, tc.Function.Name, tc.Function.Arguments) if a.OnToolCall != nil { a.OnToolCall(a.Name, tc, result) } messages = append(messages, Message{ Role: "tool", ToolCallID: tc.ID, Name: tc.Function.Name, Content: result, }) } } return "", messages, fmt.Errorf("%s: exceeded %d iterations without final reply", a.Name, maxAgentIterations) } // Do is a one-shot helper: fresh conversation of system+user → final text. func (a *Agent) Do(ctx context.Context, userMsg string) (string, error) { messages := []Message{ {Role: "system", Content: a.SystemPrompt}, {Role: "user", Content: userMsg}, } reply, _, err := a.Run(ctx, messages) return reply, err }