Files
coder/coderd/x/chatd/chatloop/chatloop_internal_test.go
T
Hugo Dutka 658a04d28f pr 3
2026-06-04 18:51:22 +00:00

112 lines
4.1 KiB
Go

package chatloop
import (
"context"
"iter"
"testing"
"charm.land/fantasy"
fantasyanthropic "charm.land/fantasy/providers/anthropic"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/codersdk"
)
func TestProcessStepStreamPreservesReasoningMetadataAcrossNilDelta(t *testing.T) {
t.Parallel()
stream := iter.Seq[fantasy.StreamPart](func(yield func(fantasy.StreamPart) bool) {
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeReasoningStart, ID: "0"})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeReasoningDelta, ID: "0", Delta: "thinking"})
yield(fantasy.StreamPart{
Type: fantasy.StreamPartTypeReasoningDelta,
ID: "0",
ProviderMetadata: fantasy.ProviderMetadata{
fantasyanthropic.Name: &fantasyanthropic.ReasoningOptionMetadata{
Signature: "sig",
},
},
})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeReasoningDelta, ID: "0", ProviderMetadata: fantasy.ProviderMetadata{}})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeReasoningDelta, ID: "0"})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeReasoningEnd, ID: "0", ProviderMetadata: fantasy.ProviderMetadata{}})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeFinish, FinishReason: fantasy.FinishReasonStop})
})
result, err := processStepStream(context.Background(), stream, func(codersdk.ChatMessageRole, codersdk.ChatMessagePart) {})
require.NoError(t, err)
require.Len(t, result.content, 1)
reasoning, ok := fantasy.AsContentType[fantasy.ReasoningContent](result.content[0])
require.True(t, ok)
require.Equal(t, "thinking", reasoning.Text)
metadata := fantasyanthropic.GetReasoningMetadata(fantasy.ProviderOptions(reasoning.ProviderMetadata))
require.NotNil(t, metadata)
require.Equal(t, "sig", metadata.Signature)
}
func TestProcessStepStreamPersistsRedactedThinkingOnEnd(t *testing.T) {
t.Parallel()
stream := iter.Seq[fantasy.StreamPart](func(yield func(fantasy.StreamPart) bool) {
reasoningMetadata := fantasy.ProviderMetadata{
fantasyanthropic.Name: &fantasyanthropic.ReasoningOptionMetadata{
RedactedData: "redacted-payload",
},
}
yield(fantasy.StreamPart{
Type: fantasy.StreamPartTypeReasoningStart,
ID: "0",
ProviderMetadata: reasoningMetadata,
})
yield(fantasy.StreamPart{
Type: fantasy.StreamPartTypeReasoningEnd,
ID: "0",
ProviderMetadata: reasoningMetadata,
})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeTextStart, ID: "1"})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeTextDelta, ID: "1", Delta: "done"})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeTextEnd, ID: "1"})
yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeFinish, FinishReason: fantasy.FinishReasonStop})
})
result, err := processStepStream(context.Background(), stream, func(codersdk.ChatMessageRole, codersdk.ChatMessagePart) {})
require.NoError(t, err)
require.Len(t, result.content, 2)
reasoning, ok := fantasy.AsContentType[fantasy.ReasoningContent](result.content[0])
require.True(t, ok)
require.Empty(t, reasoning.Text)
metadata := fantasyanthropic.GetReasoningMetadata(fantasy.ProviderOptions(reasoning.ProviderMetadata))
require.NotNil(t, metadata)
require.Equal(t, "redacted-payload", metadata.RedactedData)
}
func TestFlushActiveStatePreservesEmptySignedReasoning(t *testing.T) {
t.Parallel()
result := &stepResult{}
flushActiveState(
result,
map[string]string{},
map[string]reasoningState{
"signed": {
options: fantasy.ProviderMetadata{
fantasyanthropic.Name: &fantasyanthropic.ReasoningOptionMetadata{
RedactedData: "redacted-payload",
},
},
},
"empty": {},
},
map[string]*fantasy.ToolCallContent{},
map[string]string{},
)
require.Len(t, result.content, 1)
reasoning, ok := fantasy.AsContentType[fantasy.ReasoningContent](result.content[0])
require.True(t, ok)
require.Empty(t, reasoning.Text)
metadata := fantasyanthropic.GetReasoningMetadata(fantasy.ProviderOptions(reasoning.ProviderMetadata))
require.NotNil(t, metadata)
require.Equal(t, "redacted-payload", metadata.RedactedData)
}