fix(apicompat): preserve incomplete stream finalization

This commit is contained in:
王鹏
2026-07-15 14:31:26 +08:00
parent 2d218fbe61
commit d6d9f6ea4f
2 changed files with 33 additions and 9 deletions
@@ -211,8 +211,8 @@ func FinalizeAnthropicResponsesStream(state *AnthropicEventToResponsesState) []R
// Close any open item
events = append(events, closeCurrentResponsesItem(state)...)
// Emit response.completed
events = append(events, makeResponsesCompletedEvent(state, "completed", nil))
status, incompleteDetails := anthropicResponsesStreamTerminalState(state.StopReason)
events = append(events, makeResponsesCompletedEvent(state, status, incompleteDetails))
state.CompletedSent = true
return events
}
@@ -434,13 +434,7 @@ func anthToResHandleMessageStop(state *AnthropicEventToResponsesState) []Respons
var events []ResponsesStreamEvent
events = append(events, closeCurrentResponsesItem(state)...)
status := "completed"
var incompleteDetails *ResponsesIncompleteDetails
if state.StopReason == "max_tokens" {
status = "incomplete"
incompleteDetails = &ResponsesIncompleteDetails{Reason: "max_output_tokens"}
}
status, incompleteDetails := anthropicResponsesStreamTerminalState(state.StopReason)
events = append(events, makeResponsesCompletedEvent(state, status, incompleteDetails))
state.CompletedSent = true
return events
@@ -448,6 +442,13 @@ func anthToResHandleMessageStop(state *AnthropicEventToResponsesState) []Respons
// --- helper functions ---
func anthropicResponsesStreamTerminalState(stopReason string) (string, *ResponsesIncompleteDetails) {
if stopReason == "max_tokens" {
return "incomplete", &ResponsesIncompleteDetails{Reason: "max_output_tokens"}
}
return "completed", nil
}
func closeCurrentResponsesItem(state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
if state.CurrentItemType == "" {
return nil
@@ -44,6 +44,29 @@ func TestAnthropicStreamingMaxTokens_MapsToIncomplete(t *testing.T) {
assert.Equal(t, "max_output_tokens", completed.Response.IncompleteDetails.Reason)
}
func TestAnthropicStreamingMaxTokens_FinalizeMapsToIncompleteWithoutMessageStop(t *testing.T) {
state := NewAnthropicEventToResponsesState()
AnthropicEventToResponsesEvents(&AnthropicStreamEvent{
Type: "message_start",
Message: &AnthropicResponse{ID: "msg_test", Model: "claude-opus-4-6", Role: "assistant"},
}, state)
AnthropicEventToResponsesEvents(&AnthropicStreamEvent{
Type: "message_delta",
Delta: &AnthropicDelta{StopReason: "max_tokens"},
Usage: &AnthropicUsage{OutputTokens: 4096},
}, state)
events := FinalizeAnthropicResponsesStream(state)
require.Len(t, events, 1)
assert.Equal(t, "response.incomplete", events[0].Type)
require.NotNil(t, events[0].Response)
assert.Equal(t, "incomplete", events[0].Response.Status)
require.NotNil(t, events[0].Response.IncompleteDetails)
assert.Equal(t, "max_output_tokens", events[0].Response.IncompleteDetails.Reason)
assert.Empty(t, FinalizeAnthropicResponsesStream(state), "repeated finalization must be idempotent")
}
func TestAnthropicStreamingEndTurn_MapsToCompleted(t *testing.T) {
state := NewAnthropicEventToResponsesState()