fix: address PR review comments for opencode provider
- Add missing 'tool' role handling in Anthropic messages endpoint - Add missing 'tool' role handling in Gemini models endpoint - Fix function name mapping in Gemini (use actual function name, not ToolCallID) - Use deterministic tool call IDs in Gemini response (index-based) - Remove APIBase requirement from factory fallback condition - Add validation in factory_provider for empty opencode config All review feedback from PR #1040 has been addressed.
This commit is contained in:
parent
bce265f71e
commit
ddd6956314
3 changed files with 49 additions and 26 deletions
|
|
@ -309,7 +309,7 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
|
|||
if sel.apiBase == "" {
|
||||
sel.apiBase = "https://api.mistral.ai/v1"
|
||||
}
|
||||
case cfg.Providers.Opencode.APIKey != "" && cfg.Providers.Opencode.APIBase != "":
|
||||
case cfg.Providers.Opencode.APIKey != "":
|
||||
sel.apiKey = cfg.Providers.Opencode.APIKey
|
||||
sel.apiBase = cfg.Providers.Opencode.APIBase
|
||||
sel.proxy = cfg.Providers.Opencode.Proxy
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
|
|||
return provider, modelID, nil
|
||||
|
||||
case "opencode", "opencode-zen":
|
||||
if cfg.APIKey == "" && cfg.APIBase == "" {
|
||||
return nil, "", fmt.Errorf("api_key or api_base is required for opencode protocol")
|
||||
}
|
||||
apiBase := cfg.APIBase
|
||||
if apiBase == "" {
|
||||
apiBase = "https://opencode.ai/zen/v1"
|
||||
|
|
|
|||
|
|
@ -434,6 +434,18 @@ func (p *Provider) chatAnthropicMessages(
|
|||
"role": "assistant",
|
||||
"content": content,
|
||||
})
|
||||
case "tool":
|
||||
// Tool result - same format as user message with tool_use_id
|
||||
anthropicMessages = append(anthropicMessages, map[string]any{
|
||||
"role": "user",
|
||||
"content": []map[string]any{
|
||||
{
|
||||
"type": "tool_result",
|
||||
"tool_use_id": msg.ToolCallID,
|
||||
"content": msg.Content,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -527,6 +539,8 @@ func (p *Provider) chatGeminiModels(
|
|||
// Build contents from messages (Gemini format)
|
||||
var contents []map[string]any
|
||||
var systemParts []string
|
||||
// Track tool call ID to function name mapping for proper functionResponse
|
||||
toolCallIDToName := make(map[string]string)
|
||||
|
||||
for _, msg := range messages {
|
||||
switch msg.Role {
|
||||
|
|
@ -539,35 +553,20 @@ func (p *Provider) chatGeminiModels(
|
|||
systemParts = append(systemParts, msg.Content)
|
||||
}
|
||||
case "user":
|
||||
if msg.ToolCallID != "" {
|
||||
// Tool result
|
||||
contents = append(contents, map[string]any{
|
||||
"role": "user",
|
||||
"parts": []map[string]any{
|
||||
{
|
||||
"functionResponse": map[string]any{
|
||||
"name": msg.ToolCallID,
|
||||
"response": map[string]any{
|
||||
"result": msg.Content,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
contents = append(contents, map[string]any{
|
||||
"role": "user",
|
||||
"parts": []map[string]any{
|
||||
{"text": msg.Content},
|
||||
},
|
||||
})
|
||||
}
|
||||
contents = append(contents, map[string]any{
|
||||
"role": "user",
|
||||
"parts": []map[string]any{
|
||||
{"text": msg.Content},
|
||||
},
|
||||
})
|
||||
case "assistant":
|
||||
parts := []map[string]any{}
|
||||
if msg.Content != "" {
|
||||
parts = append(parts, map[string]any{"text": msg.Content})
|
||||
}
|
||||
for _, tc := range msg.ToolCalls {
|
||||
// Track the mapping from tool call ID to function name
|
||||
toolCallIDToName[tc.ID] = tc.Name
|
||||
parts = append(parts, map[string]any{
|
||||
"functionCall": map[string]any{
|
||||
"name": tc.Name,
|
||||
|
|
@ -579,6 +578,27 @@ func (p *Provider) chatGeminiModels(
|
|||
"role": "model",
|
||||
"parts": parts,
|
||||
})
|
||||
case "tool":
|
||||
// Tool result - use mapped function name, not ToolCallID
|
||||
funcName := toolCallIDToName[msg.ToolCallID]
|
||||
if funcName == "" {
|
||||
// Fallback: if no mapping found, this shouldn't happen in normal flow
|
||||
// but we need to handle it gracefully
|
||||
funcName = "unknown_function"
|
||||
}
|
||||
contents = append(contents, map[string]any{
|
||||
"role": "user",
|
||||
"parts": []map[string]any{
|
||||
{
|
||||
"functionResponse": map[string]any{
|
||||
"name": funcName,
|
||||
"response": map[string]any{
|
||||
"result": msg.Content,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -708,13 +728,13 @@ func parseGeminiResponse(body []byte) (*LLMResponse, error) {
|
|||
var content strings.Builder
|
||||
var toolCalls []ToolCall
|
||||
|
||||
for _, part := range candidate.Content.Parts {
|
||||
for i, part := range candidate.Content.Parts {
|
||||
if part.Text != "" {
|
||||
content.WriteString(part.Text)
|
||||
}
|
||||
if part.FunctionCall != nil {
|
||||
toolCalls = append(toolCalls, ToolCall{
|
||||
ID: fmt.Sprintf("call_%s_%d", part.FunctionCall.Name, time.Now().UnixNano()),
|
||||
ID: fmt.Sprintf("call_%s_%d", part.FunctionCall.Name, i),
|
||||
Name: part.FunctionCall.Name,
|
||||
Arguments: part.FunctionCall.Args,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue