Files
bifrost/plugins/logging/operations_test.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

628 lines
24 KiB
Go

package logging
import (
"context"
"path/filepath"
"strings"
"testing"
"time"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework/logstore"
)
type testLogger struct{}
func (testLogger) Debug(string, ...any) {}
func (testLogger) Info(string, ...any) {}
func (testLogger) Warn(string, ...any) {}
func (testLogger) Error(string, ...any) {}
func (testLogger) Fatal(string, ...any) {}
func (testLogger) SetLevel(schemas.LogLevel) {}
func (testLogger) SetOutputType(schemas.LoggerOutputType) {}
func (testLogger) LogHTTPRequest(schemas.LogLevel, string) schemas.LogEventBuilder {
return schemas.NoopLogEvent
}
func newTestStore(t *testing.T) logstore.LogStore {
t.Helper()
store, err := logstore.NewLogStore(context.Background(), &logstore.Config{
Enabled: true,
Type: logstore.LogStoreTypeSQLite,
Config: &logstore.SQLiteConfig{
Path: filepath.Join(t.TempDir(), "logging.db"),
},
}, testLogger{})
if err != nil {
t.Fatalf("NewLogStore() error = %v", err)
}
return store
}
func TestUpdateLogEntryPreservesResponsesInputContentSummary(t *testing.T) {
store := newTestStore(t)
plugin := &LoggerPlugin{
store: store,
logger: testLogger{},
}
requestID := "req-1"
now := time.Now().UTC()
inputText := "request-side text"
initial := &InitialLogData{
Object: "responses",
Provider: "openai",
Model: "gpt-4o-mini",
ResponsesInputHistory: []schemas.ResponsesMessage{{
Content: &schemas.ResponsesMessageContent{
ContentStr: &inputText,
},
}},
}
if err := plugin.insertInitialLogEntry(context.Background(), requestID, "", now, 0, nil, initial); err != nil {
t.Fatalf("insertInitialLogEntry() error = %v", err)
}
responsesText := "responses output"
update := &UpdateLogData{
Status: "success",
ResponsesOutput: []schemas.ResponsesMessage{{
Content: &schemas.ResponsesMessageContent{
ContentStr: &responsesText,
},
}},
}
if err := plugin.updateLogEntry(context.Background(), requestID, "", "", 10, "", "", "", "", 0, nil, "", update); err != nil {
t.Fatalf("updateLogEntry() error = %v", err)
}
logEntry, err := store.FindByID(context.Background(), requestID)
if err != nil {
t.Fatalf("FindByID() error = %v", err)
}
if !strings.Contains(logEntry.ContentSummary, inputText) {
t.Fatalf("expected content summary to preserve responses input, got %q", logEntry.ContentSummary)
}
if strings.Contains(logEntry.ContentSummary, responsesText) {
t.Fatalf("expected content summary to avoid overwriting with responses output-only data, got %q", logEntry.ContentSummary)
}
}
func TestUpdateLogEntryUpdatesContentSummaryForChatOutput(t *testing.T) {
store := newTestStore(t)
plugin := &LoggerPlugin{
store: store,
logger: testLogger{},
}
requestID := "req-chat"
now := time.Now().UTC()
initial := &InitialLogData{
Object: "chat_completion",
Provider: "openai",
Model: "gpt-4o-mini",
}
if err := plugin.insertInitialLogEntry(context.Background(), requestID, "", now, 0, nil, initial); err != nil {
t.Fatalf("insertInitialLogEntry() error = %v", err)
}
chatText := "assistant output"
update := &UpdateLogData{
Status: "success",
ChatOutput: &schemas.ChatMessage{
Role: schemas.ChatMessageRoleAssistant,
Content: &schemas.ChatMessageContent{
ContentStr: &chatText,
},
},
}
if err := plugin.updateLogEntry(context.Background(), requestID, "", "", 10, "", "", "", "", 0, nil, "", update); err != nil {
t.Fatalf("updateLogEntry() error = %v", err)
}
logEntry, err := store.FindByID(context.Background(), requestID)
if err != nil {
t.Fatalf("FindByID() error = %v", err)
}
if !strings.Contains(logEntry.ContentSummary, chatText) {
t.Fatalf("expected content summary to include chat output, got %q", logEntry.ContentSummary)
}
}
func TestUpdateLogEntrySuppressesChatOutputWhenContentLoggingDisabled(t *testing.T) {
store := newTestStore(t)
disableContentLogging := true
plugin := &LoggerPlugin{
store: store,
logger: testLogger{},
disableContentLogging: &disableContentLogging,
}
requestID := "req-chat-disabled"
now := time.Now().UTC()
initial := &InitialLogData{
Object: "chat_completion",
Provider: "openai",
Model: "gpt-4o-mini",
}
if err := plugin.insertInitialLogEntry(context.Background(), requestID, "", now, 0, nil, initial); err != nil {
t.Fatalf("insertInitialLogEntry() error = %v", err)
}
chatText := "assistant output should not be logged"
update := &UpdateLogData{
Status: "success",
ChatOutput: &schemas.ChatMessage{
Role: schemas.ChatMessageRoleAssistant,
Content: &schemas.ChatMessageContent{
ContentStr: &chatText,
},
},
}
if err := plugin.updateLogEntry(context.Background(), requestID, "", "", 10, "", "", "", "", 0, nil, "", update); err != nil {
t.Fatalf("updateLogEntry() error = %v", err)
}
logEntry, err := store.FindByID(context.Background(), requestID)
if err != nil {
t.Fatalf("FindByID() error = %v", err)
}
if logEntry.OutputMessage != "" {
t.Fatalf("expected output_message to be suppressed, got %q", logEntry.OutputMessage)
}
if strings.Contains(logEntry.ContentSummary, chatText) {
t.Fatalf("expected content summary to suppress chat output, got %q", logEntry.ContentSummary)
}
}
func TestStoreOrEnqueueRetryPreservesAllEntries(t *testing.T) {
// Simulate fallback/retry scenario where multiple PostLLMHook calls
// store entries under the same traceID. All entries must be preserved.
plugin := &LoggerPlugin{
logger: testLogger{},
writeQueue: make(chan *writeQueueEntry, 10),
}
traceID := "trace-retry-test"
ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
ctx.SetValue(schemas.BifrostContextKeyTraceID, traceID)
// Simulate 3 retry attempts storing entries under the same traceID
entry1 := &logstore.Log{ID: "req-attempt-1", Model: "gpt-4o"}
entry2 := &logstore.Log{ID: "req-attempt-2", Model: "gpt-4o"}
entry3 := &logstore.Log{ID: "req-attempt-3", Model: "claude-3-5-sonnet"}
plugin.storeOrEnqueueEntry(ctx, entry1, nil)
plugin.storeOrEnqueueEntry(ctx, entry2, nil)
plugin.storeOrEnqueueEntry(ctx, entry3, nil)
// Verify all 3 entries are stored
val, ok := plugin.pendingLogsToInject.Load(traceID)
if !ok {
t.Fatal("expected pending entries for traceID, got none")
}
pending, ok := val.(*pendingInjectEntries)
if !ok {
t.Fatal("expected *pendingInjectEntries type")
}
if len(pending.entries) != 3 {
t.Fatalf("expected 3 entries, got %d", len(pending.entries))
}
if pending.entries[0].ID != "req-attempt-1" || pending.entries[1].ID != "req-attempt-2" || pending.entries[2].ID != "req-attempt-3" {
t.Fatalf("entries not in expected order: %v, %v, %v", pending.entries[0].ID, pending.entries[1].ID, pending.entries[2].ID)
}
// Now test Inject flushes all entries with plugin logs attached
trace := &schemas.Trace{
TraceID: traceID,
PluginLogs: []schemas.PluginLogEntry{
{PluginName: "hello-world", Level: schemas.LogLevelInfo, Message: "test log"},
},
}
if err := plugin.Inject(context.Background(), trace); err != nil {
t.Fatalf("Inject() error = %v", err)
}
// Verify all 3 entries were enqueued to writeQueue
if len(plugin.writeQueue) != 3 {
t.Fatalf("expected 3 entries in writeQueue, got %d", len(plugin.writeQueue))
}
// Verify plugin logs were attached to each entry
for i := 0; i < 3; i++ {
qe := <-plugin.writeQueue
if qe.log.PluginLogs == "" {
t.Fatalf("entry %d: expected PluginLogs to be set", i)
}
}
// Verify pendingLogsToInject was cleaned up
if _, ok := plugin.pendingLogsToInject.Load(traceID); ok {
t.Fatal("expected pendingLogsToInject to be cleaned up after Inject")
}
}
func TestApplyRealtimeOutputToEntryBackfillsUserTranscriptFromRawRequest(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Hello!"
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: `{"type":"conversation.item.input_audio_transcription.completed","transcript":"Hello."}`,
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 1 {
t.Fatalf("len(InputHistoryParsed) = %d, want 1", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[0].Role = %q, want user", entry.InputHistoryParsed[0].Role)
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "Hello." {
t.Fatalf("InputHistoryParsed[0] = %+v, want transcript", entry.InputHistoryParsed[0])
}
if entry.OutputMessageParsed == nil || entry.OutputMessageParsed.Content == nil || entry.OutputMessageParsed.Content.ContentStr == nil || *entry.OutputMessageParsed.Content.ContentStr != assistantText {
t.Fatalf("OutputMessageParsed = %+v, want assistant text", entry.OutputMessageParsed)
}
if !strings.Contains(entry.ContentSummary, "Hello.") {
t.Fatalf("ContentSummary = %q, want user transcript", entry.ContentSummary)
}
if !strings.Contains(entry.ContentSummary, "Hello!") {
t.Fatalf("ContentSummary = %q, want assistant text", entry.ContentSummary)
}
}
func TestApplyRealtimeOutputToEntryBackfillsMissingTranscriptPlaceholder(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Hi there!"
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: `{"type":"conversation.item.input_audio_transcription.completed","transcript":""}`,
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 1 {
t.Fatalf("len(InputHistoryParsed) = %d, want 1", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != realtimeMissingTranscriptText {
t.Fatalf("InputHistoryParsed[0] = %+v, want missing transcript placeholder", entry.InputHistoryParsed[0])
}
if !strings.Contains(entry.ContentSummary, realtimeMissingTranscriptText) {
t.Fatalf("ContentSummary = %q, want missing transcript placeholder", entry.ContentSummary)
}
}
func TestApplyRealtimeOutputToEntryBackfillsDoneMissingTranscriptPlaceholder(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Hi there!"
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: `{"type":"conversation.item.done","item":{"id":"item_user","type":"message","role":"user","status":"completed","content":[{"type":"input_audio","transcript":null}]}}`,
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 1 {
t.Fatalf("len(InputHistoryParsed) = %d, want 1", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != realtimeMissingTranscriptText {
t.Fatalf("InputHistoryParsed[0] = %+v, want missing transcript placeholder", entry.InputHistoryParsed[0])
}
}
func TestApplyRealtimeOutputToEntryBackfillsRetrievedUserAndToolHistory(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "I checked that for you."
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: strings.Join([]string{
`{"type":"conversation.item.retrieved","item":{"id":"item_user","type":"message","role":"user","status":"completed","content":[{"type":"input_text","text":"Where is my order?"}]}}`,
`{"type":"conversation.item.retrieved","item":{"id":"item_tool","type":"function_call_output","call_id":"call_123","status":"completed","output":"{\"status\":\"delivered\"}"}}`,
}, "\n\n"),
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 2 {
t.Fatalf("len(InputHistoryParsed) = %d, want 2", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[0].Role = %q, want user", entry.InputHistoryParsed[0].Role)
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "Where is my order?" {
t.Fatalf("InputHistoryParsed[0] = %+v, want user content", entry.InputHistoryParsed[0])
}
if entry.InputHistoryParsed[1].Role != schemas.ChatMessageRoleTool {
t.Fatalf("InputHistoryParsed[1].Role = %q, want tool", entry.InputHistoryParsed[1].Role)
}
if entry.InputHistoryParsed[1].Content == nil || entry.InputHistoryParsed[1].Content.ContentStr == nil || *entry.InputHistoryParsed[1].Content.ContentStr != `{"status":"delivered"}` {
t.Fatalf("InputHistoryParsed[1] = %+v, want tool content", entry.InputHistoryParsed[1])
}
if entry.InputHistoryParsed[1].ChatToolMessage == nil || entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID == nil || *entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID != "call_123" {
t.Fatalf("InputHistoryParsed[1].ChatToolMessage = %+v, want tool call id", entry.InputHistoryParsed[1].ChatToolMessage)
}
}
func TestApplyRealtimeOutputToEntryBackfillsCreatedUserAndToolHistory(t *testing.T) {
t.Parallel()
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
ExtraFields: schemas.BifrostResponseExtraFields{
RawRequest: strings.Join([]string{
`{"type":"conversation.item.created","item":{"id":"item_user","type":"message","role":"user","status":"completed","content":[{"type":"input_text","text":"I need help"}]}}`,
`{"type":"conversation.item.created","item":{"id":"item_tool","type":"function_call_output","call_id":"call_456","status":"completed","output":"{\"status\":\"ok\"}"}}`,
}, "\n\n"),
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if len(entry.InputHistoryParsed) != 2 {
t.Fatalf("len(InputHistoryParsed) = %d, want 2", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[0].Role = %q, want user", entry.InputHistoryParsed[0].Role)
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "I need help" {
t.Fatalf("InputHistoryParsed[0] = %+v, want user content", entry.InputHistoryParsed[0])
}
if entry.InputHistoryParsed[1].Role != schemas.ChatMessageRoleTool {
t.Fatalf("InputHistoryParsed[1].Role = %q, want tool", entry.InputHistoryParsed[1].Role)
}
if entry.InputHistoryParsed[1].Content == nil || entry.InputHistoryParsed[1].Content.ContentStr == nil || *entry.InputHistoryParsed[1].Content.ContentStr != `{"status":"ok"}` {
t.Fatalf("InputHistoryParsed[1] = %+v, want tool content", entry.InputHistoryParsed[1])
}
if entry.InputHistoryParsed[1].ChatToolMessage == nil || entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID == nil || *entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID != "call_456" {
t.Fatalf("InputHistoryParsed[1].ChatToolMessage = %+v, want tool call id", entry.InputHistoryParsed[1].ChatToolMessage)
}
}
func TestApplyRealtimeOutputToEntryBackfillsAddedUserAndToolHistory(t *testing.T) {
t.Parallel()
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Done."
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: strings.Join([]string{
`{"type":"conversation.item.added","item":{"id":"item_user","type":"message","role":"user","status":"completed","content":[{"type":"input_text","text":"hello from added item"}]}}`,
`{"type":"conversation.item.added","item":{"id":"item_tool","type":"function_call_output","call_id":"call_added","status":"completed","output":"{\"status\":\"ok\"}"}}`,
}, "\n\n"),
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 2 {
t.Fatalf("len(InputHistoryParsed) = %d, want 2", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "hello from added item" {
t.Fatalf("InputHistoryParsed[0] = %+v, want added user content", entry.InputHistoryParsed[0])
}
if entry.InputHistoryParsed[1].ChatToolMessage == nil || entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID == nil || *entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID != "call_added" {
t.Fatalf("InputHistoryParsed[1].ChatToolMessage = %+v, want added tool call id", entry.InputHistoryParsed[1].ChatToolMessage)
}
}
func TestApplyRealtimeOutputToEntryMergesRawTranscriptIntoStructuredRealtimeHistory(t *testing.T) {
t.Parallel()
plugin := &LoggerPlugin{}
entry := &logstore.Log{
InputHistoryParsed: []schemas.ChatMessage{
{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{
ContentStr: schemas.Ptr("Can you help with my ticket?"),
},
},
{
Role: schemas.ChatMessageRoleTool,
Content: &schemas.ChatMessageContent{
ContentStr: schemas.Ptr(`{"status":"open"}`),
},
ChatToolMessage: &schemas.ChatToolMessage{
ToolCallID: schemas.Ptr("call_789"),
},
},
},
}
assistantText := "Let me check."
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: strings.Join([]string{
`{"type":"conversation.item.input_audio_transcription.completed","transcript":"Hello."}`,
`{"type":"conversation.item.retrieved","item":{"id":"item_tool","type":"function_call_output","call_id":"call_789","status":"completed","output":"{\"status\":\"open\"}"}}`,
}, "\n\n"),
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 3 {
t.Fatalf("len(InputHistoryParsed) = %d, want 3", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "Can you help with my ticket?" {
t.Fatalf("InputHistoryParsed[0] = %+v, want structured user content", entry.InputHistoryParsed[0])
}
if entry.InputHistoryParsed[1].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[1].Role = %q, want user", entry.InputHistoryParsed[1].Role)
}
if entry.InputHistoryParsed[1].Content == nil || entry.InputHistoryParsed[1].Content.ContentStr == nil || *entry.InputHistoryParsed[1].Content.ContentStr != "Hello." {
t.Fatalf("InputHistoryParsed[1] = %+v, want raw transcript merge", entry.InputHistoryParsed[1])
}
if entry.InputHistoryParsed[2].Role != schemas.ChatMessageRoleTool {
t.Fatalf("InputHistoryParsed[2].Role = %q, want tool", entry.InputHistoryParsed[2].Role)
}
if entry.InputHistoryParsed[2].ChatToolMessage == nil || entry.InputHistoryParsed[2].ChatToolMessage.ToolCallID == nil || *entry.InputHistoryParsed[2].ChatToolMessage.ToolCallID != "call_789" {
t.Fatalf("InputHistoryParsed[2].ChatToolMessage = %+v, want original tool call id", entry.InputHistoryParsed[2].ChatToolMessage)
}
if strings.Count(entry.ContentSummary, "Hello.") != 1 {
t.Fatalf("ContentSummary = %q, want one merged transcript", entry.ContentSummary)
}
}
func TestApplyRealtimeOutputToEntryDoesNotPersistRawWhenShouldStoreRawFalse(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Hello!"
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: `{"type":"conversation.item.input_audio_transcription.completed","transcript":"Hello."}`,
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, false)
if entry.RawRequest != "" {
t.Fatalf("expected RawRequest to remain empty when shouldStoreRaw=false, got %q", entry.RawRequest)
}
if entry.RawResponse != "" {
t.Fatalf("expected RawResponse to remain empty when shouldStoreRaw=false, got %q", entry.RawResponse)
}
if len(entry.InputHistoryParsed) == 0 {
t.Fatal("expected InputHistoryParsed to still be backfilled when shouldStoreRaw=false")
}
if entry.InputHistoryParsed[0].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[0].Role = %q, want user", entry.InputHistoryParsed[0].Role)
}
}