* feat: add Codex CLI provider for OpenAI subprocess integration Add CodexCliProvider that wraps `codex exec --json` as a subprocess, analogous to the existing ClaudeCliProvider pattern. This enables using OpenAI's Codex CLI tool as a local LLM backend. - CodexCliProvider: subprocess wrapper parsing JSONL event stream - Credential reader for ~/.codex/auth.json with token expiry detection - Factory integration: provider "codex-cli" and auth_method "codex-cli" - Fix tilde expansion in workspace path for CLI providers - 37 unit tests covering parsing, prompt building, credentials, and mocks * fix: add tool call extraction to Codex CLI provider - Extract shared tool call parsing into tool_call_extract.go (extractToolCallsFromText, stripToolCallsFromText, findMatchingBrace) - Both ClaudeCliProvider and CodexCliProvider now share the same tool call extraction logic for PicoClaw-specific tools - Fix cache token accounting: include cached_input_tokens in total - Add 2 new tests for tool call extraction from JSONL events - Update existing tests for corrected token calculations * fix(docker): update Go version to match go.mod requirement Dockerfile used golang:1.24-alpine but go.mod requires go >= 1.25.7. This caused Docker builds to fail on all branches with: "go: go.mod requires go >= 1.25.7 (running go 1.24.13)" Update to golang:1.25-alpine to match the project requirement. * fix: handle codex CLI stderr noise without losing valid stdout Codex writes diagnostic messages to stderr (e.g. rollout errors) which cause non-zero exit codes even when valid JSONL output exists on stdout. Parse stdout first before checking exit code to avoid false errors. * style: fix gofmt formatting and update web search API in tests - Remove trailing whitespace in web.go and base_test.go - Update config_test.go and web_test.go for WebSearchToolOptions API
73 lines
1.5 KiB
Go
73 lines
1.5 KiB
Go
package providers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
)
|
|
|
|
// extractToolCallsFromText parses tool call JSON from response text.
|
|
// Both ClaudeCliProvider and CodexCliProvider use this to extract
|
|
// tool calls that the model outputs in its response text.
|
|
func extractToolCallsFromText(text string) []ToolCall {
|
|
start := strings.Index(text, `{"tool_calls"`)
|
|
if start == -1 {
|
|
return nil
|
|
}
|
|
|
|
end := findMatchingBrace(text, start)
|
|
if end == start {
|
|
return nil
|
|
}
|
|
|
|
jsonStr := text[start:end]
|
|
|
|
var wrapper struct {
|
|
ToolCalls []struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Function struct {
|
|
Name string `json:"name"`
|
|
Arguments string `json:"arguments"`
|
|
} `json:"function"`
|
|
} `json:"tool_calls"`
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(jsonStr), &wrapper); err != nil {
|
|
return nil
|
|
}
|
|
|
|
var result []ToolCall
|
|
for _, tc := range wrapper.ToolCalls {
|
|
var args map[string]interface{}
|
|
json.Unmarshal([]byte(tc.Function.Arguments), &args)
|
|
|
|
result = append(result, ToolCall{
|
|
ID: tc.ID,
|
|
Type: tc.Type,
|
|
Name: tc.Function.Name,
|
|
Arguments: args,
|
|
Function: &FunctionCall{
|
|
Name: tc.Function.Name,
|
|
Arguments: tc.Function.Arguments,
|
|
},
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// stripToolCallsFromText removes tool call JSON from response text.
|
|
func stripToolCallsFromText(text string) string {
|
|
start := strings.Index(text, `{"tool_calls"`)
|
|
if start == -1 {
|
|
return text
|
|
}
|
|
|
|
end := findMatchingBrace(text, start)
|
|
if end == start {
|
|
return text
|
|
}
|
|
|
|
return strings.TrimSpace(text[:start] + text[end:])
|
|
}
|