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 }