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

2346 lines
102 KiB
Go

package schemas
import (
"fmt"
"reflect"
"strings"
"time"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// =============================================================================
// OPENAI RESPONSES API SCHEMAS
// =============================================================================
//
// This file contains all the schema definitions for the OpenAI Responses API.
//
// Structure:
// 1. Core API Request/Response Structures
// 2. Input Message Structures
// 3. Output Message Structures
// 4. Tool Call Structures (organized by tool type)
// 5. Tool Configuration Structures
// 6. Tool Choice Configuration
//
// Union Types:
// - Many structs use "union types" where only one field should be set
// - These are implemented with pointer fields and custom JSON marshaling
// =============================================================================
// =============================================================================
// 1. CORE API REQUEST/RESPONSE STRUCTURES
// =============================================================================
type BifrostResponsesRequest struct {
Provider ModelProvider `json:"provider"`
Model string `json:"model"`
Input []ResponsesMessage `json:"input,omitempty"`
Params *ResponsesParameters `json:"params,omitempty"`
Fallbacks []Fallback `json:"fallbacks,omitempty"`
RawRequestBody []byte `json:"-"` // set bifrost-use-raw-request-body to true in ctx to use the raw request body. Bifrost will directly send this to the downstream provider.
}
func (r *BifrostResponsesRequest) GetRawRequestBody() []byte {
return r.RawRequestBody
}
type BifrostResponsesResponse struct {
ID *string `json:"id,omitempty"` // used for internal conversions
Object string `json:"object"` // "response"
Background *bool `json:"background,omitempty"`
Conversation *ResponsesResponseConversation `json:"conversation,omitempty"`
CreatedAt int `json:"created_at"` // Unix timestamp when Response was created
CompletedAt *int `json:"completed_at"` // Unix timestamp when Response was completed
Error *ResponsesResponseError `json:"error"`
Include []string `json:"include,omitempty"` // Supported values: "web_search_call.action.sources", "code_interpreter_call.outputs", "computer_call_output.output.image_url", "file_search_call.results", "message.input_image.image_url", "message.output_text.logprobs", "reasoning.encrypted_content"
IncompleteDetails *ResponsesResponseIncompleteDetails `json:"incomplete_details"` // Details about why the response is incomplete
Instructions *ResponsesResponseInstructions `json:"instructions"`
MaxOutputTokens *int `json:"max_output_tokens"`
MaxToolCalls *int `json:"max_tool_calls"`
Metadata *map[string]any `json:"metadata,omitempty"`
Model string `json:"model"`
Output []ResponsesMessage `json:"output"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
PreviousResponseID *string `json:"previous_response_id"`
Prompt *ResponsesPrompt `json:"prompt,omitempty"` // Reference to a prompt template and variables
PromptCacheKey *string `json:"prompt_cache_key"` // Prompt cache key
PresencePenalty *float64 `json:"presence_penalty,omitempty"`
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"`
Reasoning *ResponsesParametersReasoning `json:"reasoning"` // Configuration options for reasoning models
SafetyIdentifier *string `json:"safety_identifier"` // Safety identifier
ServiceTier *string `json:"service_tier"`
Status *string `json:"status,omitempty"` // completed, failed, in_progress, cancelled, queued, or incomplete
StreamOptions *ResponsesStreamOptions `json:"stream_options,omitempty"`
StopReason *string `json:"stop_reason,omitempty"` // Not in OpenAI's spec, but sent by other providers
Store *bool `json:"store,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
Text *ResponsesTextConfig `json:"text,omitempty"`
TopLogProbs *int `json:"top_logprobs,omitempty"`
TopP *float64 `json:"top_p,omitempty"` // Controls diversity via nucleus sampling
ToolChoice *ResponsesToolChoice `json:"tool_choice,omitempty"` // Whether to call a tool
Tools []ResponsesTool `json:"tools"` // Tools to use
Truncation *string `json:"truncation,omitempty"`
Usage *ResponsesResponseUsage `json:"usage"`
ExtraFields BifrostResponseExtraFields `json:"extra_fields"`
// Perplexity-specific fields
SearchResults []SearchResult `json:"search_results,omitempty"`
Videos []VideoResult `json:"videos,omitempty"`
Citations []string `json:"citations,omitempty"`
}
// BackfillParams populates response fields from the request that are needed
func (resp *BifrostResponsesResponse) BackfillParams(request *BifrostResponsesRequest) {
if resp == nil || request == nil {
return
}
if resp.Model == "" {
resp.Model = request.Model
}
if resp.Object == "" {
resp.Object = "response"
}
if resp.CreatedAt == 0 {
resp.CreatedAt = int(time.Now().Unix())
}
}
func (resp *BifrostResponsesResponse) WithDefaults() *BifrostResponsesResponse {
if resp == nil {
return nil
}
result := &BifrostResponsesResponse{
ID: resp.ID,
CreatedAt: resp.CreatedAt,
Model: resp.Model,
}
// Object - default: "response"
if resp.Object != "" {
result.Object = resp.Object
} else {
result.Object = "response"
}
result.Conversation = resp.Conversation
result.Include = resp.Include
result.Metadata = resp.Metadata
result.Prompt = resp.Prompt
result.StreamOptions = resp.StreamOptions
result.StopReason = resp.StopReason
result.ExtraFields = resp.ExtraFields
result.SearchResults = resp.SearchResults
result.Videos = resp.Videos
result.Citations = resp.Citations
result.IncompleteDetails = resp.IncompleteDetails
result.PreviousResponseID = resp.PreviousResponseID
result.PromptCacheKey = resp.PromptCacheKey
result.SafetyIdentifier = resp.SafetyIdentifier
result.MaxToolCalls = resp.MaxToolCalls
result.Instructions = resp.Instructions
result.Error = resp.Error
result.CompletedAt = resp.CompletedAt
result.MaxOutputTokens = resp.MaxOutputTokens
// Status - default: "completed"
if resp.Status != nil {
result.Status = resp.Status
} else {
result.Status = Ptr("completed")
}
// Output array - default: empty array
if resp.Output != nil {
result.Output = resp.Output
} else {
result.Output = []ResponsesMessage{}
}
if resp.Reasoning != nil {
result.Reasoning = resp.Reasoning
} else {
result.Reasoning = &ResponsesParametersReasoning{}
}
// Sampling parameters - defaults: standard values
result.Temperature = orDefault(resp.Temperature, 1.0)
result.TopP = orDefault(resp.TopP, 1.0)
result.PresencePenalty = orDefault(resp.PresencePenalty, 0.0)
result.FrequencyPenalty = orDefault(resp.FrequencyPenalty, 0.0)
// Response configuration - defaults: standard behavior
result.Store = orDefault(resp.Store, true)
result.Background = orDefault(resp.Background, false)
result.ServiceTier = orDefault(resp.ServiceTier, "auto")
result.Truncation = orDefault(resp.Truncation, "disabled")
result.ParallelToolCalls = orDefault(resp.ParallelToolCalls, true)
// Token limits - defaults: 0 (unlimited)
result.TopLogProbs = orDefault(resp.TopLogProbs, 0)
// Tools array - default: empty array
if resp.Tools != nil {
result.Tools = resp.Tools
} else {
result.Tools = []ResponsesTool{}
}
// Tool choice - default: "auto"
if resp.ToolChoice != nil {
result.ToolChoice = resp.ToolChoice
} else {
autoStr := "auto"
result.ToolChoice = &ResponsesToolChoice{
ResponsesToolChoiceStr: &autoStr,
}
}
// Text config - default: text format with medium verbosity
if resp.Text != nil {
result.Text = &ResponsesTextConfig{
Format: resp.Text.Format,
Verbosity: resp.Text.Verbosity,
}
if result.Text.Format == nil {
result.Text.Format = &ResponsesTextConfigFormat{Type: "text"}
}
if result.Text.Verbosity == nil {
result.Text.Verbosity = Ptr("medium")
}
} else {
result.Text = &ResponsesTextConfig{
Format: &ResponsesTextConfigFormat{Type: "text"},
Verbosity: Ptr("medium"),
}
}
// Usage - ensure token details exist
result.Usage = resp.Usage
if result.Usage != nil {
result.Usage.Iterations = nil
result.Usage.Type = nil
if result.Usage.InputTokensDetails == nil {
result.Usage.InputTokensDetails = &ResponsesResponseInputTokens{CachedReadTokens: 0, CachedWriteTokens: 0}
}
if result.Usage.OutputTokensDetails == nil {
result.Usage.OutputTokensDetails = &ResponsesResponseOutputTokens{ReasoningTokens: 0}
}
}
return result
}
// orDefault returns src if non-nil, otherwise returns a pointer to defaultVal
func orDefault[T any](src *T, defaultVal T) *T {
if src != nil {
return src
}
return Ptr(defaultVal)
}
type ResponsesParameters struct {
Background *bool `json:"background,omitempty"`
Conversation *string `json:"conversation,omitempty"`
Include []string `json:"include,omitempty"` // Supported values: "web_search_call.action.sources", "code_interpreter_call.outputs", "computer_call_output.output.image_url", "file_search_call.results", "message.input_image.image_url", "message.output_text.logprobs", "reasoning.encrypted_content"
Instructions *string `json:"instructions,omitempty"`
MaxOutputTokens *int `json:"max_output_tokens,omitempty"`
MaxToolCalls *int `json:"max_tool_calls,omitempty"`
Metadata *map[string]any `json:"metadata,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
PreviousResponseID *string `json:"previous_response_id,omitempty"`
PromptCacheKey *string `json:"prompt_cache_key,omitempty"` // Prompt cache key
Reasoning *ResponsesParametersReasoning `json:"reasoning,omitempty"` // Configuration options for reasoning models
SafetyIdentifier *string `json:"safety_identifier,omitempty"` // Safety identifier
ServiceTier *string `json:"service_tier,omitempty"`
StreamOptions *ResponsesStreamOptions `json:"stream_options,omitempty"`
Store *bool `json:"store,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
Text *ResponsesTextConfig `json:"text,omitempty"`
TopLogProbs *int `json:"top_logprobs,omitempty"`
TopP *float64 `json:"top_p,omitempty"` // Controls diversity via nucleus sampling
ToolChoice *ResponsesToolChoice `json:"tool_choice,omitempty"` // Whether to call a tool
Tools []ResponsesTool `json:"tools,omitempty"` // Tools to use
Truncation *string `json:"truncation,omitempty"`
User *string `json:"user,omitempty"`
// Dynamic parameters that can be provider-specific, they are directly
// added to the request as is.
ExtraParams map[string]interface{} `json:"-"`
}
type ResponsesStreamOptions struct {
IncludeObfuscation *bool `json:"include_obfuscation,omitempty"`
}
type ResponsesTextConfig struct {
Format *ResponsesTextConfigFormat `json:"format,omitempty"` // An object specifying the format that the model must output
Verbosity *string `json:"verbosity,omitempty"` // "low" | "medium" | "high" or null
}
type ResponsesTextConfigFormat struct {
Type string `json:"type"` // "text" | "json_schema" | "json_object"
Name *string `json:"name,omitempty"` // Name of the format
JSONSchema *ResponsesTextConfigFormatJSONSchema `json:"schema,omitempty"` // when type == "json_schema"
Strict *bool `json:"strict,omitempty"`
}
// ResponsesTextConfigFormatJSONSchema represents a JSON schema specification
// It supports JSON Schema fields used by various providers for structured outputs.
type ResponsesTextConfigFormatJSONSchema struct {
Name *string `json:"name,omitempty"`
Schema *any `json:"schema,omitempty"`
Description *string `json:"description,omitempty"`
Strict *bool `json:"strict,omitempty"`
AdditionalProperties *AdditionalPropertiesStruct `json:"additionalProperties,omitempty"`
Properties *map[string]any `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
Type *string `json:"type,omitempty"`
// JSON Schema definition fields
Defs *map[string]any `json:"$defs,omitempty"` // JSON Schema draft 2019-09+ definitions
Definitions *map[string]any `json:"definitions,omitempty"` // Legacy JSON Schema draft-07 definitions
Ref *string `json:"$ref,omitempty"` // Reference to definition
// Array schema fields
Items *map[string]any `json:"items,omitempty"` // Array element schema
MinItems *int64 `json:"minItems,omitempty"` // Minimum array length
MaxItems *int64 `json:"maxItems,omitempty"` // Maximum array length
// Composition fields (union types)
AnyOf []map[string]any `json:"anyOf,omitempty"` // Union types (any of these schemas)
OneOf []map[string]any `json:"oneOf,omitempty"` // Exclusive union types (exactly one of these)
AllOf []map[string]any `json:"allOf,omitempty"` // Schema intersection (all of these)
// String validation fields
Format *string `json:"format,omitempty"` // String format (email, date, uri, etc.)
Pattern *string `json:"pattern,omitempty"` // Regex pattern for strings
MinLength *int64 `json:"minLength,omitempty"` // Minimum string length
MaxLength *int64 `json:"maxLength,omitempty"` // Maximum string length
// Number validation fields
Minimum *float64 `json:"minimum,omitempty"` // Minimum number value
Maximum *float64 `json:"maximum,omitempty"` // Maximum number value
// Misc fields
Title *string `json:"title,omitempty"` // Schema title
Default interface{} `json:"default,omitempty"` // Default value
Nullable *bool `json:"nullable,omitempty"` // Nullable indicator (OpenAPI 3.0 style)
Enum []string `json:"enum,omitempty"` // Enum values
PropertyOrdering []string `json:"propertyOrdering,omitempty"` // Ordering of properties, specific to Gemini
}
type ResponsesResponseConversation struct {
ResponsesResponseConversationStr *string
ResponsesResponseConversationStruct *ResponsesResponseConversationStruct
}
// MarshalJSON implements custom JSON marshalling for ResponsesMessageContent.
// It marshals either ContentStr or ContentBlocks directly without wrapping.
func (rc ResponsesResponseConversation) MarshalJSON() ([]byte, error) {
// Validation: ensure only one field is set at a time
if rc.ResponsesResponseConversationStr != nil && rc.ResponsesResponseConversationStruct != nil {
return nil, fmt.Errorf("both ResponsesResponseConversationStr and ResponsesResponseConversationStruct are set; only one should be non-nil")
}
if rc.ResponsesResponseConversationStr != nil {
return MarshalSorted(*rc.ResponsesResponseConversationStr)
}
if rc.ResponsesResponseConversationStruct != nil {
return MarshalSorted(rc.ResponsesResponseConversationStruct)
}
// If both are nil, return null
return MarshalSorted(nil)
}
// UnmarshalJSON implements custom JSON unmarshalling for ResponsesMessageContent.
// It determines whether "content" is a string or array and assigns to the appropriate field.
// It also handles direct string/array content without a wrapper object.
func (rc *ResponsesResponseConversation) UnmarshalJSON(data []byte) error {
// First, try to unmarshal as a direct string
var stringContent string
if err := Unmarshal(data, &stringContent); err == nil {
rc.ResponsesResponseConversationStr = &stringContent
return nil
}
// Try to unmarshal as a direct array of ContentBlock
var structContent ResponsesResponseConversationStruct
if err := Unmarshal(data, &structContent); err == nil {
rc.ResponsesResponseConversationStruct = &structContent
return nil
}
return fmt.Errorf("content field is neither a string nor a struct")
}
type ResponsesResponseInstructions struct {
ResponsesResponseInstructionsStr *string
ResponsesResponseInstructionsArray []ResponsesMessage
}
// MarshalJSON implements custom JSON marshalling for ResponsesMessageContent.
// It marshals either ContentStr or ContentBlocks directly without wrapping.
func (rc ResponsesResponseInstructions) MarshalJSON() ([]byte, error) {
// Validation: ensure only one field is set at a time
if rc.ResponsesResponseInstructionsStr != nil && rc.ResponsesResponseInstructionsArray != nil {
return nil, fmt.Errorf("both ResponsesMessageContentStr and ResponsesMessageContentBlocks are set; only one should be non-nil")
}
if rc.ResponsesResponseInstructionsStr != nil {
return MarshalSorted(*rc.ResponsesResponseInstructionsStr)
}
if rc.ResponsesResponseInstructionsArray != nil {
return MarshalSorted(rc.ResponsesResponseInstructionsArray)
}
// If both are nil, return null
return MarshalSorted(nil)
}
// UnmarshalJSON implements custom JSON unmarshalling for ResponsesMessageContent.
// It determines whether "content" is a string or array and assigns to the appropriate field.
// It also handles direct string/array content without a wrapper object.
func (rc *ResponsesResponseInstructions) UnmarshalJSON(data []byte) error {
// First, try to unmarshal as a direct string
var stringContent string
if err := Unmarshal(data, &stringContent); err == nil {
rc.ResponsesResponseInstructionsStr = &stringContent
return nil
}
// Try to unmarshal as a direct array of ContentBlock
var arrayContent []ResponsesMessage
if err := Unmarshal(data, &arrayContent); err == nil {
rc.ResponsesResponseInstructionsArray = arrayContent
return nil
}
return fmt.Errorf("content field is neither a string nor an array of Messages")
}
type ResponsesPrompt struct {
ID string `json:"id"`
Variables map[string]any `json:"variables"`
Version *string `json:"version,omitempty"`
}
type ResponsesParametersReasoning struct {
Effort *string `json:"effort"` // "none" | "minimal" | "low" | "medium" | "high" (any value other than "none" will enable reasoning)
GenerateSummary *string `json:"generate_summary,omitempty"` // Deprecated: use summary instead
Summary *string `json:"summary"` // "auto" | "concise" | "detailed"
MaxTokens *int `json:"max_tokens,omitempty"` // Maximum number of tokens to generate for the reasoning output (required for anthropic)
}
type ResponsesResponseConversationStruct struct {
ID string `json:"id"` // The unique ID of the conversation
}
type ResponsesResponseError struct {
Code string `json:"code"` // The error code for the response
Message string `json:"message"` // A human-readable description of the error
}
type ResponsesResponseIncompleteDetails struct {
Reason string `json:"reason"` // The reason why the response is incomplete
}
type ResponsesResponseUsage struct {
Type *string `json:"type,omitempty"` // type field is sent by anthropic
InputTokens int `json:"input_tokens"` // Number of input tokens (prompt tokens + cached tokens)
InputTokensDetails *ResponsesResponseInputTokens `json:"input_tokens_details"` // Detailed breakdown of input tokens
OutputTokens int `json:"output_tokens"` // Number of output tokens (completion tokens + reasoning tokens)
OutputTokensDetails *ResponsesResponseOutputTokens `json:"output_tokens_details"` // Detailed breakdown of output tokens TotalTokens int `json:"total_tokens"` // Total number of tokens used
TotalTokens int `json:"total_tokens"` // Total number of tokens used
Cost *BifrostCost `json:"cost,omitempty"` // Only for the providers which support cost calculation
Iterations []ResponsesResponseUsage `json:"iterations,omitempty"` // iterations field is sent by anthropic
}
type ResponsesResponseInputTokens struct {
TextTokens int `json:"text_tokens,omitempty"` // Tokens for text input
AudioTokens int `json:"audio_tokens,omitempty"` // Tokens for audio input
ImageTokens int `json:"image_tokens,omitempty"` // Tokens for image input
// For Providers which don't separate between cache creation and cache read tokens (like Openai, Gemini, etc), this is the total number of cached tokens read.
CachedReadTokens int `json:"cached_read_tokens"`
CachedWriteTokens int `json:"cached_write_tokens"`
}
// UnmarshalJSON maps OpenAI's cached_tokens into CachedReadTokens for compatibility.
func (d *ResponsesResponseInputTokens) UnmarshalJSON(data []byte) error {
var raw struct {
TextTokens int `json:"text_tokens"`
AudioTokens int `json:"audio_tokens"`
ImageTokens int `json:"image_tokens"`
CachedReadTokens int `json:"cached_read_tokens"`
CachedWriteTokens int `json:"cached_write_tokens"`
CachedTokens *int `json:"cached_tokens"`
}
if err := Unmarshal(data, &raw); err != nil {
return err
}
d.TextTokens = raw.TextTokens
d.AudioTokens = raw.AudioTokens
d.ImageTokens = raw.ImageTokens
d.CachedReadTokens = raw.CachedReadTokens
d.CachedWriteTokens = raw.CachedWriteTokens
// OpenAI spec providers send just cached_tokens, not separate read and write tokens and we handle them as read tokens in pricing calculations.
if raw.CachedTokens != nil && raw.CachedReadTokens == 0 && raw.CachedWriteTokens == 0 {
d.CachedReadTokens = *raw.CachedTokens
}
return nil
}
// MarshalJSON emits cached_tokens (read+write) alongside the individual fields for OpenAI spec compatibility.
func (d ResponsesResponseInputTokens) MarshalJSON() ([]byte, error) {
type raw struct {
TextTokens int `json:"text_tokens,omitempty"`
AudioTokens int `json:"audio_tokens,omitempty"`
ImageTokens int `json:"image_tokens,omitempty"`
CachedReadTokens int `json:"cached_read_tokens"`
CachedWriteTokens int `json:"cached_write_tokens"`
CachedTokens int `json:"cached_tokens"`
}
return MarshalSorted(raw{
TextTokens: d.TextTokens,
AudioTokens: d.AudioTokens,
ImageTokens: d.ImageTokens,
CachedReadTokens: d.CachedReadTokens,
CachedWriteTokens: d.CachedWriteTokens,
CachedTokens: d.CachedReadTokens + d.CachedWriteTokens,
})
}
type ResponsesResponseOutputTokens struct {
TextTokens int `json:"text_tokens,omitempty"`
AcceptedPredictionTokens int `json:"accepted_prediction_tokens,omitempty"`
AudioTokens int `json:"audio_tokens,omitempty"`
ImageTokens *int `json:"image_tokens,omitempty"`
ReasoningTokens int `json:"reasoning_tokens"` // Required for few OpenAI models
RejectedPredictionTokens int `json:"rejected_prediction_tokens,omitempty"`
CitationTokens *int `json:"citation_tokens,omitempty"`
NumSearchQueries *int `json:"num_search_queries,omitempty"`
}
// =============================================================================
// 2. INPUT MESSAGE STRUCTURES
// =============================================================================
type ResponsesMessageType string
const (
ResponsesMessageTypeMessage ResponsesMessageType = "message"
ResponsesMessageTypeFileSearchCall ResponsesMessageType = "file_search_call"
ResponsesMessageTypeComputerCall ResponsesMessageType = "computer_call"
ResponsesMessageTypeComputerCallOutput ResponsesMessageType = "computer_call_output"
ResponsesMessageTypeWebSearchCall ResponsesMessageType = "web_search_call"
ResponsesMessageTypeWebFetchCall ResponsesMessageType = "web_fetch_call"
ResponsesMessageTypeFunctionCall ResponsesMessageType = "function_call"
ResponsesMessageTypeFunctionCallOutput ResponsesMessageType = "function_call_output"
ResponsesMessageTypeCodeInterpreterCall ResponsesMessageType = "code_interpreter_call"
ResponsesMessageTypeLocalShellCall ResponsesMessageType = "local_shell_call"
ResponsesMessageTypeLocalShellCallOutput ResponsesMessageType = "local_shell_call_output"
ResponsesMessageTypeMCPCall ResponsesMessageType = "mcp_call"
ResponsesMessageTypeCustomToolCall ResponsesMessageType = "custom_tool_call"
ResponsesMessageTypeCustomToolCallOutput ResponsesMessageType = "custom_tool_call_output"
ResponsesMessageTypeImageGenerationCall ResponsesMessageType = "image_generation_call"
ResponsesMessageTypeMCPListTools ResponsesMessageType = "mcp_list_tools"
ResponsesMessageTypeMCPApprovalRequest ResponsesMessageType = "mcp_approval_request"
ResponsesMessageTypeMCPApprovalResponses ResponsesMessageType = "mcp_approval_responses"
ResponsesMessageTypeReasoning ResponsesMessageType = "reasoning"
ResponsesMessageTypeItemReference ResponsesMessageType = "item_reference"
ResponsesMessageTypeRefusal ResponsesMessageType = "refusal"
)
// ResponsesMessage is a union type that can contain different types of input items
// Only one of the fields should be set at a time
type ResponsesMessage struct {
ID *string `json:"id,omitempty"` // Common ID field for most item types
Type *ResponsesMessageType `json:"type,omitempty"`
Status *string `json:"status,omitempty"` // "in_progress" | "completed" | "incomplete" | "interpreting" | "failed"
Role *ResponsesMessageRoleType `json:"role,omitempty"`
Content *ResponsesMessageContent `json:"content,omitempty"`
*ResponsesToolMessage // For Tool calls and outputs
CacheControl *CacheControl `json:"cache_control,omitempty"` // Carries cache_control for function_call and function_call_output message types
// Reasoning
// gpt-oss models include only reasoning_text content blocks in a message, while other openai models include summaries+encrypted_content
*ResponsesReasoning
}
type ResponsesMessageRoleType string
const (
ResponsesInputMessageRoleAssistant ResponsesMessageRoleType = "assistant"
ResponsesInputMessageRoleUser ResponsesMessageRoleType = "user"
ResponsesInputMessageRoleSystem ResponsesMessageRoleType = "system"
ResponsesInputMessageRoleDeveloper ResponsesMessageRoleType = "developer"
)
// ResponsesMessageContent is a union type that can be either a string or array of content blocks
type ResponsesMessageContent struct {
ContentStr *string // Simple text content
// Output will ALWAYS be an array of content blocks
ContentBlocks []ResponsesMessageContentBlock // Rich content with multiple media types
}
// MarshalJSON implements custom JSON marshalling for ResponsesMessageContent.
// It marshals either ContentStr or ContentBlocks directly without wrapping.
func (rc ResponsesMessageContent) MarshalJSON() ([]byte, error) {
// Validation: ensure only one field is set at a time
if rc.ContentStr != nil && rc.ContentBlocks != nil {
return nil, fmt.Errorf("both ResponsesMessageContentStr and ResponsesMessageContentBlocks are set; only one should be non-nil")
}
if rc.ContentStr != nil {
return MarshalSorted(*rc.ContentStr)
}
if rc.ContentBlocks != nil {
return MarshalSorted(rc.ContentBlocks)
}
// If both are nil, return null
return MarshalSorted(nil)
}
// UnmarshalJSON implements custom JSON unmarshalling for ResponsesMessageContent.
// It determines whether "content" is a string or array and assigns to the appropriate field.
// It also handles direct string/array content without a wrapper object.
func (rc *ResponsesMessageContent) UnmarshalJSON(data []byte) error {
// First, try to unmarshal as a direct string
var stringContent string
if err := Unmarshal(data, &stringContent); err == nil {
rc.ContentStr = &stringContent
return nil
}
// Try to unmarshal as a direct array of ContentBlock
var arrayContent []ResponsesMessageContentBlock
if err := Unmarshal(data, &arrayContent); err == nil {
rc.ContentBlocks = arrayContent
return nil
}
return fmt.Errorf("content field is neither a string nor an array of Content blocks")
}
type ResponsesMessageContentBlockType string
const (
ResponsesInputMessageContentBlockTypeText ResponsesMessageContentBlockType = "input_text"
ResponsesInputMessageContentBlockTypeImage ResponsesMessageContentBlockType = "input_image"
ResponsesInputMessageContentBlockTypeFile ResponsesMessageContentBlockType = "input_file"
ResponsesInputMessageContentBlockTypeAudio ResponsesMessageContentBlockType = "input_audio"
ResponsesOutputMessageContentTypeText ResponsesMessageContentBlockType = "output_text"
ResponsesOutputMessageContentTypeRefusal ResponsesMessageContentBlockType = "refusal"
ResponsesOutputMessageContentTypeReasoning ResponsesMessageContentBlockType = "reasoning_text"
// gemini sends rendered content in google search results
ResponsesOutputMessageContentTypeRenderedContent ResponsesMessageContentBlockType = "rendered_content"
ResponsesOutputMessageContentTypeCompaction ResponsesMessageContentBlockType = "compaction"
)
// ResponsesMessageContentBlock represents different types of content (text, image, file, audio)
// Only one of the content type fields should be set
type ResponsesMessageContentBlock struct {
Type ResponsesMessageContentBlockType `json:"type"`
FileID *string `json:"file_id,omitempty"` // Reference to uploaded file
Text *string `json:"text,omitempty"`
Signature *string `json:"signature,omitempty"` // Signature of the content (for reasoning)
*ResponsesInputMessageContentBlockImage
*ResponsesInputMessageContentBlockFile
Audio *ResponsesInputMessageContentBlockAudio `json:"input_audio,omitempty"`
*ResponsesOutputMessageContentText // Normal text output from the model
*ResponsesOutputMessageContentRefusal // Model refusal to answer
*ResponsesOutputMessageContentRenderedContent // Rendered content from search entry point
*ResponsesOutputMessageContentCompaction // Compaction content from the model
// Not in OpenAI's schemas, but sent by a few providers (Anthropic, Bedrock are some of them)
CacheControl *CacheControl `json:"cache_control,omitempty"`
Citations *Citations `json:"citations,omitempty"`
}
type ResponsesOutputMessageContentCompaction struct {
Summary string `json:"summary,omitempty"` // The compaction summary text
}
type ResponsesOutputMessageContentRenderedContent struct {
RenderedContent string `json:"rendered_content"` // HTML/styled content from search entry point
}
type Citations struct {
Enabled *bool `json:"enabled,omitempty"`
}
type ResponsesInputMessageContentBlockImage struct {
ImageURL *string `json:"image_url,omitempty"`
Detail *string `json:"detail,omitempty"` // "low" | "high" | "auto"
}
type ResponsesInputMessageContentBlockFile struct {
FileData *string `json:"file_data,omitempty"` // Base64 encoded file data or plain text
FileURL *string `json:"file_url,omitempty"` // Direct URL to file
Filename *string `json:"filename,omitempty"` // Name of the file
FileType *string `json:"file_type,omitempty"` // MIME type (e.g., "application/pdf", "text/plain")
}
type ResponsesInputMessageContentBlockAudio struct {
Format string `json:"format"` // "mp3" or "wav"
Data string `json:"data"` // base64 encoded audio data
}
// =============================================================================
// 3. OUTPUT MESSAGE STRUCTURES
// =============================================================================
type ResponsesOutputMessageContentText struct {
Annotations []ResponsesOutputMessageContentTextAnnotation `json:"annotations"` // Citations and references
LogProbs []ResponsesOutputMessageContentTextLogProb `json:"logprobs"` // Token log probabilities
}
type ResponsesOutputMessageContentTextAnnotation struct {
Type string `json:"type"` // "file_citation" | "url_citation" | "container_file_citation" | "file_path"
Index *int `json:"index,omitempty"` // Common index field (FileCitation, FilePath)
FileID *string `json:"file_id,omitempty"` // Common file ID field (FileCitation, ContainerFileCitation, FilePath)
Text *string `json:"text,omitempty"` // Text of the citation
StartIndex *int `json:"start_index,omitempty"` // Common start index field (URLCitation, ContainerFileCitation)
EndIndex *int `json:"end_index,omitempty"` // Common end index field (URLCitation, ContainerFileCitation)
Filename *string `json:"filename,omitempty"`
Title *string `json:"title,omitempty"`
URL *string `json:"url,omitempty"`
ContainerID *string `json:"container_id,omitempty"`
// Anthropic specific fields
StartCharIndex *int `json:"start_char_index,omitempty"`
EndCharIndex *int `json:"end_char_index,omitempty"`
StartPageNumber *int `json:"start_page_number,omitempty"`
EndPageNumber *int `json:"end_page_number,omitempty"`
StartBlockIndex *int `json:"start_block_index,omitempty"`
EndBlockIndex *int `json:"end_block_index,omitempty"`
Source *string `json:"source,omitempty"`
EncryptedIndex *string `json:"encrypted_index,omitempty"`
}
// ResponsesOutputMessageContentTextLogProb represents log probability information for content.
type ResponsesOutputMessageContentTextLogProb struct {
Bytes []int `json:"bytes"`
LogProb float64 `json:"logprob"`
Token string `json:"token"`
TopLogProbs []LogProb `json:"top_logprobs"`
}
type ResponsesOutputMessageContentRefusal struct {
Refusal string `json:"refusal"`
}
type ResponsesToolMessage struct {
CallID *string `json:"call_id,omitempty"` // Common call ID for tool calls and outputs
Name *string `json:"name,omitempty"` // Common name field for tool calls
Arguments *string `json:"arguments,omitempty"`
Output *ResponsesToolMessageOutputStruct `json:"output,omitempty"`
Action *ResponsesToolMessageActionStruct `json:"action,omitempty"`
Error *string `json:"error,omitempty"`
// Tool calls and outputs
*ResponsesFileSearchToolCall
*ResponsesComputerToolCall
*ResponsesComputerToolCallOutput
*ResponsesCodeInterpreterToolCall
*ResponsesMCPToolCall
*ResponsesCustomToolCall
*ResponsesImageGenerationCall
// MCP-specific
*ResponsesMCPListTools
*ResponsesMCPApprovalResponse
}
type ResponsesToolMessageActionStruct struct {
ResponsesComputerToolCallAction *ResponsesComputerToolCallAction
ResponsesWebSearchToolCallAction *ResponsesWebSearchToolCallAction
ResponsesWebFetchToolCallAction *ResponsesWebFetchToolCallAction
ResponsesLocalShellToolCallAction *ResponsesLocalShellToolCallAction
ResponsesMCPApprovalRequestAction *ResponsesMCPApprovalRequestAction
}
func (action ResponsesToolMessageActionStruct) MarshalJSON() ([]byte, error) {
if action.ResponsesComputerToolCallAction != nil {
return MarshalSorted(action.ResponsesComputerToolCallAction)
}
if action.ResponsesWebSearchToolCallAction != nil {
return MarshalSorted(action.ResponsesWebSearchToolCallAction)
}
if action.ResponsesWebFetchToolCallAction != nil {
return MarshalSorted(action.ResponsesWebFetchToolCallAction)
}
if action.ResponsesLocalShellToolCallAction != nil {
return MarshalSorted(action.ResponsesLocalShellToolCallAction)
}
if action.ResponsesMCPApprovalRequestAction != nil {
return MarshalSorted(action.ResponsesMCPApprovalRequestAction)
}
return nil, fmt.Errorf("responses tool message action struct is empty")
}
func (action *ResponsesToolMessageActionStruct) UnmarshalJSON(data []byte) error {
// First, peek at the type field to determine which variant to unmarshal
var typeStruct struct {
Type string `json:"type"`
}
if err := Unmarshal(data, &typeStruct); err != nil {
return fmt.Errorf("failed to peek at type field: %w", err)
}
// Based on the type, unmarshal into the appropriate variant
switch typeStruct.Type {
case "exec":
var localShellToolCallAction ResponsesLocalShellToolCallAction
if err := Unmarshal(data, &localShellToolCallAction); err != nil {
return fmt.Errorf("failed to unmarshal local shell tool call action: %w", err)
}
action.ResponsesLocalShellToolCallAction = &localShellToolCallAction
return nil
case "mcp_approval_request":
var mcpApprovalRequestAction ResponsesMCPApprovalRequestAction
if err := Unmarshal(data, &mcpApprovalRequestAction); err != nil {
return fmt.Errorf("failed to unmarshal mcp approval request action: %w", err)
}
action.ResponsesMCPApprovalRequestAction = &mcpApprovalRequestAction
return nil
case "search", "open_page", "find":
var webSearchToolCallAction ResponsesWebSearchToolCallAction
if err := Unmarshal(data, &webSearchToolCallAction); err != nil {
return fmt.Errorf("failed to unmarshal web search tool call action: %w", err)
}
action.ResponsesWebSearchToolCallAction = &webSearchToolCallAction
return nil
case "fetch":
var webFetchToolCallAction ResponsesWebFetchToolCallAction
if err := Unmarshal(data, &webFetchToolCallAction); err != nil {
return fmt.Errorf("failed to unmarshal web fetch tool call action: %w", err)
}
action.ResponsesWebFetchToolCallAction = &webFetchToolCallAction
return nil
case "click", "double_click", "drag", "keypress", "move", "screenshot", "scroll", "type", "wait", "zoom":
var computerToolCallAction ResponsesComputerToolCallAction
if err := Unmarshal(data, &computerToolCallAction); err != nil {
return fmt.Errorf("failed to unmarshal computer tool call action: %w", err)
}
action.ResponsesComputerToolCallAction = &computerToolCallAction
return nil
default:
// use computer tool, as it can have many possible actions
var computerToolCallAction ResponsesComputerToolCallAction
if err := Unmarshal(data, &computerToolCallAction); err != nil {
return fmt.Errorf("failed to unmarshal computer tool call action: %w", err)
}
action.ResponsesComputerToolCallAction = &computerToolCallAction
return nil
}
}
type ResponsesToolMessageOutputStruct struct {
ResponsesToolCallOutputStr *string // Common output string for tool calls and outputs (used by function, custom and local shell tool calls)
ResponsesFunctionToolCallOutputBlocks []ResponsesMessageContentBlock
ResponsesComputerToolCallOutput *ResponsesComputerToolCallOutputData
}
func (output ResponsesToolMessageOutputStruct) MarshalJSON() ([]byte, error) {
if output.ResponsesToolCallOutputStr != nil {
return MarshalSorted(*output.ResponsesToolCallOutputStr)
}
if output.ResponsesFunctionToolCallOutputBlocks != nil {
return MarshalSorted(output.ResponsesFunctionToolCallOutputBlocks)
}
if output.ResponsesComputerToolCallOutput != nil {
return MarshalSorted(output.ResponsesComputerToolCallOutput)
}
return nil, fmt.Errorf("responses tool message output struct is neither a string nor an array of responses message content blocks nor a computer tool call output data nor an image generation call output")
}
func (output *ResponsesToolMessageOutputStruct) UnmarshalJSON(data []byte) error {
var str string
if err := Unmarshal(data, &str); err == nil {
output.ResponsesToolCallOutputStr = &str
return nil
}
var array []ResponsesMessageContentBlock
if err := Unmarshal(data, &array); err == nil {
output.ResponsesFunctionToolCallOutputBlocks = array
return nil
}
var computerToolCallOutput ResponsesComputerToolCallOutputData
if err := Unmarshal(data, &computerToolCallOutput); err == nil {
output.ResponsesComputerToolCallOutput = &computerToolCallOutput
return nil
}
return fmt.Errorf("responses tool message output struct is neither a string nor an array of responses message content blocks nor a computer tool call output data nor an image generation call output")
}
// =============================================================================
// 4. TOOL CALL STRUCTURES (organized by tool type)
// =============================================================================
// -----------------------------------------------------------------------------
// File Search Tool
// -----------------------------------------------------------------------------
type ResponsesFileSearchToolCall struct {
Queries []string `json:"queries"`
Results []ResponsesFileSearchToolCallResult `json:"results,omitempty"`
}
type ResponsesFileSearchToolCallResult struct {
Attributes *map[string]any `json:"attributes,omitempty"`
FileID *string `json:"file_id,omitempty"`
Filename *string `json:"filename,omitempty"`
Score *float64 `json:"score,omitempty"`
Text *string `json:"text,omitempty"`
}
// ResponsesComputerToolCall represents a computer tool call
type ResponsesComputerToolCall struct {
PendingSafetyChecks []ResponsesComputerToolCallPendingSafetyCheck `json:"pending_safety_checks,omitempty"`
}
// ResponsesComputerToolCallPendingSafetyCheck represents a pending safety check
type ResponsesComputerToolCallPendingSafetyCheck struct {
ID string `json:"id"`
Code string `json:"code"`
Message string `json:"message"`
}
// ResponsesComputerToolCallAction represents the different types of computer actions
type ResponsesComputerToolCallAction struct {
Type string `json:"type"` // "click" | "double_click" | "drag" | "keypress" | "move" | "screenshot" | "scroll" | "type" | "wait" | "zoom"
X *int `json:"x,omitempty"` // Common X coordinate field (Click, DoubleClick, Move, Scroll)
Y *int `json:"y,omitempty"` // Common Y coordinate field (Click, DoubleClick, Move, Scroll)
Button *string `json:"button,omitempty"` // "left" | "right" | "wheel" | "back" | "forward"
Path []ResponsesComputerToolCallActionPath `json:"path,omitempty"`
Keys []string `json:"keys,omitempty"`
ScrollX *int `json:"scroll_x,omitempty"`
ScrollY *int `json:"scroll_y,omitempty"`
Text *string `json:"text,omitempty"`
Region []int `json:"region,omitempty"` // [x1, y1, x2, y2] for zoom action (Anthropic Opus 4.5)
}
type ResponsesComputerToolCallActionPath struct {
X int `json:"x"`
Y int `json:"y"`
}
// ResponsesComputerToolCallOutput represents a computer tool call output
type ResponsesComputerToolCallOutput struct {
AcknowledgedSafetyChecks []ResponsesComputerToolCallAcknowledgedSafetyCheck `json:"acknowledged_safety_checks,omitempty"`
}
// ResponsesComputerToolCallOutputData represents a computer screenshot image used with the computer use tool
type ResponsesComputerToolCallOutputData struct {
Type string `json:"type"` // always "computer_screenshot"
FileID *string `json:"file_id,omitempty"`
ImageURL *string `json:"image_url,omitempty"`
}
// ResponsesComputerToolCallAcknowledgedSafetyCheck represents a safety check that has been acknowledged by the developer
type ResponsesComputerToolCallAcknowledgedSafetyCheck struct {
ID string `json:"id"`
Code *string `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
}
// -----------------------------------------------------------------------------
// Web Search Tool
// -----------------------------------------------------------------------------
// ResponsesWebSearchToolCallAction represents the different types of web search actions
type ResponsesWebSearchToolCallAction struct {
Type string `json:"type"` // "search" | "open_page" | "find"
URL *string `json:"url,omitempty"` // Common URL field (OpenPage, Find)
Query *string `json:"query,omitempty"`
Queries []string `json:"queries,omitempty"`
Sources []ResponsesWebSearchToolCallActionSearchSource `json:"sources,omitempty"`
Pattern *string `json:"pattern,omitempty"`
}
// ResponsesWebSearchToolCallActionSearchSource represents a web search action search source
type ResponsesWebSearchToolCallActionSearchSource struct {
Type string `json:"type"` // always "url"
URL string `json:"url"`
// Anthropic specific fields
Title *string `json:"title,omitempty"`
EncryptedContent *string `json:"encrypted_content,omitempty"`
PageAge *string `json:"page_age,omitempty"`
}
// -----------------------------------------------------------------------------
// Web Fetch Tool
// -----------------------------------------------------------------------------
// ResponsesWebFetchToolCallAction represents a web fetch action
type ResponsesWebFetchToolCallAction struct {
Type string `json:"type,omitempty"` // "fetch"
URL string `json:"url"`
}
// -----------------------------------------------------------------------------
// Function Tool
// -----------------------------------------------------------------------------
// ResponsesFunctionToolCallOutput represents a function tool call output
type ResponsesFunctionToolCallOutput struct {
ResponsesFunctionToolCallOutputStr *string // A JSON string of the output of the function tool call.
ResponsesFunctionToolCallOutputBlocks []ResponsesMessageContentBlock
}
// MarshalJSON implements custom JSON marshalling for ResponsesFunctionToolCallOutput.
// It marshals either ContentStr or ContentBlocks directly without wrapping.
func (rf ResponsesFunctionToolCallOutput) MarshalJSON() ([]byte, error) {
// Validation: ensure only one field is set at a time
if rf.ResponsesFunctionToolCallOutputStr != nil && rf.ResponsesFunctionToolCallOutputBlocks != nil {
return nil, fmt.Errorf("both ResponsesFunctionToolCallOutputStr and ResponsesFunctionToolCallOutputBlocks are set; only one should be non-nil")
}
if rf.ResponsesFunctionToolCallOutputStr != nil {
return MarshalSorted(*rf.ResponsesFunctionToolCallOutputStr)
}
if rf.ResponsesFunctionToolCallOutputBlocks != nil {
return MarshalSorted(rf.ResponsesFunctionToolCallOutputBlocks)
}
// If both are nil, return null
return MarshalSorted(nil)
}
// UnmarshalJSON implements custom JSON unmarshalling for ResponsesFunctionToolCallOutput.
// It determines whether "content" is a string or array and assigns to the appropriate field.
// It also handles direct string/array content without a wrapper object.
func (rf *ResponsesFunctionToolCallOutput) UnmarshalJSON(data []byte) error {
// Parse as generic object to check if it contains content-like fields
var genericObj map[string]interface{}
if err := Unmarshal(data, &genericObj); err != nil {
return err
}
// If the object doesn't contain typical content fields, it's probably not meant for this struct
// (e.g., it's a tool call, not a tool call output)
hasContentFields := false
for key := range genericObj {
if key == "content" || key == "output" || key == "result" {
hasContentFields = true
break
}
}
if !hasContentFields {
return nil // Skip unmarshaling if no relevant content fields
}
// First, try to unmarshal as a direct string
var stringContent string
if err := Unmarshal(data, &stringContent); err == nil {
rf.ResponsesFunctionToolCallOutputStr = &stringContent
return nil
}
// Try to unmarshal as a direct array of ContentBlock
var arrayContent []ResponsesMessageContentBlock
if err := Unmarshal(data, &arrayContent); err == nil {
rf.ResponsesFunctionToolCallOutputBlocks = arrayContent
return nil
}
return fmt.Errorf("content field is neither a string nor an array of Content blocks")
}
// -----------------------------------------------------------------------------
// Reasoning
// -----------------------------------------------------------------------------
// ResponsesReasoning represents a reasoning output
type ResponsesReasoning struct {
Summary []ResponsesReasoningSummary `json:"summary"`
EncryptedContent *string `json:"encrypted_content,omitempty"`
}
// ResponsesReasoningContentBlockType represents the type of reasoning content
type ResponsesReasoningContentBlockType string
// ResponsesReasoningContentBlockType values
const (
ResponsesReasoningContentBlockTypeSummaryText ResponsesReasoningContentBlockType = "summary_text"
)
// ResponsesReasoningSummary represents a reasoning content block
type ResponsesReasoningSummary struct {
Type ResponsesReasoningContentBlockType `json:"type"`
Text string `json:"text"`
}
// -----------------------------------------------------------------------------
// Image Generation Tool
// -----------------------------------------------------------------------------
// ResponsesImageGenerationCall represents an image generation tool call
type ResponsesImageGenerationCall struct {
Result string `json:"result"`
}
// -----------------------------------------------------------------------------
// Code Interpreter Tool
// -----------------------------------------------------------------------------
// ResponsesCodeInterpreterToolCall represents a code interpreter tool call
type ResponsesCodeInterpreterToolCall struct {
Code *string `json:"code"` // The code to run, or null if not available
ContainerID string `json:"container_id"` // The ID of the container used to run the code
Outputs []ResponsesCodeInterpreterOutput `json:"outputs"` // The outputs generated by the code interpreter, can be null
}
// ResponsesCodeInterpreterOutput represents a code interpreter output
type ResponsesCodeInterpreterOutput struct {
*ResponsesCodeInterpreterOutputLogs
*ResponsesCodeInterpreterOutputImage
}
// MarshalJSON implements custom JSON marshaling for ResponsesCodeInterpreterOutput
func (o ResponsesCodeInterpreterOutput) MarshalJSON() ([]byte, error) {
// Error if both variants are set
if o.ResponsesCodeInterpreterOutputLogs != nil && o.ResponsesCodeInterpreterOutputImage != nil {
return nil, fmt.Errorf("ResponsesCodeInterpreterOutput cannot have both Logs and Image set")
}
// Marshal whichever one is present
if o.ResponsesCodeInterpreterOutputLogs != nil {
return MarshalSorted(o.ResponsesCodeInterpreterOutputLogs)
}
if o.ResponsesCodeInterpreterOutputImage != nil {
return MarshalSorted(o.ResponsesCodeInterpreterOutputImage)
}
// Return null if neither is set
return []byte("null"), nil
}
// UnmarshalJSON implements custom JSON unmarshaling for ResponsesCodeInterpreterOutput
func (o *ResponsesCodeInterpreterOutput) UnmarshalJSON(data []byte) error {
// Handle null case
if string(data) == "null" {
return nil
}
// First, peek at the type field to determine which variant to unmarshal
var typeStruct struct {
Type string `json:"type"`
}
if err := Unmarshal(data, &typeStruct); err != nil {
return fmt.Errorf("failed to read type field: %w", err)
}
// Unmarshal into the appropriate concrete type based on the type field
switch typeStruct.Type {
case "logs":
var logs ResponsesCodeInterpreterOutputLogs
if err := Unmarshal(data, &logs); err != nil {
return fmt.Errorf("failed to unmarshal logs output: %w", err)
}
o.ResponsesCodeInterpreterOutputLogs = &logs
o.ResponsesCodeInterpreterOutputImage = nil
return nil
case "image":
var image ResponsesCodeInterpreterOutputImage
if err := Unmarshal(data, &image); err != nil {
return fmt.Errorf("failed to unmarshal image output: %w", err)
}
o.ResponsesCodeInterpreterOutputImage = &image
o.ResponsesCodeInterpreterOutputLogs = nil
return nil
default:
return fmt.Errorf("unknown ResponsesCodeInterpreterOutput type: %s", typeStruct.Type)
}
}
// ResponsesCodeInterpreterOutputLogs represents the logs output from the code interpreter
type ResponsesCodeInterpreterOutputLogs struct {
Logs string `json:"logs"`
Type string `json:"type"` // always "logs"
}
// ResponsesCodeInterpreterOutputImage represents the image output from the code interpreter
type ResponsesCodeInterpreterOutputImage struct {
Type string `json:"type"` // always "image"
URL string `json:"url"`
}
// -----------------------------------------------------------------------------
// Local Shell Tool
// -----------------------------------------------------------------------------
// ResponsesLocalShellCallAction represents the different types of local shell actions
type ResponsesLocalShellToolCallAction struct {
Command []string `json:"command"`
Env []string `json:"env"`
Type string `json:"type"` // always "exec"
TimeoutMS *int `json:"timeout_ms,omitempty"`
User *string `json:"user,omitempty"`
WorkingDirectory *string `json:"working_directory,omitempty"`
}
// -----------------------------------------------------------------------------
// MCP (Model Context Protocol) Tools
// -----------------------------------------------------------------------------
// ResponsesMCPListTools represents a list of MCP tools
type ResponsesMCPListTools struct {
ServerLabel string `json:"server_label"`
Tools []ResponsesMCPTool `json:"tools"`
}
// ResponsesMCPTool represents an MCP tool
type ResponsesMCPTool struct {
Name string `json:"name"`
InputSchema map[string]any `json:"input_schema"`
Description *string `json:"description,omitempty"`
Annotations *map[string]any `json:"annotations,omitempty"`
}
// ResponsesMCPApprovalRequestAction represents the different types of MCP approval request actions
type ResponsesMCPApprovalRequestAction struct {
ID string `json:"id"`
Type string `json:"type"` // always "mcp_approval_request"
Name string `json:"name"`
ServerLabel string `json:"server_label"`
Arguments string `json:"arguments"`
}
// ResponsesMCPApprovalResponse represents a MCP approval response
type ResponsesMCPApprovalResponse struct {
ApprovalResponseID string `json:"approval_response_id"`
Approve bool `json:"approve"`
Reason *string `json:"reason,omitempty"`
}
// ResponsesMCPToolCall represents a MCP tool call
type ResponsesMCPToolCall struct {
ServerLabel string `json:"server_label"` // The label of the MCP server running the tool
}
// -----------------------------------------------------------------------------
// Custom Tools
// -----------------------------------------------------------------------------
// ResponsesCustomToolCall represents a custom tool call
type ResponsesCustomToolCall struct {
Input string `json:"input"` // The input for the custom tool call generated by the model
}
// =============================================================================
// 5. TOOL CHOICE CONFIGURATION
// =============================================================================
// Combined tool choices for all providers, make sure to check the provider's
// documentation to see which tool choices are supported
// ResponsesToolChoiceType represents the type of tool choice
type ResponsesToolChoiceType string
// ResponsesToolChoiceType values
const (
// ResponsesToolChoiceTypeNone means no tool should be called
ResponsesToolChoiceTypeNone ResponsesToolChoiceType = "none"
// ResponsesToolChoiceTypeAuto means an automatic tool should be called
ResponsesToolChoiceTypeAuto ResponsesToolChoiceType = "auto"
// ResponsesToolChoiceTypeAny means any tool can be called
ResponsesToolChoiceTypeAny ResponsesToolChoiceType = "any"
// ResponsesToolChoiceTypeRequired means a specific tool must be called
ResponsesToolChoiceTypeRequired ResponsesToolChoiceType = "required"
// ResponsesToolChoiceTypeFunction means a specific tool must be called
ResponsesToolChoiceTypeFunction ResponsesToolChoiceType = "function"
// ResponsesToolChoiceTypeAllowedTools means a specific tool must be called
ResponsesToolChoiceTypeAllowedTools ResponsesToolChoiceType = "allowed_tools"
// ResponsesToolChoiceTypeFileSearch means a file search tool must be called
ResponsesToolChoiceTypeFileSearch ResponsesToolChoiceType = "file_search"
// ResponsesToolChoiceTypeWebSearchPreview means a web search preview tool must be called
ResponsesToolChoiceTypeWebSearchPreview ResponsesToolChoiceType = "web_search_preview"
// ResponsesToolChoiceTypeComputerUsePreview means a computer use preview tool must be called
ResponsesToolChoiceTypeComputerUsePreview ResponsesToolChoiceType = "computer_use_preview"
// ResponsesToolChoiceTypeCodeInterpreter means a code interpreter tool must be called
ResponsesToolChoiceTypeCodeInterpreter ResponsesToolChoiceType = "code_interpreter"
// ResponsesToolChoiceTypeImageGeneration means an image generation tool must be called
ResponsesToolChoiceTypeImageGeneration ResponsesToolChoiceType = "image_generation"
// ResponsesToolChoiceTypeMCP means an MCP tool must be called
ResponsesToolChoiceTypeMCP ResponsesToolChoiceType = "mcp"
// ResponsesToolChoiceTypeCustom means a custom tool must be called
ResponsesToolChoiceTypeCustom ResponsesToolChoiceType = "custom"
)
// ResponsesToolChoiceStruct represents a tool choice struct
type ResponsesToolChoiceStruct struct {
Type ResponsesToolChoiceType `json:"type"` // Type of tool choice
Mode *string `json:"mode,omitempty"` //"none" | "auto" | "required"
Name *string `json:"name,omitempty"` // Common name field for function/MCP/custom tools
ServerLabel *string `json:"server_label,omitempty"` // Common server label field for MCP tools
Tools []ResponsesToolChoiceAllowedToolDef `json:"tools,omitempty"`
}
// ResponsesToolChoice represents a tool choice
type ResponsesToolChoice struct {
ResponsesToolChoiceStr *string
ResponsesToolChoiceStruct *ResponsesToolChoiceStruct
}
// MarshalJSON implements custom JSON marshalling for ChatMessageContent.
// It marshals either ContentStr or ContentBlocks directly without wrapping.
func (tc ResponsesToolChoice) MarshalJSON() ([]byte, error) {
// Validation: ensure only one field is set at a time
if tc.ResponsesToolChoiceStr != nil && tc.ResponsesToolChoiceStruct != nil {
return nil, fmt.Errorf("both ResponsesToolChoiceStr, ResponsesToolChoiceStruct are set; only one should be non-nil")
}
if tc.ResponsesToolChoiceStr != nil {
return MarshalSorted(tc.ResponsesToolChoiceStr)
}
if tc.ResponsesToolChoiceStruct != nil {
return MarshalSorted(tc.ResponsesToolChoiceStruct)
}
// If both are nil, return null
return MarshalSorted(nil)
}
// UnmarshalJSON implements custom JSON unmarshalling for ChatMessageContent.
// It determines whether "content" is a string or array and assigns to the appropriate field.
// It also handles direct string/array content without a wrapper object.
func (tc *ResponsesToolChoice) UnmarshalJSON(data []byte) error {
// First, try to unmarshal as a direct string
var toolChoiceStr string
if err := Unmarshal(data, &toolChoiceStr); err == nil {
tc.ResponsesToolChoiceStr = &toolChoiceStr
return nil
}
// Try to unmarshal as a direct array of ContentBlock
var responsesToolChoiceStruct ResponsesToolChoiceStruct
if err := Unmarshal(data, &responsesToolChoiceStruct); err == nil {
tc.ResponsesToolChoiceStruct = &responsesToolChoiceStruct
return nil
}
return fmt.Errorf("tool_choice field is neither a string nor a ResponsesToolChoiceStruct object")
}
// ResponsesToolChoiceAllowedToolDef represents a tool choice allowed tool definition
type ResponsesToolChoiceAllowedToolDef struct {
Type string `json:"type"` // "function" | "mcp" | "image_generation"
Name *string `json:"name,omitempty"` // for function tools
ServerLabel *string `json:"server_label,omitempty"` // for MCP tools
}
// =============================================================================
// 7. TOOL CONFIGURATION STRUCTURES
// =============================================================================
type ResponsesToolType string
const (
ResponsesToolTypeFunction ResponsesToolType = "function"
ResponsesToolTypeFileSearch ResponsesToolType = "file_search"
ResponsesToolTypeComputerUsePreview ResponsesToolType = "computer_use_preview"
ResponsesToolTypeWebSearch ResponsesToolType = "web_search"
ResponsesToolTypeWebFetch ResponsesToolType = "web_fetch"
ResponsesToolTypeMCP ResponsesToolType = "mcp"
ResponsesToolTypeCodeInterpreter ResponsesToolType = "code_interpreter"
ResponsesToolTypeImageGeneration ResponsesToolType = "image_generation"
ResponsesToolTypeLocalShell ResponsesToolType = "local_shell"
ResponsesToolTypeCustom ResponsesToolType = "custom"
ResponsesToolTypeWebSearchPreview ResponsesToolType = "web_search_preview"
ResponsesToolTypeMemory ResponsesToolType = "memory"
ResponsesToolTypeToolSearch ResponsesToolType = "tool_search"
ResponsesToolTypeNamespace ResponsesToolType = "namespace"
)
// normalizeResponsesToolType maps versioned/provider-specific tool type strings
// to their canonical ResponsesToolType. For example, "web_search_20250305" → "web_search".
// Returns the input unchanged if it's already canonical or unrecognized.
func normalizeResponsesToolType(t ResponsesToolType) ResponsesToolType {
s := string(t)
switch {
// web_search_preview must be checked before web_search (prefix overlap)
case t == ResponsesToolTypeWebSearchPreview:
return t
case strings.HasPrefix(s, "web_search_preview"):
return ResponsesToolTypeWebSearchPreview
case t == ResponsesToolTypeWebSearch:
return t
case strings.HasPrefix(s, "web_search"):
return ResponsesToolTypeWebSearch
case t == ResponsesToolTypeWebFetch:
return t
case strings.HasPrefix(s, "web_fetch"):
return ResponsesToolTypeWebFetch
case strings.HasPrefix(s, "computer") && t != ResponsesToolTypeComputerUsePreview:
// Covers "computer_20250124", "computer_20251124", etc.
return ResponsesToolTypeComputerUsePreview
case strings.HasPrefix(s, "code_execution"):
return ResponsesToolTypeCodeInterpreter
case strings.HasPrefix(s, "memory") && t != ResponsesToolTypeMemory:
return ResponsesToolTypeMemory
default:
return t
}
}
// ResponsesTool represents a tool
type ResponsesTool struct {
Type ResponsesToolType `json:"type"` // "function" | "file_search" | "computer_use_preview" | "web_search" | "web_search_2025_08_26" | "mcp" | "code_interpreter" | "image_generation" | "local_shell" | "custom" | "web_search_preview" | "web_search_preview_2025_03_11"
Name *string `json:"name,omitempty"` // Common name field (Function, Custom tools)
Description *string `json:"description,omitempty"` // Common description field (Function, Custom tools)
// Not in OpenAI's schemas, but sent by a few providers (Anthropic, Bedrock are some of them)
CacheControl *CacheControl `json:"cache_control,omitempty"`
// Anthropic-native tool flags promoted to the neutral layer. All optional;
// ignored by providers that don't support them. Gated per ProviderFeatures
// in core/providers/anthropic/types.go.
DeferLoading *bool `json:"defer_loading,omitempty"` // Anthropic advanced-tool-use: defer loading of tool definition
AllowedCallers []string `json:"allowed_callers,omitempty"` // Anthropic advanced-tool-use: which callers can invoke this tool
InputExamples []ChatToolInputExample `json:"input_examples,omitempty"` // Anthropic tool-examples-2025-10-29: example inputs for the tool
EagerInputStreaming *bool `json:"eager_input_streaming,omitempty"` // Anthropic fine-grained-tool-streaming-2025-05-14
*ResponsesToolFunction
*ResponsesToolFileSearch
*ResponsesToolComputerUsePreview
*ResponsesToolWebSearch
*ResponsesToolWebFetch
*ResponsesToolMCP
*ResponsesToolCodeInterpreter
*ResponsesToolImageGeneration
*ResponsesToolLocalShell
*ResponsesToolCustom
*ResponsesToolWebSearchPreview
*ResponsesToolNamespace
}
// mergeJSONFields merges all top-level fields from src into dst using sjson,
// preserving the key order from src. This avoids map[string]interface{} which
// has non-deterministic iteration order in Go, breaking prompt caching.
func mergeJSONFields(dst, src []byte) ([]byte, error) {
var mergeErr error
gjson.ParseBytes(src).ForEach(func(key, value gjson.Result) bool {
dst, mergeErr = sjson.SetRawBytes(dst, key.String(), []byte(value.Raw))
return mergeErr == nil
})
return dst, mergeErr
}
// MarshalJSON implements custom JSON marshaling for ResponsesTool.
// It merges common fields with the appropriate embedded struct based on type.
// Uses sjson to build JSON bytes incrementally, ensuring deterministic key
// ordering critical for prompt caching (OpenAI caches based on request prefix).
func (t ResponsesTool) MarshalJSON() ([]byte, error) {
// Build JSON bytes with deterministic key order using sjson
data := []byte(`{}`)
var err error
// Set common fields in a fixed order
if data, err = sjson.SetBytes(data, "type", t.Type); err != nil {
return nil, err
}
if t.Name != nil {
if data, err = sjson.SetBytes(data, "name", *t.Name); err != nil {
return nil, err
}
}
if t.Description != nil {
if data, err = sjson.SetBytes(data, "description", *t.Description); err != nil {
return nil, err
}
}
if t.CacheControl != nil {
ccBytes, ccErr := MarshalSorted(t.CacheControl)
if ccErr != nil {
return nil, ccErr
}
if data, err = sjson.SetRawBytes(data, "cache_control", ccBytes); err != nil {
return nil, err
}
}
// Anthropic-native tool flags promoted to the neutral layer. Must be
// emitted here (before the type-specific merge) so the wire format carries
// them to providers that gate features on these keys. Without this block
// MarshalJSON silently drops the fields despite their json tags.
if t.DeferLoading != nil {
if data, err = sjson.SetBytes(data, "defer_loading", *t.DeferLoading); err != nil {
return nil, err
}
}
if len(t.AllowedCallers) > 0 {
callersBytes, callersErr := MarshalSorted(t.AllowedCallers)
if callersErr != nil {
return nil, callersErr
}
if data, err = sjson.SetRawBytes(data, "allowed_callers", callersBytes); err != nil {
return nil, err
}
}
if len(t.InputExamples) > 0 {
examplesBytes, examplesErr := MarshalSorted(t.InputExamples)
if examplesErr != nil {
return nil, examplesErr
}
if data, err = sjson.SetRawBytes(data, "input_examples", examplesBytes); err != nil {
return nil, err
}
}
if t.EagerInputStreaming != nil {
if data, err = sjson.SetBytes(data, "eager_input_streaming", *t.EagerInputStreaming); err != nil {
return nil, err
}
}
// Marshal the type-specific embedded struct and merge its fields
var typeBytes []byte
switch t.Type {
case ResponsesToolTypeFunction:
if t.ResponsesToolFunction != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolFunction)
}
case ResponsesToolTypeFileSearch:
if t.ResponsesToolFileSearch != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolFileSearch)
}
case ResponsesToolTypeComputerUsePreview:
if t.ResponsesToolComputerUsePreview != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolComputerUsePreview)
}
case ResponsesToolTypeWebSearch:
if t.ResponsesToolWebSearch != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolWebSearch)
}
case ResponsesToolTypeWebFetch:
if t.ResponsesToolWebFetch != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolWebFetch)
}
case ResponsesToolTypeMCP:
if t.ResponsesToolMCP != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolMCP)
}
case ResponsesToolTypeCodeInterpreter:
if t.ResponsesToolCodeInterpreter != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolCodeInterpreter)
}
case ResponsesToolTypeImageGeneration:
if t.ResponsesToolImageGeneration != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolImageGeneration)
}
case ResponsesToolTypeLocalShell:
if t.ResponsesToolLocalShell != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolLocalShell)
}
case ResponsesToolTypeCustom:
if t.ResponsesToolCustom != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolCustom)
}
case ResponsesToolTypeWebSearchPreview:
if t.ResponsesToolWebSearchPreview != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolWebSearchPreview)
}
case ResponsesToolTypeNamespace:
if t.ResponsesToolNamespace != nil {
typeBytes, err = MarshalSorted(t.ResponsesToolNamespace)
}
}
if err != nil {
return nil, err
}
// Merge type-specific fields into data preserving their serialization order
if typeBytes != nil {
data, err = mergeJSONFields(data, typeBytes)
if err != nil {
return nil, err
}
}
return data, nil
}
// UnmarshalJSON implements custom JSON unmarshaling for ResponsesTool
// It unmarshals common fields first, then the appropriate embedded struct based on type
func (t *ResponsesTool) UnmarshalJSON(data []byte) error {
// First unmarshal into a map to inspect the type
var raw map[string]interface{}
if err := Unmarshal(data, &raw); err != nil {
return err
}
// Extract type field
typeValue, ok := raw["type"]
if !ok {
return fmt.Errorf("missing required 'type' field in ResponsesTool")
}
typeStr, ok := typeValue.(string)
if !ok {
return fmt.Errorf("'type' field must be a string")
}
t.Type = normalizeResponsesToolType(ResponsesToolType(typeStr))
// Unmarshal common fields
if name, ok := raw["name"].(string); ok {
t.Name = &name
}
if description, ok := raw["description"].(string); ok {
t.Description = &description
}
if cacheControl, ok := raw["cache_control"]; ok {
bytes, err := MarshalSorted(cacheControl)
if err != nil {
return err
}
var cc CacheControl
if err := Unmarshal(bytes, &cc); err != nil {
return err
}
t.CacheControl = &cc
}
// Anthropic-native tool flags. Mirror the emit side in MarshalJSON above —
// without these reads, a round-trip silently drops the fields.
if v, ok := raw["defer_loading"].(bool); ok {
t.DeferLoading = Ptr(v)
}
if v, ok := raw["allowed_callers"]; ok {
bytes, err := MarshalSorted(v)
if err != nil {
return err
}
if err := Unmarshal(bytes, &t.AllowedCallers); err != nil {
return err
}
}
if v, ok := raw["input_examples"]; ok {
bytes, err := MarshalSorted(v)
if err != nil {
return err
}
if err := Unmarshal(bytes, &t.InputExamples); err != nil {
return err
}
}
if v, ok := raw["eager_input_streaming"].(bool); ok {
t.EagerInputStreaming = Ptr(v)
}
// Based on type, unmarshal into the appropriate embedded struct
switch t.Type {
case ResponsesToolTypeFunction:
var funcTool ResponsesToolFunction
if err := Unmarshal(data, &funcTool); err != nil {
return err
}
t.ResponsesToolFunction = &funcTool
case ResponsesToolTypeFileSearch:
var fileSearchTool ResponsesToolFileSearch
if err := Unmarshal(data, &fileSearchTool); err != nil {
return err
}
t.ResponsesToolFileSearch = &fileSearchTool
case ResponsesToolTypeComputerUsePreview:
var computerTool ResponsesToolComputerUsePreview
if err := Unmarshal(data, &computerTool); err != nil {
return err
}
t.ResponsesToolComputerUsePreview = &computerTool
case ResponsesToolTypeWebSearch:
var webSearchTool ResponsesToolWebSearch
if err := Unmarshal(data, &webSearchTool); err != nil {
return err
}
t.ResponsesToolWebSearch = &webSearchTool
case ResponsesToolTypeWebFetch:
var webFetchTool ResponsesToolWebFetch
if err := Unmarshal(data, &webFetchTool); err != nil {
return err
}
t.ResponsesToolWebFetch = &webFetchTool
case ResponsesToolTypeMCP:
var mcpTool ResponsesToolMCP
if err := Unmarshal(data, &mcpTool); err != nil {
return err
}
t.ResponsesToolMCP = &mcpTool
case ResponsesToolTypeCodeInterpreter:
var codeInterpreterTool ResponsesToolCodeInterpreter
if err := Unmarshal(data, &codeInterpreterTool); err != nil {
return err
}
t.ResponsesToolCodeInterpreter = &codeInterpreterTool
case ResponsesToolTypeImageGeneration:
var imageGenTool ResponsesToolImageGeneration
if err := Unmarshal(data, &imageGenTool); err != nil {
return err
}
t.ResponsesToolImageGeneration = &imageGenTool
case ResponsesToolTypeLocalShell:
var localShellTool ResponsesToolLocalShell
if err := Unmarshal(data, &localShellTool); err != nil {
return err
}
t.ResponsesToolLocalShell = &localShellTool
case ResponsesToolTypeCustom:
var customTool ResponsesToolCustom
if err := Unmarshal(data, &customTool); err != nil {
return err
}
t.ResponsesToolCustom = &customTool
case ResponsesToolTypeWebSearchPreview:
var webSearchPreviewTool ResponsesToolWebSearchPreview
if err := Unmarshal(data, &webSearchPreviewTool); err != nil {
return err
}
t.ResponsesToolWebSearchPreview = &webSearchPreviewTool
case ResponsesToolTypeNamespace:
var namespaceTool ResponsesToolNamespace
if err := Unmarshal(data, &namespaceTool); err != nil {
return err
}
t.ResponsesToolNamespace = &namespaceTool
}
return nil
}
// ResponsesToolFunction represents a tool function
type ResponsesToolFunction struct {
Parameters *ToolFunctionParameters `json:"parameters,omitempty"` // A JSON schema object describing the parameters
Strict *bool `json:"strict"` // Whether to enforce strict parameter validation
}
// ResponsesToolFileSearch represents a tool file search
type ResponsesToolFileSearch struct {
VectorStoreIDs []string `json:"vector_store_ids"` // The IDs of the vector stores to search
Filters *ResponsesToolFileSearchFilter `json:"filters,omitempty"` // A filter to apply
MaxNumResults *int `json:"max_num_results,omitempty"` // Maximum results (1-50)
RankingOptions *ResponsesToolFileSearchRankingOptions `json:"ranking_options,omitempty"` // Ranking options for search
}
// ResponsesToolFileSearchFilter represents a file search filter
type ResponsesToolFileSearchFilter struct {
Type string `json:"type"` // "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "and" | "or"
// Filter types - only one should be set
*ResponsesToolFileSearchComparisonFilter
*ResponsesToolFileSearchCompoundFilter
}
// MarshalJSON implements custom JSON marshaling for ResponsesToolFileSearchFilter
func (f *ResponsesToolFileSearchFilter) MarshalJSON() ([]byte, error) {
// Validate that exactly one filter type is set
if f.ResponsesToolFileSearchComparisonFilter != nil && f.ResponsesToolFileSearchCompoundFilter != nil {
return nil, fmt.Errorf("both comparison and compound filters are set; only one should be non-nil")
}
if f.ResponsesToolFileSearchComparisonFilter == nil && f.ResponsesToolFileSearchCompoundFilter == nil {
return nil, fmt.Errorf("neither comparison nor compound filter is set; exactly one must be non-nil")
}
// Build JSON bytes with deterministic key order using sjson
data := []byte(`{}`)
var err error
if data, err = sjson.SetBytes(data, "type", f.Type); err != nil {
return nil, err
}
switch f.Type {
case "eq", "ne", "gt", "gte", "lt", "lte":
if f.ResponsesToolFileSearchComparisonFilter == nil {
return nil, fmt.Errorf("comparison filter is nil but type is %s", f.Type)
}
if data, err = sjson.SetBytes(data, "key", f.ResponsesToolFileSearchComparisonFilter.Key); err != nil {
return nil, err
}
if data, err = sjson.SetBytes(data, "value", f.ResponsesToolFileSearchComparisonFilter.Value); err != nil {
return nil, err
}
case "and", "or":
if f.ResponsesToolFileSearchCompoundFilter == nil {
return nil, fmt.Errorf("compound filter is nil but type is %s", f.Type)
}
filtersBytes, fErr := MarshalSorted(f.ResponsesToolFileSearchCompoundFilter.Filters)
if fErr != nil {
return nil, fErr
}
if data, err = sjson.SetRawBytes(data, "filters", filtersBytes); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown filter type: %s", f.Type)
}
return data, nil
}
// UnmarshalJSON implements custom JSON unmarshaling for ResponsesToolFileSearchFilter
func (f *ResponsesToolFileSearchFilter) UnmarshalJSON(data []byte) error {
// First, unmarshal into a map to inspect the type field
var raw map[string]interface{}
if err := Unmarshal(data, &raw); err != nil {
return fmt.Errorf("failed to unmarshal filter JSON: %w", err)
}
// Extract the type field
typeValue, ok := raw["type"]
if !ok {
return fmt.Errorf("missing required 'type' field in filter")
}
typeStr, ok := typeValue.(string)
if !ok {
return fmt.Errorf("'type' field must be a string, got %T", typeValue)
}
f.Type = typeStr
// Initialize the appropriate embedded struct based on type
switch typeStr {
case "eq", "ne", "gt", "gte", "lt", "lte":
// This is a comparison filter
f.ResponsesToolFileSearchComparisonFilter = &ResponsesToolFileSearchComparisonFilter{}
f.ResponsesToolFileSearchCompoundFilter = nil
// Unmarshal into the comparison filter
if err := Unmarshal(data, f.ResponsesToolFileSearchComparisonFilter); err != nil {
return fmt.Errorf("failed to unmarshal comparison filter: %w", err)
}
// Validate required fields
if f.ResponsesToolFileSearchComparisonFilter.Key == "" {
return fmt.Errorf("comparison filter missing required 'key' field")
}
if f.ResponsesToolFileSearchComparisonFilter.Value == nil {
return fmt.Errorf("comparison filter missing required 'value' field")
}
case "and", "or":
// This is a compound filter
f.ResponsesToolFileSearchCompoundFilter = &ResponsesToolFileSearchCompoundFilter{}
f.ResponsesToolFileSearchComparisonFilter = nil
// Unmarshal into the compound filter
if err := Unmarshal(data, f.ResponsesToolFileSearchCompoundFilter); err != nil {
return fmt.Errorf("failed to unmarshal compound filter: %w", err)
}
// Validate required fields
if f.ResponsesToolFileSearchCompoundFilter.Filters == nil {
return fmt.Errorf("compound filter missing required 'filters' field")
}
if len(f.ResponsesToolFileSearchCompoundFilter.Filters) == 0 {
return fmt.Errorf("compound filter 'filters' array cannot be empty")
}
default:
return fmt.Errorf("unknown filter type: %s (supported types: eq, ne, gt, gte, lt, lte, and, or)", typeStr)
}
return nil
}
// ResponsesToolFileSearchComparisonFilter represents a file search comparison filter
type ResponsesToolFileSearchComparisonFilter struct {
Key string `json:"key"` // The key to compare against the value
Type string `json:"type"` //
Value interface{} `json:"value"` // The value to compare (string, number, or boolean)
}
// ResponsesToolFileSearchCompoundFilter represents a file search compound filter
type ResponsesToolFileSearchCompoundFilter struct {
Filters []ResponsesToolFileSearchFilter `json:"filters"` // Array of filters to combine
}
// ResponsesToolFileSearchRankingOptions represents a file search ranking options
type ResponsesToolFileSearchRankingOptions struct {
Ranker *string `json:"ranker,omitempty"` // The ranker to use
ScoreThreshold *float64 `json:"score_threshold,omitempty"` // Score threshold (0-1)
}
// ResponsesToolComputerUsePreview represents a tool computer use preview
type ResponsesToolComputerUsePreview struct {
DisplayHeight int `json:"display_height"` // The height of the computer display
DisplayWidth int `json:"display_width"` // The width of the computer display
Environment string `json:"environment"` // The type of computer environment to control
EnableZoom *bool `json:"enable_zoom,omitempty"` // for computer tool in anthropic only
}
// ResponsesToolWebSearch represents a tool web search
type ResponsesToolWebSearch struct {
Filters *ResponsesToolWebSearchFilters `json:"filters,omitempty"` // Filters for the search
SearchContextSize *string `json:"search_context_size,omitempty"` // "low" | "medium" | "high"
UserLocation *ResponsesToolWebSearchUserLocation `json:"user_location,omitempty"` // The approximate location of the user
// Anthropic only
MaxUses *int `json:"max_uses,omitempty"` // Maximum number of uses for the search
}
// ResponsesToolWebSearchFilters represents filters for web search
type ResponsesToolWebSearchFilters struct {
AllowedDomains []string `json:"allowed_domains,omitempty"` // Allowed domains for the search
BlockedDomains []string `json:"blocked_domains,omitempty"` // Blocked domains for the search, only used in anthropic
// Gemini only
// Filter search results to a specific time range.
// If users set a start time, they must set an end time (and vice versa).
TimeRangeFilter *Interval `json:"time_range_filter,omitempty"`
}
// Interval represents a time interval, encoded as a start time (inclusive) and an end time (exclusive).
// The start time must be less than or equal to the end time.
// When the start equals the end time, the interval is an empty interval.
// (matches no time)
// When both start and end are unspecified, the interval matches any time.
type Interval struct {
// Optional. The start time of the interval.
StartTime time.Time `json:"start_time,omitempty"`
// Optional. The end time of the interval.
EndTime time.Time `json:"end_time,omitempty"`
}
func (i *Interval) UnmarshalJSON(data []byte) error {
type Alias Interval
aux := &struct {
StartTime *time.Time `json:"start_time,omitempty"`
EndTime *time.Time `json:"end_time,omitempty"`
*Alias
}{
Alias: (*Alias)(i),
}
if err := Unmarshal(data, &aux); err != nil {
return err
}
if !reflect.ValueOf(aux.StartTime).IsZero() {
i.StartTime = time.Time(*aux.StartTime)
}
if !reflect.ValueOf(aux.EndTime).IsZero() {
i.EndTime = time.Time(*aux.EndTime)
}
return nil
}
func (i *Interval) MarshalJSON() ([]byte, error) {
type Alias Interval
aux := &struct {
StartTime *time.Time `json:"start_time,omitempty"`
EndTime *time.Time `json:"end_time,omitempty"`
*Alias
}{
Alias: (*Alias)(i),
}
if !reflect.ValueOf(i.StartTime).IsZero() {
aux.StartTime = (*time.Time)(&i.StartTime)
}
if !reflect.ValueOf(i.EndTime).IsZero() {
aux.EndTime = (*time.Time)(&i.EndTime)
}
return MarshalSorted(aux)
}
// ResponsesToolWebSearchUserLocation - The approximate location of the user
type ResponsesToolWebSearchUserLocation struct {
City *string `json:"city,omitempty"` // Free text input for the city
Country *string `json:"country,omitempty"` // Two-letter ISO country code
Region *string `json:"region,omitempty"` // Free text input for the region
Timezone *string `json:"timezone,omitempty"` // IANA timezone
Type *string `json:"type,omitempty"` // always "approximate"
}
// ResponsesToolMCP - Give the model access to additional tools via remote MCP servers
type ResponsesToolMCP struct {
ServerLabel string `json:"server_label"` // A label for this MCP server
AllowedTools *ResponsesToolMCPAllowedTools `json:"allowed_tools,omitempty"` // List of allowed tool names or filter
Authorization *string `json:"authorization,omitempty"` // OAuth access token
ConnectorID *string `json:"connector_id,omitempty"` // Service connector ID
Headers *map[string]string `json:"headers,omitempty"` // Optional HTTP headers
RequireApproval *ResponsesToolMCPAllowedToolsApprovalSetting `json:"require_approval,omitempty"` // Tool approval settings
ServerDescription *string `json:"server_description,omitempty"` // Optional server description
ServerURL *string `json:"server_url,omitempty"` // The URL for the MCP server
}
// ResponsesToolMCPAllowedTools - List of allowed tool names or a filter object
type ResponsesToolMCPAllowedTools struct {
// Either a simple array of tool names or a filter object
ToolNames []string `json:",omitempty"`
Filter *ResponsesToolMCPAllowedToolsFilter `json:",omitempty"`
}
// ResponsesToolMCPAllowedToolsFilter - A filter object to specify which tools are allowed
type ResponsesToolMCPAllowedToolsFilter struct {
ReadOnly *bool `json:"read_only,omitempty"` // Whether tool is read-only
ToolNames []string `json:"tool_names,omitempty"` // List of allowed tool names
}
// ResponsesToolMCPAllowedToolsApprovalSetting - Specify which tools require approval
type ResponsesToolMCPAllowedToolsApprovalSetting struct {
// Either a string setting or filter objects
Setting *string `json:",omitempty"` // "always" | "never"
Always *ResponsesToolMCPAllowedToolsApprovalFilter `json:"always,omitempty"`
Never *ResponsesToolMCPAllowedToolsApprovalFilter `json:"never,omitempty"`
}
// MarshalJSON implements custom JSON marshalling for ResponsesToolMCPAllowedToolsApprovalSetting
func (as ResponsesToolMCPAllowedToolsApprovalSetting) MarshalJSON() ([]byte, error) {
// Validation: ensure only one representation is set
if as.Setting != nil && (as.Always != nil || as.Never != nil) {
return nil, fmt.Errorf("only one of 'Setting' or ('Always'/'Never') can be set")
}
if as.Setting != nil {
return MarshalSorted(*as.Setting)
}
if as.Always != nil || as.Never != nil {
// Build JSON bytes with deterministic key order using sjson
data := []byte(`{}`)
var err error
if as.Always != nil {
alwaysBytes, aErr := MarshalSorted(as.Always)
if aErr != nil {
return nil, aErr
}
if data, err = sjson.SetRawBytes(data, "always", alwaysBytes); err != nil {
return nil, err
}
}
if as.Never != nil {
neverBytes, nErr := MarshalSorted(as.Never)
if nErr != nil {
return nil, nErr
}
if data, err = sjson.SetRawBytes(data, "never", neverBytes); err != nil {
return nil, err
}
}
return data, nil
}
// If all are nil, return null
return MarshalSorted(nil)
}
// UnmarshalJSON implements custom JSON unmarshalling for ResponsesToolMCPAllowedToolsApprovalSetting
func (as *ResponsesToolMCPAllowedToolsApprovalSetting) UnmarshalJSON(data []byte) error {
// First, try to unmarshal as a direct string
var settingStr string
if err := Unmarshal(data, &settingStr); err == nil {
as.Setting = &settingStr
return nil
}
// Try to unmarshal as an object with always/never fields
var obj struct {
Always *ResponsesToolMCPAllowedToolsApprovalFilter `json:"always,omitempty"`
Never *ResponsesToolMCPAllowedToolsApprovalFilter `json:"never,omitempty"`
}
if err := Unmarshal(data, &obj); err == nil {
as.Always = obj.Always
as.Never = obj.Never
return nil
}
return fmt.Errorf("require_approval field is neither a string nor an object with always/never filters")
}
// ResponsesToolMCPAllowedToolsApprovalFilter - Filter for approval settings
type ResponsesToolMCPAllowedToolsApprovalFilter struct {
ReadOnly *bool `json:"read_only,omitempty"` // Whether tool is read-only
ToolNames []string `json:"tool_names,omitempty"` // List of tool names
}
// ResponsesToolCodeInterpreter represents a tool code interpreter
type ResponsesToolCodeInterpreter struct {
Container interface{} `json:"container"` // Container ID or object with file IDs
}
// ResponsesToolImageGeneration represents a tool image generation
type ResponsesToolImageGeneration struct {
Background *string `json:"background,omitempty"` // "transparent" | "opaque" | "auto"
InputFidelity *string `json:"input_fidelity,omitempty"` // "high" | "low"
InputImageMask *ResponsesToolImageGenerationInputImageMask `json:"input_image_mask,omitempty"` // Optional mask for inpainting
Model *string `json:"model,omitempty"` // Image generation model
Moderation *string `json:"moderation,omitempty"` // Moderation level
OutputCompression *int `json:"output_compression,omitempty"` // Compression level (0-100)
OutputFormat *string `json:"output_format,omitempty"` // "png" | "webp" | "jpeg"
PartialImages *int `json:"partial_images,omitempty"` // Number of partial images (0-3)
Quality *string `json:"quality,omitempty"` // "low" | "medium" | "high" | "auto"
Size *string `json:"size,omitempty"` // Image size
}
// ResponsesToolImageGenerationInputImageMask represents a image generation input image mask
type ResponsesToolImageGenerationInputImageMask struct {
FileID *string `json:"file_id,omitempty"` // File ID for the mask image
ImageURL *string `json:"image_url,omitempty"` // Base64-encoded mask image
}
// ResponsesToolLocalShell represents a tool local shell
type ResponsesToolLocalShell struct {
// No unique fields needed since Type is now in the top-level struct
}
// ResponsesToolCustom represents a custom tool
type ResponsesToolCustom struct {
Format *ResponsesToolCustomFormat `json:"format,omitempty"` // The input format
}
// ResponsesToolCustomFormat represents the input format for the custom tool
type ResponsesToolCustomFormat struct {
Type string `json:"type"` // always "text"
// For Grammar
Definition *string `json:"definition,omitempty"` // The grammar definition
Syntax *string `json:"syntax,omitempty"` // "lark" | "regex"
}
// ResponsesToolWebSearchPreview represents a web search preview
type ResponsesToolWebSearchPreview struct {
SearchContextSize *string `json:"search_context_size,omitempty"` // "low" | "medium" | "high"
UserLocation *ResponsesToolWebSearchUserLocation `json:"user_location,omitempty"` // The user's location
}
// ResponsesToolWebFetch represents a web fetch tool
type ResponsesToolWebFetch struct {
MaxUses *int `json:"max_uses,omitempty"`
Filters *ResponsesToolWebSearchFilters `json:"filters,omitempty"`
MaxContentTokens *int `json:"max_content_tokens,omitempty"`
}
// ResponsesToolNamespace represents a namespace tool that groups related function tools.
type ResponsesToolNamespace struct {
Tools []ResponsesTool `json:"tools,omitempty"`
}
// ======================================================= Streaming Structs =======================================================
type ResponsesStreamResponseType string
const (
// Ping events are just keepalive (sent by very few providers, Anthropic is one of them)
ResponsesStreamResponseTypePing ResponsesStreamResponseType = "response.ping"
ResponsesStreamResponseTypeCreated ResponsesStreamResponseType = "response.created"
ResponsesStreamResponseTypeInProgress ResponsesStreamResponseType = "response.in_progress"
ResponsesStreamResponseTypeCompleted ResponsesStreamResponseType = "response.completed"
ResponsesStreamResponseTypeFailed ResponsesStreamResponseType = "response.failed"
ResponsesStreamResponseTypeIncomplete ResponsesStreamResponseType = "response.incomplete"
ResponsesStreamResponseTypeOutputItemAdded ResponsesStreamResponseType = "response.output_item.added"
ResponsesStreamResponseTypeOutputItemDone ResponsesStreamResponseType = "response.output_item.done"
ResponsesStreamResponseTypeContentPartAdded ResponsesStreamResponseType = "response.content_part.added"
ResponsesStreamResponseTypeContentPartDone ResponsesStreamResponseType = "response.content_part.done"
ResponsesStreamResponseTypeOutputTextDelta ResponsesStreamResponseType = "response.output_text.delta"
ResponsesStreamResponseTypeOutputTextDone ResponsesStreamResponseType = "response.output_text.done"
ResponsesStreamResponseTypeRefusalDelta ResponsesStreamResponseType = "response.refusal.delta"
ResponsesStreamResponseTypeRefusalDone ResponsesStreamResponseType = "response.refusal.done"
ResponsesStreamResponseTypeFunctionCallArgumentsDelta ResponsesStreamResponseType = "response.function_call_arguments.delta"
ResponsesStreamResponseTypeFunctionCallArgumentsDone ResponsesStreamResponseType = "response.function_call_arguments.done"
ResponsesStreamResponseTypeFileSearchCallInProgress ResponsesStreamResponseType = "response.file_search_call.in_progress"
ResponsesStreamResponseTypeFileSearchCallSearching ResponsesStreamResponseType = "response.file_search_call.searching"
ResponsesStreamResponseTypeFileSearchCallResultsAdded ResponsesStreamResponseType = "response.file_search_call.results.added"
ResponsesStreamResponseTypeFileSearchCallResultsCompleted ResponsesStreamResponseType = "response.file_search_call.results.completed"
ResponsesStreamResponseTypeWebSearchCallInProgress ResponsesStreamResponseType = "response.web_search_call.in_progress"
ResponsesStreamResponseTypeWebSearchCallSearching ResponsesStreamResponseType = "response.web_search_call.searching"
ResponsesStreamResponseTypeWebSearchCallCompleted ResponsesStreamResponseType = "response.web_search_call.completed"
ResponsesStreamResponseTypeWebSearchCallResultsAdded ResponsesStreamResponseType = "response.web_search_call.results.added"
ResponsesStreamResponseTypeWebSearchCallResultsCompleted ResponsesStreamResponseType = "response.web_search_call.results.completed"
ResponsesStreamResponseTypeWebFetchCallInProgress ResponsesStreamResponseType = "response.web_fetch_call.in_progress"
ResponsesStreamResponseTypeWebFetchCallFetching ResponsesStreamResponseType = "response.web_fetch_call.fetching"
ResponsesStreamResponseTypeWebFetchCallCompleted ResponsesStreamResponseType = "response.web_fetch_call.completed"
ResponsesStreamResponseTypeReasoningSummaryPartAdded ResponsesStreamResponseType = "response.reasoning_summary_part.added"
ResponsesStreamResponseTypeReasoningSummaryPartDone ResponsesStreamResponseType = "response.reasoning_summary_part.done"
ResponsesStreamResponseTypeReasoningSummaryTextDelta ResponsesStreamResponseType = "response.reasoning_summary_text.delta"
ResponsesStreamResponseTypeReasoningSummaryTextDone ResponsesStreamResponseType = "response.reasoning_summary_text.done"
ResponsesStreamResponseTypeImageGenerationCallCompleted ResponsesStreamResponseType = "response.image_generation_call.completed"
ResponsesStreamResponseTypeImageGenerationCallGenerating ResponsesStreamResponseType = "response.image_generation_call.generating"
ResponsesStreamResponseTypeImageGenerationCallInProgress ResponsesStreamResponseType = "response.image_generation_call.in_progress"
ResponsesStreamResponseTypeImageGenerationCallPartialImage ResponsesStreamResponseType = "response.image_generation_call.partial_image"
ResponsesStreamResponseTypeMCPCallArgumentsDelta ResponsesStreamResponseType = "response.mcp_call_arguments.delta"
ResponsesStreamResponseTypeMCPCallArgumentsDone ResponsesStreamResponseType = "response.mcp_call_arguments.done"
ResponsesStreamResponseTypeMCPCallCompleted ResponsesStreamResponseType = "response.mcp_call.completed"
ResponsesStreamResponseTypeMCPCallFailed ResponsesStreamResponseType = "response.mcp_call.failed"
ResponsesStreamResponseTypeMCPCallInProgress ResponsesStreamResponseType = "response.mcp_call.in_progress"
ResponsesStreamResponseTypeMCPListToolsCompleted ResponsesStreamResponseType = "response.mcp_list_tools.completed"
ResponsesStreamResponseTypeMCPListToolsFailed ResponsesStreamResponseType = "response.mcp_list_tools.failed"
ResponsesStreamResponseTypeMCPListToolsInProgress ResponsesStreamResponseType = "response.mcp_list_tools.in_progress"
ResponsesStreamResponseTypeCodeInterpreterCallInProgress ResponsesStreamResponseType = "response.code_interpreter_call.in_progress"
ResponsesStreamResponseTypeCodeInterpreterCallInterpreting ResponsesStreamResponseType = "response.code_interpreter_call.interpreting"
ResponsesStreamResponseTypeCodeInterpreterCallCompleted ResponsesStreamResponseType = "response.code_interpreter_call.completed"
ResponsesStreamResponseTypeCodeInterpreterCallCodeDelta ResponsesStreamResponseType = "response.code_interpreter_call_code.delta"
ResponsesStreamResponseTypeCodeInterpreterCallCodeDone ResponsesStreamResponseType = "response.code_interpreter_call_code.done"
ResponsesStreamResponseTypeOutputTextAnnotationAdded ResponsesStreamResponseType = "response.output_text.annotation.added"
ResponsesStreamResponseTypeOutputTextAnnotationDone ResponsesStreamResponseType = "response.output_text.annotation.done"
ResponsesStreamResponseTypeQueued ResponsesStreamResponseType = "response.queued"
ResponsesStreamResponseTypeCustomToolCallInputDelta ResponsesStreamResponseType = "response.custom_tool_call_input.delta"
ResponsesStreamResponseTypeCustomToolCallInputDone ResponsesStreamResponseType = "response.custom_tool_call_input.done"
ResponsesStreamResponseTypeError ResponsesStreamResponseType = "error"
)
type BifrostResponsesStreamResponse struct {
Type ResponsesStreamResponseType `json:"type"`
SequenceNumber int `json:"sequence_number"`
Response *BifrostResponsesResponse `json:"response,omitempty"`
OutputIndex *int `json:"output_index,omitempty"`
Item *ResponsesMessage `json:"item"`
ContentIndex *int `json:"content_index,omitempty"`
ItemID *string `json:"item_id,omitempty"`
Part *ResponsesMessageContentBlock `json:"part,omitempty"`
Delta *string `json:"delta,omitempty"`
Signature *string `json:"signature,omitempty"` // Not in OpenAI's spec, but sent by other providers
LogProbs []ResponsesOutputMessageContentTextLogProb `json:"logprobs"`
Text *string `json:"text,omitempty"` // Full text of the output item, comes with event "response.output_text.done"
Refusal *string `json:"refusal,omitempty"`
Arguments *string `json:"arguments,omitempty"`
PartialImageB64 *string `json:"partial_image_b64,omitempty"`
PartialImageIndex *int `json:"partial_image_index,omitempty"`
Annotation *ResponsesOutputMessageContentTextAnnotation `json:"annotation,omitempty"`
AnnotationIndex *int `json:"annotation_index,omitempty"`
Code *string `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
Param *string `json:"param,omitempty"`
ExtraFields BifrostResponseExtraFields `json:"extra_fields"`
// Perplexity-specific fields
SearchResults []SearchResult `json:"search_results,omitempty"`
Videos []VideoResult `json:"videos,omitempty"`
Citations []string `json:"citations,omitempty"`
}
func (resp *BifrostResponsesStreamResponse) WithDefaults() *BifrostResponsesStreamResponse {
if resp == nil {
return nil
}
// Filter out non-OpenAI response types
if resp.Type == ResponsesStreamResponseTypePing {
return nil
}
result := &BifrostResponsesStreamResponse{
Type: resp.Type,
SequenceNumber: resp.SequenceNumber,
}
// Copy nested response (applies defaults)
result.Response = resp.Response.WithDefaults()
// Copy all streaming-specific fields
result.OutputIndex = resp.OutputIndex
result.Item = resp.Item
result.ContentIndex = resp.ContentIndex
result.ItemID = resp.ItemID
result.Part = resp.Part
result.Delta = resp.Delta
result.Signature = resp.Signature
result.Text = resp.Text
result.Refusal = resp.Refusal
result.Arguments = resp.Arguments
result.PartialImageB64 = resp.PartialImageB64
result.PartialImageIndex = resp.PartialImageIndex
result.Annotation = resp.Annotation
result.AnnotationIndex = resp.AnnotationIndex
result.Code = resp.Code
result.Message = resp.Message
result.Param = resp.Param
result.LogProbs = resp.LogProbs
// Apply event-specific defaults
switch resp.Type {
case ResponsesStreamResponseTypeOutputItemAdded:
// Default item status to "in_progress"
if result.Item != nil && result.Item.Status == nil {
result.Item.Status = Ptr("in_progress")
}
case ResponsesStreamResponseTypeOutputTextDelta, ResponsesStreamResponseTypeOutputTextDone:
// Ensure logprobs array exists
if result.LogProbs == nil {
result.LogProbs = []ResponsesOutputMessageContentTextLogProb{}
}
case ResponsesStreamResponseTypeContentPartAdded, ResponsesStreamResponseTypeContentPartDone:
// Ensure part has proper structure
if result.Part == nil {
result.Part = &ResponsesMessageContentBlock{
Type: ResponsesOutputMessageContentTypeText,
Text: Ptr(""),
ResponsesOutputMessageContentText: &ResponsesOutputMessageContentText{
LogProbs: []ResponsesOutputMessageContentTextLogProb{},
Annotations: []ResponsesOutputMessageContentTextAnnotation{},
},
}
} else if result.Part.ResponsesOutputMessageContentText == nil {
result.Part.ResponsesOutputMessageContentText = &ResponsesOutputMessageContentText{
LogProbs: []ResponsesOutputMessageContentTextLogProb{},
Annotations: []ResponsesOutputMessageContentTextAnnotation{},
}
} else {
// Ensure nested arrays exist
if result.Part.ResponsesOutputMessageContentText.LogProbs == nil {
result.Part.ResponsesOutputMessageContentText.LogProbs = []ResponsesOutputMessageContentTextLogProb{}
}
if result.Part.ResponsesOutputMessageContentText.Annotations == nil {
result.Part.ResponsesOutputMessageContentText.Annotations = []ResponsesOutputMessageContentTextAnnotation{}
}
}
}
return result
}