diff --git a/agent/loop.go b/agent/loop.go index b6eda63..9837524 100644 --- a/agent/loop.go +++ b/agent/loop.go @@ -98,6 +98,15 @@ func Run(ctx context.Context, cfg Config, state State) error { var results []ToolResult for _, tc := range toolCalls { + if tc.Name == "" { + results = append(results, ToolResult{ + ToolCallID: tc.ID, + Name: tc.Name, + Content: "error: empty tool name", + IsError: true, + }) + continue + } emit(cfg, OutputMsg{Role: "call", Name: tc.Name, Content: string(tc.Arguments)}) var result string diff --git a/backend/openai.go b/backend/openai.go index 938387c..bf1ae85 100644 --- a/backend/openai.go +++ b/backend/openai.go @@ -32,7 +32,7 @@ func NewOpenAI(baseURL, apiKey string) *OpenAIBackend { type openAIMessage struct { Role string `json:"role"` - Content string `json:"content,omitempty"` + Content *string `json:"content"` ToolCalls []openAIToolCall `json:"tool_calls,omitempty"` ToolCallID string `json:"tool_call_id,omitempty"` } @@ -99,13 +99,12 @@ type openAIStreamResponse struct { func (b *OpenAIBackend) ChatStream(ctx context.Context, model string, messages []Message, tools []Tool) (<-chan StreamEvent, error) { wireMessages := make([]openAIMessage, len(messages)) for i, m := range messages { - wireMessages[i] = openAIMessage{ + wm := openAIMessage{ Role: m.Role, - Content: m.Content, ToolCallID: m.ToolCallID, } for _, tc := range m.ToolCalls { - wireMessages[i].ToolCalls = append(wireMessages[i].ToolCalls, openAIToolCall{ + wm.ToolCalls = append(wm.ToolCalls, openAIToolCall{ ID: tc.ID, Type: "function", Function: openAIFunctionCall{ @@ -114,6 +113,11 @@ func (b *OpenAIBackend) ChatStream(ctx context.Context, model string, messages [ }, }) } + // OpenAI spec: content must be null (not "") when tool_calls is present. + if len(wm.ToolCalls) == 0 { + wm.Content = &m.Content + } + wireMessages[i] = wm } var wireTools []openAITool diff --git a/main.go b/main.go index 53c6289..f94a880 100644 --- a/main.go +++ b/main.go @@ -587,6 +587,9 @@ func (m *model) apply(am agentMsg) { case "error": m.finalizeBuf() m.appendDisplay("Error: " + am.content) + if m.session != nil { + m.session.Rollback() + } case "confirm": m.state = agentConfirming