package model import ( "encoding/json" ) // openAIChatResponse is the JSON body returned by /chat/completions, for both // the non-streaming payload and each SSE stream chunk. type openAIChatResponse struct { ID string `json:"id"` Model string `json:"model"` Choices []openAIChoice `json:"choices"` Usage *openAIUsage `json:"usage,omitempty"` } // openAIChoice is one candidate completion. type openAIChoice struct { Index int `json:"index"` Message openAIResponseMessage `json:"message"` Delta *openAIDelta `json:"delta,omitempty"` FinishReason string `json:"finish_reason,omitempty"` } // openAIResponseMessage is the assistant message inside a non-streaming choice. type openAIResponseMessage struct { Role string `json:"role"` Content string `json:"content"` ToolCalls []openAIToolCall `json:"tool_calls,omitempty"` } // openAIDelta is the incremental payload inside a stream chunk. Reasoning content // (OpenAI o-series / DeepSeek / DashScope) arrives in ReasoningContent; streaming // function calls arrive in ToolCalls, accumulated per fragment index. type openAIDelta struct { Role string `json:"role"` Content string `json:"content"` ReasoningContent string `json:"reasoning_content"` ToolCalls []openAIToolCallDelta `json:"tool_calls"` } // openAIToolCallDelta is one streaming function-call fragment. OpenAI emits a // sequence of these sharing the same Index: the ID and Function.Name usually // arrive once (in the first fragment) while Function.Arguments streams across // fragments. type openAIToolCallDelta struct { Index int `json:"index"` ID string `json:"id"` Function openAIToolCallFn `json:"function"` } // openAIUsage reports token consumption. type openAIUsage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` TotalTokens int `json:"total_tokens"` } func responseFromWire(wire *openAIChatResponse) Response { out := Response{Model: wire.Model} if len(wire.Choices) > 0 { text := wire.Choices[0].Message.Content out.Content = text out.Message = Message{Role: RoleAssistant, Content: []Content{{Type: ContentTypeText, Text: text}}} out.StopReason = wire.Choices[0].FinishReason for _, tc := range wire.Choices[0].Message.ToolCalls { args := map[string]any{} if tc.Function.Arguments != "" { _ = json.Unmarshal([]byte(tc.Function.Arguments), &args) } out.ToolCalls = append(out.ToolCalls, ToolCall{Name: tc.Function.Name, ID: tc.ID, Args: args}) } } if u := wire.Usage; u != nil { out.PromptTokens = u.PromptTokens out.CompletionTokens = u.CompletionTokens out.TotalTokens = u.TotalTokens } return out }