mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
Merge pull request #2967 from miruchigawa/main
fix(codex): preserve streamed output text deltas
This commit is contained in:
@@ -104,8 +104,12 @@ func (p *CodexProvider) Chat(
|
||||
defer stream.Close()
|
||||
|
||||
var resp *responses.Response
|
||||
var streamedText strings.Builder
|
||||
for stream.Next() {
|
||||
evt := stream.Current()
|
||||
if evt.Type == "response.output_text.delta" {
|
||||
streamedText.WriteString(evt.Delta)
|
||||
}
|
||||
if evt.Type == "response.completed" || evt.Type == "response.failed" || evt.Type == "response.incomplete" {
|
||||
evtResp := evt.Response
|
||||
if evtResp.ID != "" {
|
||||
@@ -153,7 +157,11 @@ func (p *CodexProvider) Chat(
|
||||
return nil, fmt.Errorf("codex API call: stream ended without completed response")
|
||||
}
|
||||
|
||||
return orc.ParseResponseFromStruct(resp), nil
|
||||
parsed := orc.ParseResponseFromStruct(resp)
|
||||
if parsed.Content == "" && streamedText.Len() > 0 {
|
||||
parsed.Content = streamedText.String()
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func (p *CodexProvider) GetDefaultModel() string {
|
||||
|
||||
@@ -374,6 +374,51 @@ func TestCodexProvider_ChatRoundTrip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexProvider_ChatRoundTrip_OutputTextDeltaFallback(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/responses" {
|
||||
http.Error(w, "not found: "+r.URL.Path, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var reqBody map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
|
||||
http.Error(w, "invalid json", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if reqBody["stream"] != true {
|
||||
http.Error(w, "stream must be true", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resp := map[string]any{
|
||||
"id": "resp_test",
|
||||
"object": "response",
|
||||
"status": "completed",
|
||||
"output": nil,
|
||||
}
|
||||
writeOutputTextDeltaSSE(w, "OK", resp)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
provider := NewCodexProvider("test-token", "acc-123")
|
||||
provider.client = createOpenAITestClient(server.URL, "test-token", "acc-123")
|
||||
|
||||
resp, err := provider.Chat(
|
||||
t.Context(),
|
||||
[]Message{{Role: "user", Content: "Hello"}},
|
||||
nil,
|
||||
"gpt-4o",
|
||||
map[string]any{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Chat() error: %v", err)
|
||||
}
|
||||
if resp.Content != "OK" {
|
||||
t.Errorf("Content = %q, want %q", resp.Content, "OK")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexProvider_ChatRoundTrip_WebSearchDisabled(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/responses" {
|
||||
@@ -647,3 +692,24 @@ func writeCompletedSSE(w http.ResponseWriter, response map[string]any) {
|
||||
fmt.Fprintf(w, "data: %s\n\n", string(b))
|
||||
fmt.Fprintf(w, "data: [DONE]\n\n")
|
||||
}
|
||||
|
||||
func writeOutputTextDeltaSSE(w http.ResponseWriter, delta string, response map[string]any) {
|
||||
deltaEvent := map[string]any{
|
||||
"type": "response.output_text.delta",
|
||||
"sequence_number": 1,
|
||||
"delta": delta,
|
||||
}
|
||||
completedEvent := map[string]any{
|
||||
"type": "response.completed",
|
||||
"sequence_number": 2,
|
||||
"response": response,
|
||||
}
|
||||
deltaBytes, _ := json.Marshal(deltaEvent)
|
||||
completedBytes, _ := json.Marshal(completedEvent)
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
fmt.Fprintf(w, "event: response.output_text.delta\n")
|
||||
fmt.Fprintf(w, "data: %s\n\n", string(deltaBytes))
|
||||
fmt.Fprintf(w, "event: response.completed\n")
|
||||
fmt.Fprintf(w, "data: %s\n\n", string(completedBytes))
|
||||
fmt.Fprintf(w, "data: [DONE]\n\n")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user