// Package tracing provides distributed tracing utilities for Bifrost. package tracing import ( "fmt" "strings" "github.com/maximhq/bifrost/core/schemas" ) // PopulateRequestAttributes extracts common request attributes from a BifrostRequest. // This is the main entry point for populating request attributes on a span. func PopulateRequestAttributes(req *schemas.BifrostRequest) map[string]any { attrs := make(map[string]any) if req == nil { return attrs } provider, model, _ := req.GetRequestFields() attrs[schemas.AttrProviderName] = string(provider) attrs[schemas.AttrRequestModel] = model switch req.RequestType { case schemas.ChatCompletionRequest, schemas.ChatCompletionStreamRequest: PopulateChatRequestAttributes(req.ChatRequest, attrs) case schemas.TextCompletionRequest, schemas.TextCompletionStreamRequest: PopulateTextCompletionRequestAttributes(req.TextCompletionRequest, attrs) case schemas.EmbeddingRequest: PopulateEmbeddingRequestAttributes(req.EmbeddingRequest, attrs) case schemas.TranscriptionRequest, schemas.TranscriptionStreamRequest: PopulateTranscriptionRequestAttributes(req.TranscriptionRequest, attrs) case schemas.SpeechRequest, schemas.SpeechStreamRequest: PopulateSpeechRequestAttributes(req.SpeechRequest, attrs) case schemas.ResponsesRequest, schemas.ResponsesStreamRequest: PopulateResponsesRequestAttributes(req.ResponsesRequest, attrs) case schemas.BatchCreateRequest: PopulateBatchCreateRequestAttributes(req.BatchCreateRequest, attrs) case schemas.BatchListRequest: PopulateBatchListRequestAttributes(req.BatchListRequest, attrs) case schemas.BatchRetrieveRequest: PopulateBatchRetrieveRequestAttributes(req.BatchRetrieveRequest, attrs) case schemas.BatchCancelRequest: PopulateBatchCancelRequestAttributes(req.BatchCancelRequest, attrs) case schemas.BatchResultsRequest: PopulateBatchResultsRequestAttributes(req.BatchResultsRequest, attrs) case schemas.FileUploadRequest: PopulateFileUploadRequestAttributes(req.FileUploadRequest, attrs) case schemas.FileListRequest: PopulateFileListRequestAttributes(req.FileListRequest, attrs) case schemas.FileRetrieveRequest: PopulateFileRetrieveRequestAttributes(req.FileRetrieveRequest, attrs) case schemas.FileDeleteRequest: PopulateFileDeleteRequestAttributes(req.FileDeleteRequest, attrs) case schemas.FileContentRequest: PopulateFileContentRequestAttributes(req.FileContentRequest, attrs) } return attrs } // PopulateResponseAttributes extracts common response attributes from a BifrostResponse. // This is the main entry point for populating response attributes on a span. func PopulateResponseAttributes(resp *schemas.BifrostResponse) map[string]any { attrs := make(map[string]any) if resp == nil { return attrs } switch { case resp.ChatResponse != nil: PopulateChatResponseAttributes(resp.ChatResponse, attrs) case resp.TextCompletionResponse != nil: PopulateTextCompletionResponseAttributes(resp.TextCompletionResponse, attrs) case resp.EmbeddingResponse != nil: PopulateEmbeddingResponseAttributes(resp.EmbeddingResponse, attrs) case resp.TranscriptionResponse != nil: PopulateTranscriptionResponseAttributes(resp.TranscriptionResponse, attrs) case resp.SpeechResponse != nil: PopulateSpeechResponseAttributes(resp.SpeechResponse, attrs) case resp.ResponsesResponse != nil: PopulateResponsesResponseAttributes(resp.ResponsesResponse, attrs) case resp.BatchCreateResponse != nil: PopulateBatchCreateResponseAttributes(resp.BatchCreateResponse, attrs) case resp.BatchListResponse != nil: PopulateBatchListResponseAttributes(resp.BatchListResponse, attrs) case resp.BatchRetrieveResponse != nil: PopulateBatchRetrieveResponseAttributes(resp.BatchRetrieveResponse, attrs) case resp.BatchCancelResponse != nil: PopulateBatchCancelResponseAttributes(resp.BatchCancelResponse, attrs) case resp.BatchResultsResponse != nil: PopulateBatchResultsResponseAttributes(resp.BatchResultsResponse, attrs) case resp.FileUploadResponse != nil: PopulateFileUploadResponseAttributes(resp.FileUploadResponse, attrs) case resp.FileListResponse != nil: PopulateFileListResponseAttributes(resp.FileListResponse, attrs) case resp.FileRetrieveResponse != nil: PopulateFileRetrieveResponseAttributes(resp.FileRetrieveResponse, attrs) case resp.FileDeleteResponse != nil: PopulateFileDeleteResponseAttributes(resp.FileDeleteResponse, attrs) case resp.FileContentResponse != nil: PopulateFileContentResponseAttributes(resp.FileContentResponse, attrs) } return attrs } // PopulateErrorAttributes extracts error attributes from a BifrostError. func PopulateErrorAttributes(err *schemas.BifrostError) map[string]any { attrs := make(map[string]any) if err == nil || err.Error == nil { return attrs } attrs[schemas.AttrError] = err.Error.Message if err.Error.Type != nil { attrs[schemas.AttrErrorType] = *err.Error.Type } if err.Error.Code != nil { attrs[schemas.AttrErrorCode] = *err.Error.Code } return attrs } // PopulateContextAttributes extracts context-related attributes (virtual keys, retries, routing rules, etc.) func PopulateContextAttributes( attrs map[string]any, virtualKeyID, virtualKeyName string, selectedKeyID, selectedKeyName string, routingRuleID, routingRuleName string, teamID, teamName string, customerID, customerName string, numberOfRetries, fallbackIndex int, ) { if virtualKeyID != "" { attrs[schemas.AttrVirtualKeyID] = virtualKeyID attrs[schemas.AttrVirtualKeyName] = virtualKeyName } if selectedKeyID != "" { attrs[schemas.AttrSelectedKeyID] = selectedKeyID attrs[schemas.AttrSelectedKeyName] = selectedKeyName } if routingRuleID != "" { attrs[schemas.AttrRoutingRuleID] = routingRuleID attrs[schemas.AttrRoutingRuleName] = routingRuleName } if teamID != "" { attrs[schemas.AttrTeamID] = teamID attrs[schemas.AttrTeamName] = teamName } if customerID != "" { attrs[schemas.AttrCustomerID] = customerID attrs[schemas.AttrCustomerName] = customerName } attrs[schemas.AttrNumberOfRetries] = numberOfRetries attrs[schemas.AttrFallbackIndex] = fallbackIndex } // =============================================== // Chat Completion Request/Response // =============================================== // PopulateChatRequestAttributes extracts chat completion request attributes. func PopulateChatRequestAttributes(req *schemas.BifrostChatRequest, attrs map[string]any) { if req == nil { return } if req.Params != nil { if req.Params.MaxCompletionTokens != nil { attrs[schemas.AttrMaxTokens] = *req.Params.MaxCompletionTokens } if req.Params.Temperature != nil { attrs[schemas.AttrTemperature] = *req.Params.Temperature } if req.Params.TopP != nil { attrs[schemas.AttrTopP] = *req.Params.TopP } if req.Params.Stop != nil { attrs[schemas.AttrStopSequences] = strings.Join(req.Params.Stop, ",") } if req.Params.PresencePenalty != nil { attrs[schemas.AttrPresencePenalty] = *req.Params.PresencePenalty } if req.Params.FrequencyPenalty != nil { attrs[schemas.AttrFrequencyPenalty] = *req.Params.FrequencyPenalty } if req.Params.ParallelToolCalls != nil { attrs[schemas.AttrParallelToolCall] = *req.Params.ParallelToolCalls } if req.Params.User != nil { attrs[schemas.AttrRequestUser] = *req.Params.User } // ExtraParams for k, v := range req.Params.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // Extract input messages if req.Input != nil { attrs[schemas.AttrMessageCount] = len(req.Input) messages := extractChatMessages(req.Input) if len(messages) > 0 { attrs[schemas.AttrInputMessages] = messages } } } // PopulateChatResponseAttributes extracts chat completion response attributes. func PopulateChatResponseAttributes(resp *schemas.BifrostChatResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrResponseID] = resp.ID attrs[schemas.AttrResponseModel] = resp.Model if resp.Object != "" { attrs[schemas.AttrObject] = resp.Object } if resp.SystemFingerprint != "" { attrs[schemas.AttrSystemFprint] = resp.SystemFingerprint } attrs[schemas.AttrCreated] = resp.Created if resp.ServiceTier != nil { attrs[schemas.AttrServiceTier] = *resp.ServiceTier } // Extract output messages outputMessages := extractChatResponseMessages(resp) if len(outputMessages) > 0 { attrs[schemas.AttrOutputMessages] = outputMessages } // Extract finish reason from first choice if len(resp.Choices) > 0 && resp.Choices[0].FinishReason != nil { attrs[schemas.AttrFinishReason] = *resp.Choices[0].FinishReason } // Usage if resp.Usage != nil { attrs[schemas.AttrPromptTokens] = resp.Usage.PromptTokens attrs[schemas.AttrCompletionTokens] = resp.Usage.CompletionTokens attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens if resp.Usage.PromptTokensDetails != nil { if resp.Usage.PromptTokensDetails.TextTokens > 0 { attrs[schemas.AttrPromptTokenDetailsText] = resp.Usage.PromptTokensDetails.TextTokens } if resp.Usage.PromptTokensDetails.AudioTokens > 0 { attrs[schemas.AttrPromptTokenDetailsAudio] = resp.Usage.PromptTokensDetails.AudioTokens } if resp.Usage.PromptTokensDetails.ImageTokens > 0 { attrs[schemas.AttrPromptTokenDetailsImage] = resp.Usage.PromptTokensDetails.ImageTokens } if resp.Usage.PromptTokensDetails.CachedReadTokens > 0 { attrs[schemas.AttrPromptTokenDetailsCachedRead] = resp.Usage.PromptTokensDetails.CachedReadTokens } if resp.Usage.PromptTokensDetails.CachedWriteTokens > 0 { attrs[schemas.AttrPromptTokenDetailsCachedWrite] = resp.Usage.PromptTokensDetails.CachedWriteTokens } } if resp.Usage.CompletionTokensDetails != nil { if resp.Usage.CompletionTokensDetails.TextTokens > 0 { attrs[schemas.AttrCompletionTokenDetailsText] = resp.Usage.CompletionTokensDetails.TextTokens } if resp.Usage.CompletionTokensDetails.AudioTokens > 0 { attrs[schemas.AttrCompletionTokenDetailsAudio] = resp.Usage.CompletionTokensDetails.AudioTokens } if resp.Usage.CompletionTokensDetails.ImageTokens != nil && *resp.Usage.CompletionTokensDetails.ImageTokens > 0 { attrs[schemas.AttrCompletionTokenDetailsImage] = *resp.Usage.CompletionTokensDetails.ImageTokens } if resp.Usage.CompletionTokensDetails.ReasoningTokens > 0 { attrs[schemas.AttrCompletionTokenDetailsReason] = resp.Usage.CompletionTokensDetails.ReasoningTokens } if resp.Usage.CompletionTokensDetails.AcceptedPredictionTokens > 0 { attrs[schemas.AttrCompletionTokenDetailsAccept] = resp.Usage.CompletionTokensDetails.AcceptedPredictionTokens } if resp.Usage.CompletionTokensDetails.RejectedPredictionTokens > 0 { attrs[schemas.AttrCompletionTokenDetailsReject] = resp.Usage.CompletionTokensDetails.RejectedPredictionTokens } if resp.Usage.CompletionTokensDetails.CitationTokens != nil && *resp.Usage.CompletionTokensDetails.CitationTokens > 0 { attrs[schemas.AttrCompletionTokenDetailsCite] = *resp.Usage.CompletionTokensDetails.CitationTokens } if resp.Usage.CompletionTokensDetails.NumSearchQueries != nil && *resp.Usage.CompletionTokensDetails.NumSearchQueries > 0 { attrs[schemas.AttrCompletionTokenDetailsSearch] = *resp.Usage.CompletionTokensDetails.NumSearchQueries } } } } // =============================================== // Text Completion Request/Response // =============================================== // PopulateTextCompletionRequestAttributes extracts text completion request attributes. func PopulateTextCompletionRequestAttributes(req *schemas.BifrostTextCompletionRequest, attrs map[string]any) { if req == nil { return } if req.Params != nil { if req.Params.MaxTokens != nil { attrs[schemas.AttrMaxTokens] = *req.Params.MaxTokens } if req.Params.Temperature != nil { attrs[schemas.AttrTemperature] = *req.Params.Temperature } if req.Params.TopP != nil { attrs[schemas.AttrTopP] = *req.Params.TopP } if req.Params.Stop != nil { attrs[schemas.AttrStopSequences] = strings.Join(req.Params.Stop, ",") } if req.Params.PresencePenalty != nil { attrs[schemas.AttrPresencePenalty] = *req.Params.PresencePenalty } if req.Params.FrequencyPenalty != nil { attrs[schemas.AttrFrequencyPenalty] = *req.Params.FrequencyPenalty } if req.Params.BestOf != nil { attrs[schemas.AttrBestOf] = *req.Params.BestOf } if req.Params.Echo != nil { attrs[schemas.AttrEcho] = *req.Params.Echo } if req.Params.LogitBias != nil { attrs[schemas.AttrLogitBias] = fmt.Sprintf("%v", req.Params.LogitBias) } if req.Params.LogProbs != nil { attrs[schemas.AttrLogProbs] = *req.Params.LogProbs } if req.Params.N != nil { attrs[schemas.AttrN] = *req.Params.N } if req.Params.Seed != nil { attrs[schemas.AttrSeed] = *req.Params.Seed } if req.Params.Suffix != nil { attrs[schemas.AttrSuffix] = *req.Params.Suffix } if req.Params.User != nil { attrs[schemas.AttrRequestUser] = *req.Params.User } // ExtraParams for k, v := range req.Params.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // Extract input text if req.Input != nil { if req.Input.PromptStr != nil { attrs[schemas.AttrInputText] = *req.Input.PromptStr } else if req.Input.PromptArray != nil { attrs[schemas.AttrInputText] = strings.Join(req.Input.PromptArray, ",") } } } // PopulateTextCompletionResponseAttributes extracts text completion response attributes. func PopulateTextCompletionResponseAttributes(resp *schemas.BifrostTextCompletionResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrResponseID] = resp.ID attrs[schemas.AttrResponseModel] = resp.Model if resp.Object != "" { attrs[schemas.AttrObject] = resp.Object } if resp.SystemFingerprint != "" { attrs[schemas.AttrSystemFprint] = resp.SystemFingerprint } // Extract output text var outputs []string for _, choice := range resp.Choices { if choice.TextCompletionResponseChoice != nil && choice.TextCompletionResponseChoice.Text != nil { outputs = append(outputs, *choice.TextCompletionResponseChoice.Text) } } if len(outputs) > 0 { attrs[schemas.AttrOutputMessages] = outputs } // Usage if resp.Usage != nil { attrs[schemas.AttrPromptTokens] = resp.Usage.PromptTokens attrs[schemas.AttrCompletionTokens] = resp.Usage.CompletionTokens attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens } } // =============================================== // Embedding Request/Response // =============================================== // PopulateEmbeddingRequestAttributes extracts embedding request attributes. func PopulateEmbeddingRequestAttributes(req *schemas.BifrostEmbeddingRequest, attrs map[string]any) { if req == nil { return } if req.Params != nil { if req.Params.Dimensions != nil { attrs[schemas.AttrDimensions] = *req.Params.Dimensions } if req.Params.EncodingFormat != nil { attrs[schemas.AttrEncodingFormat] = *req.Params.EncodingFormat } // ExtraParams for k, v := range req.Params.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // Extract input if req.Input != nil { if req.Input.Text != nil { attrs[schemas.AttrInputText] = *req.Input.Text } else if req.Input.Texts != nil { attrs[schemas.AttrInputText] = strings.Join(req.Input.Texts, ",") } else if req.Input.Embedding != nil { embedding := make([]string, len(req.Input.Embedding)) for i, v := range req.Input.Embedding { // Use a float‑safe representation; adjust precision as needed. embedding[i] = fmt.Sprintf("%v", v) } attrs[schemas.AttrInputEmbedding] = strings.Join(embedding, ",") } } } // PopulateEmbeddingResponseAttributes extracts embedding response attributes. func PopulateEmbeddingResponseAttributes(resp *schemas.BifrostEmbeddingResponse, attrs map[string]any) { if resp == nil { return } // Usage if resp.Usage != nil { attrs[schemas.AttrPromptTokens] = resp.Usage.PromptTokens attrs[schemas.AttrCompletionTokens] = resp.Usage.CompletionTokens attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens } } // =============================================== // Transcription Request/Response // =============================================== // PopulateTranscriptionRequestAttributes extracts transcription request attributes. func PopulateTranscriptionRequestAttributes(req *schemas.BifrostTranscriptionRequest, attrs map[string]any) { if req == nil || req.Params == nil { return } if req.Params.Language != nil { attrs[schemas.AttrLanguage] = *req.Params.Language } if req.Params.Prompt != nil { attrs[schemas.AttrPrompt] = *req.Params.Prompt } if req.Params.ResponseFormat != nil { attrs[schemas.AttrResponseFormat] = *req.Params.ResponseFormat } if req.Params.Format != nil { attrs[schemas.AttrFormat] = *req.Params.Format } } // PopulateTranscriptionResponseAttributes extracts transcription response attributes. func PopulateTranscriptionResponseAttributes(resp *schemas.BifrostTranscriptionResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrOutputMessages] = resp.Text // Usage if resp.Usage != nil { if resp.Usage.InputTokens != nil { attrs[schemas.AttrInputTokens] = *resp.Usage.InputTokens } if resp.Usage.OutputTokens != nil { attrs[schemas.AttrOutputTokens] = *resp.Usage.OutputTokens } if resp.Usage.TotalTokens != nil { attrs[schemas.AttrTotalTokens] = *resp.Usage.TotalTokens } if resp.Usage.InputTokenDetails != nil { attrs[schemas.AttrInputTokenDetailsText] = resp.Usage.InputTokenDetails.TextTokens attrs[schemas.AttrInputTokenDetailsAudio] = resp.Usage.InputTokenDetails.AudioTokens } } } // =============================================== // Speech Request/Response // =============================================== // PopulateSpeechRequestAttributes extracts speech request attributes. func PopulateSpeechRequestAttributes(req *schemas.BifrostSpeechRequest, attrs map[string]any) { if req == nil { return } if req.Params != nil { if req.Params.VoiceConfig != nil { if req.Params.VoiceConfig.Voice != nil { attrs[schemas.AttrVoice] = *req.Params.VoiceConfig.Voice } if len(req.Params.VoiceConfig.MultiVoiceConfig) > 0 { voices := make([]string, len(req.Params.VoiceConfig.MultiVoiceConfig)) for i, vc := range req.Params.VoiceConfig.MultiVoiceConfig { voices[i] = vc.Voice } attrs[schemas.AttrMultiVoiceConfig] = strings.Join(voices, ",") } } if req.Params.Instructions != "" { attrs[schemas.AttrInstructions] = req.Params.Instructions } if req.Params.ResponseFormat != "" { attrs[schemas.AttrResponseFormat] = req.Params.ResponseFormat } if req.Params.Speed != nil { attrs[schemas.AttrSpeed] = *req.Params.Speed } } if req.Input != nil && req.Input.Input != "" { attrs[schemas.AttrInputSpeech] = req.Input.Input } } // PopulateSpeechResponseAttributes extracts speech response attributes. func PopulateSpeechResponseAttributes(resp *schemas.BifrostSpeechResponse, attrs map[string]any) { if resp == nil { return } // Usage if resp.Usage != nil { attrs[schemas.AttrInputTokens] = resp.Usage.InputTokens attrs[schemas.AttrOutputTokens] = resp.Usage.OutputTokens attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens } } // =============================================== // Responses API Request/Response // =============================================== // PopulateResponsesRequestAttributes extracts responses API request attributes. func PopulateResponsesRequestAttributes(req *schemas.BifrostResponsesRequest, attrs map[string]any) { if req == nil || req.Params == nil { return } if req.Input != nil { attrs[schemas.AttrMessageCount] = len(req.Input) inputMessages := extractResponsesInputMessages(req.Input) if len(inputMessages) > 0 { attrs[schemas.AttrInputMessages] = inputMessages } } if req.Params.ParallelToolCalls != nil { attrs[schemas.AttrParallelToolCall] = *req.Params.ParallelToolCalls } if req.Params.PromptCacheKey != nil { attrs[schemas.AttrPromptCacheKey] = *req.Params.PromptCacheKey } if req.Params.Reasoning != nil { if req.Params.Reasoning.Effort != nil { attrs[schemas.AttrReasoningEffort] = *req.Params.Reasoning.Effort } if req.Params.Reasoning.Summary != nil { attrs[schemas.AttrReasoningSummary] = *req.Params.Reasoning.Summary } if req.Params.Reasoning.GenerateSummary != nil { attrs[schemas.AttrReasoningGenSummary] = *req.Params.Reasoning.GenerateSummary } } if req.Params.SafetyIdentifier != nil { attrs[schemas.AttrSafetyIdentifier] = *req.Params.SafetyIdentifier } if req.Params.ServiceTier != nil { attrs[schemas.AttrServiceTier] = *req.Params.ServiceTier } if req.Params.Store != nil { attrs[schemas.AttrStore] = *req.Params.Store } if req.Params.Temperature != nil { attrs[schemas.AttrTemperature] = *req.Params.Temperature } if req.Params.Text != nil { if req.Params.Text.Verbosity != nil { attrs[schemas.AttrTextVerbosity] = *req.Params.Text.Verbosity } if req.Params.Text.Format != nil { attrs[schemas.AttrTextFormatType] = req.Params.Text.Format.Type } } if req.Params.TopLogProbs != nil { attrs[schemas.AttrTopLogProbs] = *req.Params.TopLogProbs } if req.Params.TopP != nil { attrs[schemas.AttrTopP] = *req.Params.TopP } if req.Params.ToolChoice != nil { if req.Params.ToolChoice.ResponsesToolChoiceStr != nil && *req.Params.ToolChoice.ResponsesToolChoiceStr != "" { attrs[schemas.AttrToolChoiceType] = *req.Params.ToolChoice.ResponsesToolChoiceStr } if req.Params.ToolChoice.ResponsesToolChoiceStruct != nil && req.Params.ToolChoice.ResponsesToolChoiceStruct.Name != nil { attrs[schemas.AttrToolChoiceName] = *req.Params.ToolChoice.ResponsesToolChoiceStruct.Name } } if req.Params.Tools != nil { tools := make([]string, len(req.Params.Tools)) for i, tool := range req.Params.Tools { tools[i] = string(tool.Type) } attrs[schemas.AttrTools] = strings.Join(tools, ",") } if req.Params.Truncation != nil { attrs[schemas.AttrTruncation] = *req.Params.Truncation } // ExtraParams for k, v := range req.Params.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateResponsesResponseAttributes extracts responses API response attributes. func PopulateResponsesResponseAttributes(resp *schemas.BifrostResponsesResponse, attrs map[string]any) { if resp == nil { return } if resp.ID != nil && *resp.ID != "" { attrs[schemas.AttrResponseID] = *resp.ID } if resp.Model != "" { attrs[schemas.AttrResponseModel] = resp.Model } if resp.ServiceTier != nil { attrs[schemas.AttrServiceTier] = *resp.ServiceTier } // Extract output messages (includes reasoning) outputMessages := extractResponsesOutputMessages(resp) if len(outputMessages) > 0 { attrs[schemas.AttrOutputMessages] = outputMessages } // Additional response fields if resp.Include != nil { attrs[schemas.AttrRespInclude] = strings.Join(resp.Include, ",") } if resp.MaxOutputTokens != nil { attrs[schemas.AttrRespMaxOutputTokens] = *resp.MaxOutputTokens } if resp.MaxToolCalls != nil { attrs[schemas.AttrRespMaxToolCalls] = *resp.MaxToolCalls } if resp.Metadata != nil { attrs[schemas.AttrRespMetadata] = fmt.Sprintf("%v", resp.Metadata) } if resp.PreviousResponseID != nil { attrs[schemas.AttrRespPreviousRespID] = *resp.PreviousResponseID } if resp.PromptCacheKey != nil { attrs[schemas.AttrRespPromptCacheKey] = *resp.PromptCacheKey } if resp.Reasoning != nil { if resp.Reasoning.Summary != nil { attrs[schemas.AttrRespReasoningText] = *resp.Reasoning.Summary } if resp.Reasoning.Effort != nil { attrs[schemas.AttrRespReasoningEffort] = *resp.Reasoning.Effort } if resp.Reasoning.GenerateSummary != nil { attrs[schemas.AttrRespReasoningGenSum] = *resp.Reasoning.GenerateSummary } } if resp.SafetyIdentifier != nil { attrs[schemas.AttrRespSafetyIdentifier] = *resp.SafetyIdentifier } if resp.Store != nil { attrs[schemas.AttrRespStore] = *resp.Store } if resp.Temperature != nil { attrs[schemas.AttrRespTemperature] = *resp.Temperature } if resp.Text != nil { if resp.Text.Verbosity != nil { attrs[schemas.AttrRespTextVerbosity] = *resp.Text.Verbosity } if resp.Text.Format != nil { attrs[schemas.AttrRespTextFormatType] = resp.Text.Format.Type } } if resp.TopLogProbs != nil { attrs[schemas.AttrRespTopLogProbs] = *resp.TopLogProbs } if resp.TopP != nil { attrs[schemas.AttrRespTopP] = *resp.TopP } if resp.ToolChoice != nil { if resp.ToolChoice.ResponsesToolChoiceStr != nil { attrs[schemas.AttrRespToolChoiceType] = *resp.ToolChoice.ResponsesToolChoiceStr } if resp.ToolChoice.ResponsesToolChoiceStruct != nil && resp.ToolChoice.ResponsesToolChoiceStruct.Name != nil { attrs[schemas.AttrRespToolChoiceName] = *resp.ToolChoice.ResponsesToolChoiceStruct.Name } } if resp.Truncation != nil { attrs[schemas.AttrRespTruncation] = *resp.Truncation } if resp.Tools != nil { tools := make([]string, len(resp.Tools)) for i, tool := range resp.Tools { tools[i] = string(tool.Type) } attrs[schemas.AttrRespTools] = strings.Join(tools, ",") } // Usage if resp.Usage != nil { attrs[schemas.AttrInputTokens] = resp.Usage.InputTokens attrs[schemas.AttrOutputTokens] = resp.Usage.OutputTokens attrs[schemas.AttrTotalTokens] = resp.Usage.TotalTokens } } // =============================================== // Batch Operations Request/Response // =============================================== // PopulateBatchCreateRequestAttributes extracts batch create request attributes. func PopulateBatchCreateRequestAttributes(req *schemas.BifrostBatchCreateRequest, attrs map[string]any) { if req == nil { return } if req.InputFileID != "" { attrs[schemas.AttrBatchInputFileID] = req.InputFileID } if req.Endpoint != "" { attrs[schemas.AttrBatchEndpoint] = string(req.Endpoint) } if req.CompletionWindow != "" { attrs[schemas.AttrBatchCompletionWin] = req.CompletionWindow } if len(req.Requests) > 0 { attrs[schemas.AttrBatchRequestsCount] = len(req.Requests) } if len(req.Metadata) > 0 { attrs[schemas.AttrBatchMetadata] = fmt.Sprintf("%v", req.Metadata) } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateBatchListRequestAttributes extracts batch list request attributes. func PopulateBatchListRequestAttributes(req *schemas.BifrostBatchListRequest, attrs map[string]any) { if req == nil { return } if req.Limit > 0 { attrs[schemas.AttrBatchLimit] = req.Limit } if req.After != nil { attrs[schemas.AttrBatchAfter] = *req.After } if req.BeforeID != nil { attrs[schemas.AttrBatchBeforeID] = *req.BeforeID } if req.AfterID != nil { attrs[schemas.AttrBatchAfterID] = *req.AfterID } if req.PageToken != nil { attrs[schemas.AttrBatchPageToken] = *req.PageToken } if req.PageSize > 0 { attrs[schemas.AttrBatchPageSize] = req.PageSize } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateBatchRetrieveRequestAttributes extracts batch retrieve request attributes. func PopulateBatchRetrieveRequestAttributes(req *schemas.BifrostBatchRetrieveRequest, attrs map[string]any) { if req == nil { return } if req.BatchID != "" { attrs[schemas.AttrBatchID] = req.BatchID } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateBatchCancelRequestAttributes extracts batch cancel request attributes. func PopulateBatchCancelRequestAttributes(req *schemas.BifrostBatchCancelRequest, attrs map[string]any) { if req == nil { return } if req.BatchID != "" { attrs[schemas.AttrBatchID] = req.BatchID } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateBatchResultsRequestAttributes extracts batch results request attributes. func PopulateBatchResultsRequestAttributes(req *schemas.BifrostBatchResultsRequest, attrs map[string]any) { if req == nil { return } if req.BatchID != "" { attrs[schemas.AttrBatchID] = req.BatchID } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateBatchCreateResponseAttributes extracts batch create response attributes. func PopulateBatchCreateResponseAttributes(resp *schemas.BifrostBatchCreateResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrBatchID] = resp.ID attrs[schemas.AttrBatchStatus] = string(resp.Status) if resp.Object != "" { attrs[schemas.AttrBatchObject] = resp.Object } if resp.Endpoint != "" { attrs[schemas.AttrBatchEndpoint] = resp.Endpoint } if resp.InputFileID != "" { attrs[schemas.AttrBatchInputFileID] = resp.InputFileID } if resp.CompletionWindow != "" { attrs[schemas.AttrBatchCompletionWin] = resp.CompletionWindow } if resp.CreatedAt != 0 { attrs[schemas.AttrBatchCreatedAt] = resp.CreatedAt } if resp.ExpiresAt != nil { attrs[schemas.AttrBatchExpiresAt] = *resp.ExpiresAt } if resp.OutputFileID != nil { attrs[schemas.AttrBatchOutputFileID] = *resp.OutputFileID } if resp.ErrorFileID != nil { attrs[schemas.AttrBatchErrorFileID] = *resp.ErrorFileID } attrs[schemas.AttrBatchCountTotal] = resp.RequestCounts.Total attrs[schemas.AttrBatchCountCompleted] = resp.RequestCounts.Completed attrs[schemas.AttrBatchCountFailed] = resp.RequestCounts.Failed } // PopulateBatchListResponseAttributes extracts batch list response attributes. func PopulateBatchListResponseAttributes(resp *schemas.BifrostBatchListResponse, attrs map[string]any) { if resp == nil { return } if resp.Object != "" { attrs[schemas.AttrBatchObject] = resp.Object } attrs[schemas.AttrBatchDataCount] = len(resp.Data) attrs[schemas.AttrBatchHasMore] = resp.HasMore if resp.FirstID != nil { attrs[schemas.AttrBatchFirstID] = *resp.FirstID } if resp.LastID != nil { attrs[schemas.AttrBatchLastID] = *resp.LastID } } // PopulateBatchRetrieveResponseAttributes extracts batch retrieve response attributes. func PopulateBatchRetrieveResponseAttributes(resp *schemas.BifrostBatchRetrieveResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrBatchID] = resp.ID attrs[schemas.AttrBatchStatus] = string(resp.Status) if resp.Object != "" { attrs[schemas.AttrBatchObject] = resp.Object } if resp.Endpoint != "" { attrs[schemas.AttrBatchEndpoint] = resp.Endpoint } if resp.InputFileID != "" { attrs[schemas.AttrBatchInputFileID] = resp.InputFileID } if resp.CompletionWindow != "" { attrs[schemas.AttrBatchCompletionWin] = resp.CompletionWindow } if resp.CreatedAt != 0 { attrs[schemas.AttrBatchCreatedAt] = resp.CreatedAt } if resp.ExpiresAt != nil { attrs[schemas.AttrBatchExpiresAt] = *resp.ExpiresAt } if resp.InProgressAt != nil { attrs[schemas.AttrBatchInProgressAt] = *resp.InProgressAt } if resp.FinalizingAt != nil { attrs[schemas.AttrBatchFinalizingAt] = *resp.FinalizingAt } if resp.CompletedAt != nil { attrs[schemas.AttrBatchCompletedAt] = *resp.CompletedAt } if resp.FailedAt != nil { attrs[schemas.AttrBatchFailedAt] = *resp.FailedAt } if resp.ExpiredAt != nil { attrs[schemas.AttrBatchExpiredAt] = *resp.ExpiredAt } if resp.CancellingAt != nil { attrs[schemas.AttrBatchCancellingAt] = *resp.CancellingAt } if resp.CancelledAt != nil { attrs[schemas.AttrBatchCancelledAt] = *resp.CancelledAt } if resp.OutputFileID != nil { attrs[schemas.AttrBatchOutputFileID] = *resp.OutputFileID } if resp.ErrorFileID != nil { attrs[schemas.AttrBatchErrorFileID] = *resp.ErrorFileID } attrs[schemas.AttrBatchCountTotal] = resp.RequestCounts.Total attrs[schemas.AttrBatchCountCompleted] = resp.RequestCounts.Completed attrs[schemas.AttrBatchCountFailed] = resp.RequestCounts.Failed } // PopulateBatchCancelResponseAttributes extracts batch cancel response attributes. func PopulateBatchCancelResponseAttributes(resp *schemas.BifrostBatchCancelResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrBatchID] = resp.ID attrs[schemas.AttrBatchStatus] = string(resp.Status) if resp.Object != "" { attrs[schemas.AttrBatchObject] = resp.Object } if resp.CancellingAt != nil { attrs[schemas.AttrBatchCancellingAt] = *resp.CancellingAt } if resp.CancelledAt != nil { attrs[schemas.AttrBatchCancelledAt] = *resp.CancelledAt } attrs[schemas.AttrBatchCountTotal] = resp.RequestCounts.Total attrs[schemas.AttrBatchCountCompleted] = resp.RequestCounts.Completed attrs[schemas.AttrBatchCountFailed] = resp.RequestCounts.Failed } // PopulateBatchResultsResponseAttributes extracts batch results response attributes. func PopulateBatchResultsResponseAttributes(resp *schemas.BifrostBatchResultsResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrBatchID] = resp.BatchID attrs[schemas.AttrBatchResultsCount] = len(resp.Results) attrs[schemas.AttrBatchHasMore] = resp.HasMore if resp.NextCursor != nil { attrs[schemas.AttrBatchNextCursor] = *resp.NextCursor } } // =============================================== // File Operations Request/Response // =============================================== // PopulateFileUploadRequestAttributes extracts file upload request attributes. func PopulateFileUploadRequestAttributes(req *schemas.BifrostFileUploadRequest, attrs map[string]any) { if req == nil { return } if req.Filename != "" { attrs[schemas.AttrFileFilename] = req.Filename } if req.Purpose != "" { attrs[schemas.AttrFilePurpose] = string(req.Purpose) } if len(req.File) > 0 { attrs[schemas.AttrFileBytes] = len(req.File) } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateFileListRequestAttributes extracts file list request attributes. func PopulateFileListRequestAttributes(req *schemas.BifrostFileListRequest, attrs map[string]any) { if req == nil { return } if req.Purpose != "" { attrs[schemas.AttrFilePurpose] = string(req.Purpose) } if req.Limit > 0 { attrs[schemas.AttrFileLimit] = req.Limit } if req.After != nil { attrs[schemas.AttrFileAfter] = *req.After } if req.Order != nil { attrs[schemas.AttrFileOrder] = *req.Order } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateFileRetrieveRequestAttributes extracts file retrieve request attributes. func PopulateFileRetrieveRequestAttributes(req *schemas.BifrostFileRetrieveRequest, attrs map[string]any) { if req == nil { return } if req.FileID != "" { attrs[schemas.AttrFileID] = req.FileID } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateFileDeleteRequestAttributes extracts file delete request attributes. func PopulateFileDeleteRequestAttributes(req *schemas.BifrostFileDeleteRequest, attrs map[string]any) { if req == nil { return } if req.FileID != "" { attrs[schemas.AttrFileID] = req.FileID } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateFileContentRequestAttributes extracts file content request attributes. func PopulateFileContentRequestAttributes(req *schemas.BifrostFileContentRequest, attrs map[string]any) { if req == nil { return } if req.FileID != "" { attrs[schemas.AttrFileID] = req.FileID } // ExtraParams for k, v := range req.ExtraParams { attrs[k] = fmt.Sprintf("%v", v) } } // PopulateFileUploadResponseAttributes extracts file upload response attributes. func PopulateFileUploadResponseAttributes(resp *schemas.BifrostFileUploadResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrFileID] = resp.ID if resp.Object != "" { attrs[schemas.AttrFileObject] = resp.Object } attrs[schemas.AttrFileBytes] = resp.Bytes attrs[schemas.AttrFileCreatedAt] = resp.CreatedAt attrs[schemas.AttrFileFilename] = resp.Filename attrs[schemas.AttrFilePurpose] = string(resp.Purpose) if resp.Status != "" { attrs[schemas.AttrFileStatus] = string(resp.Status) } if resp.StorageBackend != "" { attrs[schemas.AttrFileStorageBackend] = string(resp.StorageBackend) } } // PopulateFileListResponseAttributes extracts file list response attributes. func PopulateFileListResponseAttributes(resp *schemas.BifrostFileListResponse, attrs map[string]any) { if resp == nil { return } if resp.Object != "" { attrs[schemas.AttrFileObject] = resp.Object } attrs[schemas.AttrFileDataCount] = len(resp.Data) attrs[schemas.AttrFileHasMore] = resp.HasMore } // PopulateFileRetrieveResponseAttributes extracts file retrieve response attributes. func PopulateFileRetrieveResponseAttributes(resp *schemas.BifrostFileRetrieveResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrFileID] = resp.ID if resp.Object != "" { attrs[schemas.AttrFileObject] = resp.Object } attrs[schemas.AttrFileBytes] = resp.Bytes attrs[schemas.AttrFileCreatedAt] = resp.CreatedAt attrs[schemas.AttrFileFilename] = resp.Filename attrs[schemas.AttrFilePurpose] = string(resp.Purpose) if resp.Status != "" { attrs[schemas.AttrFileStatus] = string(resp.Status) } if resp.StorageBackend != "" { attrs[schemas.AttrFileStorageBackend] = string(resp.StorageBackend) } } // PopulateFileDeleteResponseAttributes extracts file delete response attributes. func PopulateFileDeleteResponseAttributes(resp *schemas.BifrostFileDeleteResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrFileID] = resp.ID if resp.Object != "" { attrs[schemas.AttrFileObject] = resp.Object } attrs[schemas.AttrFileDeleted] = resp.Deleted } // PopulateFileContentResponseAttributes extracts file content response attributes. func PopulateFileContentResponseAttributes(resp *schemas.BifrostFileContentResponse, attrs map[string]any) { if resp == nil { return } attrs[schemas.AttrFileID] = resp.FileID if resp.ContentType != "" { attrs[schemas.AttrFileContentType] = resp.ContentType } if len(resp.Content) > 0 { attrs[schemas.AttrFileContentBytes] = len(resp.Content) } } // =============================================== // Helper functions for extracting messages // =============================================== // MessageSummary represents a summarized chat message for tracing type MessageSummary struct { Role string `json:"role"` Content string `json:"content"` ToolCalls []ToolCallSummary `json:"tool_calls,omitempty"` Reasoning string `json:"reasoning,omitempty"` ReasoningDetails []ReasoningDetailSummary `json:"reasoning_details,omitempty"` Audio *AudioSummary `json:"audio,omitempty"` Refusal string `json:"refusal,omitempty"` } // ToolCallSummary represents a summarized tool call for tracing type ToolCallSummary struct { ID string `json:"id"` Type string `json:"type"` Name string `json:"name"` Args string `json:"args,omitempty"` } // ReasoningDetailSummary represents a summarized reasoning detail for tracing type ReasoningDetailSummary struct { Type string `json:"type"` Text string `json:"text,omitempty"` } // AudioSummary represents summarized audio data for tracing type AudioSummary struct { ID string `json:"id,omitempty"` Transcript string `json:"transcript,omitempty"` } // extractChatMessages extracts chat messages into a slice of MessageSummary func extractChatMessages(messages []schemas.ChatMessage) []MessageSummary { result := make([]MessageSummary, 0, len(messages)) for _, msg := range messages { summary := extractMessageSummary(&msg) result = append(result, summary) } return result } // extractChatResponseMessages extracts output messages from chat response func extractChatResponseMessages(resp *schemas.BifrostChatResponse) []MessageSummary { if resp == nil { return nil } result := make([]MessageSummary, 0, len(resp.Choices)) for _, choice := range resp.Choices { if choice.ChatNonStreamResponseChoice == nil || choice.ChatNonStreamResponseChoice.Message == nil { continue } msg := choice.ChatNonStreamResponseChoice.Message summary := extractMessageSummary(msg) result = append(result, summary) } return result } // extractMessageSummary extracts a full MessageSummary from a ChatMessage func extractMessageSummary(msg *schemas.ChatMessage) MessageSummary { if msg == nil { return MessageSummary{} } summary := MessageSummary{ Role: string(schemas.ChatMessageRoleAssistant), Content: extractMessageContent(msg.Content), } if msg.Role != "" { summary.Role = string(msg.Role) } // Extract assistant-specific fields if msg.ChatAssistantMessage != nil { am := msg.ChatAssistantMessage // Extract refusal if am.Refusal != nil && *am.Refusal != "" { summary.Refusal = *am.Refusal } // Extract reasoning if am.Reasoning != nil && *am.Reasoning != "" { summary.Reasoning = *am.Reasoning } // Extract reasoning details if len(am.ReasoningDetails) > 0 { summary.ReasoningDetails = make([]ReasoningDetailSummary, 0, len(am.ReasoningDetails)) for _, rd := range am.ReasoningDetails { detail := ReasoningDetailSummary{ Type: string(rd.Type), } if rd.Text != nil { detail.Text = *rd.Text } summary.ReasoningDetails = append(summary.ReasoningDetails, detail) } } // Extract audio if am.Audio != nil { summary.Audio = &AudioSummary{ ID: am.Audio.ID, Transcript: am.Audio.Transcript, } } // Extract tool calls if len(am.ToolCalls) > 0 { summary.ToolCalls = make([]ToolCallSummary, 0, len(am.ToolCalls)) for _, tc := range am.ToolCalls { toolCall := ToolCallSummary{ Type: "function", } if tc.ID != nil { toolCall.ID = *tc.ID } if tc.Type != nil { toolCall.Type = *tc.Type } if tc.Function.Name != nil { toolCall.Name = *tc.Function.Name } toolCall.Args = tc.Function.Arguments summary.ToolCalls = append(summary.ToolCalls, toolCall) } } } return summary } // ResponsesMessageSummary extends MessageSummary with reasoning type ResponsesMessageSummary struct { Role string `json:"role"` Content string `json:"content"` Reasoning string `json:"reasoning,omitempty"` ToolCalls []ToolCallSummary `json:"tool_calls,omitempty"` ToolCallID string `json:"tool_call_id,omitempty"` } // extractResponsesOutputMessages extracts output messages from a Responses API response. func extractResponsesOutputMessages(resp *schemas.BifrostResponsesResponse) []ResponsesMessageSummary { if resp == nil { return nil } result := make([]ResponsesMessageSummary, 0, len(resp.Output)) for _, msg := range resp.Output { msgType := schemas.ResponsesMessageTypeMessage if msg.Type != nil { msgType = *msg.Type } switch msgType { case schemas.ResponsesMessageTypeMessage: if msg.Role == nil { continue } result = append(result, ResponsesMessageSummary{ Role: string(*msg.Role), Content: extractResponsesMessageTextContent(&msg), Reasoning: extractResponsesReasoning(msg.ResponsesReasoning), }) case schemas.ResponsesMessageTypeReasoning: reasoning := extractResponsesReasoning(msg.ResponsesReasoning) if reasoning == "" { continue } result = append(result, ResponsesMessageSummary{ Role: "assistant", Reasoning: reasoning, }) case schemas.ResponsesMessageTypeFunctionCall: if msg.ResponsesToolMessage == nil || msg.ResponsesToolMessage.Name == nil { continue } tc := ToolCallSummary{ Type: "function", Name: *msg.ResponsesToolMessage.Name, } if msg.ID != nil { tc.ID = *msg.ID } if msg.ResponsesToolMessage.Arguments != nil { tc.Args = *msg.ResponsesToolMessage.Arguments } result = append(result, ResponsesMessageSummary{ Role: "assistant", ToolCalls: []ToolCallSummary{tc}, }) case schemas.ResponsesMessageTypeMCPCall: if msg.ResponsesToolMessage == nil || msg.ResponsesToolMessage.Name == nil { continue } tc := ToolCallSummary{ Type: "mcp", Name: *msg.ResponsesToolMessage.Name, } if msg.ID != nil { tc.ID = *msg.ID } if msg.ResponsesToolMessage.Arguments != nil { tc.Args = *msg.ResponsesToolMessage.Arguments } result = append(result, ResponsesMessageSummary{ Role: "assistant", ToolCalls: []ToolCallSummary{tc}, }) case schemas.ResponsesMessageTypeWebSearchCall: result = append(result, ResponsesMessageSummary{ Role: "assistant", Content: "[web_search_call]", }) case schemas.ResponsesMessageTypeComputerCall: result = append(result, ResponsesMessageSummary{ Role: "assistant", Content: "[computer_call]", }) case schemas.ResponsesMessageTypeFileSearchCall, schemas.ResponsesMessageTypeCodeInterpreterCall, schemas.ResponsesMessageTypeLocalShellCall, schemas.ResponsesMessageTypeCustomToolCall, schemas.ResponsesMessageTypeImageGenerationCall: name := "" if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.Name != nil { name = *msg.ResponsesToolMessage.Name } content := "[" + string(msgType) + "]" if name != "" { content += " " + name } result = append(result, ResponsesMessageSummary{ Role: "assistant", Content: content, }) default: continue } } return result } // extractResponsesInputMessages extracts input messages from a Responses API request. func extractResponsesInputMessages(messages []schemas.ResponsesMessage) []ResponsesMessageSummary { if len(messages) == 0 { return nil } result := make([]ResponsesMessageSummary, 0, len(messages)) for _, msg := range messages { // Nil Type is treated as "message", consistent with the provider converters. msgType := schemas.ResponsesMessageTypeMessage if msg.Type != nil { msgType = *msg.Type } switch msgType { case schemas.ResponsesMessageTypeMessage: role := "" if msg.Role != nil { role = string(*msg.Role) } summary := ResponsesMessageSummary{ Role: role, Content: extractResponsesMessageTextContent(&msg), Reasoning: extractResponsesReasoning(msg.ResponsesReasoning), } result = append(result, summary) case schemas.ResponsesMessageTypeReasoning: reasoning := extractResponsesReasoning(msg.ResponsesReasoning) if reasoning == "" { continue } result = append(result, ResponsesMessageSummary{ Role: "assistant", Reasoning: reasoning, }) case schemas.ResponsesMessageTypeFunctionCall: if msg.ResponsesToolMessage == nil || msg.ResponsesToolMessage.Name == nil { continue } tc := ToolCallSummary{ Type: "function", Name: *msg.ResponsesToolMessage.Name, } if msg.ID != nil { tc.ID = *msg.ID } if msg.ResponsesToolMessage.Arguments != nil { tc.Args = *msg.ResponsesToolMessage.Arguments } result = append(result, ResponsesMessageSummary{ Role: "assistant", ToolCalls: []ToolCallSummary{tc}, }) case schemas.ResponsesMessageTypeFunctionCallOutput: if msg.ResponsesToolMessage == nil { continue } summary := ResponsesMessageSummary{ Role: "tool", Content: extractResponsesToolOutputContent(msg.ResponsesToolMessage.Output), } if msg.ResponsesToolMessage.CallID != nil { summary.ToolCallID = *msg.ResponsesToolMessage.CallID } result = append(result, summary) case schemas.ResponsesMessageTypeMCPCall: if msg.ResponsesToolMessage == nil { continue } if msg.ResponsesToolMessage.Name != nil { // Outbound MCP tool call (assistant side) tc := ToolCallSummary{ Type: "mcp", Name: *msg.ResponsesToolMessage.Name, } if msg.ID != nil { tc.ID = *msg.ID } if msg.ResponsesToolMessage.Arguments != nil { tc.Args = *msg.ResponsesToolMessage.Arguments } result = append(result, ResponsesMessageSummary{ Role: "assistant", ToolCalls: []ToolCallSummary{tc}, }) } else if msg.ResponsesToolMessage.CallID != nil { // Inbound MCP tool result (user side) result = append(result, ResponsesMessageSummary{ Role: "tool", ToolCallID: *msg.ResponsesToolMessage.CallID, Content: extractResponsesToolOutputContent(msg.ResponsesToolMessage.Output), }) } case schemas.ResponsesMessageTypeMCPApprovalRequest: content := "[mcp_approval_request]" if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.Action != nil && msg.ResponsesToolMessage.Action.ResponsesMCPApprovalRequestAction != nil { content = msg.ResponsesToolMessage.Action.ResponsesMCPApprovalRequestAction.Name } result = append(result, ResponsesMessageSummary{ Role: "assistant", Content: content, }) case schemas.ResponsesMessageTypeWebSearchCall: result = append(result, ResponsesMessageSummary{ Role: "assistant", Content: "[web_search_call]", }) case schemas.ResponsesMessageTypeComputerCall: result = append(result, ResponsesMessageSummary{ Role: "assistant", Content: "[computer_call]", }) case schemas.ResponsesMessageTypeComputerCallOutput: result = append(result, ResponsesMessageSummary{ Role: "tool", Content: "[computer_call_output]", }) case schemas.ResponsesMessageTypeFileSearchCall, schemas.ResponsesMessageTypeCodeInterpreterCall, schemas.ResponsesMessageTypeLocalShellCall, schemas.ResponsesMessageTypeCustomToolCall, schemas.ResponsesMessageTypeImageGenerationCall: name := "" if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.Name != nil { name = *msg.ResponsesToolMessage.Name } content := "[" + string(msgType) + "]" if name != "" { content += " " + name } result = append(result, ResponsesMessageSummary{ Role: "assistant", Content: content, }) case schemas.ResponsesMessageTypeLocalShellCallOutput, schemas.ResponsesMessageTypeCustomToolCallOutput: content := "" if msg.ResponsesToolMessage != nil { content = extractResponsesToolOutputContent(msg.ResponsesToolMessage.Output) } summary := ResponsesMessageSummary{ Role: "tool", Content: content, } if msg.ResponsesToolMessage != nil && msg.ResponsesToolMessage.CallID != nil { summary.ToolCallID = *msg.ResponsesToolMessage.CallID } result = append(result, summary) default: continue } } return result } // extractResponsesMessageTextContent extracts plain text from a ResponsesMessage's Content field. func extractResponsesMessageTextContent(msg *schemas.ResponsesMessage) string { if msg.Content == nil { return "" } if msg.Content.ContentStr != nil && *msg.Content.ContentStr != "" { return *msg.Content.ContentStr } var sb strings.Builder for _, block := range msg.Content.ContentBlocks { if block.Text != nil { sb.WriteString(*block.Text) } else if block.ResponsesOutputMessageContentRefusal != nil && block.ResponsesOutputMessageContentRefusal.Refusal != "" { sb.WriteString(block.ResponsesOutputMessageContentRefusal.Refusal) } } return sb.String() } // extractResponsesToolOutputContent extracts text from a ResponsesToolMessageOutputStruct, func extractResponsesToolOutputContent(output *schemas.ResponsesToolMessageOutputStruct) string { if output == nil { return "" } if output.ResponsesToolCallOutputStr != nil { return *output.ResponsesToolCallOutputStr } var sb strings.Builder for _, block := range output.ResponsesFunctionToolCallOutputBlocks { if block.Text != nil { sb.WriteString(*block.Text) } } return sb.String() } // extractResponsesReasoning concatenates all summary text blocks from a ResponsesReasoning. func extractResponsesReasoning(r *schemas.ResponsesReasoning) string { if r == nil { return "" } var sb strings.Builder for _, block := range r.Summary { if block.Text != "" { sb.WriteString(block.Text) } } return sb.String() } // extractMessageContent extracts text content from ChatMessageContent func extractMessageContent(content *schemas.ChatMessageContent) string { if content == nil { return "" } if content.ContentStr != nil { return *content.ContentStr } if content.ContentBlocks != nil { var builder strings.Builder for _, block := range content.ContentBlocks { if block.Text != nil { builder.WriteString(*block.Text) } } return builder.String() } return "" }