From 84dc1a34821f9eecc6fecb0b9e0c47534f709cf5 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 12 Mar 2026 16:00:51 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20hide=20web=5Fsearch=20tool=20=E2=80=94?= =?UTF-8?q?=20preserve=20ProviderExecuted=20on=20DB-loaded=20tool=20result?= =?UTF-8?q?s=20(#23014)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- coderd/database/db2sdk/db2sdk.go | 8 +++- coderd/database/db2sdk/db2sdk_test.go | 55 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index e3c8781140..4b6139ca95 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -1336,19 +1336,23 @@ func contentBlockToPart(block fantasy.Content) codersdk.ChatMessagePart { Data: value.Data, } case fantasy.ToolResultContent: - return chatprompt.ToolResultToPart( + part := chatprompt.ToolResultToPart( value.ToolCallID, value.ToolName, toolResultOutputToRawJSON(value.Result), toolResultOutputIsError(value.Result), ) + part.ProviderExecuted = value.ProviderExecuted + return part case *fantasy.ToolResultContent: - return chatprompt.ToolResultToPart( + part := chatprompt.ToolResultToPart( value.ToolCallID, value.ToolName, toolResultOutputToRawJSON(value.Result), toolResultOutputIsError(value.Result), ) + part.ProviderExecuted = value.ProviderExecuted + return part default: return codersdk.ChatMessagePart{} } diff --git a/coderd/database/db2sdk/db2sdk_test.go b/coderd/database/db2sdk/db2sdk_test.go index db550400f1..ef05cf5e6c 100644 --- a/coderd/database/db2sdk/db2sdk_test.go +++ b/coderd/database/db2sdk/db2sdk_test.go @@ -437,6 +437,61 @@ func TestAIBridgeInterception(t *testing.T) { } } +func TestChatMessage_PreservesProviderExecutedOnToolResults(t *testing.T) { + t.Parallel() + + toolCallID := uuid.New().String() + toolName := "web_search" + + // Build assistant content blocks with ProviderExecuted set. + toolCall := fantasy.ToolCallContent{ + ToolCallID: toolCallID, + ToolName: toolName, + Input: `{"query":"test"}`, + ProviderExecuted: true, + } + toolResult := fantasy.ToolResultContent{ + ToolCallID: toolCallID, + ToolName: toolName, + Result: fantasy.ToolResultOutputContentText{Text: `{"results":[]}`}, + ProviderExecuted: true, + } + + tcJSON, err := json.Marshal(toolCall) + require.NoError(t, err) + trJSON, err := json.Marshal(toolResult) + require.NoError(t, err) + + rawContent := json.RawMessage("[" + string(tcJSON) + "," + string(trJSON) + "]") + + dbMsg := database.ChatMessage{ + ID: 1, + ChatID: uuid.New(), + Role: string(fantasy.MessageRoleAssistant), + Content: pqtype.NullRawMessage{ + RawMessage: rawContent, + Valid: true, + }, + CreatedAt: time.Now(), + } + + result := db2sdk.ChatMessage(dbMsg) + + require.Len(t, result.Content, 2) + + // First part: tool call. + require.Equal(t, codersdk.ChatMessagePartTypeToolCall, result.Content[0].Type) + require.Equal(t, toolCallID, result.Content[0].ToolCallID) + require.Equal(t, toolName, result.Content[0].ToolName) + require.True(t, result.Content[0].ProviderExecuted, "tool call should preserve ProviderExecuted") + + // Second part: tool result. + require.Equal(t, codersdk.ChatMessagePartTypeToolResult, result.Content[1].Type) + require.Equal(t, toolCallID, result.Content[1].ToolCallID) + require.Equal(t, toolName, result.Content[1].ToolName) + require.True(t, result.Content[1].ProviderExecuted, "tool result should preserve ProviderExecuted") +} + func TestChatQueuedMessage_ParsesUserContentParts(t *testing.T) { t.Parallel()