Files
coder/coderd/x/chatd/chattest/openai_responses_validation_test.go
T
Michael Suchacz 62e9752acd fix: prevent malformed OpenAI Responses continuations (#24725)
> Worked on by Mux on Mike's behalf.

## Summary

- Disable OpenAI Responses `previous_response_id` chain mode when the
prior assistant response has unresolved local tool calls, so the next
request can include paired tool outputs instead of sending an incomplete
continuation.
- Update the fantasy pin to a Responses replay fix that preserves stored
reasoning references, only replays web search references when paired
with reasoning, and validates local function-call output pairing before
send.
- Add fake OpenAI Responses input validation for the two production 400
shapes and integration coverage for full-history reasoning plus web
search replay.
- Add sanitized diagnostics for the OpenAI Responses continuity errors.

## Tests

- `go test ./providers/openai -run
'TestResponsesToPrompt_(ReasoningWithStore|ReasoningWithWebSearchCombined|WebSearchRequiresReasoningReference|ReasoningWithFunctionCallCombined|WebSearchProviderExecutedToolResults)|TestPrepareParams_(SkipsProviderExecutedToolReferences|ValidatesFunctionCallOutputPairing)|TestValidateResponsesInput_WebSearchReferenceRequiresReasoning'
-count=1`
- `go test ./providers/openai -count=1`
- `GOWORK=off go test ./coderd/x/chatd/chattest -run
TestValidateResponsesAPIInput -count=1`
- `GOWORK=off go test ./coderd/x/chatd -run
'TestOpenAIResponses(NoStaleWebSearchReplay|FullReplayPairsReasoningAndWebSearch|ChainModeSkipsWhenLocalCallPending|ChainModeStillFiresForProviderExecutedOnly)$|TestResolveChainMode_'
-count=1`
- `GOWORK=off go test ./coderd/x/chatd/chatprompt -run
'TestInjectMissingToolResults_' -count=1`
- `GOWORK=off go test ./coderd/x/chatd/chaterror -run
TestClassify_OpenAIResponsesAPIDiagnostics -count=1`
- `GOWORK=off go test ./coderd/x/chatd/... -count=1`
- `git diff --check`
- `git commit` pre-commit hook
2026-04-26 21:23:06 +02:00

101 lines
3.4 KiB
Go

package chattest_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/x/chatd/chattest"
)
func TestValidateResponsesAPIInput(t *testing.T) {
t.Parallel()
t.Run("valid reasoning and web search references", func(t *testing.T) {
t.Parallel()
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
map[string]interface{}{"type": "item_reference", "id": "rs_valid"},
map[string]interface{}{"type": "item_reference", "id": "ws_valid"},
})
require.Nil(t, errResp)
})
t.Run("rejects web search without reasoning", func(t *testing.T) {
t.Parallel()
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
map[string]interface{}{"type": "item_reference", "id": "ws_orphan"},
})
require.NotNil(t, errResp)
require.Equal(t, 400, errResp.StatusCode)
require.Contains(t, errResp.Message, "web_search_call")
require.Contains(t, errResp.Message, "reasoning")
})
t.Run("valid function call and output", func(t *testing.T) {
t.Parallel()
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
map[string]interface{}{"type": "function_call", "call_id": "call_valid"},
map[string]interface{}{"type": "function_call_output", "call_id": "call_valid"},
})
require.Nil(t, errResp)
})
t.Run("rejects function call without output", func(t *testing.T) {
t.Parallel()
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
map[string]interface{}{"type": "function_call", "call_id": "call_orphan"},
})
require.NotNil(t, errResp)
require.Contains(t, errResp.Message, "No tool output found for function call call_orphan")
})
t.Run("rejects output before function call", func(t *testing.T) {
t.Parallel()
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
map[string]interface{}{"type": "function_call_output", "call_id": "call_late"},
map[string]interface{}{"type": "function_call", "call_id": "call_late"},
})
require.NotNil(t, errResp)
require.Contains(t, errResp.Message, "Tool output found without preceding function call call_late")
})
t.Run("rejects duplicate function call", func(t *testing.T) {
t.Parallel()
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
map[string]interface{}{"type": "function_call", "call_id": "call_duplicate"},
map[string]interface{}{"type": "function_call", "call_id": "call_duplicate"},
map[string]interface{}{"type": "function_call_output", "call_id": "call_duplicate"},
})
require.NotNil(t, errResp)
require.Contains(t, errResp.Message, "Duplicate function call found for call_id call_duplicate")
})
t.Run("rejects duplicate function call output", func(t *testing.T) {
t.Parallel()
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
map[string]interface{}{"type": "function_call", "call_id": "call_duplicate_output"},
map[string]interface{}{"type": "function_call_output", "call_id": "call_duplicate_output"},
map[string]interface{}{"type": "function_call_output", "call_id": "call_duplicate_output"},
})
require.NotNil(t, errResp)
require.Contains(t, errResp.Message, "Duplicate tool output found for function call call_duplicate_output")
})
t.Run("classifies item reference by prefix without type field", func(t *testing.T) {
t.Parallel()
errResp := chattest.ValidateResponsesAPIInput([]interface{}{
map[string]interface{}{"id": "rs_prefix_only"},
map[string]interface{}{"id": "ws_prefix_only"},
})
require.Nil(t, errResp)
})
}