mirror of
https://github.com/coder/coder.git
synced 2026-06-05 14:08:20 +00:00
112 lines
4.1 KiB
Go
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)
|
|
}
|