Handle streamed function/tool calls in Codex provider
Parse and assemble streamed function_call events in pkg/providers/oauth/codex_provider.go. Added handling for response.output_item.added, response.function_call_arguments.delta and .done events to accumulate partial tool call info (name, call ID, argument deltas) into an ordered map, then convert them into protocoltypes.ToolCall entries when no tool calls were present in the final parsed response. Introduced a streamedToolCall struct and rememberToolCall helper, added imports (encoding/json, protocoltypes), and fallbacks for argument JSON parsing and finish reason adjustment.
This commit is contained in:
parent
d9e643f472
commit
8af6459b52
1 changed files with 77 additions and 0 deletions
|
|
@ -2,6 +2,7 @@ package oauthprovider
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
|
@ -13,6 +14,7 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/auth"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
orc "github.com/sipeed/picoclaw/pkg/providers/openai_responses_common"
|
||||
"github.com/sipeed/picoclaw/pkg/providers/protocoltypes"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -119,11 +121,53 @@ func (p *CodexProvider) Chat(
|
|||
|
||||
var resp *responses.Response
|
||||
var streamedText strings.Builder
|
||||
streamedToolCalls := map[string]*streamedToolCall{}
|
||||
streamedToolCallOrder := make([]string, 0, 4)
|
||||
rememberToolCall := func(key string) *streamedToolCall {
|
||||
if key == "" {
|
||||
key = fmt.Sprintf("streamed_tool_call_%d", len(streamedToolCallOrder))
|
||||
}
|
||||
if tc, ok := streamedToolCalls[key]; ok {
|
||||
return tc
|
||||
}
|
||||
tc := &streamedToolCall{key: key}
|
||||
streamedToolCalls[key] = tc
|
||||
streamedToolCallOrder = append(streamedToolCallOrder, key)
|
||||
return tc
|
||||
}
|
||||
for stream.Next() {
|
||||
evt := stream.Current()
|
||||
switch evt.Type {
|
||||
case "response.output_text.delta":
|
||||
streamedText.WriteString(evt.Delta)
|
||||
case "response.output_item.added":
|
||||
item := evt.AsResponseOutputItemAdded().Item
|
||||
if item.Type == "function_call" {
|
||||
fc := item.AsFunctionCall()
|
||||
st := rememberToolCall(fc.ID)
|
||||
if fc.CallID != "" {
|
||||
st.callID = fc.CallID
|
||||
}
|
||||
if fc.Name != "" {
|
||||
st.name = fc.Name
|
||||
}
|
||||
if fc.Arguments != "" {
|
||||
st.arguments = fc.Arguments
|
||||
}
|
||||
}
|
||||
case "response.function_call_arguments.delta":
|
||||
delta := evt.AsResponseFunctionCallArgumentsDelta()
|
||||
st := rememberToolCall(delta.ItemID)
|
||||
st.arguments += delta.Delta
|
||||
case "response.function_call_arguments.done":
|
||||
done := evt.AsResponseFunctionCallArgumentsDone()
|
||||
st := rememberToolCall(done.ItemID)
|
||||
if done.Name != "" {
|
||||
st.name = done.Name
|
||||
}
|
||||
if done.Arguments != "" {
|
||||
st.arguments = done.Arguments
|
||||
}
|
||||
case "response.completed", "response.failed", "response.incomplete":
|
||||
evtResp := evt.Response
|
||||
if evtResp.ID != "" {
|
||||
|
|
@ -172,6 +216,32 @@ func (p *CodexProvider) Chat(
|
|||
}
|
||||
|
||||
parsed := orc.ParseResponseFromStruct(resp)
|
||||
if len(parsed.ToolCalls) == 0 && len(streamedToolCalls) > 0 {
|
||||
for _, key := range streamedToolCallOrder {
|
||||
if tc := streamedToolCalls[key]; tc != nil && tc.name != "" {
|
||||
arguments := strings.TrimSpace(tc.arguments)
|
||||
if arguments == "" {
|
||||
arguments = "{}"
|
||||
}
|
||||
var args map[string]any
|
||||
if err := json.Unmarshal([]byte(arguments), &args); err != nil {
|
||||
args = map[string]any{"raw": arguments}
|
||||
}
|
||||
callID := tc.callID
|
||||
if callID == "" {
|
||||
callID = tc.key
|
||||
}
|
||||
parsed.ToolCalls = append(parsed.ToolCalls, protocoltypes.ToolCall{
|
||||
ID: callID,
|
||||
Name: tc.name,
|
||||
Arguments: args,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(parsed.ToolCalls) > 0 && parsed.FinishReason == "" {
|
||||
parsed.FinishReason = "tool_calls"
|
||||
}
|
||||
}
|
||||
if parsed.Content == "" && streamedText.Len() > 0 {
|
||||
parsed.Content = streamedText.String()
|
||||
if parsed.FinishReason == "" {
|
||||
|
|
@ -265,6 +335,13 @@ func buildCodexParams(
|
|||
return params
|
||||
}
|
||||
|
||||
type streamedToolCall struct {
|
||||
key string
|
||||
callID string
|
||||
name string
|
||||
arguments string
|
||||
}
|
||||
|
||||
func CreateCodexTokenSource() func() (string, string, error) {
|
||||
return func() (string, string, error) {
|
||||
cred, err := auth.GetCredential("openai")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue