@@ -23,13 +23,73 @@ import (
2323
2424// chatCompletionStreamState manages state for ChatCompletion stream conversion
2525type chatCompletionStreamState struct {
26- messageID string
27- meta * meta.Meta
28- c * gin.Context
29- currentToolCall * relaymodel.ToolCall
30- currentToolCallID string
31- toolCallArgs string
32- hasToolCall bool
26+ messageID string
27+ created int64
28+ meta * meta.Meta
29+ c * gin.Context
30+ toolCallIndexByItemID map [string ]int
31+ toolCallIndexByOutputIndex map [int ]int
32+ nextToolCallIndex int
33+ hasToolCall bool
34+ }
35+
36+ func (s * chatCompletionStreamState ) createdAt () int64 {
37+ if s .created == 0 {
38+ s .created = time .Now ().Unix ()
39+ }
40+
41+ return s .created
42+ }
43+
44+ func (s * chatCompletionStreamState ) registerToolCall (
45+ event * relaymodel.ResponseStreamEvent ,
46+ ) int {
47+ if event .Item .ID != "" {
48+ if index , ok := s .toolCallIndexByItemID [event .Item .ID ]; ok {
49+ return index
50+ }
51+ }
52+
53+ if event .OutputIndex != nil {
54+ if index , ok := s .toolCallIndexByOutputIndex [* event .OutputIndex ]; ok {
55+ return index
56+ }
57+ }
58+
59+ index := s .nextToolCallIndex
60+ s .nextToolCallIndex ++
61+
62+ if event .Item .ID != "" {
63+ s .toolCallIndexByItemID [event .Item .ID ] = index
64+ }
65+
66+ if event .OutputIndex != nil {
67+ s .toolCallIndexByOutputIndex [* event .OutputIndex ] = index
68+ }
69+
70+ return index
71+ }
72+
73+ func (s * chatCompletionStreamState ) toolCallIndex (
74+ event * relaymodel.ResponseStreamEvent ,
75+ ) (int , bool ) {
76+ if event .ItemID != "" {
77+ index , ok := s .toolCallIndexByItemID [event .ItemID ]
78+ if ok {
79+ return index , true
80+ }
81+ }
82+
83+ if event .OutputIndex != nil {
84+ index , ok := s .toolCallIndexByOutputIndex [* event .OutputIndex ]
85+ return index , ok
86+ }
87+
88+ if event .ItemID == "" && s .nextToolCallIndex > 0 {
89+ return s .nextToolCallIndex - 1 , true
90+ }
91+
92+ return 0 , false
3393}
3494
3595func responseModelName (meta * meta.Meta ) string {
@@ -132,11 +192,14 @@ func (s *chatCompletionStreamState) handleResponseCreated(
132192 }
133193
134194 s .messageID = event .Response .ID
195+ if event .Response .CreatedAt != 0 {
196+ s .created = event .Response .CreatedAt
197+ }
135198
136199 return & relaymodel.ChatCompletionsStreamResponse {
137200 ID : s .messageID ,
138201 Object : relaymodel .ChatCompletionChunkObject ,
139- Created : event . Response . CreatedAt ,
202+ Created : s . createdAt () ,
140203 Model : responseModelName (s .meta ),
141204 Choices : []* relaymodel.ChatCompletionsStreamResponseChoice {
142205 {
@@ -160,7 +223,7 @@ func (s *chatCompletionStreamState) handleOutputTextDelta(
160223 return & relaymodel.ChatCompletionsStreamResponse {
161224 ID : s .messageID ,
162225 Object : relaymodel .ChatCompletionChunkObject ,
163- Created : time . Now (). Unix (),
226+ Created : s . createdAt (),
164227 Model : responseModelName (s .meta ),
165228 Choices : []* relaymodel.ChatCompletionsStreamResponseChoice {
166229 {
@@ -183,7 +246,7 @@ func (s *chatCompletionStreamState) handleReasoningSummaryTextDelta(
183246 return & relaymodel.ChatCompletionsStreamResponse {
184247 ID : s .messageID ,
185248 Object : relaymodel .ChatCompletionChunkObject ,
186- Created : time . Now (). Unix (),
249+ Created : s . createdAt (),
187250 Model : responseModelName (s .meta ),
188251 Choices : []* relaymodel.ChatCompletionsStreamResponseChoice {
189252 {
@@ -207,30 +270,21 @@ func (s *chatCompletionStreamState) handleOutputItemAdded(
207270 // Track function calls
208271 if event .Item .Type == relaymodel .InputItemTypeFunctionCall {
209272 s .hasToolCall = true
210- s .currentToolCallID = event .Item .ID
211- s .currentToolCall = & relaymodel.ToolCall {
212- ID : event .Item .CallID ,
213- Type : relaymodel .ToolChoiceTypeFunction ,
214- Function : relaymodel.Function {
215- Name : event .Item .Name ,
216- Arguments : "" ,
217- },
218- }
219- s .toolCallArgs = ""
273+ toolCallIndex := s .registerToolCall (event )
220274
221275 // Send tool call start
222276 return & relaymodel.ChatCompletionsStreamResponse {
223277 ID : s .messageID ,
224278 Object : relaymodel .ChatCompletionChunkObject ,
225- Created : time . Now (). Unix (),
279+ Created : s . createdAt (),
226280 Model : responseModelName (s .meta ),
227281 Choices : []* relaymodel.ChatCompletionsStreamResponseChoice {
228282 {
229283 Index : 0 ,
230284 Delta : relaymodel.Message {
231285 ToolCalls : []relaymodel.ToolCall {
232286 {
233- Index : 0 ,
287+ Index : toolCallIndex ,
234288 ID : event .Item .CallID ,
235289 Type : relaymodel .ToolChoiceTypeFunction ,
236290 Function : relaymodel.Function {
@@ -249,7 +303,7 @@ func (s *chatCompletionStreamState) handleOutputItemAdded(
249303 return & relaymodel.ChatCompletionsStreamResponse {
250304 ID : s .messageID ,
251305 Object : relaymodel .ChatCompletionChunkObject ,
252- Created : time . Now (). Unix (),
306+ Created : s . createdAt (),
253307 Model : responseModelName (s .meta ),
254308 Choices : []* relaymodel.ChatCompletionsStreamResponseChoice {
255309 {
@@ -269,26 +323,28 @@ func (s *chatCompletionStreamState) handleOutputItemAdded(
269323func (s * chatCompletionStreamState ) handleFunctionCallArgumentsDelta (
270324 event * relaymodel.ResponseStreamEvent ,
271325) * relaymodel.ChatCompletionsStreamResponse {
272- if event .Delta == "" || s . currentToolCall == nil {
326+ if event .Delta == "" {
273327 return nil
274328 }
275329
276- // Accumulate arguments
277- s .toolCallArgs += event .Delta
330+ toolCallIndex , ok := s .toolCallIndex (event )
331+ if ! ok {
332+ return nil
333+ }
278334
279335 // Send delta
280336 return & relaymodel.ChatCompletionsStreamResponse {
281337 ID : s .messageID ,
282338 Object : relaymodel .ChatCompletionChunkObject ,
283- Created : time . Now (). Unix (),
339+ Created : s . createdAt (),
284340 Model : responseModelName (s .meta ),
285341 Choices : []* relaymodel.ChatCompletionsStreamResponseChoice {
286342 {
287343 Index : 0 ,
288344 Delta : relaymodel.Message {
289345 ToolCalls : []relaymodel.ToolCall {
290346 {
291- Index : 0 ,
347+ Index : toolCallIndex ,
292348 Function : relaymodel.Function {
293349 Arguments : event .Delta ,
294350 },
@@ -300,32 +356,6 @@ func (s *chatCompletionStreamState) handleFunctionCallArgumentsDelta(
300356 }
301357}
302358
303- // handleOutputItemDone handles response.output_item.done event for ChatCompletion
304- func (s * chatCompletionStreamState ) handleOutputItemDone (
305- event * relaymodel.ResponseStreamEvent ,
306- ) {
307- if event .Item == nil {
308- return
309- }
310-
311- // Handle function call completion
312- if event .Item .Type == relaymodel .InputItemTypeFunctionCall && s .currentToolCall != nil &&
313- event .Item .ID == s .currentToolCallID {
314- // Update with final arguments
315- if s .toolCallArgs != "" {
316- s .currentToolCall .Function .Arguments = s .toolCallArgs
317- }
318-
319- // Reset state
320- s .currentToolCall = nil
321- s .currentToolCallID = ""
322- s .toolCallArgs = ""
323-
324- // No need to send another chunk - arguments already streamed
325- return
326- }
327- }
328-
329359// handleResponseCompleted handles response.completed/done event for ChatCompletion
330360func (s * chatCompletionStreamState ) handleResponseCompleted (
331361 event * relaymodel.ResponseStreamEvent ,
@@ -344,7 +374,7 @@ func (s *chatCompletionStreamState) handleResponseCompleted(
344374 return & relaymodel.ChatCompletionsStreamResponse {
345375 ID : s .messageID ,
346376 Object : relaymodel .ChatCompletionChunkObject ,
347- Created : time . Now (). Unix (),
377+ Created : s . createdAt (),
348378 Model : responseModelName (s .meta ),
349379 Choices : []* relaymodel.ChatCompletionsStreamResponseChoice {
350380 {
@@ -1341,6 +1371,8 @@ func ConvertResponsesToChatCompletionResponse(
13411371
13421372 reasonContent := responseReasoningSummaryText (& responsesResp )
13431373
1374+ var toolCallChoice * relaymodel.TextResponseChoice
1375+
13441376 // Convert output items to choices
13451377 for _ , outputItem := range responsesResp .Output {
13461378 switch outputItem .Type {
@@ -1380,31 +1412,36 @@ func ConvertResponsesToChatCompletionResponse(
13801412 toolCallID = outputItem .ID
13811413 }
13821414
1383- finishReason := responseToChatFinishReason (& responsesResp )
1384- if finishReason == relaymodel .FinishReasonStop {
1385- finishReason = relaymodel .FinishReasonToolCalls
1415+ if toolCallChoice == nil {
1416+ finishReason := responseToChatFinishReason (& responsesResp )
1417+ if finishReason == relaymodel .FinishReasonStop {
1418+ finishReason = relaymodel .FinishReasonToolCalls
1419+ }
1420+
1421+ toolCallChoice = & relaymodel.TextResponseChoice {
1422+ Index : len (chatResp .Choices ),
1423+ Message : relaymodel.Message {
1424+ Role : relaymodel .RoleAssistant ,
1425+ ReasoningContent : reasonContent ,
1426+ },
1427+ FinishReason : finishReason ,
1428+ }
1429+ chatResp .Choices = append (chatResp .Choices , toolCallChoice )
1430+ reasonContent = ""
13861431 }
13871432
1388- chatResp .Choices = append (chatResp .Choices , & relaymodel.TextResponseChoice {
1389- Index : len (chatResp .Choices ),
1390- Message : relaymodel.Message {
1391- Role : relaymodel .RoleAssistant ,
1392- ReasoningContent : reasonContent ,
1393- ToolCalls : []relaymodel.ToolCall {
1394- {
1395- Index : 0 ,
1396- ID : toolCallID ,
1397- Type : relaymodel .ToolChoiceTypeFunction ,
1398- Function : relaymodel.Function {
1399- Name : outputItem .Name ,
1400- Arguments : outputItem .Arguments .String (),
1401- },
1402- },
1433+ toolCallChoice .Message .ToolCalls = append (
1434+ toolCallChoice .Message .ToolCalls ,
1435+ relaymodel.ToolCall {
1436+ Index : len (toolCallChoice .Message .ToolCalls ),
1437+ ID : toolCallID ,
1438+ Type : relaymodel .ToolChoiceTypeFunction ,
1439+ Function : relaymodel.Function {
1440+ Name : outputItem .Name ,
1441+ Arguments : outputItem .Arguments .String (),
14031442 },
14041443 },
1405- FinishReason : finishReason ,
1406- })
1407- reasonContent = ""
1444+ )
14081445
14091446 default :
14101447 continue
@@ -1556,8 +1593,10 @@ func ConvertResponsesToChatCompletionStreamResponse(
15561593 errorState := responsesStreamErrorState {}
15571594
15581595 state := & chatCompletionStreamState {
1559- meta : meta ,
1560- c : c ,
1596+ meta : meta ,
1597+ c : c ,
1598+ toolCallIndexByItemID : make (map [string ]int ),
1599+ toolCallIndexByOutputIndex : make (map [int ]int ),
15611600 }
15621601 stopStream := false
15631602
@@ -1635,8 +1674,6 @@ func ConvertResponsesToChatCompletionStreamResponse(
16351674 chatStreamResp = state .handleOutputItemAdded (& event )
16361675 case relaymodel .EventFunctionCallArgumentsDelta :
16371676 chatStreamResp = state .handleFunctionCallArgumentsDelta (& event )
1638- case relaymodel .EventOutputItemDone :
1639- state .handleOutputItemDone (& event )
16401677 case relaymodel .EventResponseCompleted ,
16411678 relaymodel .EventResponseIncomplete ,
16421679 relaymodel .EventResponseDone :
0 commit comments