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

1613 lines
65 KiB
Go

package schemas
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
// BifrostChatRequest is the request struct for chat completion requests
type BifrostChatRequest struct {
Provider ModelProvider `json:"provider"`
Model string `json:"model"`
Input []ChatMessage `json:"input,omitempty"`
Params *ChatParameters `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.
}
// GetRawRequestBody returns the raw request body
func (cr *BifrostChatRequest) GetRawRequestBody() []byte {
return cr.RawRequestBody
}
func (cr *BifrostChatRequest) GetExtraParams() map[string]interface{} {
if cr.Params == nil {
return make(map[string]interface{}, 0)
}
return cr.Params.ExtraParams
}
// BifrostChatResponse represents the complete result from a chat completion request.
type BifrostChatResponse struct {
ID string `json:"id"`
Choices []BifrostResponseChoice `json:"choices"`
Created int `json:"created"` // The Unix timestamp (in seconds).
Model string `json:"model"`
Object string `json:"object"` // "chat.completion" or "chat.completion.chunk"
ServiceTier *string `json:"service_tier,omitempty"`
SystemFingerprint string `json:"system_fingerprint"`
Usage *BifrostLLMUsage `json:"usage"`
ExtraFields BifrostResponseExtraFields `json:"extra_fields"`
ExtraParams map[string]interface{} `json:"-"`
// 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 (cr *BifrostChatResponse) BackfillParams(request *BifrostChatRequest) {
if cr == nil || request == nil {
return
}
if cr.Model == "" {
cr.Model = request.Model
}
if cr.Object == "" {
cr.Object = "chat.completion"
}
if cr.Created == 0 {
cr.Created = int(time.Now().Unix())
}
}
// ToTextCompletionResponse converts a BifrostChatResponse to a BifrostTextCompletionResponse
func (cr *BifrostChatResponse) ToTextCompletionResponse() *BifrostTextCompletionResponse {
if cr == nil {
return nil
}
if len(cr.Choices) == 0 {
return &BifrostTextCompletionResponse{
ID: cr.ID,
Model: cr.Model,
Object: "text_completion",
SystemFingerprint: cr.SystemFingerprint,
Usage: cr.Usage,
ExtraFields: BifrostResponseExtraFields{
RequestType: TextCompletionRequest,
ChunkIndex: cr.ExtraFields.ChunkIndex,
Provider: cr.ExtraFields.Provider,
OriginalModelRequested: cr.ExtraFields.OriginalModelRequested,
ResolvedModelUsed: cr.ExtraFields.ResolvedModelUsed,
Latency: cr.ExtraFields.Latency,
RawResponse: cr.ExtraFields.RawResponse,
CacheDebug: cr.ExtraFields.CacheDebug,
ProviderResponseHeaders: cr.ExtraFields.ProviderResponseHeaders,
},
}
}
choice := cr.Choices[0]
// Handle streaming response choice
if choice.ChatStreamResponseChoice != nil && choice.ChatStreamResponseChoice.Delta != nil {
return &BifrostTextCompletionResponse{
ID: cr.ID,
Model: cr.Model,
Object: "text_completion",
SystemFingerprint: cr.SystemFingerprint,
Choices: []BifrostResponseChoice{
{
Index: 0,
TextCompletionResponseChoice: &TextCompletionResponseChoice{
Text: choice.ChatStreamResponseChoice.Delta.Content,
},
FinishReason: choice.FinishReason,
LogProbs: choice.LogProbs,
},
},
Usage: cr.Usage,
ExtraFields: BifrostResponseExtraFields{
RequestType: TextCompletionRequest,
ChunkIndex: cr.ExtraFields.ChunkIndex,
Provider: cr.ExtraFields.Provider,
OriginalModelRequested: cr.ExtraFields.OriginalModelRequested,
ResolvedModelUsed: cr.ExtraFields.ResolvedModelUsed,
Latency: cr.ExtraFields.Latency,
RawResponse: cr.ExtraFields.RawResponse,
CacheDebug: cr.ExtraFields.CacheDebug,
ProviderResponseHeaders: cr.ExtraFields.ProviderResponseHeaders,
},
}
}
// Handle non-streaming response choice
if choice.ChatNonStreamResponseChoice != nil {
msg := choice.ChatNonStreamResponseChoice.Message
var textContent *string
if msg != nil && msg.Content != nil && msg.Content.ContentStr != nil {
textContent = msg.Content.ContentStr
}
return &BifrostTextCompletionResponse{
ID: cr.ID,
Model: cr.Model,
Object: "text_completion",
SystemFingerprint: cr.SystemFingerprint,
Choices: []BifrostResponseChoice{
{
Index: 0,
TextCompletionResponseChoice: &TextCompletionResponseChoice{
Text: textContent,
},
FinishReason: choice.FinishReason,
LogProbs: choice.LogProbs,
},
},
Usage: cr.Usage,
ExtraFields: BifrostResponseExtraFields{
RequestType: TextCompletionRequest,
ChunkIndex: cr.ExtraFields.ChunkIndex,
Provider: cr.ExtraFields.Provider,
OriginalModelRequested: cr.ExtraFields.OriginalModelRequested,
ResolvedModelUsed: cr.ExtraFields.ResolvedModelUsed,
Latency: cr.ExtraFields.Latency,
RawResponse: cr.ExtraFields.RawResponse,
CacheDebug: cr.ExtraFields.CacheDebug,
ProviderResponseHeaders: cr.ExtraFields.ProviderResponseHeaders,
},
}
}
// Fallback case - return basic response structure
return &BifrostTextCompletionResponse{
ID: cr.ID,
Model: cr.Model,
Object: "text_completion",
SystemFingerprint: cr.SystemFingerprint,
Usage: cr.Usage,
ExtraFields: BifrostResponseExtraFields{
RequestType: TextCompletionRequest,
ChunkIndex: cr.ExtraFields.ChunkIndex,
Provider: cr.ExtraFields.Provider,
OriginalModelRequested: cr.ExtraFields.OriginalModelRequested,
ResolvedModelUsed: cr.ExtraFields.ResolvedModelUsed,
Latency: cr.ExtraFields.Latency,
RawResponse: cr.ExtraFields.RawResponse,
CacheDebug: cr.ExtraFields.CacheDebug,
ProviderResponseHeaders: cr.ExtraFields.ProviderResponseHeaders,
},
}
}
// ChatParameters represents the parameters for a chat completion.
type ChatParameters struct {
Audio *ChatAudioParameters `json:"audio,omitempty"` // Audio parameters
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // Penalizes frequent tokens
LogitBias *map[string]float64 `json:"logit_bias,omitempty"` // Bias for logit values
LogProbs *bool `json:"logprobs,omitempty"` // Number of logprobs to return
MaxCompletionTokens *int `json:"max_completion_tokens,omitempty"` // Maximum number of tokens to generate
Metadata *map[string]any `json:"metadata,omitempty"` // Metadata to be returned with the response
Modalities []string `json:"modalities,omitempty"` // Modalities to be returned with the response
N *int `json:"n,omitempty"` // Number of chat completions to generate when supported
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
Prediction *ChatPrediction `json:"prediction,omitempty"` // Predicted output content (OpenAI only)
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // Penalizes repeated tokens
PromptCacheKey *string `json:"prompt_cache_key,omitempty"` // Prompt cache key
PromptCacheRetention *string `json:"prompt_cache_retention,omitempty"` // Prompt cache retention ("in-memory" or "24h")
Reasoning *ChatReasoning `json:"reasoning,omitempty"` // Reasoning parameters
ResponseFormat *interface{} `json:"response_format,omitempty"` // Format for the response
SafetyIdentifier *string `json:"safety_identifier,omitempty"` // Safety identifier
Seed *int `json:"seed,omitempty"`
ServiceTier *string `json:"service_tier,omitempty"`
StreamOptions *ChatStreamOptions `json:"stream_options,omitempty"`
Stop []string `json:"stop,omitempty"`
Store *bool `json:"store,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopLogProbs *int `json:"top_logprobs,omitempty"`
TopP *float64 `json:"top_p,omitempty"` // Controls diversity via nucleus sampling
ToolChoice *ChatToolChoice `json:"tool_choice,omitempty"` // Whether to call a tool
Tools []ChatTool `json:"tools,omitempty"` // Tools to use
User *string `json:"user,omitempty"` // User identifier for tracking
Verbosity *string `json:"verbosity,omitempty"` // "low" | "medium" | "high"
WebSearchOptions *ChatWebSearchOptions `json:"web_search_options,omitempty"` // Web search options (OpenAI only)
// Anthropic-native knobs promoted to the neutral layer. These pass through
// typed to Anthropic-family providers (honored/stripped per ProviderFeatures
// in core/providers/anthropic/types.go). Non-Anthropic providers (OpenAI
// etc.) silently ignore them.
TopK *int `json:"top_k,omitempty"` // Anthropic top_k sampling
Speed *string `json:"speed,omitempty"` // "fast" (Anthropic fast-mode-2026-02-01 beta, Opus 4.6 only)
InferenceGeo *string `json:"inference_geo,omitempty"` // Anthropic inference_geo (Claude API only)
MCPServers []ChatMCPServer `json:"mcp_servers,omitempty"` // Anthropic MCP connector (mcp-client-2025-11-20)
Container *ChatContainer `json:"container,omitempty"` // Anthropic container (string id, or object with skills[] — beta skills-2025-10-02)
CacheControl *CacheControl `json:"cache_control,omitempty"` // Top-level request cache control (Anthropic family)
TaskBudget *ChatTaskBudget `json:"task_budget,omitempty"` // Anthropic output_config.task_budget (task-budgets-2026-03-13 beta)
ContextManagement json.RawMessage `json:"context_management,omitempty"` // Anthropic context_management — complex union, passed as raw JSON to the provider layer
// Dynamic parameters that can be provider-specific, they are directly
// added to the request as is.
ExtraParams map[string]interface{} `json:"-"`
}
// UnmarshalJSON implements custom JSON unmarshalling for ChatParameters.
func (cp *ChatParameters) UnmarshalJSON(data []byte) error {
// Alias to avoid recursion
type Alias ChatParameters
// Aux struct adds reasoning_effort for decoding
var aux struct {
*Alias
ReasoningEffort *string `json:"reasoning_effort"` // only for input
ReasoningMaxTokens *int `json:"reasoning_max_tokens"`
}
aux.Alias = (*Alias)(cp)
// Single unmarshal
if err := Unmarshal(data, &aux); err != nil {
return err
}
// Now aux.Reasoning (from Alias) and aux.ReasoningEffort are filled
// Validate that specific fields don't conflict
if aux.ReasoningEffort != nil && aux.Reasoning != nil && aux.Reasoning.Effort != nil {
return fmt.Errorf("both reasoning_effort and reasoning.effort cannot be present at the same time")
}
if aux.ReasoningMaxTokens != nil && aux.Reasoning != nil && aux.Reasoning.MaxTokens != nil {
return fmt.Errorf("both reasoning_max_tokens and reasoning.max_tokens cannot be present at the same time")
}
if aux.ReasoningEffort != nil || aux.ReasoningMaxTokens != nil {
if cp.Reasoning == nil {
cp.Reasoning = &ChatReasoning{}
}
// Merge top-level fields into the reasoning object
if aux.ReasoningEffort != nil {
cp.Reasoning.Effort = aux.ReasoningEffort
}
if aux.ReasoningMaxTokens != nil {
cp.Reasoning.MaxTokens = aux.ReasoningMaxTokens
}
}
// ExtraParams etc. are already handled by the alias
return nil
}
// ChatAudioParameters represents the parameters for a chat audio completion. (Only supported by OpenAI Models that support audio input)
type ChatAudioParameters struct {
Format string `json:"format,omitempty"` // Format for the audio completion
Voice string `json:"voice,omitempty"` // Voice to use for the audio completion
}
// Not in OpenAI's spec, but needed to support extra parameters for reasoning.
type ChatReasoning struct {
Enabled *bool `json:"enabled,omitempty"` // Explicitly enable or disable reasoning (required by OpenRouter to disable reasoning for some models)
Effort *string `json:"effort,omitempty"` // "none" | "minimal" | "low" | "medium" | "high" (any value other than "none" will enable reasoning)
MaxTokens *int `json:"max_tokens,omitempty"` // Maximum number of tokens to generate for the reasoning output (required for anthropic)
Display *string `json:"display,omitempty"` // Anthropic thinking.display: "summarized" | "omitted" (requires model support for adaptive thinking)
}
// ChatPrediction represents predicted output content for the model to reference (OpenAI only).
// Providing prediction content can significantly reduce latency for certain models.
type ChatPrediction struct {
Type string `json:"type"` // Always "content"
Content interface{} `json:"content"` // String or array of content parts
}
// ChatWebSearchOptions represents web search options for chat completions (OpenAI only).
type ChatWebSearchOptions struct {
SearchContextSize *string `json:"search_context_size,omitempty"` // "low" | "medium" | "high"
UserLocation *ChatWebSearchOptionsUserLocation `json:"user_location,omitempty"`
}
// ChatWebSearchOptionsUserLocation represents user location for web search.
type ChatWebSearchOptionsUserLocation struct {
Type string `json:"type"` // "approximate"
Approximate *ChatWebSearchOptionsUserLocationApproximate `json:"approximate,omitempty"`
}
// ChatWebSearchOptionsUserLocationApproximate represents approximate user location details.
type ChatWebSearchOptionsUserLocationApproximate struct {
City *string `json:"city,omitempty"`
Country *string `json:"country,omitempty"` // Two-letter ISO country code (e.g., "US")
Region *string `json:"region,omitempty"` // e.g., "California"
Timezone *string `json:"timezone,omitempty"` // IANA timezone (e.g., "America/Los_Angeles")
}
// ChatStreamOptions represents the stream options for a chat completion.
type ChatStreamOptions struct {
IncludeObfuscation *bool `json:"include_obfuscation,omitempty"`
IncludeUsage *bool `json:"include_usage,omitempty"` // Bifrost marks this as true by default
}
// ChatToolType represents the type of tool.
type ChatToolType string
// ChatToolType values
const (
ChatToolTypeFunction ChatToolType = "function"
ChatToolTypeCustom ChatToolType = "custom"
)
type MCPToolAnnotations struct {
Title string `json:"title,omitempty"` // Human-readable title for the tool
ReadOnlyHint *bool `json:"readOnlyHint,omitempty"` // If true, the tool does not modify its environment
DestructiveHint *bool `json:"destructiveHint,omitempty"` // If true, the tool may perform destructive updates
IdempotentHint *bool `json:"idempotentHint,omitempty"` // If true, repeated calls with same args have no additional effect
OpenWorldHint *bool `json:"openWorldHint,omitempty"` // If true, the tool interacts with external entities
}
// ChatTool represents a tool definition.
//
// Three shapes coexist under this type:
// 1. OpenAI function tool: Type="function", Function non-nil.
// 2. Custom tool: Type="custom", Custom non-nil.
// 3. Anthropic server tool: Type=server-tool version string (e.g.
// "web_search_20260209", "computer_20251124", "mcp_toolset"), Function/Custom
// nil, Name populated at top level, and the variant-specific fields
// (MaxUses, DisplayWidthPx, etc.) populated inline.
//
// JSON shape for (3) matches Anthropic's native tool format directly
// (e.g. {"type":"web_search_20260209","name":"web_search","max_uses":5}).
//
// Custom MarshalJSON/UnmarshalJSON enforce the union invariant:
// - On marshal, fields that don't match Type are cleared on a copy so the
// wire format always carries exactly one variant. Mixed caller state
// (e.g. Type="web_search_20260209" with Function also set) gets
// canonicalized instead of being forwarded ambiguously to providers.
// - On unmarshal, tolerantly accept whatever JSON shape comes in, then
// normalize the decoded struct so downstream code sees a canonical shape.
type ChatTool struct {
Type ChatToolType `json:"type"`
Function *ChatToolFunction `json:"function,omitempty"` // Function definition (shape 1)
Custom *ChatToolCustom `json:"custom,omitempty"` // Custom tool definition (shape 2)
CacheControl *CacheControl `json:"cache_control,omitempty"` // Cache control for the tool
Annotations *MCPToolAnnotations `json:"-"` // MCP tool annotations (Bifrost-internal, never forwarded to providers)
// Anthropic-native tool flags promoted to the neutral layer. All optional;
// ignored by providers that don't support them. Gating 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 ("direct", "code_execution_20250825", "code_execution_20260120")
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: stream input_json_delta before full args are determined (custom tools only)
// Anthropic server-tool fields (shape 3). All optional; only populated when
// Type is a server-tool version string. Function tools carry their name
// inside Function.Name — use omitempty here so Name doesn't double-emit.
Name string `json:"name,omitempty"`
// web_search_* and web_fetch_*:
MaxUses *int `json:"max_uses,omitempty"`
AllowedDomains []string `json:"allowed_domains,omitempty"`
BlockedDomains []string `json:"blocked_domains,omitempty"`
UserLocation *ChatToolUserLocation `json:"user_location,omitempty"`
// web_fetch_* only:
MaxContentTokens *int `json:"max_content_tokens,omitempty"`
Citations *ChatToolCitationsConfig `json:"citations,omitempty"`
UseCache *bool `json:"use_cache,omitempty"` // web_fetch_20260309+ only
// computer_*:
DisplayWidthPx *int `json:"display_width_px,omitempty"`
DisplayHeightPx *int `json:"display_height_px,omitempty"`
DisplayNumber *int `json:"display_number,omitempty"`
EnableZoom *bool `json:"enable_zoom,omitempty"` // computer_20251124 only
// text_editor_20250728+:
MaxCharacters *int `json:"max_characters,omitempty"`
// mcp_toolset:
MCPServerName string `json:"mcp_server_name,omitempty"`
DefaultConfig *ChatMCPToolsetConfig `json:"default_config,omitempty"`
Configs map[string]*ChatMCPToolsetConfig `json:"configs,omitempty"`
}
// normalizeShape clears fields that don't belong to the ChatTool's active
// variant, encoding the three-way union invariant:
//
// 1. Type="function": keep Function; nil Custom, server-tool Name, and
// variant metadata (function tools carry their name inside Function.Name).
// 2. Type="custom": keep Custom and top-level Name; nil Function and
// server-tool variant metadata.
// 3. Any other Type: server-tool variant — keep Name and variant fields;
// nil Function and Custom.
//
// Called by both Marshal (strict wire format) and Unmarshal (canonicalize
// after tolerant decode of potentially mixed input).
func (t *ChatTool) normalizeShape() {
switch t.Type {
case ChatToolTypeFunction:
t.Custom = nil
t.Name = ""
t.clearServerToolVariantFields()
case ChatToolTypeCustom:
t.Function = nil
t.clearServerToolVariantFields()
default:
t.Function = nil
t.Custom = nil
}
}
func (t *ChatTool) clearServerToolVariantFields() {
t.MaxUses = nil
t.AllowedDomains = nil
t.BlockedDomains = nil
t.UserLocation = nil
t.MaxContentTokens = nil
t.Citations = nil
t.UseCache = nil
t.DisplayWidthPx = nil
t.DisplayHeightPx = nil
t.DisplayNumber = nil
t.EnableZoom = nil
t.MaxCharacters = nil
t.MCPServerName = ""
t.DefaultConfig = nil
t.Configs = nil
}
// MarshalJSON enforces the ChatTool union invariant: exactly one variant's
// fields are emitted on the wire, matching Type. A mix-state tool
// (e.g. Type="web_search_20260209" with Function also populated) would
// otherwise serialize both, and downstream provider converters — which
// dispatch on the top-level Type/Name shape — could misinterpret or
// silently forward the stray fields.
func (t ChatTool) MarshalJSON() ([]byte, error) {
normalized := t
normalized.normalizeShape()
type Alias ChatTool
return MarshalSorted((*Alias)(&normalized))
}
// UnmarshalJSON tolerantly decodes whatever JSON shape arrives, then
// canonicalizes the struct via normalizeShape so downstream code sees a
// single-variant result even if the input mixed multiple variants.
// Resets the receiver before decoding so omitted optional fields from a
// prior payload don't survive the new decode; mirrors ChatContainer.UnmarshalJSON.
func (t *ChatTool) UnmarshalJSON(data []byte) error {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
*t = ChatTool{}
return nil
}
type Alias ChatTool
var temp Alias
if err := Unmarshal(data, &temp); err != nil {
return err
}
*t = ChatTool(temp)
t.normalizeShape()
return nil
}
// ChatToolUserLocation is the neutral user_location for web_search tools.
type ChatToolUserLocation struct {
Type *string `json:"type,omitempty"` // "approximate"
City *string `json:"city,omitempty"`
Region *string `json:"region,omitempty"`
Country *string `json:"country,omitempty"`
Timezone *string `json:"timezone,omitempty"`
}
// ChatToolCitationsConfig is the request-side citations config on web_fetch
// ({"enabled": true/false}). Distinct from response-side text citations.
type ChatToolCitationsConfig struct {
Enabled *bool `json:"enabled,omitempty"`
}
// ChatMCPToolsetConfig configures an MCP toolset entry (mcp_toolset tool).
type ChatMCPToolsetConfig struct {
Enabled *bool `json:"enabled,omitempty"`
DeferLoading *bool `json:"defer_loading,omitempty"`
}
// ChatToolFunction represents a function definition.
type ChatToolFunction struct {
Name string `json:"name"` // Name of the function
Description *string `json:"description,omitempty"` // Description of the parameters
Parameters *ToolFunctionParameters `json:"parameters,omitempty"` // A JSON schema object describing the parameters
Strict *bool `json:"strict,omitempty"` // Whether to enforce strict parameter validation
}
// ToolFunctionParameters represents the parameters for a function definition.
// It supports JSON Schema fields used by various providers (OpenAI, Anthropic, Gemini, etc.).
// Field order follows JSON Schema / OpenAI conventions for consistent serialization.
//
// IMPORTANT: When marshalling to JSON, key order is preserved from the original input
// (captured during UnmarshalJSON). When constructing programmatically, the default
// struct field declaration order is used. This is critical because LLMs are
// sensitive to JSON key ordering in tool schemas.
type ToolFunctionParameters struct {
Type string `json:"type"` // Type of the parameters
Description *string `json:"description,omitempty"` // Description of the parameters
Properties *OrderedMap `json:"properties"` // Parameter properties - always include even if empty (required by JSON Schema and some providers like OpenAI)
Required []string `json:"required,omitempty"` // Required parameter names
AdditionalProperties *AdditionalPropertiesStruct `json:"additionalProperties,omitempty"` // Whether to allow additional properties
Enum []string `json:"enum,omitempty"` // Enum values for the parameters
// JSON Schema definition fields
Defs *OrderedMap `json:"$defs,omitempty"` // JSON Schema draft 2019-09+ definitions
Definitions *OrderedMap `json:"definitions,omitempty"` // Legacy JSON Schema draft-07 definitions
Ref *string `json:"$ref,omitempty"` // Reference to definition
// Array schema fields
Items *OrderedMap `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 []OrderedMap `json:"anyOf,omitempty"` // Union types (any of these schemas)
OneOf []OrderedMap `json:"oneOf,omitempty"` // Exclusive union types (exactly one of these)
AllOf []OrderedMap `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)
// keyOrder preserves the JSON key order from the original input so that
// MarshalJSON can emit keys in the same order the client sent them.
keyOrder JSONKeyOrder `json:"-"`
// explicitEmptyObject tracks a client-supplied raw {} schema.
explicitEmptyObject bool `json:"-"`
}
// MarshalJSON serializes ToolFunctionParameters while preserving the original
// top-level key order when available. A client-supplied raw `{}` stays `{}`;
// otherwise object schemas always emit `properties` as an object, never null.
func (t ToolFunctionParameters) MarshalJSON() ([]byte, error) {
if t.explicitEmptyObject && !t.hasDefinedSchemaFields() {
return []byte("{}"), nil
}
if t.Properties == nil {
// Initialize with an empty map (not nil values) so it marshals to {} instead of null
// Required by OpenAI and JSON Schema spec
t.Properties = &OrderedMap{values: make(map[string]interface{})}
}
type Alias ToolFunctionParameters
data, err := MarshalSorted(Alias(t))
if err != nil {
return nil, err
}
return t.keyOrder.Apply(data)
}
// UnmarshalJSON implements custom JSON unmarshalling for ToolFunctionParameters.
// It handles both JSON object format (standard) and JSON string format (used by some providers like xAI).
// It captures the original key order for order-preserving re-serialization and
// records whether the client provided an explicit empty object schema.
func (t *ToolFunctionParameters) UnmarshalJSON(data []byte) error {
// Try to unmarshal as a JSON string first (xAI sends parameters as a string)
var jsonStr string
if err := Unmarshal(data, &jsonStr); err == nil {
data = []byte(jsonStr)
}
type Alias ToolFunctionParameters
var temp Alias
if err := Unmarshal(data, &temp); err != nil {
return fmt.Errorf("failed to unmarshal ToolFunctionParameters: %w", err)
}
*t = ToolFunctionParameters(temp)
// Normalize additionalProperties: null to omitted field
if t.AdditionalProperties != nil &&
t.AdditionalProperties.AdditionalPropertiesBool == nil &&
t.AdditionalProperties.AdditionalPropertiesMap == nil {
t.AdditionalProperties = nil
}
trimmed := bytes.TrimSpace(data)
if len(trimmed) >= 2 && trimmed[0] == '{' && trimmed[len(trimmed)-1] == '}' {
inner := bytes.TrimSpace(trimmed[1 : len(trimmed)-1])
t.explicitEmptyObject = len(inner) == 0
} else {
t.explicitEmptyObject = false
}
t.keyOrder.Capture(data)
return nil
}
// Normalized returns a shallow copy of the ToolFunctionParameters with JSON
// Schema structural keys sorted by priority (type, description, properties,
// required first, then alphabetically), while preserving the client's original
// ordering of user-defined property names inside "properties" maps. The copy
// shares primitive values with the original but has independent key slices,
// so sorting does not mutate the caller's data.
//
// User-defined property names (e.g., "chain_of_thought", "answer") are kept
// in their original order because LLMs generate structured output fields in
// schema-declared order. Reordering them alphabetically can degrade output
// quality (e.g., forcing the model to write an answer before its reasoning).
//
// The captured keyOrder is cleared so the struct field declaration order is
// used for the top-level keys. This produces deterministic JSON serialization
// regardless of the client's original structural key ordering, which is
// critical for Anthropic's prefix-based prompt caching.
func (t *ToolFunctionParameters) Normalized() *ToolFunctionParameters {
if t == nil {
return nil
}
out := *t
out.keyOrder = JSONKeyOrder{}
// Properties contains user-defined field names whose order is semantically
// meaningful for LLM structured output generation. Preserve their key order
// while sorting nested schema structural keys for caching determinism.
out.Properties = t.Properties.preserveKeysWithPropertyAwareness()
out.Defs = t.Defs.SortedCopyPreservingProperties()
out.Definitions = t.Definitions.SortedCopyPreservingProperties()
out.Items = t.Items.SortedCopyPreservingProperties()
if len(t.AnyOf) > 0 {
out.AnyOf = make([]OrderedMap, len(t.AnyOf))
for i := range t.AnyOf {
if cp := t.AnyOf[i].SortedCopyPreservingProperties(); cp != nil {
out.AnyOf[i] = *cp
}
}
}
if len(t.OneOf) > 0 {
out.OneOf = make([]OrderedMap, len(t.OneOf))
for i := range t.OneOf {
if cp := t.OneOf[i].SortedCopyPreservingProperties(); cp != nil {
out.OneOf[i] = *cp
}
}
}
if len(t.AllOf) > 0 {
out.AllOf = make([]OrderedMap, len(t.AllOf))
for i := range t.AllOf {
if cp := t.AllOf[i].SortedCopyPreservingProperties(); cp != nil {
out.AllOf[i] = *cp
}
}
}
if t.AdditionalProperties != nil && t.AdditionalProperties.AdditionalPropertiesMap != nil {
out.AdditionalProperties = &AdditionalPropertiesStruct{
AdditionalPropertiesBool: t.AdditionalProperties.AdditionalPropertiesBool,
AdditionalPropertiesMap: t.AdditionalProperties.AdditionalPropertiesMap.SortedCopyPreservingProperties(),
}
}
switch v := t.Default.(type) {
case *OrderedMap:
out.Default = v.SortedCopy()
case map[string]interface{}:
out.Default = OrderedMapFromMap(v).SortedCopy()
case []interface{}:
out.Default = sortedCopySlice(v)
}
return &out
}
// hasDefinedSchemaFields reports whether the schema contains any real JSON Schema
// fields, allowing MarshalJSON to distinguish an explicit raw `{}` from a
// populated object schema such as `{"type":"object","properties":{}}`.
func (t *ToolFunctionParameters) hasDefinedSchemaFields() bool {
if t == nil {
return false
}
if t.Type != "" || t.Description != nil || len(t.Required) > 0 || t.AdditionalProperties != nil || len(t.Enum) > 0 {
return true
}
if t.Properties != nil || t.Defs != nil || t.Definitions != nil || t.Ref != nil {
return true
}
if t.Items != nil || t.MinItems != nil || t.MaxItems != nil {
return true
}
if len(t.AnyOf) > 0 || len(t.OneOf) > 0 || len(t.AllOf) > 0 {
return true
}
if t.Format != nil || t.Pattern != nil || t.MinLength != nil || t.MaxLength != nil {
return true
}
if t.Minimum != nil || t.Maximum != nil {
return true
}
return t.Title != nil || t.Default != nil || t.Nullable != nil
}
type AdditionalPropertiesStruct struct {
AdditionalPropertiesBool *bool
AdditionalPropertiesMap *OrderedMap
}
// MarshalJSON implements custom JSON marshalling for AdditionalPropertiesStruct.
// It marshals either AdditionalPropertiesBool or AdditionalPropertiesMap based on which is set.
func (a AdditionalPropertiesStruct) MarshalJSON() ([]byte, error) {
// if both are set, return an error
if a.AdditionalPropertiesBool != nil && a.AdditionalPropertiesMap != nil {
return nil, fmt.Errorf("both AdditionalPropertiesBool and AdditionalPropertiesMap are set; only one should be non-nil")
}
// If bool is set, marshal as boolean
if a.AdditionalPropertiesBool != nil {
return MarshalSorted(*a.AdditionalPropertiesBool)
}
// If map is set, marshal as object
if a.AdditionalPropertiesMap != nil {
return MarshalSorted(a.AdditionalPropertiesMap)
}
// If both are nil, return null
return nil, fmt.Errorf("additionalProperties cannot be null; omit the field instead")
}
// UnmarshalJSON implements custom JSON unmarshalling for AdditionalPropertiesStruct.
// It handles both boolean and object types for additionalProperties.
func (a *AdditionalPropertiesStruct) UnmarshalJSON(data []byte) error {
if bytes.Equal(bytes.TrimSpace(data), []byte("null")) {
a.AdditionalPropertiesBool = nil
a.AdditionalPropertiesMap = nil
return nil
}
// First, try to unmarshal as a boolean
var boolValue bool
if err := Unmarshal(data, &boolValue); err == nil {
a.AdditionalPropertiesMap = nil
a.AdditionalPropertiesBool = &boolValue
return nil
}
// If that fails, try to unmarshal as a map
var mapValue OrderedMap
if err := Unmarshal(data, &mapValue); err == nil {
a.AdditionalPropertiesBool = nil
a.AdditionalPropertiesMap = &mapValue
return nil
}
// If both fail, return an error
return fmt.Errorf("additionalProperties must be either a boolean or an object")
}
type ChatToolCustom struct {
Format *ChatToolCustomFormat `json:"format,omitempty"` // The input format
}
type ChatToolCustomFormat struct {
Type string `json:"type"` // always "text"
Grammar *ChatToolCustomGrammarFormat `json:"grammar,omitempty"`
}
// ChatToolCustomGrammarFormat - A grammar defined by the user
type ChatToolCustomGrammarFormat struct {
Definition string `json:"definition"` // The grammar definition
Syntax string `json:"syntax"` // "lark" | "regex"
}
// ChatToolChoiceType for all providers, make sure to check the provider's
// documentation to see which tool choices are supported.
type ChatToolChoiceType string
// ChatToolChoiceType values
const (
ChatToolChoiceTypeNone ChatToolChoiceType = "none"
ChatToolChoiceTypeAuto ChatToolChoiceType = "auto"
ChatToolChoiceTypeAny ChatToolChoiceType = "any"
ChatToolChoiceTypeRequired ChatToolChoiceType = "required"
// ChatToolChoiceTypeFunction means a specific tool must be called
ChatToolChoiceTypeFunction ChatToolChoiceType = "function"
// ChatToolChoiceTypeAllowedTools means a specific tool must be called
ChatToolChoiceTypeAllowedTools ChatToolChoiceType = "allowed_tools"
// ChatToolChoiceTypeCustom means a custom tool must be called
ChatToolChoiceTypeCustom ChatToolChoiceType = "custom"
)
// ChatToolChoiceStruct represents a tool choice.
type ChatToolChoiceStruct struct {
Type ChatToolChoiceType `json:"type"` // Type of tool choice
Function *ChatToolChoiceFunction `json:"function,omitempty"` // Function to call if type is ToolChoiceTypeFunction
Custom *ChatToolChoiceCustom `json:"custom,omitempty"` // Custom tool to call if type is ToolChoiceTypeCustom
AllowedTools *ChatToolChoiceAllowedTools `json:"allowed_tools,omitempty"` // Allowed tools to call if type is ToolChoiceTypeAllowedTools
}
// MarshalJSON serializes ChatToolChoiceStruct to JSON, emitting only the "type"
// field and the active variant. This prevents zero-value fields from unused
// variants (e.g., "custom", "allowed_tools") from appearing in the output,
// and ensures consistent field ordering with "type" always first.
func (s ChatToolChoiceStruct) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte('{')
// Always emit "type" first
typeBytes, err := MarshalSorted(string(s.Type))
if err != nil {
return nil, err
}
buf.WriteString(`"type":`)
buf.Write(typeBytes)
switch s.Type {
case ChatToolChoiceTypeFunction:
if s.Function != nil {
funcBytes, err := MarshalSorted(s.Function)
if err != nil {
return nil, err
}
buf.WriteString(`,"function":`)
buf.Write(funcBytes)
}
case ChatToolChoiceTypeCustom:
if s.Custom != nil {
customBytes, err := MarshalSorted(s.Custom)
if err != nil {
return nil, err
}
buf.WriteString(`,"custom":`)
buf.Write(customBytes)
}
case ChatToolChoiceTypeAllowedTools:
if s.AllowedTools != nil {
allowedBytes, err := MarshalSorted(s.AllowedTools)
if err != nil {
return nil, err
}
buf.WriteString(`,"allowed_tools":`)
buf.Write(allowedBytes)
}
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
// UnmarshalJSON deserializes JSON into ChatToolChoiceStruct and cleans up
// zero-value pointers that sonic may allocate for absent fields.
func (s *ChatToolChoiceStruct) UnmarshalJSON(data []byte) error {
type Alias ChatToolChoiceStruct
var temp Alias
if err := Unmarshal(data, &temp); err != nil {
return err
}
*s = ChatToolChoiceStruct(temp)
// Clean up zero-value pointers that sonic may allocate even when the
// corresponding key was absent from the JSON input.
switch s.Type {
case ChatToolChoiceTypeFunction:
s.Custom = nil
s.AllowedTools = nil
case ChatToolChoiceTypeCustom:
s.Function = nil
s.AllowedTools = nil
case ChatToolChoiceTypeAllowedTools:
s.Function = nil
s.Custom = nil
}
return nil
}
type ChatToolChoice struct {
ChatToolChoiceStr *string
ChatToolChoiceStruct *ChatToolChoiceStruct
}
// MarshalJSON implements custom JSON marshalling for ChatMessageContent.
// It marshals either ContentStr or ContentBlocks directly without wrapping.
func (ctc ChatToolChoice) MarshalJSON() ([]byte, error) {
// Validation: ensure only one field is set at a time
if ctc.ChatToolChoiceStr != nil && ctc.ChatToolChoiceStruct != nil {
return nil, fmt.Errorf("both ChatToolChoiceStr, ChatToolChoiceStruct are set; only one should be non-nil")
}
if ctc.ChatToolChoiceStr != nil {
return MarshalSorted(ctc.ChatToolChoiceStr)
}
if ctc.ChatToolChoiceStruct != nil {
return MarshalSorted(ctc.ChatToolChoiceStruct)
}
// 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 (ctc *ChatToolChoice) UnmarshalJSON(data []byte) error {
// First, try to unmarshal as a direct string
var toolChoiceStr string
if err := Unmarshal(data, &toolChoiceStr); err == nil {
ctc.ChatToolChoiceStr = &toolChoiceStr
ctc.ChatToolChoiceStruct = nil
return nil
}
// Try to unmarshal as a direct array of ContentBlock
var chatToolChoice ChatToolChoiceStruct
if err := Unmarshal(data, &chatToolChoice); err == nil {
ctc.ChatToolChoiceStr = nil
ctc.ChatToolChoiceStruct = &chatToolChoice
return nil
}
return fmt.Errorf("tool_choice field is neither a string nor a ChatToolChoiceStruct object")
}
// ChatToolChoiceFunction represents a function choice.
type ChatToolChoiceFunction struct {
Name string `json:"name"`
}
// ChatToolChoiceCustom represents a custom choice.
type ChatToolChoiceCustom struct {
Name string `json:"name"`
}
// ChatToolChoiceAllowedTools represents a allowed tools choice.
type ChatToolChoiceAllowedTools struct {
Mode string `json:"mode"` // "auto" | "required"
Tools []ChatToolChoiceAllowedToolsTool `json:"tools"`
}
// ChatToolChoiceAllowedToolsTool represents a allowed tools tool.
type ChatToolChoiceAllowedToolsTool struct {
Type string `json:"type"` // "function"
Function ChatToolChoiceFunction `json:"function,omitempty"`
}
// ChatMessageRole represents the role of a chat message
type ChatMessageRole string
// ChatMessageRole values
const (
ChatMessageRoleAssistant ChatMessageRole = "assistant"
ChatMessageRoleUser ChatMessageRole = "user"
ChatMessageRoleSystem ChatMessageRole = "system"
ChatMessageRoleTool ChatMessageRole = "tool"
ChatMessageRoleDeveloper ChatMessageRole = "developer"
)
// ChatMessage represents a message in a chat conversation.
type ChatMessage struct {
Name *string `json:"name,omitempty"` // for chat completions
Role ChatMessageRole `json:"role,omitempty"`
Content *ChatMessageContent `json:"content,omitempty"`
// Embedded pointer structs - when non-nil, their exported fields are flattened into the top-level JSON object
// IMPORTANT: Only one of the following can be non-nil at a time, otherwise the JSON marshalling will override the common fields
*ChatToolMessage
*ChatAssistantMessage
}
// UnmarshalJSON implements custom JSON unmarshalling for ChatMessage.
// This is needed because ChatAssistantMessage has a custom UnmarshalJSON method,
// which interferes with the JSON library's handling of other fields in ChatMessage.
func (cm *ChatMessage) UnmarshalJSON(data []byte) error {
// Unmarshal the base fields directly
type baseFields struct {
Name *string `json:"name,omitempty"`
Role ChatMessageRole `json:"role,omitempty"`
Content *ChatMessageContent `json:"content,omitempty"`
}
var base baseFields
if err := Unmarshal(data, &base); err != nil {
return err
}
cm.Name = base.Name
cm.Role = base.Role
cm.Content = base.Content
// Unmarshal ChatToolMessage fields
type toolMsgAlias ChatToolMessage
var toolMsg toolMsgAlias
if err := Unmarshal(data, &toolMsg); err != nil {
return err
}
if toolMsg.ToolCallID != nil {
cm.ChatToolMessage = (*ChatToolMessage)(&toolMsg)
}
// Unmarshal ChatAssistantMessage (which has its own custom unmarshaller)
var assistantMsg ChatAssistantMessage
if err := Unmarshal(data, &assistantMsg); err != nil {
return err
}
// Only set if any field is populated
if assistantMsg.Refusal != nil || assistantMsg.Reasoning != nil ||
len(assistantMsg.ReasoningDetails) > 0 || len(assistantMsg.Annotations) > 0 ||
len(assistantMsg.ToolCalls) > 0 || assistantMsg.Audio != nil {
cm.ChatAssistantMessage = &assistantMsg
}
return nil
}
// ChatMessageContent represents a content in a message.
type ChatMessageContent struct {
ContentStr *string
ContentBlocks []ChatContentBlock
}
// MarshalJSON implements custom JSON marshalling for ChatMessageContent.
// It marshals either ContentStr or ContentBlocks directly without wrapping.
func (mc ChatMessageContent) MarshalJSON() ([]byte, error) {
// Validation: ensure only one field is set at a time
if mc.ContentStr != nil && mc.ContentBlocks != nil {
return nil, fmt.Errorf("both Content string and Content blocks are set; only one should be non-nil")
}
if mc.ContentStr != nil {
return MarshalSorted(*mc.ContentStr)
}
if mc.ContentBlocks != nil {
return MarshalSorted(mc.ContentBlocks)
}
// 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 (mc *ChatMessageContent) UnmarshalJSON(data []byte) error {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
mc.ContentStr = nil
mc.ContentBlocks = nil
return nil
}
// First, try to unmarshal as a direct string
var stringContent string
if err := Unmarshal(data, &stringContent); err == nil {
mc.ContentStr = &stringContent
mc.ContentBlocks = nil
return nil
}
// Try to unmarshal as a direct array of ContentBlock
var arrayContent []ChatContentBlock
if err := Unmarshal(data, &arrayContent); err == nil {
mc.ContentBlocks = arrayContent
mc.ContentStr = nil
return nil
}
return fmt.Errorf("content field is neither a string nor an array of Content blocks")
}
// ChatContentBlockType represents the type of content block in a message.
type ChatContentBlockType string
// ChatContentBlockType values
const (
ChatContentBlockTypeText ChatContentBlockType = "text"
ChatContentBlockTypeImage ChatContentBlockType = "image_url"
ChatContentBlockTypeInputAudio ChatContentBlockType = "input_audio"
ChatContentBlockTypeFile ChatContentBlockType = "file"
ChatContentBlockTypeRefusal ChatContentBlockType = "refusal"
)
// ChatContentBlock represents a content block in a message.
type ChatContentBlock struct {
Type ChatContentBlockType `json:"type"`
Text *string `json:"text,omitempty"`
Refusal *string `json:"refusal,omitempty"`
ImageURLStruct *ChatInputImage `json:"image_url,omitempty"`
InputAudio *ChatInputAudio `json:"input_audio,omitempty"`
File *ChatInputFile `json:"file,omitempty"`
// 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"`
// CachePoint is a Bedrock-specific field for standalone cache point blocks
// When present without other content, this indicates a cache point marker
CachePoint *CachePoint `json:"cachePoint,omitempty"`
}
// CachePoint represents a cache point marker (Bedrock-specific)
type CachePoint struct {
Type string `json:"type"` // "default"
}
type CacheControlType string
const (
CacheControlTypeEphemeral CacheControlType = "ephemeral"
)
type CacheControl struct {
Type CacheControlType `json:"type"`
TTL *string `json:"ttl,omitempty"` // "1m" | "1h"
Scope *string `json:"scope,omitempty"` // "user" | "global"
}
// ---------------------------------------------------------------------------
// Neutral mirror types for Anthropic-native knobs promoted onto ChatParameters
// ---------------------------------------------------------------------------
// These live in schemas/ (not provider-specific) so ChatParameters stays
// import-free of provider packages. The anthropic provider reads them in
// ToAnthropicChatRequest and maps them to AnthropicMessageRequest fields.
// ChatContainerSkill describes one skill attached to a container.
// Origin: Anthropic container.skills[] (beta skills-2025-10-02).
type ChatContainerSkill struct {
SkillID string `json:"skill_id"`
Type string `json:"type"` // "anthropic" | "custom"
Version *string `json:"version,omitempty"` // Optional version pin
}
// ChatContainerObject is the object form of ChatContainer.
// Both fields are optional — ID alone is a bare container reference;
// adding Skills makes it beta-gated.
type ChatContainerObject struct {
ID *string `json:"id,omitempty"`
Skills []ChatContainerSkill `json:"skills,omitempty"`
}
// ChatContainer is the union "container" field on a chat request.
// Anthropic's API accepts either a plain string (container id) or an object
// with id + skills[]. Mirrors AnthropicContainer in the provider package.
type ChatContainer struct {
ContainerStr *string
ContainerObject *ChatContainerObject
}
// MarshalJSON emits the raw string or the object form directly.
func (c ChatContainer) MarshalJSON() ([]byte, error) {
if c.ContainerStr != nil && c.ContainerObject != nil {
return nil, fmt.Errorf("both ContainerStr and ContainerObject are set; only one should be non-nil")
}
if c.ContainerStr != nil {
return MarshalSorted(*c.ContainerStr)
}
if c.ContainerObject != nil {
return MarshalSorted(c.ContainerObject)
}
return MarshalSorted(nil)
}
// UnmarshalJSON accepts either a plain string or the object form.
// Uses the build-tag-aware package-level Unmarshal (sonic on native, stdlib
// json on wasm/tinygo) and clears the inactive union arm on each success so
// repeated decodes into the same value don't leave both arms populated.
// JSON null clears both arms. Follows the ChatToolChoice.UnmarshalJSON pattern.
func (c *ChatContainer) UnmarshalJSON(data []byte) error {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
c.ContainerStr = nil
c.ContainerObject = nil
return nil
}
var s string
if err := Unmarshal(data, &s); err == nil {
c.ContainerStr = &s
c.ContainerObject = nil
return nil
}
var obj ChatContainerObject
if err := Unmarshal(data, &obj); err == nil {
c.ContainerStr = nil
c.ContainerObject = &obj
return nil
}
return fmt.Errorf("container field is neither a string nor an object")
}
// ChatTaskBudget advises the model of a full-loop token budget.
// Origin: Anthropic output_config.task_budget (beta task-budgets-2026-03-13).
type ChatTaskBudget struct {
Type string `json:"type"` // Always "tokens"
Total int `json:"total"` // Total advisory budget
Remaining *int `json:"remaining,omitempty"` // Optional client-side counter
}
// ChatToolInputExample is one example input for a tool, shown to the model.
// Origin: Anthropic tool.input_examples (beta tool-examples-2025-10-29).
type ChatToolInputExample struct {
Input json.RawMessage `json:"input"`
Description *string `json:"description,omitempty"`
}
// ChatMCPServer is an MCP server definition attached to a chat request.
// Origin: Anthropic mcp_servers[] (mcp-client-2025-11-20 format).
type ChatMCPServer struct {
Type string `json:"type"` // "url"
URL string `json:"url"`
Name string `json:"name"`
AuthorizationToken *string `json:"authorization_token,omitempty"`
}
// ChatInputImage represents image data in a message.
type ChatInputImage struct {
URL string `json:"url"`
Detail *string `json:"detail,omitempty"`
}
// ChatInputAudio represents audio data in a message.
// Data carries the audio payload as a string (e.g., data URL or provider-accepted encoded content).
// Format is optional (e.g., "wav", "mp3"); when nil, providers may attempt auto-detection.
type ChatInputAudio struct {
Data string `json:"data"`
Format *string `json:"format,omitempty"`
}
// ChatInputFile represents a file in a message.
type ChatInputFile struct {
FileData *string `json:"file_data,omitempty"` // Base64 encoded file data
FileURL *string `json:"file_url,omitempty"` // Direct URL to file
FileID *string `json:"file_id,omitempty"` // Reference to uploaded file
Filename *string `json:"filename,omitempty"` // Name of the file
FileType *string `json:"file_type,omitempty"` // Type of the file
}
// ChatToolMessage represents a tool message in a chat conversation.
type ChatToolMessage struct {
ToolCallID *string `json:"tool_call_id,omitempty"`
}
// ChatAssistantMessage represents a message in a chat conversation.
type ChatAssistantMessage struct {
Refusal *string `json:"refusal,omitempty"`
Audio *ChatAudioMessageAudio `json:"audio,omitempty"`
Reasoning *string `json:"reasoning,omitempty"`
ReasoningDetails []ChatReasoningDetails `json:"reasoning_details,omitempty"`
Annotations []ChatAssistantMessageAnnotation `json:"annotations,omitempty"`
ToolCalls []ChatAssistantMessageToolCall `json:"tool_calls,omitempty"`
}
// UnmarshalJSON implements custom unmarshalling for ChatAssistantMessage.
// If Reasoning is non-nil and ReasoningDetails is nil/empty, it adds a single
// ChatReasoningDetails entry of type "reasoning.text" with the text set to Reasoning.
func (cm *ChatAssistantMessage) UnmarshalJSON(data []byte) error {
if cm == nil {
return nil
}
// Alias to avoid infinite recursion
type Alias ChatAssistantMessage
// Auxiliary struct to capture xAI's reasoning_content field
var aux struct {
Alias
ReasoningContent *string `json:"reasoning_content,omitempty"` // xAI uses this field name
}
if err := Unmarshal(data, &aux); err != nil {
return err
}
// Copy decoded data back into the original type
*cm = ChatAssistantMessage(aux.Alias)
// Map xAI's reasoning_content to Bifrost's Reasoning field
// This allows both OpenAI's "reasoning" and xAI's "reasoning_content" to work
if aux.ReasoningContent != nil && cm.Reasoning == nil {
cm.Reasoning = aux.ReasoningContent
}
// If Reasoning is present and there are no reasoning_details,
// synthesize a text reasoning_details entry.
if cm.Reasoning != nil && len(cm.ReasoningDetails) == 0 {
text := *cm.Reasoning
cm.ReasoningDetails = []ChatReasoningDetails{
{
Index: 0,
Type: BifrostReasoningDetailsTypeText,
Text: &text,
},
}
}
return nil
}
// ChatAssistantMessageAnnotation represents an annotation in a response.
type ChatAssistantMessageAnnotation struct {
Type string `json:"type"`
URLCitation ChatAssistantMessageAnnotationCitation `json:"url_citation"`
}
// ChatAssistantMessageAnnotationCitation represents a citation in a response.
type ChatAssistantMessageAnnotationCitation struct {
StartIndex int `json:"start_index"`
EndIndex int `json:"end_index"`
Title string `json:"title"`
URL *string `json:"url,omitempty"`
Sources *interface{} `json:"sources,omitempty"`
Type *string `json:"type,omitempty"`
}
// ChatAssistantMessageToolCall represents a tool call in a message
type ChatAssistantMessageToolCall struct {
Index uint16 `json:"index"`
Type *string `json:"type,omitempty"`
ID *string `json:"id,omitempty"`
Function ChatAssistantMessageToolCallFunction `json:"function"`
}
// ChatAssistantMessageToolCallFunction represents a call to a function.
type ChatAssistantMessageToolCallFunction struct {
Name *string `json:"name"`
Arguments string `json:"arguments"` // stringified json as retured by OpenAI, might not be a valid JSON always
}
// ChatAudioMessageAudio represents audio data in a message.
type ChatAudioMessageAudio struct {
ID string `json:"id"`
Data string `json:"data"`
ExpiresAt int `json:"expires_at"`
Transcript string `json:"transcript"`
}
// BifrostResponseChoice represents a choice in the completion result.
// This struct can represent either a streaming or non-streaming response choice.
// IMPORTANT: Only one of TextCompletionResponseChoice, NonStreamResponseChoice or StreamResponseChoice
// should be non-nil at a time.
type BifrostResponseChoice struct {
Index int `json:"index"`
FinishReason *string `json:"finish_reason,omitempty"`
LogProbs *BifrostLogProbs `json:"logprobs,omitempty"`
*TextCompletionResponseChoice
*ChatNonStreamResponseChoice
*ChatStreamResponseChoice
}
// BifrostFinishReason represents the reason why the model stopped generating.
type BifrostFinishReason string
// BifrostFinishReason values
const (
BifrostFinishReasonStop BifrostFinishReason = "stop"
BifrostFinishReasonLength BifrostFinishReason = "length"
BifrostFinishReasonToolCalls BifrostFinishReason = "tool_calls"
)
type BifrostReasoningDetailsType string
const (
BifrostReasoningDetailsTypeSummary BifrostReasoningDetailsType = "reasoning.summary"
BifrostReasoningDetailsTypeEncrypted BifrostReasoningDetailsType = "reasoning.encrypted"
BifrostReasoningDetailsTypeText BifrostReasoningDetailsType = "reasoning.text"
BifrostReasoningDetailsTypeContentBlocks BifrostReasoningDetailsType = "reasoning.content_blocks"
)
// Not in OpenAI's spec, but needed to support inter provider reasoning capabilities.
type ChatReasoningDetails struct {
ID *string `json:"id,omitempty"`
Index int `json:"index"`
Type BifrostReasoningDetailsType `json:"type"`
Summary *string `json:"summary,omitempty"`
Text *string `json:"text,omitempty"`
Signature *string `json:"signature,omitempty"`
Data *string `json:"data,omitempty"` // for encrypted data
}
// BifrostLogProbs represents the log probabilities for different aspects of a response.
type BifrostLogProbs struct {
Content []ContentLogProb `json:"content,omitempty"`
Refusal []LogProb `json:"refusal,omitempty"`
*TextCompletionLogProb
}
type TextCompletionResponseChoice struct {
Text *string `json:"text,omitempty"`
}
// ChatNonStreamResponseChoice represents a choice in the non-stream response
type ChatNonStreamResponseChoice struct {
Message *ChatMessage `json:"message"`
StopString *string `json:"stop,omitempty"`
}
// ChatStreamResponseChoice represents a choice in the stream response
type ChatStreamResponseChoice struct {
Delta *ChatStreamResponseChoiceDelta `json:"delta,omitempty"` // Partial message info
}
// ChatStreamResponseChoiceDelta represents a delta in the stream response
type ChatStreamResponseChoiceDelta struct {
Role *string `json:"role,omitempty"` // Only in the first chunk
Content *string `json:"content,omitempty"` // May be empty string or null
Refusal *string `json:"refusal,omitempty"` // Refusal content if any
Audio *ChatAudioMessageAudio `json:"audio,omitempty"` // Audio data if any
Reasoning *string `json:"reasoning,omitempty"` // May be empty string or null
ReasoningDetails []ChatReasoningDetails `json:"reasoning_details,omitempty"`
ToolCalls []ChatAssistantMessageToolCall `json:"tool_calls,omitempty"` // If tool calls used (supports incremental updates)
}
// UnmarshalJSON implements custom unmarshalling for ChatStreamResponseChoiceDelta.
// If Reasoning is non-nil and ReasoningDetails is nil/empty, it adds a single
// ChatReasoningDetails entry of type "reasoning.text" with the text set to Reasoning.
func (d *ChatStreamResponseChoiceDelta) UnmarshalJSON(data []byte) error {
// Alias to avoid infinite recursion
type Alias ChatStreamResponseChoiceDelta
// Auxiliary struct to capture xAI's reasoning_content field
var aux struct {
Alias
ReasoningContent *string `json:"reasoning_content,omitempty"` // xAI uses this field name
}
if err := Unmarshal(data, &aux); err != nil {
return err
}
// Copy decoded data back into the original type
*d = ChatStreamResponseChoiceDelta(aux.Alias)
// Map xAI's reasoning_content to Bifrost's Reasoning field
// This allows both OpenAI's "reasoning" and xAI's "reasoning_content" to work
if aux.ReasoningContent != nil && d.Reasoning == nil {
d.Reasoning = aux.ReasoningContent
}
// If Reasoning is present and there are no reasoning_details,
// synthesize a text reasoning_details entry.
if d.Reasoning != nil && len(d.ReasoningDetails) == 0 {
text := *d.Reasoning
d.ReasoningDetails = []ChatReasoningDetails{
{
Index: 0,
Type: BifrostReasoningDetailsTypeText,
Text: &text,
},
}
}
return nil
}
// LogProb represents the log probability of a token.
type LogProb struct {
Bytes []int `json:"bytes,omitempty"`
LogProb float64 `json:"logprob"`
Token string `json:"token"`
}
// ContentLogProb represents log probability information for content.
type ContentLogProb struct {
Bytes []int `json:"bytes"`
LogProb float64 `json:"logprob"`
Token string `json:"token"`
TopLogProbs []LogProb `json:"top_logprobs"`
}
// BifrostLLMUsage represents token usage information
type BifrostLLMUsage struct {
PromptTokens int `json:"prompt_tokens,omitempty"`
PromptTokensDetails *ChatPromptTokensDetails `json:"prompt_tokens_details,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"`
CompletionTokensDetails *ChatCompletionTokensDetails `json:"completion_tokens_details,omitempty"`
TotalTokens int `json:"total_tokens"`
Cost *BifrostCost `json:"cost,omitempty"` // Only for the providers which support cost calculation
}
type ChatPromptTokensDetails struct {
TextTokens int `json:"text_tokens,omitempty"`
AudioTokens int `json:"audio_tokens,omitempty"`
ImageTokens int `json:"image_tokens,omitempty"`
// 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,omitempty"`
CachedWriteTokens int `json:"cached_write_tokens,omitempty"`
}
// UnmarshalJSON maps OpenAI's cached_tokens into CachedReadTokens for compatibility.
func (d *ChatPromptTokensDetails) 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 ChatPromptTokensDetails) 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,omitempty"`
CachedWriteTokens int `json:"cached_write_tokens,omitempty"`
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 ChatCompletionTokensDetails struct {
TextTokens int `json:"text_tokens,omitempty"`
AcceptedPredictionTokens int `json:"accepted_prediction_tokens,omitempty"`
AudioTokens int `json:"audio_tokens,omitempty"`
CitationTokens *int `json:"citation_tokens,omitempty"`
NumSearchQueries *int `json:"num_search_queries,omitempty"`
ReasoningTokens int `json:"reasoning_tokens,omitempty"`
ImageTokens *int `json:"image_tokens,omitempty"`
RejectedPredictionTokens int `json:"rejected_prediction_tokens,omitempty"`
}
type BifrostCost struct {
InputTokensCost float64 `json:"input_tokens_cost,omitempty"`
OutputTokensCost float64 `json:"output_tokens_cost,omitempty"`
ReasoningTokensCost float64 `json:"reasoning_tokens_cost,omitempty"`
CitationTokensCost float64 `json:"citation_tokens_cost,omitempty"`
SearchQueriesCost float64 `json:"search_queries_cost,omitempty"`
RequestCost float64 `json:"request_cost,omitempty"`
TotalCost float64 `json:"total_cost,omitempty"`
}
// UnmarshalJSON implements custom JSON unmarshalling for BifrostCost.
func (bc *BifrostCost) UnmarshalJSON(data []byte) error {
// First, try to unmarshal as a direct float
var costFloat float64
if err := Unmarshal(data, &costFloat); err == nil {
bc.TotalCost = costFloat
return nil
}
// Try to unmarshal as a full BifrostCost struct
// Use a type alias to avoid infinite recursion
type Alias BifrostCost
var costStruct Alias
if err := Unmarshal(data, &costStruct); err == nil {
*bc = BifrostCost(costStruct)
return nil
}
return fmt.Errorf("cost field is neither a float nor an object")
}
type SearchResult struct {
Title string `json:"title"`
URL string `json:"url"`
Date *string `json:"date,omitempty"`
LastUpdated *string `json:"last_updated,omitempty"`
Snippet *string `json:"snippet,omitempty"`
Source *string `json:"source,omitempty"`
}
type VideoResult struct {
URL string `json:"url"`
ThumbnailURL *string `json:"thumbnail_url,omitempty"`
ThumbnailWidth *int `json:"thumbnail_width,omitempty"`
ThumbnailHeight *int `json:"thumbnail_height,omitempty"`
Duration *float64 `json:"duration,omitempty"`
}