11package core
22
3- import "encoding/json"
3+ import (
4+ "bytes"
5+ "encoding/json"
6+ )
47
58// StreamOptions controls streaming behavior options.
69// This is used to request usage data in streaming responses.
@@ -20,36 +23,90 @@ type Reasoning struct {
2023
2124// ChatRequest represents the incoming chat completion request
2225type ChatRequest struct {
23- Temperature * float64 `json:"temperature,omitempty"`
24- MaxTokens * int `json:"max_tokens,omitempty"`
25- Model string `json:"model"`
26- Provider string `json:"provider,omitempty"`
27- Messages []Message `json:"messages"`
28- Stream bool `json:"stream,omitempty"`
29- StreamOptions * StreamOptions `json:"stream_options,omitempty"`
30- Reasoning * Reasoning `json:"reasoning,omitempty"`
26+ Temperature * float64 `json:"temperature,omitempty"`
27+ MaxTokens * int `json:"max_tokens,omitempty"`
28+ Model string `json:"model"`
29+ Provider string `json:"provider,omitempty"`
30+ Messages []Message `json:"messages"`
31+ Tools []map [string ]any `json:"tools,omitempty"`
32+ ToolChoice any `json:"tool_choice,omitempty"` // string or object
33+ ParallelToolCalls * bool `json:"parallel_tool_calls,omitempty"`
34+ Stream bool `json:"stream,omitempty"`
35+ StreamOptions * StreamOptions `json:"stream_options,omitempty"`
36+ Reasoning * Reasoning `json:"reasoning,omitempty"`
3137}
3238
3339// WithStreaming returns a shallow copy of the request with Stream set to true.
3440// This avoids mutating the caller's request object.
3541func (r * ChatRequest ) WithStreaming () * ChatRequest {
36- return & ChatRequest {
37- Temperature : r .Temperature ,
38- MaxTokens : r .MaxTokens ,
39- Model : r .Model ,
40- Provider : r .Provider ,
41- Messages : r .Messages ,
42- Stream : true ,
43- StreamOptions : r .StreamOptions ,
44- Reasoning : r .Reasoning ,
45- }
42+ cp := * r
43+ cp .Stream = true
44+ return & cp
4645}
4746
4847// Message represents a single message in the chat
4948type Message struct {
50- Role string `json:"role"`
51- Content string `json:"content"`
52- ToolCalls []ToolCall `json:"tool_calls,omitempty"`
49+ Role string `json:"role"`
50+ Content string `json:"content"`
51+ ToolCalls []ToolCall `json:"tool_calls,omitempty"`
52+ ToolCallID string `json:"tool_call_id,omitempty"`
53+ ContentNull bool `json:"-"`
54+ }
55+
56+ // UnmarshalJSON accepts content as string or null for compatibility with
57+ // tool-calling responses that omit assistant text.
58+ func (m * Message ) UnmarshalJSON (data []byte ) error {
59+ type rawMessage struct {
60+ Role string `json:"role"`
61+ Content json.RawMessage `json:"content"`
62+ ToolCalls []ToolCall `json:"tool_calls,omitempty"`
63+ ToolCallID string `json:"tool_call_id,omitempty"`
64+ }
65+
66+ var raw rawMessage
67+ if err := json .Unmarshal (data , & raw ); err != nil {
68+ return err
69+ }
70+
71+ m .Role = raw .Role
72+ m .ContentNull = false
73+ switch trimmed := bytes .TrimSpace (raw .Content ); {
74+ case len (trimmed ) == 0 :
75+ m .Content = ""
76+ case bytes .Equal (trimmed , []byte ("null" )):
77+ m .Content = ""
78+ m .ContentNull = true
79+ default :
80+ if err := json .Unmarshal (trimmed , & m .Content ); err != nil {
81+ return err
82+ }
83+ }
84+ m .ToolCalls = raw .ToolCalls
85+ m .ToolCallID = raw .ToolCallID
86+
87+ return nil
88+ }
89+
90+ // MarshalJSON preserves explicit null content for tool-calling assistant messages.
91+ func (m Message ) MarshalJSON () ([]byte , error ) {
92+ type rawMessage struct {
93+ Role string `json:"role"`
94+ Content any `json:"content"`
95+ ToolCalls []ToolCall `json:"tool_calls,omitempty"`
96+ ToolCallID string `json:"tool_call_id,omitempty"`
97+ }
98+
99+ content := any (m .Content )
100+ if m .ContentNull && m .Content == "" {
101+ content = nil
102+ }
103+
104+ return json .Marshal (rawMessage {
105+ Role : m .Role ,
106+ Content : content ,
107+ ToolCalls : m .ToolCalls ,
108+ ToolCallID : m .ToolCallID ,
109+ })
53110}
54111
55112// ToolCall represents a single tool invocation emitted by a model.
0 commit comments