package loop import ( "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/agent" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" ) // appendToolCallHistory renders the assistant tool-call turn followed by every // tool result, in call order. When every call carries a provider id (native // Anthropic tool_use / OpenAI tool_calls), the turn is emitted as native // tool_use + tool_result blocks so the provider can correlate each result to its // call; a batch without ids (the legacy JSON-envelope convention) keeps the // plain RoleTool text feed-back shape. func appendToolCallHistory(messages []model.Message, calls []agent.ToolCall, outcomes []ToolOutcome) []model.Message { native := nativeModelToolCalls(calls) if len(native) == 0 { for i := range calls { messages = append(messages, toolResultMessage(calls[i], outcomes[i])) } return messages } messages = append(messages, model.AssistantToolCallMessage(native)) for i := range calls { isError := outcomes[i].Err != nil || outcomes[i].Skipped messages = append(messages, model.ToolResultMessage(calls[i].ID, toolResultText(calls[i], outcomes[i]), isError)) } return messages } // nativeModelToolCalls converts a batch into model.ToolCall only when every call // carries a provider id; a batch without ids (the legacy JSON-envelope // convention) returns nil so the caller keeps the plain-text feed-back shape. func nativeModelToolCalls(calls []agent.ToolCall) []model.ToolCall { if len(calls) == 0 { return nil } out := make([]model.ToolCall, 0, len(calls)) for _, c := range calls { if c.ID == "" { return nil } args := c.Args if args == nil { args = map[string]any{} } out = append(out, model.ToolCall{ID: c.ID, Name: c.Name, Args: args}) } return out }