diff --git a/providers/openai/openai_test.go b/providers/openai/openai_test.go index d69be21d7..8d7100e27 100644 --- a/providers/openai/openai_test.go +++ b/providers/openai/openai_test.go @@ -3819,7 +3819,7 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) @@ -3829,6 +3829,35 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) { require.Contains(t, warnings[0].Message, "dropping empty assistant message") }) + t.Run("should drop assistant messages with only reasoning that cannot be sent", func(t *testing.T) { + t.Parallel() + + prompt := fantasy.Prompt{ + { + Role: fantasy.MessageRoleUser, + Content: []fantasy.MessagePart{ + fantasy.TextPart{Text: "Hello"}, + }, + }, + { + Role: fantasy.MessageRoleAssistant, + Content: []fantasy.MessagePart{ + fantasy.ReasoningPart{ProviderOptions: fantasy.ProviderOptions{ + Name: &ResponsesReasoningMetadata{ItemID: "rs_1"}, + }}, + }, + }, + } + + input, warnings, err := toResponsesPrompt(prompt, "system") + + require.NoError(t, err) + + require.Len(t, input, 1, "should only have user message") + require.Len(t, warnings, 1) + require.Contains(t, warnings[0].Message, "dropping empty assistant message") + }) + t.Run("should keep assistant messages with text content", func(t *testing.T) { t.Parallel() @@ -3847,7 +3876,7 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) @@ -3886,7 +3915,7 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) @@ -3909,7 +3938,7 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) @@ -3933,7 +3962,7 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) @@ -3966,7 +3995,7 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) @@ -3999,7 +4028,7 @@ func TestResponsesToPrompt_DropsEmptyMessages(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) @@ -4696,42 +4725,20 @@ func TestResponsesToPrompt_WebSearchProviderExecutedToolResults(t *testing.T) { }, } - t.Run("store false skips search without metadata", func(t *testing.T) { - t.Parallel() - - input, warnings, err := toResponsesPrompt(prompt, "system instructions", false) + input, warnings, err := toResponsesPrompt(prompt, "system instructions") - require.NoError(t, err) - - require.Equal(t, []fantasy.CallWarning{{ - Type: fantasy.CallWarningTypeOther, - Message: `dropping web search "ws_01": it has no web search metadata`, - }}, warnings) - require.Len(t, input, 2, - "expected user + assistant text when store=false") - require.Nil(t, input[0].OfItemReference) - require.Nil(t, input[1].OfItemReference) - }) - - t.Run("store true skips search without metadata", func(t *testing.T) { - t.Parallel() - - input, warnings, err := toResponsesPrompt(prompt, "system instructions", true) - - require.NoError(t, err) + require.NoError(t, err) - require.Equal(t, []fantasy.CallWarning{{ - Type: fantasy.CallWarningTypeOther, - Message: `dropping web search "ws_01": it has no web search metadata`, - }}, warnings) - require.Len(t, input, 2, - "expected user + assistant text when store=true") - require.Nil(t, input[0].OfItemReference) - require.Nil(t, input[1].OfItemReference) - }) + require.Equal(t, []fantasy.CallWarning{{ + Type: fantasy.CallWarningTypeOther, + Message: `dropping web search "ws_01": it has no web search metadata`, + }}, warnings) + require.Len(t, input, 2, "expected user + assistant text") + require.Nil(t, input[0].OfItemReference) + require.Nil(t, input[1].OfItemReference) } -func TestResponsesToPrompt_ReasoningWithStore(t *testing.T) { +func TestResponsesToPrompt_SkipsReasoningThatCannotGoInline(t *testing.T) { t.Parallel() encryptedContent := "gAAAAABpvAwtDPh5dSXW86hwbwoTo4DJHANQ" @@ -4770,31 +4777,7 @@ func TestResponsesToPrompt_ReasoningWithStore(t *testing.T) { }, } - t.Run("store true emits item_reference for reasoning", func(t *testing.T) { - t.Parallel() - - input, warnings, err := toResponsesPrompt(prompt, "system", true) - - require.NoError(t, err) - require.Empty(t, warnings) - - // Unfinalized metadata (stream placeholders or rows persisted - // before Finalized existed) falls back to an item_reference with - // store=true. Order: user, item_reference(rs_*), assistant text, - // user. - require.Len(t, input, 4) - - for _, item := range input { - require.Nil(t, item.OfReasoning, - "unfinalized reasoning must not be replayed inline") - } - - require.NotNil(t, input[1].OfItemReference, - "expected reasoning replayed via item_reference") - require.Equal(t, reasoningItemID, input[1].OfItemReference.ID) - }) - - t.Run("store true skips reasoning when item id missing", func(t *testing.T) { + t.Run("skips reasoning when item id missing", func(t *testing.T) { t.Parallel() noIDPrompt := fantasy.Prompt{ @@ -4810,7 +4793,11 @@ func TestResponsesToPrompt_ReasoningWithStore(t *testing.T) { fantasy.ReasoningPart{ Text: "thinking", ProviderOptions: fantasy.ProviderOptions{ - Name: &ResponsesReasoningMetadata{Summary: []string{}}, + Name: &ResponsesReasoningMetadata{ + EncryptedContent: &encryptedContent, + Summary: []string{}, + Finalized: true, + }, }, }, fantasy.TextPart{Text: "4"}, @@ -4824,13 +4811,12 @@ func TestResponsesToPrompt_ReasoningWithStore(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(noIDPrompt, "system", true) + input, warnings, err := toResponsesPrompt(noIDPrompt, "system") require.NoError(t, err) require.Empty(t, warnings) - // Without an ItemID we cannot reference the reasoning - // item. Order: user, assistant text, user. + // user, assistant text, user. require.Len(t, input, 3) for _, item := range input { require.Nil(t, item.OfReasoning) @@ -4838,185 +4824,24 @@ func TestResponsesToPrompt_ReasoningWithStore(t *testing.T) { } }) - t.Run("store false skips reasoning", func(t *testing.T) { + t.Run("skips unfinalized reasoning", func(t *testing.T) { t.Parallel() - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) require.Empty(t, warnings) - // Unfinalized metadata is skipped with store=false: user, - // assistant text, follow-up user. + // user, assistant text, follow-up user. require.Len(t, input, 3) for _, item := range input { - require.Nil(t, item.OfReasoning, - "reasoning items must not appear when store=false") - require.Nil(t, item.OfItemReference, - "reasoning item_reference must not appear when store=false") - } - }) -} - -func TestResponsesToPrompt_ReasoningWithWebSearchCombined(t *testing.T) { - t.Parallel() - - reasoningItemID := "rs_002" - webSearchItemID := "ws_002" - - prompt := fantasy.Prompt{ - { - Role: fantasy.MessageRoleUser, - Content: []fantasy.MessagePart{ - fantasy.TextPart{Text: "What is the weather in San Francisco?"}, - }, - }, - { - Role: fantasy.MessageRoleAssistant, - Content: []fantasy.MessagePart{ - fantasy.ReasoningPart{ - Text: "I should look this up.", - ProviderOptions: fantasy.ProviderOptions{ - Name: &ResponsesReasoningMetadata{ - ItemID: reasoningItemID, - Summary: []string{}, - }, - }, - }, - fantasy.ToolCallPart{ - ToolCallID: webSearchItemID, - ToolName: "web_search", - ProviderExecuted: true, - }, - fantasy.ToolResultPart{ - ToolCallID: webSearchItemID, - ProviderExecuted: true, - ProviderOptions: fantasy.ProviderOptions{ - Name: &WebSearchCallMetadata{ - ItemID: webSearchItemID, - Status: "completed", - Action: &WebSearchAction{Type: "search", Query: "weather in San Francisco"}, - }, - }, - }, - fantasy.TextPart{Text: "Sunny."}, - }, - }, - { - Role: fantasy.MessageRoleUser, - Content: []fantasy.MessagePart{ - fantasy.TextPart{Text: "And Tokyo?"}, - }, - }, - } - - t.Run("store true references reasoning before the search", func(t *testing.T) { - t.Parallel() - - input, warnings, err := toResponsesPrompt(prompt, "system", true) - - require.NoError(t, err) - require.Empty(t, warnings) - - // Order: user, item_reference(rs_*), web_search_call, - // assistant text, user. - require.Len(t, input, 5) - - require.NotNil(t, input[1].OfItemReference) - require.Equal(t, reasoningItemID, input[1].OfItemReference.ID) - - require.NotNil(t, input[2].OfWebSearchCall) - }) - - t.Run("store false replays the search without reasoning", func(t *testing.T) { - t.Parallel() - - input, warnings, err := toResponsesPrompt(prompt, "system", false) - - require.NoError(t, err) - require.Empty(t, warnings) - - // Reasoning without encrypted content is skipped under - // store=false. user, web_search_call, assistant text, user. - require.Len(t, input, 4) - require.NotNil(t, input[1].OfWebSearchCall) - for _, item := range input { - require.Nil(t, item.OfItemReference) require.Nil(t, item.OfReasoning) + require.Nil(t, item.OfItemReference) } }) } -func TestResponsesToPrompt_ReasoningWithFunctionCallCombined(t *testing.T) { - t.Parallel() - - reasoningItemID := "rs_003" - functionCallID := "call_003" - - prompt := fantasy.Prompt{ - { - Role: fantasy.MessageRoleUser, - Content: []fantasy.MessagePart{ - fantasy.TextPart{Text: "compute 1+1"}, - }, - }, - { - Role: fantasy.MessageRoleAssistant, - Content: []fantasy.MessagePart{ - fantasy.ReasoningPart{ - Text: "I'll call add.", - ProviderOptions: fantasy.ProviderOptions{ - Name: &ResponsesReasoningMetadata{ - ItemID: reasoningItemID, - Summary: []string{}, - }, - }, - }, - fantasy.ToolCallPart{ - ToolCallID: functionCallID, - ToolName: "add", - Input: `{"a":1,"b":1}`, - }, - }, - }, - { - Role: fantasy.MessageRoleTool, - Content: []fantasy.MessagePart{ - fantasy.ToolResultPart{ - ToolCallID: functionCallID, - Output: fantasy.ToolResultOutputContentText{Text: "2"}, - }, - }, - }, - { - Role: fantasy.MessageRoleUser, - Content: []fantasy.MessagePart{ - fantasy.TextPart{Text: "thanks"}, - }, - }, - } - - input, warnings, err := toResponsesPrompt(prompt, "system", true) - - require.NoError(t, err) - require.Empty(t, warnings) - - // Order: user, item_reference(rs_003), function_call(call_003), - // function_call_output(call_003), user. - require.Len(t, input, 5) - - require.NotNil(t, input[1].OfItemReference) - require.Equal(t, reasoningItemID, input[1].OfItemReference.ID, - "reasoning item_reference must precede function_call") - - require.NotNil(t, input[2].OfFunctionCall) - require.Equal(t, functionCallID, input[2].OfFunctionCall.CallID) - - require.NotNil(t, input[3].OfFunctionCallOutput) - require.Equal(t, functionCallID, input[3].OfFunctionCallOutput.CallID) -} - func TestResponsesStream_RequiresTerminalEventBeforeFinish(t *testing.T) { t.Parallel() @@ -5611,7 +5436,7 @@ func TestComputerUseGenerateRoundTrip_NonImageResult(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) // Should warn about non-image result. diff --git a/providers/openai/responses_language_model.go b/providers/openai/responses_language_model.go index a92affca8..2512ec528 100644 --- a/providers/openai/responses_language_model.go +++ b/providers/openai/responses_language_model.go @@ -222,14 +222,13 @@ func (o responsesLanguageModel) prepareParams(call fantasy.Call) (*responses.Res allowPreviousResponseToolOutputs = true } - storeEnabled := openaiOptions != nil && openaiOptions.Store != nil && *openaiOptions.Store var input responses.ResponseInputParam var inputWarnings []fantasy.CallWarning var err error if allowPreviousResponseToolOutputs { - input, inputWarnings, err = toResponsesPromptWithValidation(call.Prompt, modelConfig.systemMessageMode, storeEnabled, true) + input, inputWarnings, err = toResponsesPromptWithValidation(call.Prompt, modelConfig.systemMessageMode, true) } else { - input, inputWarnings, err = toResponsesPrompt(call.Prompt, modelConfig.systemMessageMode, storeEnabled) + input, inputWarnings, err = toResponsesPrompt(call.Prompt, modelConfig.systemMessageMode) } warnings = append(warnings, inputWarnings...) if err != nil { @@ -462,11 +461,11 @@ func responsesUsage(resp responses.Response) fantasy.Usage { return usage } -func toResponsesPrompt(prompt fantasy.Prompt, systemMessageMode string, store bool) (responses.ResponseInputParam, []fantasy.CallWarning, error) { - return toResponsesPromptWithValidation(prompt, systemMessageMode, store, false) +func toResponsesPrompt(prompt fantasy.Prompt, systemMessageMode string) (responses.ResponseInputParam, []fantasy.CallWarning, error) { + return toResponsesPromptWithValidation(prompt, systemMessageMode, false) } -func toResponsesPromptWithValidation(prompt fantasy.Prompt, systemMessageMode string, store bool, allowUnpairedFunctionCallOutputs bool) (responses.ResponseInputParam, []fantasy.CallWarning, error) { +func toResponsesPromptWithValidation(prompt fantasy.Prompt, systemMessageMode string, allowUnpairedFunctionCallOutputs bool) (responses.ResponseInputParam, []fantasy.CallWarning, error) { var input responses.ResponseInputParam var warnings []fantasy.CallWarning @@ -682,13 +681,11 @@ func toResponsesPromptWithValidation(prompt fantasy.Prompt, systemMessageMode st } if item, ok := responsesReasoningInputItem(meta); ok { input = append(input, item) - } else if store { - input = append(input, responses.ResponseInputItemParamOfItemReference(meta.ItemID)) - } else { - // Unstored reasoning without finalized encrypted - // content cannot be resolved by the API. - continue } + // Reasoning that cannot be sent inline is skipped, not + // referenced: a reference to an item OpenAI does not hold, + // such as one from an unstored response, fails the whole + // request. continue case fantasy.ContentTypeToolResult: toolResultPart, ok := fantasy.AsContentType[fantasy.ToolResultPart](c) @@ -1120,7 +1117,7 @@ func hasVisibleResponsesUserContent(content responses.ResponseInputMessageConten func hasVisibleResponsesAssistantContent(items []responses.ResponseInputItemUnionParam, startIdx int) bool { // Check if we added any assistant content parts from this message for i := startIdx; i < len(items); i++ { - if items[i].OfMessage != nil || items[i].OfFunctionCall != nil || items[i].OfItemReference != nil || items[i].OfReasoning != nil || items[i].OfComputerCall != nil || items[i].OfWebSearchCall != nil { + if items[i].OfMessage != nil || items[i].OfFunctionCall != nil || items[i].OfReasoning != nil || items[i].OfComputerCall != nil || items[i].OfWebSearchCall != nil { return true } } diff --git a/providers/openai/responses_options.go b/providers/openai/responses_options.go index 4abfe76f8..aea3b2545 100644 --- a/providers/openai/responses_options.go +++ b/providers/openai/responses_options.go @@ -150,8 +150,9 @@ type ResponsesReasoningMetadata struct { EncryptedContent *string `json:"encrypted_content"` Summary []string `json:"summary"` // Finalized marks metadata copied from the completed reasoning output - // item. Only finalized encrypted content is replayed inline; streaming - // placeholders and metadata persisted before this field existed are not. + // item. Only finalized metadata with encrypted content is replayed; + // other reasoning, such as streaming placeholders and metadata persisted + // before this field existed, is skipped. Finalized bool `json:"finalized,omitempty"` } diff --git a/providers/openai/responses_params_test.go b/providers/openai/responses_params_test.go index 63955acf9..17865a4f5 100644 --- a/providers/openai/responses_params_test.go +++ b/providers/openai/responses_params_test.go @@ -580,7 +580,7 @@ func TestPrepareParams_ValidatesFunctionCallOutputPairing(t *testing.T) { input, warnings, err := toResponsesPrompt(fantasy.Prompt{ testResponsesProviderToolResultMessage("ws_01"), - }, "system", false) + }, "system") require.NoError(t, err) require.Equal(t, []fantasy.CallWarning{{ Type: fantasy.CallWarningTypeOther, diff --git a/providers/openai/responses_reasoning_replay_test.go b/providers/openai/responses_reasoning_replay_test.go index d0b2aa651..9c27fab6c 100644 --- a/providers/openai/responses_reasoning_replay_test.go +++ b/providers/openai/responses_reasoning_replay_test.go @@ -109,8 +109,6 @@ func TestResponsesGenerate_ReasoningMetadataIsFinalized(t *testing.T) { func TestResponsesReplay_FinalizedReasoningInput(t *testing.T) { t.Parallel() - // The SDK marshals item references without a type field. - reference := map[string]any{"id": "rs_1"} fullItem := func(summary ...any) map[string]any { return map[string]any{ "type": "reasoning", @@ -126,6 +124,9 @@ func TestResponsesReplay_FinalizedReasoningInput(t *testing.T) { encrypted *string summary []string webSearch bool + // unfinalized clears Finalized, as in streaming placeholders and + // metadata saved before Finalized existed. + unfinalized bool // want is the reasoning input item; nil means none is sent. want map[string]any }{ @@ -154,11 +155,16 @@ func TestResponsesReplay_FinalizedReasoningInput(t *testing.T) { { name: "no blob store true", store: true, - want: reference, }, { name: "no blob store false", }, + { + name: "unfinalized store true", + store: true, + encrypted: new("enc-final"), + unfinalized: true, + }, { name: "store true before web search", store: true, @@ -185,7 +191,7 @@ func TestResponsesReplay_FinalizedReasoningInput(t *testing.T) { ItemID: "rs_1", EncryptedContent: tc.encrypted, Summary: tc.summary, - Finalized: true, + Finalized: !tc.unfinalized, }}}, next, fantasy.TextPart{Text: "a"}, diff --git a/providers/openai/responses_text_phase_test.go b/providers/openai/responses_text_phase_test.go index aab070916..f7f9b78b1 100644 --- a/providers/openai/responses_text_phase_test.go +++ b/providers/openai/responses_text_phase_test.go @@ -123,24 +123,18 @@ func TestResponsesToPrompt_ReplaysTextPhase(t *testing.T) { }, } - for _, store := range []bool{true, false} { - t.Run(fmt.Sprintf("store %t", store), func(t *testing.T) { - t.Parallel() - - input, warnings, err := toResponsesPrompt(prompt, "system", store) - require.NoError(t, err) - require.Empty(t, warnings) - require.Len(t, input, 5) - - var phases []any - for _, item := range input[1:] { - raw, err := json.Marshal(item) - require.NoError(t, err) - var fields map[string]any - require.NoError(t, json.Unmarshal(raw, &fields)) - phases = append(phases, fields["phase"]) - } - require.Equal(t, []any{"commentary", "final_answer", nil, nil}, phases) - }) + input, warnings, err := toResponsesPrompt(prompt, "system") + require.NoError(t, err) + require.Empty(t, warnings) + require.Len(t, input, 5) + + var phases []any + for _, item := range input[1:] { + raw, err := json.Marshal(item) + require.NoError(t, err) + var fields map[string]any + require.NoError(t, json.Unmarshal(raw, &fields)) + phases = append(phases, fields["phase"]) } + require.Equal(t, []any{"commentary", "final_answer", nil, nil}, phases) } diff --git a/providers/openai/responses_web_search_test.go b/providers/openai/responses_web_search_test.go index 530370e0f..b7cb73c8f 100644 --- a/providers/openai/responses_web_search_test.go +++ b/providers/openai/responses_web_search_test.go @@ -459,10 +459,10 @@ func withoutItemID(meta *WebSearchCallMetadata) *WebSearchCallMetadata { return &copied } -func requireResponsesInputJSON(t *testing.T, prompt fantasy.Prompt, store bool, want string) []fantasy.CallWarning { +func requireResponsesInputJSON(t *testing.T, prompt fantasy.Prompt, want string) []fantasy.CallWarning { t.Helper() - input, warnings, err := toResponsesPrompt(prompt, "system", store) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) encoded, err := json.Marshal(input) require.NoError(t, err) @@ -470,7 +470,7 @@ func requireResponsesInputJSON(t *testing.T, prompt fantasy.Prompt, store bool, return warnings } -func TestResponsesToPrompt_UnstoredWebSearchCallAction(t *testing.T) { +func TestResponsesToPrompt_WebSearchCallAction(t *testing.T) { t.Parallel() for _, tc := range webSearchCallActionCases { @@ -484,7 +484,7 @@ func TestResponsesToPrompt_UnstoredWebSearchCallAction(t *testing.T) { fantasy.TextPart{Text: "Answer."}, )}, } - warnings := requireResponsesInputJSON(t, prompt, false, `[`+ + warnings := requireResponsesInputJSON(t, prompt, `[`+ `{"role":"user","content":[{"type":"input_text","text":"Search."}]},`+ tc.wantInline+`,`+ `{"role":"assistant","content":"Answer."}]`) @@ -537,7 +537,6 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { tests := []struct { name string - store bool content []fantasy.MessagePart laterContent []fantasy.MessagePart // toolContentBefore and toolContentAfter are tool messages before @@ -548,19 +547,12 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { wantWarning string }{ { - name: "UnstoredInterleavedWithReasoning", + name: "InterleavedWithReasoning", content: interleaved, want: `[` + userJSON + `,` + searchJSON + `,` + reasoningJSON + `,` + openPageJSON + `,` + answerJSON + `]`, }, { - // A stored search is sent inline too, never referenced. - name: "StoredInterleavedWithReasoning", - store: true, - content: interleaved, - want: `[` + userJSON + `,` + searchJSON + `,` + reasoningJSON + `,` + openPageJSON + `,` + answerJSON + `]`, - }, - { - name: "UnstoredRepeatedParts", + name: "RepeatedParts", content: slices.Concat( webSearchCallParts("ws_01", search), webSearchCallParts("ws_01", search), @@ -568,7 +560,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { want: `[` + userJSON + `,` + searchJSON + `]`, }, { - name: "UnstoredWithoutIDs", + name: "WithoutIDs", content: []fantasy.MessagePart{ webSearchCallParts("", withoutItemID(search))[1], webSearchCallParts("", withoutItemID(openPage))[1], @@ -577,7 +569,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { }, { // Without a call ID, the metadata's item ID identifies the search. - name: "UnstoredRepeatedResultWithoutCallID", + name: "RepeatedResultWithoutCallID", content: []fantasy.MessagePart{ webSearchCallParts("", search)[1], webSearchCallParts("", search)[1], @@ -587,18 +579,18 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { { // No warning: the assistant message after the tool message // replays the search. - name: "UnstoredResultInEarlierToolMessage", + name: "ResultInEarlierToolMessage", toolContentBefore: []fantasy.MessagePart{webSearchCallParts("ws_01", search)[1]}, content: []fantasy.MessagePart{webSearchCallParts("ws_01", search)[1], answer}, want: `[` + userJSON + `,` + searchJSON + `,` + answerJSON + `]`, }, { - name: "UnstoredSearchOnly", + name: "SearchOnly", content: webSearchCallParts("ws_01", search), want: `[` + userJSON + `,` + searchJSON + `]`, }, { - name: "UnstoredPre62SearchWithoutStatus", + name: "LegacySearchWithoutStatus", content: append( webSearchCallParts("ws_01", &WebSearchCallMetadata{ ItemID: "ws_01", @@ -611,7 +603,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { { // Before #62 only Type and Query were saved. The API accepts a // find_in_page action without url and pattern. - name: "UnstoredPre62FindInPageWithoutStatus", + name: "LegacyFindInPageWithoutStatus", content: append( webSearchCallParts("ws_01", &WebSearchCallMetadata{ ItemID: "ws_01", @@ -622,7 +614,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { want: `[` + userJSON + `,{"type":"web_search_call","action":{"type":"find_in_page"}},` + answerJSON + `]`, }, { - name: "UnstoredWithoutAction", + name: "WithoutAction", content: append( webSearchCallParts("ws_01", &WebSearchCallMetadata{ItemID: "ws_01", Status: "completed"}), answer, @@ -631,7 +623,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { wantWarning: `dropping web search "ws_01": metadata has no action`, }, { - name: "UnstoredWithoutMetadata", + name: "WithoutMetadata", content: append(webSearchCallParts("ws_01", nil), answer), want: `[` + userJSON + `,` + answerJSON + `]`, wantWarning: `dropping web search "ws_01": it has no web search metadata`, @@ -639,7 +631,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { { // A part that can't replay a search doesn't warn when another // part replays it. - name: "UnstoredUnreplayableThenReplayable", + name: "UnreplayableThenReplayable", content: []fantasy.MessagePart{ webSearchCallParts("ws_01", &WebSearchCallMetadata{ItemID: "ws_01", Status: "completed"})[1], answer, @@ -648,7 +640,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { want: `[` + userJSON + `,` + answerJSON + `,` + searchJSON + `]`, }, { - name: "UnstoredRepeatedWithoutMetadata", + name: "RepeatedWithoutMetadata", content: []fantasy.MessagePart{ webSearchCallParts("ws_01", nil)[1], webSearchCallParts("ws_01", nil)[1], @@ -658,7 +650,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { wantWarning: `dropping web search "ws_01": it has no web search metadata`, }, { - name: "UnstoredRepeatedInToolMessages", + name: "RepeatedInToolMessages", toolContentBefore: []fantasy.MessagePart{webSearchCallParts("ws_01", search)[1]}, content: []fantasy.MessagePart{answer}, toolContentAfter: []fantasy.MessagePart{webSearchCallParts("ws_01", search)[1]}, @@ -667,14 +659,14 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { }, { // The assistant message's reason wins over the tool message's. - name: "UnstoredWithoutMetadataAlsoInToolMessage", + name: "WithoutMetadataAlsoInToolMessage", toolContentBefore: []fantasy.MessagePart{webSearchCallParts("ws_01", search)[1]}, content: []fantasy.MessagePart{webSearchCallParts("ws_01", nil)[1], answer}, want: `[` + userJSON + `,` + answerJSON + `]`, wantWarning: `dropping web search "ws_01": it has no web search metadata`, }, { - name: "UnstoredUnknownActionType", + name: "UnknownActionType", content: append( webSearchCallParts("ws_01", &WebSearchCallMetadata{ ItemID: "ws_01", @@ -702,7 +694,7 @@ func TestResponsesToPrompt_WebSearchCallReplay(t *testing.T) { if tc.toolContentAfter != nil { prompt = append(prompt, fantasy.Message{Role: fantasy.MessageRoleTool, Content: tc.toolContentAfter}) } - warnings := requireResponsesInputJSON(t, prompt, tc.store, tc.want) + warnings := requireResponsesInputJSON(t, prompt, tc.want) var messages []string for _, warning := range warnings { messages = append(messages, warning.Message) diff --git a/providers/openai/tool_result_media_test.go b/providers/openai/tool_result_media_test.go index aa86cbd67..c24b8dafa 100644 --- a/providers/openai/tool_result_media_test.go +++ b/providers/openai/tool_result_media_test.go @@ -328,7 +328,7 @@ func TestToResponsesPrompt_MediaToolResult_ImagePNG(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) require.Empty(t, warnings) // Assistant function call + function_call_output + synthetic user image @@ -373,7 +373,7 @@ func TestToResponsesPrompt_MediaToolResult_UnsupportedMediaType(t *testing.T) { }, } - input, warnings, err := toResponsesPrompt(prompt, "system", false) + input, warnings, err := toResponsesPrompt(prompt, "system") require.NoError(t, err) // Assistant function call + function_call_output, but no synthetic user