From 5713a39d9af43c788f5f2fe11a8fa1f7af79ec0e Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Thu, 28 May 2026 16:39:09 +0000 Subject: [PATCH] fix(coderd/x/chatd): preserve quickgen user input --- coderd/x/chatd/quickgen.go | 10 ------- coderd/x/chatd/quickgen_internal_test.go | 33 +++++++----------------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/coderd/x/chatd/quickgen.go b/coderd/x/chatd/quickgen.go index 10e470a84d..e8cf68d412 100644 --- a/coderd/x/chatd/quickgen.go +++ b/coderd/x/chatd/quickgen.go @@ -424,10 +424,6 @@ func (p *Server) prepareQuickgenDebugCandidate( return runCtx, debugModel, finishDebugRun } -// Synthetic quickgen prompts end with an assistant marker because AI Bridge -// records final user-role messages as user prompts. -const quickgenStructuredOutputReady = "Ready to provide the structured output." - func syntheticObjectGenerationPrompt(systemPrompt, userInput string) fantasy.Prompt { return fantasy.Prompt{ { @@ -442,12 +438,6 @@ func syntheticObjectGenerationPrompt(systemPrompt, userInput string) fantasy.Pro fantasy.TextPart{Text: userInput}, }, }, - { - Role: fantasy.MessageRoleAssistant, - Content: []fantasy.MessagePart{ - fantasy.TextPart{Text: quickgenStructuredOutputReady}, - }, - }, } } diff --git a/coderd/x/chatd/quickgen_internal_test.go b/coderd/x/chatd/quickgen_internal_test.go index 56d5bd08fe..42b578e7e2 100644 --- a/coderd/x/chatd/quickgen_internal_test.go +++ b/coderd/x/chatd/quickgen_internal_test.go @@ -417,7 +417,7 @@ func TestMaybeGenerateChatTitlePreservesUpdatedAt(t *testing.T) { model := &chattest.FakeModel{ GenerateObjectFn: func(ctx context.Context, call fantasy.ObjectCall) (*fantasy.ObjectResponse, error) { require.Equal(t, "propose_title", call.SchemaName) - requireSyntheticQuickgenContext(ctx, t) + require.True(t, suppressAIBridgeSessionHeadersFromContext(ctx)) requireSyntheticQuickgenPrompt(t, call.Prompt, userPrompt) return &fantasy.ObjectResponse{ Object: map[string]any{"title": wantTitle}, @@ -496,7 +496,7 @@ func Test_generateManualTitle_UsesTimeout(t *testing.T) { deadline, 2*time.Second, ) - requireSyntheticQuickgenContext(ctx, t) + require.True(t, suppressAIBridgeSessionHeadersFromContext(ctx)) requireSyntheticQuickgenPrompt(t, call.Prompt, "refresh chat title") require.Equal(t, "propose_title", call.SchemaName) return &fantasy.ObjectResponse{Object: map[string]any{"title": "Refresh title"}}, nil @@ -527,7 +527,7 @@ func Test_generateManualTitle_TruncatesFirstUserInput(t *testing.T) { model := &chattest.FakeModel{ GenerateObjectFn: func(ctx context.Context, call fantasy.ObjectCall) (*fantasy.ObjectResponse, error) { - requireSyntheticQuickgenContext(ctx, t) + require.True(t, suppressAIBridgeSessionHeadersFromContext(ctx)) requireSyntheticQuickgenPrompt(t, call.Prompt, truncateRunes(longFirstUserText, maxLatestUserMessageRunes)) // The manual title system prompt also includes the latest user excerpt. systemText, ok := call.Prompt[0].Content[0].(fantasy.TextPart) @@ -689,7 +689,7 @@ func TestGenerateStructuredTitleWithUsage_OpenAICompatibleRequiredToolChoice(t * body := testutil.TryReceive(t.Context(), t, requests) require.Equal(t, "required", body["tool_choice"]) - requireOpenAICompatAssistantFinalMessage(t, body) + requireOpenAICompatFinalUserMessage(t, body, "summarize failed workspace build logs") } func newOpenAICompatStructuredOutputServer( @@ -767,33 +767,20 @@ func openAICompatTestModel(t *testing.T, baseURL string) fantasy.LanguageModel { return model } -func requireSyntheticQuickgenContext(ctx context.Context, t *testing.T) { - t.Helper() - - require.True(t, suppressAIBridgeSessionHeadersFromContext(ctx)) -} - func requireSyntheticQuickgenPrompt(t *testing.T, prompt fantasy.Prompt, userInput string) { t.Helper() - require.Len(t, prompt, 3) + require.Len(t, prompt, 2) require.Equal(t, fantasy.MessageRoleSystem, prompt[0].Role) require.Equal(t, fantasy.MessageRoleUser, prompt[1].Role) - require.Equal(t, fantasy.MessageRoleAssistant, prompt[2].Role) - require.Len(t, prompt[1].Content, 1) - require.Len(t, prompt[2].Content, 1) userText, ok := prompt[1].Content[0].(fantasy.TextPart) require.True(t, ok) require.Equal(t, userInput, userText.Text) - - assistantText, ok := prompt[2].Content[0].(fantasy.TextPart) - require.True(t, ok) - require.Equal(t, quickgenStructuredOutputReady, assistantText.Text) } -func requireOpenAICompatAssistantFinalMessage(t *testing.T, body map[string]any) { +func requireOpenAICompatFinalUserMessage(t *testing.T, body map[string]any, userInput string) { t.Helper() messages, ok := body["messages"].([]any) @@ -802,8 +789,8 @@ func requireOpenAICompatAssistantFinalMessage(t *testing.T, body map[string]any) lastMessage, ok := messages[len(messages)-1].(map[string]any) require.True(t, ok) - require.Equal(t, "assistant", lastMessage["role"]) - require.Equal(t, quickgenStructuredOutputReady, lastMessage["content"]) + require.Equal(t, "user", lastMessage["role"]) + require.Equal(t, userInput, lastMessage["content"]) } func TestGenerateStructuredTurnStatusLabel(t *testing.T) { @@ -815,7 +802,7 @@ func TestGenerateStructuredTurnStatusLabel(t *testing.T) { model := &chattest.FakeModel{ GenerateObjectFn: func(ctx context.Context, call fantasy.ObjectCall) (*fantasy.ObjectResponse, error) { require.Equal(t, "propose_turn_status_label", call.SchemaName) - requireSyntheticQuickgenContext(ctx, t) + require.True(t, suppressAIBridgeSessionHeadersFromContext(ctx)) requireSyntheticQuickgenPrompt(t, call.Prompt, "done") return &fantasy.ObjectResponse{ Object: map[string]any{"label": "Submitted PR"}, @@ -841,7 +828,7 @@ func TestGenerateStructuredTurnStatusLabel(t *testing.T) { body := testutil.TryReceive(t.Context(), t, requests) require.Equal(t, "required", body["tool_choice"]) - requireOpenAICompatAssistantFinalMessage(t, body) + requireOpenAICompatFinalUserMessage(t, body, "done") }) t.Run("rejects narrative label", func(t *testing.T) {