| @@ -4,6 +4,7 @@ import ( · 3 unmodified lines |
| 4 | "bytes" | 4 | "bytes" |
| 5 | "context" | 5 | "context" |
| 6 | "fmt" | 6 | "fmt" |
| | 7 | "sort" |
| 7 | "strings" | 8 | "strings" |
| 8 | "sync/atomic" | 9 | "sync/atomic" |
| 9 | "time" | 10 | "time" |
| @@ -16,6 +17,7 @@ import ( · 6 unmodified lines |
| 16 | type oaiToResponsesStateReasoning struct { | 17 | type oaiToResponsesStateReasoning struct { |
| 17 | ReasoningID string | 18 | ReasoningID string |
| 18 | ReasoningData string | 19 | ReasoningData string |
| | 20 | OutputIndex int |
| 19 | } | 21 | } |
| 20 | type oaiToResponsesState struct { | 22 | type oaiToResponsesState struct { |
| 21 | Seq int | 23 | Seq int |
| @@ -29,16 +31,19 @@ type oaiToResponsesState struct { · 7 unmodified lines |
| 29 | MsgTextBuf map[int]*strings.Builder | 31 | MsgTextBuf map[int]*strings.Builder |
| 30 | ReasoningBuf strings.Builder | 32 | ReasoningBuf strings.Builder |
| 31 | Reasonings []oaiToResponsesStateReasoning | 33 | Reasonings []oaiToResponsesStateReasoning |
| 32 | FuncArgsBuf map[int]*strings.Builder // index -> args | 34 | FuncArgsBuf map[string]*strings.Builder |
| 33 | FuncNames map[int]string // index -> name | 35 | FuncNames map[string]string |
| 34 | FuncCallIDs map[int]string // index -> call_id | 36 | FuncCallIDs map[string]string |
| | 37 | FuncOutputIx map[string]int |
| | 38 | MsgOutputIx map[int]int |
| | 39 | NextOutputIx int |
| 35 | // message item state per output index | 40 | // message item state per output index |
| 36 | MsgItemAdded map[int]bool // whether response.output_item.added emitted for message | 41 | MsgItemAdded map[int]bool // whether response.output_item.added emitted for message |
| 37 | MsgContentAdded map[int]bool // whether response.content_part.added emitted for message | 42 | MsgContentAdded map[int]bool // whether response.content_part.added emitted for message |
| 38 | MsgItemDone map[int]bool // whether message done events were emitted | 43 | MsgItemDone map[int]bool // whether message done events were emitted |
| 39 | // function item done state | 44 | // function item done state |
| 40 | FuncArgsDone map[int]bool | 45 | FuncArgsDone map[string]bool |
| 41 | FuncItemDone map[int]bool | 46 | FuncItemDone map[string]bool |
| 42 | // usage aggregation | 47 | // usage aggregation |
| 43 | PromptTokens int64 | 48 | PromptTokens int64 |
| 44 | CachedTokens int64 | 49 | CachedTokens int64 |
| @@ -60,15 +65,17 @@ func emitRespEvent(event string, payload []byte) []byte { · 15 unmodified lines |
| 60 | func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte { | 65 | func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte { |
| 61 | if *param == nil { | 66 | if *param == nil { |
| 62 | *param = &oaiToResponsesState{ | 67 | *param = &oaiToResponsesState{ |
| 63 | FuncArgsBuf: make(map[int]*strings.Builder), | 68 | FuncArgsBuf: make(map[string]*strings.Builder), |
| 64 | FuncNames: make(map[int]string), | 69 | FuncNames: make(map[string]string), |
| 65 | FuncCallIDs: make(map[int]string), | 70 | FuncCallIDs: make(map[string]string), |
| | 71 | FuncOutputIx: make(map[string]int), |
| | 72 | MsgOutputIx: make(map[int]int), |
| 66 | MsgTextBuf: make(map[int]*strings.Builder), | 73 | MsgTextBuf: make(map[int]*strings.Builder), |
| 67 | MsgItemAdded: make(map[int]bool), | 74 | MsgItemAdded: make(map[int]bool), |
| 68 | MsgContentAdded: make(map[int]bool), | 75 | MsgContentAdded: make(map[int]bool), |
| 69 | MsgItemDone: make(map[int]bool), | 76 | MsgItemDone: make(map[int]bool), |
| 70 | FuncArgsDone: make(map[int]bool), | 77 | FuncArgsDone: make(map[string]bool), |
| 71 | FuncItemDone: make(map[int]bool), | 78 | FuncItemDone: make(map[string]bool), |
| 72 | Reasonings: make([]oaiToResponsesStateReasoning, 0), | 79 | Reasonings: make([]oaiToResponsesStateReasoning, 0), |
| 73 | } | 80 | } |
| 74 | } | 81 | } |
| @@ -125,6 +132,12 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 50 unmodified lines |
| 125 | } | 132 | } |
| 126 | | 133 | |
| 127 | nextSeq := func() int { st.Seq++; return st.Seq } | 134 | nextSeq := func() int { st.Seq++; return st.Seq } |
| | 135 | allocOutputIndex := func() int { |
| | 136 | ix := st.NextOutputIx |
| | 137 | st.NextOutputIx++ |
| | 138 | return ix |
| | 139 | } |
| | 140 | toolStateKey := func(outputIndex, toolIndex int) string { return fmt.Sprintf("%d:%d", outputIndex, toolIndex) } |
| 128 | var out [][]byte | 141 | var out [][]byte |
| 129 | | 142 | |
| 130 | if !st.Started { | 143 | if !st.Started { |
| @@ -135,14 +148,17 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 4 unmodified lines |
| 135 | st.ReasoningBuf.Reset() | 148 | st.ReasoningBuf.Reset() |
| 136 | st.ReasoningID = "" | 149 | st.ReasoningID = "" |
| 137 | st.ReasoningIndex = 0 | 150 | st.ReasoningIndex = 0 |
| 138 | st.FuncArgsBuf = make(map[int]*strings.Builder) | 151 | st.FuncArgsBuf = make(map[string]*strings.Builder) |
| 139 | st.FuncNames = make(map[int]string) | 152 | st.FuncNames = make(map[string]string) |
| 140 | st.FuncCallIDs = make(map[int]string) | 153 | st.FuncCallIDs = make(map[string]string) |
| | 154 | st.FuncOutputIx = make(map[string]int) |
| | 155 | st.MsgOutputIx = make(map[int]int) |
| | 156 | st.NextOutputIx = 0 |
| 141 | st.MsgItemAdded = make(map[int]bool) | 157 | st.MsgItemAdded = make(map[int]bool) |
| 142 | st.MsgContentAdded = make(map[int]bool) | 158 | st.MsgContentAdded = make(map[int]bool) |
| 143 | st.MsgItemDone = make(map[int]bool) | 159 | st.MsgItemDone = make(map[int]bool) |
| 144 | st.FuncArgsDone = make(map[int]bool) | 160 | st.FuncArgsDone = make(map[string]bool) |
| 145 | st.FuncItemDone = make(map[int]bool) | 161 | st.FuncItemDone = make(map[string]bool) |
| 146 | st.PromptTokens = 0 | 162 | st.PromptTokens = 0 |
| 147 | st.CachedTokens = 0 | 163 | st.CachedTokens = 0 |
| 148 | st.CompletionTokens = 0 | 164 | st.CompletionTokens = 0 |
| @@ -185,7 +201,7 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 36 unmodified lines |
| 185 | outputItemDone, _ = sjson.SetBytes(outputItemDone, "item.summary.text", text) | 201 | outputItemDone, _ = sjson.SetBytes(outputItemDone, "item.summary.text", text) |
| 186 | out = append(out, emitRespEvent("response.output_item.done", outputItemDone)) | 202 | out = append(out, emitRespEvent("response.output_item.done", outputItemDone)) |
| 187 | | 203 | |
| 188 | st.Reasonings = append(st.Reasonings, oaiToResponsesStateReasoning{ReasoningID: st.ReasoningID, ReasoningData: text}) | 204 | st.Reasonings = append(st.Reasonings, oaiToResponsesStateReasoning{ReasoningID: st.ReasoningID, ReasoningData: text, OutputIndex: st.ReasoningIndex}) |
| 189 | st.ReasoningID = "" | 205 | st.ReasoningID = "" |
| 190 | } | 206 | } |
| 191 | | 207 | |
| @@ -201,10 +217,14 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 9 unmodified lines |
| 201 | stopReasoning(st.ReasoningBuf.String()) | 217 | stopReasoning(st.ReasoningBuf.String()) |
| 202 | st.ReasoningBuf.Reset() | 218 | st.ReasoningBuf.Reset() |
| 203 | } | 219 | } |
| | 220 | if _, exists := st.MsgOutputIx[idx]; !exists { |
| | 221 | st.MsgOutputIx[idx] = allocOutputIndex() |
| | 222 | } |
| | 223 | msgOutputIndex := st.MsgOutputIx[idx] |
| 204 | if !st.MsgItemAdded[idx] { | 224 | if !st.MsgItemAdded[idx] { |
| 205 | item := []byte(`{"type":"response.output_item.added","sequence_number":0,"output_index":0,"item":{"id":"","type":"message","status":"in_progress","content":[],"role":"assistant"}}`) | 225 | item := []byte(`{"type":"response.output_item.added","sequence_number":0,"output_index":0,"item":{"id":"","type":"message","status":"in_progress","content":[],"role":"assistant"}}`) |
| 206 | item, _ = sjson.SetBytes(item, "sequence_number", nextSeq()) | 226 | item, _ = sjson.SetBytes(item, "sequence_number", nextSeq()) |
| 207 | item, _ = sjson.SetBytes(item, "output_index", idx) | 227 | item, _ = sjson.SetBytes(item, "output_index", msgOutputIndex) |
| 208 | item, _ = sjson.SetBytes(item, "item.id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) | 228 | item, _ = sjson.SetBytes(item, "item.id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) |
| 209 | out = append(out, emitRespEvent("response.output_item.added", item)) | 229 | out = append(out, emitRespEvent("response.output_item.added", item)) |
| 210 | st.MsgItemAdded[idx] = true | 230 | st.MsgItemAdded[idx] = true |
| @@ -213,7 +233,7 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 2 unmodified lines |
| 213 | part := []byte(`{"type":"response.content_part.added","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}}`) | 233 | part := []byte(`{"type":"response.content_part.added","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}}`) |
| 214 | part, _ = sjson.SetBytes(part, "sequence_number", nextSeq()) | 234 | part, _ = sjson.SetBytes(part, "sequence_number", nextSeq()) |
| 215 | part, _ = sjson.SetBytes(part, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) | 235 | part, _ = sjson.SetBytes(part, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) |
| 216 | part, _ = sjson.SetBytes(part, "output_index", idx) | 236 | part, _ = sjson.SetBytes(part, "output_index", msgOutputIndex) |
| 217 | part, _ = sjson.SetBytes(part, "content_index", 0) | 237 | part, _ = sjson.SetBytes(part, "content_index", 0) |
| 218 | out = append(out, emitRespEvent("response.content_part.added", part)) | 238 | out = append(out, emitRespEvent("response.content_part.added", part)) |
| 219 | st.MsgContentAdded[idx] = true | 239 | st.MsgContentAdded[idx] = true |
| @@ -222,7 +242,7 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 2 unmodified lines |
| 222 | msg := []byte(`{"type":"response.output_text.delta","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"delta":"","logprobs":[]}`) | 242 | msg := []byte(`{"type":"response.output_text.delta","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"delta":"","logprobs":[]}`) |
| 223 | msg, _ = sjson.SetBytes(msg, "sequence_number", nextSeq()) | 243 | msg, _ = sjson.SetBytes(msg, "sequence_number", nextSeq()) |
| 224 | msg, _ = sjson.SetBytes(msg, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) | 244 | msg, _ = sjson.SetBytes(msg, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) |
| 225 | msg, _ = sjson.SetBytes(msg, "output_index", idx) | 245 | msg, _ = sjson.SetBytes(msg, "output_index", msgOutputIndex) |
| 226 | msg, _ = sjson.SetBytes(msg, "content_index", 0) | 246 | msg, _ = sjson.SetBytes(msg, "content_index", 0) |
| 227 | msg, _ = sjson.SetBytes(msg, "delta", c.String()) | 247 | msg, _ = sjson.SetBytes(msg, "delta", c.String()) |
| 228 | out = append(out, emitRespEvent("response.output_text.delta", msg)) | 248 | out = append(out, emitRespEvent("response.output_text.delta", msg)) |
| @@ -238,10 +258,10 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 9 unmodified lines |
| 238 | // On first appearance, add reasoning item and part | 258 | // On first appearance, add reasoning item and part |
| 239 | if st.ReasoningID == "" { | 259 | if st.ReasoningID == "" { |
| 240 | st.ReasoningID = fmt.Sprintf("rs_%s_%d", st.ResponseID, idx) | 260 | st.ReasoningID = fmt.Sprintf("rs_%s_%d", st.ResponseID, idx) |
| 241 | st.ReasoningIndex = idx | 261 | st.ReasoningIndex = allocOutputIndex() |
| 242 | item := []byte(`{"type":"response.output_item.added","sequence_number":0,"output_index":0,"item":{"id":"","type":"reasoning","status":"in_progress","summary":[]}}`) | 262 | item := []byte(`{"type":"response.output_item.added","sequence_number":0,"output_index":0,"item":{"id":"","type":"reasoning","status":"in_progress","summary":[]}}`) |
| 243 | item, _ = sjson.SetBytes(item, "sequence_number", nextSeq()) | 263 | item, _ = sjson.SetBytes(item, "sequence_number", nextSeq()) |
| 244 | item, _ = sjson.SetBytes(item, "output_index", idx) | 264 | item, _ = sjson.SetBytes(item, "output_index", st.ReasoningIndex) |
| 245 | item, _ = sjson.SetBytes(item, "item.id", st.ReasoningID) | 265 | item, _ = sjson.SetBytes(item, "item.id", st.ReasoningID) |
| 246 | out = append(out, emitRespEvent("response.output_item.added", item)) | 266 | out = append(out, emitRespEvent("response.output_item.added", item)) |
| 247 | part := []byte(`{"type":"response.reasoning_summary_part.added","sequence_number":0,"item_id":"","output_index":0,"summary_index":0,"part":{"type":"summary_text","text":""}}`) | 267 | part := []byte(`{"type":"response.reasoning_summary_part.added","sequence_number":0,"item_id":"","output_index":0,"summary_index":0,"part":{"type":"summary_text","text":""}}`) |
| @@ -269,6 +289,7 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 21 unmodified lines |
| 269 | // Before emitting any function events, if a message is open for this index, | 289 | // Before emitting any function events, if a message is open for this index, |
| 270 | // close its text/content to match Codex expected ordering. | 290 | // close its text/content to match Codex expected ordering. |
| 271 | if st.MsgItemAdded[idx] && !st.MsgItemDone[idx] { | 291 | if st.MsgItemAdded[idx] && !st.MsgItemDone[idx] { |
| | 292 | msgOutputIndex := st.MsgOutputIx[idx] |
| 272 | fullText := "" | 293 | fullText := "" |
| 273 | if b := st.MsgTextBuf[idx]; b != nil { | 294 | if b := st.MsgTextBuf[idx]; b != nil { |
| 274 | fullText = b.String() | 295 | fullText = b.String() |
| @@ -276,7 +297,7 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 1 unmodified line |
| 276 | done := []byte(`{"type":"response.output_text.done","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"text":"","logprobs":[]}`) | 297 | done := []byte(`{"type":"response.output_text.done","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"text":"","logprobs":[]}`) |
| 277 | done, _ = sjson.SetBytes(done, "sequence_number", nextSeq()) | 298 | done, _ = sjson.SetBytes(done, "sequence_number", nextSeq()) |
| 278 | done, _ = sjson.SetBytes(done, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) | 299 | done, _ = sjson.SetBytes(done, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) |
| 279 | done, _ = sjson.SetBytes(done, "output_index", idx) | 300 | done, _ = sjson.SetBytes(done, "output_index", msgOutputIndex) |
| 280 | done, _ = sjson.SetBytes(done, "content_index", 0) | 301 | done, _ = sjson.SetBytes(done, "content_index", 0) |
| 281 | done, _ = sjson.SetBytes(done, "text", fullText) | 302 | done, _ = sjson.SetBytes(done, "text", fullText) |
| 282 | out = append(out, emitRespEvent("response.output_text.done", done)) | 303 | out = append(out, emitRespEvent("response.output_text.done", done)) |
| @@ -284,69 +305,72 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 1 unmodified line |
| 284 | partDone := []byte(`{"type":"response.content_part.done","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}}`) | 305 | partDone := []byte(`{"type":"response.content_part.done","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}}`) |
| 285 | partDone, _ = sjson.SetBytes(partDone, "sequence_number", nextSeq()) | 306 | partDone, _ = sjson.SetBytes(partDone, "sequence_number", nextSeq()) |
| 286 | partDone, _ = sjson.SetBytes(partDone, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) | 307 | partDone, _ = sjson.SetBytes(partDone, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) |
| 287 | partDone, _ = sjson.SetBytes(partDone, "output_index", idx) | 308 | partDone, _ = sjson.SetBytes(partDone, "output_index", msgOutputIndex) |
| 288 | partDone, _ = sjson.SetBytes(partDone, "content_index", 0) | 309 | partDone, _ = sjson.SetBytes(partDone, "content_index", 0) |
| 289 | partDone, _ = sjson.SetBytes(partDone, "part.text", fullText) | 310 | partDone, _ = sjson.SetBytes(partDone, "part.text", fullText) |
| 290 | out = append(out, emitRespEvent("response.content_part.done", partDone)) | 311 | out = append(out, emitRespEvent("response.content_part.done", partDone)) |
| 291 | | 312 | |
| 292 | itemDone := []byte(`{"type":"response.output_item.done","sequence_number":0,"output_index":0,"item":{"id":"","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":""}],"role":"assistant"}}`) | 313 | itemDone := []byte(`{"type":"response.output_item.done","sequence_number":0,"output_index":0,"item":{"id":"","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":""}],"role":"assistant"}}`) |
| 293 | itemDone, _ = sjson.SetBytes(itemDone, "sequence_number", nextSeq()) | 314 | itemDone, _ = sjson.SetBytes(itemDone, "sequence_number", nextSeq()) |
| 294 | itemDone, _ = sjson.SetBytes(itemDone, "output_index", idx) | 315 | itemDone, _ = sjson.SetBytes(itemDone, "output_index", msgOutputIndex) |
| 295 | itemDone, _ = sjson.SetBytes(itemDone, "item.id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) | 316 | itemDone, _ = sjson.SetBytes(itemDone, "item.id", fmt.Sprintf("msg_%s_%d", st.ResponseID, idx)) |
| 296 | itemDone, _ = sjson.SetBytes(itemDone, "item.content.0.text", fullText) | 317 | itemDone, _ = sjson.SetBytes(itemDone, "item.content.0.text", fullText) |
| 297 | out = append(out, emitRespEvent("response.output_item.done", itemDone)) | 318 | out = append(out, emitRespEvent("response.output_item.done", itemDone)) |
| 298 | st.MsgItemDone[idx] = true | 319 | st.MsgItemDone[idx] = true |
| 299 | } | 320 | } |
| 300 | | 321 | |
| 301 | // Only emit item.added once per tool call and preserve call_id across chunks. | 322 | tcs.ForEach(func(_, tc gjson.Result) bool { |
| 302 | newCallID := tcs.Get("0.id").String() | 323 | toolIndex := int(tc.Get("index").Int()) |
| 303 | nameChunk := tcs.Get("0.function.name").String() | 324 | key := toolStateKey(idx, toolIndex) |
| 304 | if nameChunk != "" { | 325 | newCallID := tc.Get("id").String() |
| 305 | st.FuncNames[idx] = nameChunk | 326 | nameChunk := tc.Get("function.name").String() |
| 306 | } | 327 | if nameChunk != "" { |
| 307 | existingCallID := st.FuncCallIDs[idx] | 328 | st.FuncNames[key] = nameChunk |
| 308 | effectiveCallID := existingCallID | | |
| 309 | shouldEmitItem := false | | |
| 310 | if existingCallID == "" && newCallID != "" { | | |
| 311 | // First time seeing a valid call_id for this index | | |
| 312 | effectiveCallID = newCallID | | |
| 313 | st.FuncCallIDs[idx] = newCallID | | |
| 314 | shouldEmitItem = true | | |
| 315 | } | | |
| 316 | | | |
| 317 | if shouldEmitItem && effectiveCallID != "" { | | |
| 318 | o := []byte(`{"type":"response.output_item.added","sequence_number":0,"output_index":0,"item":{"id":"","type":"function_call","status":"in_progress","arguments":"","call_id":"","name":""}}`) | | |
| 319 | o, _ = sjson.SetBytes(o, "sequence_number", nextSeq()) | | |
| 320 | o, _ = sjson.SetBytes(o, "output_index", idx) | | |
| 321 | o, _ = sjson.SetBytes(o, "item.id", fmt.Sprintf("fc_%s", effectiveCallID)) | | |
| 322 | o, _ = sjson.SetBytes(o, "item.call_id", effectiveCallID) | | |
| 323 | name := st.FuncNames[idx] | | |
| 324 | o, _ = sjson.SetBytes(o, "item.name", name) | | |
| 325 | out = append(out, emitRespEvent("response.output_item.added", o)) | | |
| 326 | } | | |
| 327 | | | |
| 328 | // Ensure args buffer exists for this index | | |
| 329 | if st.FuncArgsBuf[idx] == nil { | | |
| 330 | st.FuncArgsBuf[idx] = &strings.Builder{} | | |
| 331 | } | | |
| 332 | | | |
| 333 | // Append arguments delta if available and we have a valid call_id to reference | | |
| 334 | if args := tcs.Get("0.function.arguments"); args.Exists() && args.String() != "" { | | |
| 335 | // Prefer an already known call_id; fall back to newCallID if first time | | |
| 336 | refCallID := st.FuncCallIDs[idx] | | |
| 337 | if refCallID == "" { | | |
| 338 | refCallID = newCallID | | |
| 339 | } | 329 | } |
| 340 | if refCallID != "" { | 330 | |
| 341 | ad := []byte(`{"type":"response.function_call_arguments.delta","sequence_number":0,"item_id":"","output_index":0,"delta":""}`) | 331 | existingCallID := st.FuncCallIDs[key] |
| 342 | ad, _ = sjson.SetBytes(ad, "sequence_number", nextSeq()) | 332 | effectiveCallID := existingCallID |
| 343 | ad, _ = sjson.SetBytes(ad, "item_id", fmt.Sprintf("fc_%s", refCallID)) | 333 | shouldEmitItem := false |
| 344 | ad, _ = sjson.SetBytes(ad, "output_index", idx) | 334 | if existingCallID == "" && newCallID != "" { |
| 345 | ad, _ = sjson.SetBytes(ad, "delta", args.String()) | 335 | effectiveCallID = newCallID |
| 346 | out = append(out, emitRespEvent("response.function_call_arguments.delta", ad)) | 336 | st.FuncCallIDs[key] = newCallID |
| | 337 | st.FuncOutputIx[key] = allocOutputIndex() |
| | 338 | shouldEmitItem = true |
| 347 | } | 339 | } |
| 348 | st.FuncArgsBuf[idx].WriteString(args.String()) | 340 | |
| 349 | } | 341 | if shouldEmitItem && effectiveCallID != "" { |
| | 342 | outputIndex := st.FuncOutputIx[key] |
| | 343 | o := []byte(`{"type":"response.output_item.added","sequence_number":0,"output_index":0,"item":{"id":"","type":"function_call","status":"in_progress","arguments":"","call_id":"","name":""}}`) |
| | 344 | o, _ = sjson.SetBytes(o, "sequence_number", nextSeq()) |
| | 345 | o, _ = sjson.SetBytes(o, "output_index", outputIndex) |
| | 346 | o, _ = sjson.SetBytes(o, "item.id", fmt.Sprintf("fc_%s", effectiveCallID)) |
| | 347 | o, _ = sjson.SetBytes(o, "item.call_id", effectiveCallID) |
| | 348 | o, _ = sjson.SetBytes(o, "item.name", st.FuncNames[key]) |
| | 349 | out = append(out, emitRespEvent("response.output_item.added", o)) |
| | 350 | } |
| | 351 | |
| | 352 | if st.FuncArgsBuf[key] == nil { |
| | 353 | st.FuncArgsBuf[key] = &strings.Builder{} |
| | 354 | } |
| | 355 | |
| | 356 | if args := tc.Get("function.arguments"); args.Exists() && args.String() != "" { |
| | 357 | refCallID := st.FuncCallIDs[key] |
| | 358 | if refCallID == "" { |
| | 359 | refCallID = newCallID |
| | 360 | } |
| | 361 | if refCallID != "" { |
| | 362 | outputIndex := st.FuncOutputIx[key] |
| | 363 | ad := []byte(`{"type":"response.function_call_arguments.delta","sequence_number":0,"item_id":"","output_index":0,"delta":""}`) |
| | 364 | ad, _ = sjson.SetBytes(ad, "sequence_number", nextSeq()) |
| | 365 | ad, _ = sjson.SetBytes(ad, "item_id", fmt.Sprintf("fc_%s", refCallID)) |
| | 366 | ad, _ = sjson.SetBytes(ad, "output_index", outputIndex) |
| | 367 | ad, _ = sjson.SetBytes(ad, "delta", args.String()) |
| | 368 | out = append(out, emitRespEvent("response.function_call_arguments.delta", ad)) |
| | 369 | } |
| | 370 | st.FuncArgsBuf[key].WriteString(args.String()) |
| | 371 | } |
| | 372 | return true |
| | 373 | }) |
| 350 | } | 374 | } |
| 351 | } | 375 | } |
| 352 | | 376 | |
| @@ -360,15 +384,10 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 7 unmodified lines |
| 360 | for i := range st.MsgItemAdded { | 384 | for i := range st.MsgItemAdded { |
| 361 | idxs = append(idxs, i) | 385 | idxs = append(idxs, i) |
| 362 | } | 386 | } |
| 363 | for i := 0; i < len(idxs); i++ { | 387 | sort.Slice(idxs, func(i, j int) bool { return st.MsgOutputIx[idxs[i]] < st.MsgOutputIx[idxs[j]] }) |
| 364 | for j := i + 1; j < len(idxs); j++ { | | |
| 365 | if idxs[j] < idxs[i] { | | |
| 366 | idxs[i], idxs[j] = idxs[j], idxs[i] | | |
| 367 | } | | |
| 368 | } | | |
| 369 | } | | |
| 370 | for _, i := range idxs { | 388 | for _, i := range idxs { |
| 371 | if st.MsgItemAdded[i] && !st.MsgItemDone[i] { | 389 | if st.MsgItemAdded[i] && !st.MsgItemDone[i] { |
| | 390 | msgOutputIndex := st.MsgOutputIx[i] |
| 372 | fullText := "" | 391 | fullText := "" |
| 373 | if b := st.MsgTextBuf[i]; b != nil { | 392 | if b := st.MsgTextBuf[i]; b != nil { |
| 374 | fullText = b.String() | 393 | fullText = b.String() |
| @@ -376,7 +395,7 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 1 unmodified line |
| 376 | done := []byte(`{"type":"response.output_text.done","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"text":"","logprobs":[]}`) | 395 | done := []byte(`{"type":"response.output_text.done","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"text":"","logprobs":[]}`) |
| 377 | done, _ = sjson.SetBytes(done, "sequence_number", nextSeq()) | 396 | done, _ = sjson.SetBytes(done, "sequence_number", nextSeq()) |
| 378 | done, _ = sjson.SetBytes(done, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, i)) | 397 | done, _ = sjson.SetBytes(done, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, i)) |
| 379 | done, _ = sjson.SetBytes(done, "output_index", i) | 398 | done, _ = sjson.SetBytes(done, "output_index", msgOutputIndex) |
| 380 | done, _ = sjson.SetBytes(done, "content_index", 0) | 399 | done, _ = sjson.SetBytes(done, "content_index", 0) |
| 381 | done, _ = sjson.SetBytes(done, "text", fullText) | 400 | done, _ = sjson.SetBytes(done, "text", fullText) |
| 382 | out = append(out, emitRespEvent("response.output_text.done", done)) | 401 | out = append(out, emitRespEvent("response.output_text.done", done)) |
| @@ -384,14 +403,14 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 1 unmodified line |
| 384 | partDone := []byte(`{"type":"response.content_part.done","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}}`) | 403 | partDone := []byte(`{"type":"response.content_part.done","sequence_number":0,"item_id":"","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}}`) |
| 385 | partDone, _ = sjson.SetBytes(partDone, "sequence_number", nextSeq()) | 404 | partDone, _ = sjson.SetBytes(partDone, "sequence_number", nextSeq()) |
| 386 | partDone, _ = sjson.SetBytes(partDone, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, i)) | 405 | partDone, _ = sjson.SetBytes(partDone, "item_id", fmt.Sprintf("msg_%s_%d", st.ResponseID, i)) |
| 387 | partDone, _ = sjson.SetBytes(partDone, "output_index", i) | 406 | partDone, _ = sjson.SetBytes(partDone, "output_index", msgOutputIndex) |
| 388 | partDone, _ = sjson.SetBytes(partDone, "content_index", 0) | 407 | partDone, _ = sjson.SetBytes(partDone, "content_index", 0) |
| 389 | partDone, _ = sjson.SetBytes(partDone, "part.text", fullText) | 408 | partDone, _ = sjson.SetBytes(partDone, "part.text", fullText) |
| 390 | out = append(out, emitRespEvent("response.content_part.done", partDone)) | 409 | out = append(out, emitRespEvent("response.content_part.done", partDone)) |
| 391 | | 410 | |
| 392 | itemDone := []byte(`{"type":"response.output_item.done","sequence_number":0,"output_index":0,"item":{"id":"","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":""}],"role":"assistant"}}`) | 411 | itemDone := []byte(`{"type":"response.output_item.done","sequence_number":0,"output_index":0,"item":{"id":"","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":""}],"role":"assistant"}}`) |
| 393 | itemDone, _ = sjson.SetBytes(itemDone, "sequence_number", nextSeq()) | 412 | itemDone, _ = sjson.SetBytes(itemDone, "sequence_number", nextSeq()) |
| 394 | itemDone, _ = sjson.SetBytes(itemDone, "output_index", i) | 413 | itemDone, _ = sjson.SetBytes(itemDone, "output_index", msgOutputIndex) |
| 395 | itemDone, _ = sjson.SetBytes(itemDone, "item.id", fmt.Sprintf("msg_%s_%d", st.ResponseID, i)) | 414 | itemDone, _ = sjson.SetBytes(itemDone, "item.id", fmt.Sprintf("msg_%s_%d", st.ResponseID, i)) |
| 396 | itemDone, _ = sjson.SetBytes(itemDone, "item.content.0.text", fullText) | 415 | itemDone, _ = sjson.SetBytes(itemDone, "item.content.0.text", fullText) |
| 397 | out = append(out, emitRespEvent("response.output_item.done", itemDone)) | 416 | out = append(out, emitRespEvent("response.output_item.done", itemDone)) |
| @@ -407,43 +426,42 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 9 unmodified lines |
| 407 | | 426 | |
| 408 | // Emit function call done events for any active function calls | 427 | // Emit function call done events for any active function calls |
| 409 | if len(st.FuncCallIDs) > 0 { | 428 | if len(st.FuncCallIDs) > 0 { |
| 410 | idxs := make([]int, 0, len(st.FuncCallIDs)) | 429 | keys := make([]string, 0, len(st.FuncCallIDs)) |
| 411 | for i := range st.FuncCallIDs { | 430 | for key := range st.FuncCallIDs { |
| 412 | idxs = append(idxs, i) | 431 | keys = append(keys, key) |
| 413 | } | 432 | } |
| 414 | for i := 0; i < len(idxs); i++ { | 433 | sort.Slice(keys, func(i, j int) bool { |
| 415 | for j := i + 1; j < len(idxs); j++ { | 434 | left := st.FuncOutputIx[keys[i]] |
| 416 | if idxs[j] < idxs[i] { | 435 | right := st.FuncOutputIx[keys[j]] |
| 417 | idxs[i], idxs[j] = idxs[j], idxs[i] | 436 | return left < right || (left == right && keys[i] < keys[j]) |
| 418 | } | 437 | }) |
| 419 | } | 438 | for _, key := range keys { |
| 420 | } | 439 | callID := st.FuncCallIDs[key] |
| 421 | for _, i := range idxs { | 440 | if callID == "" || st.FuncItemDone[key] { |
| 422 | callID := st.FuncCallIDs[i] | | |
| 423 | if callID == "" || st.FuncItemDone[i] { | | |
| 424 | continue | 441 | continue |
| 425 | } | 442 | } |
| | 443 | outputIndex := st.FuncOutputIx[key] |
| 426 | args := "{}" | 444 | args := "{}" |
| 427 | if b := st.FuncArgsBuf[i]; b != nil && b.Len() > 0 { | 445 | if b := st.FuncArgsBuf[key]; b != nil && b.Len() > 0 { |
| 428 | args = b.String() | 446 | args = b.String() |
| 429 | } | 447 | } |
| 430 | fcDone := []byte(`{"type":"response.function_call_arguments.done","sequence_number":0,"item_id":"","output_index":0,"arguments":""}`) | 448 | fcDone := []byte(`{"type":"response.function_call_arguments.done","sequence_number":0,"item_id":"","output_index":0,"arguments":""}`) |
| 431 | fcDone, _ = sjson.SetBytes(fcDone, "sequence_number", nextSeq()) | 449 | fcDone, _ = sjson.SetBytes(fcDone, "sequence_number", nextSeq()) |
| 432 | fcDone, _ = sjson.SetBytes(fcDone, "item_id", fmt.Sprintf("fc_%s", callID)) | 450 | fcDone, _ = sjson.SetBytes(fcDone, "item_id", fmt.Sprintf("fc_%s", callID)) |
| 433 | fcDone, _ = sjson.SetBytes(fcDone, "output_index", i) | 451 | fcDone, _ = sjson.SetBytes(fcDone, "output_index", outputIndex) |
| 434 | fcDone, _ = sjson.SetBytes(fcDone, "arguments", args) | 452 | fcDone, _ = sjson.SetBytes(fcDone, "arguments", args) |
| 435 | out = append(out, emitRespEvent("response.function_call_arguments.done", fcDone)) | 453 | out = append(out, emitRespEvent("response.function_call_arguments.done", fcDone)) |
| 436 | | 454 | |
| 437 | itemDone := []byte(`{"type":"response.output_item.done","sequence_number":0,"output_index":0,"item":{"id":"","type":"function_call","status":"completed","arguments":"","call_id":"","name":""}}`) | 455 | itemDone := []byte(`{"type":"response.output_item.done","sequence_number":0,"output_index":0,"item":{"id":"","type":"function_call","status":"completed","arguments":"","call_id":"","name":""}}`) |
| 438 | itemDone, _ = sjson.SetBytes(itemDone, "sequence_number", nextSeq()) | 456 | itemDone, _ = sjson.SetBytes(itemDone, "sequence_number", nextSeq()) |
| 439 | itemDone, _ = sjson.SetBytes(itemDone, "output_index", i) | 457 | itemDone, _ = sjson.SetBytes(itemDone, "output_index", outputIndex) |
| 440 | itemDone, _ = sjson.SetBytes(itemDone, "item.id", fmt.Sprintf("fc_%s", callID)) | 458 | itemDone, _ = sjson.SetBytes(itemDone, "item.id", fmt.Sprintf("fc_%s", callID)) |
| 441 | itemDone, _ = sjson.SetBytes(itemDone, "item.arguments", args) | 459 | itemDone, _ = sjson.SetBytes(itemDone, "item.arguments", args) |
| 442 | itemDone, _ = sjson.SetBytes(itemDone, "item.call_id", callID) | 460 | itemDone, _ = sjson.SetBytes(itemDone, "item.call_id", callID) |
| 443 | itemDone, _ = sjson.SetBytes(itemDone, "item.name", st.FuncNames[i]) | 461 | itemDone, _ = sjson.SetBytes(itemDone, "item.name", st.FuncNames[key]) |
| 444 | out = append(out, emitRespEvent("response.output_item.done", itemDone)) | 462 | out = append(out, emitRespEvent("response.output_item.done", itemDone)) |
| 445 | st.FuncItemDone[i] = true | 463 | st.FuncItemDone[key] = true |
| 446 | st.FuncArgsDone[i] = true | 464 | st.FuncArgsDone[key] = true |
| 447 | } | 465 | } |
| 448 | } | 466 | } |
| 449 | completed := []byte(`{"type":"response.completed","sequence_number":0,"response":{"id":"","object":"response","created_at":0,"status":"completed","background":false,"error":null}}`) | 467 | completed := []byte(`{"type":"response.completed","sequence_number":0,"response":{"id":"","object":"response","created_at":0,"status":"completed","background":false,"error":null}}`) |
| @@ -516,28 +534,21 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 66 unmodified lines |
| 516 | } | 534 | } |
| 517 | // Build response.output using aggregated buffers | 535 | // Build response.output using aggregated buffers |
| 518 | outputsWrapper := []byte(`{"arr":[]}`) | 536 | outputsWrapper := []byte(`{"arr":[]}`) |
| | 537 | type completedOutputItem struct { |
| | 538 | index int |
| | 539 | raw []byte |
| | 540 | } |
| | 541 | outputItems := make([]completedOutputItem, 0, len(st.Reasonings)+len(st.MsgItemAdded)+len(st.FuncArgsBuf)) |
| 519 | if len(st.Reasonings) > 0 { | 542 | if len(st.Reasonings) > 0 { |
| 520 | for _, r := range st.Reasonings { | 543 | for _, r := range st.Reasonings { |
| 521 | item := []byte(`{"id":"","type":"reasoning","summary":[{"type":"summary_text","text":""}]}`) | 544 | item := []byte(`{"id":"","type":"reasoning","summary":[{"type":"summary_text","text":""}]}`) |
| 522 | item, _ = sjson.SetBytes(item, "id", r.ReasoningID) | 545 | item, _ = sjson.SetBytes(item, "id", r.ReasoningID) |
| 523 | item, _ = sjson.SetBytes(item, "summary.0.text", r.ReasoningData) | 546 | item, _ = sjson.SetBytes(item, "summary.0.text", r.ReasoningData) |
| 524 | outputsWrapper, _ = sjson.SetRawBytes(outputsWrapper, "arr.-1", item) | 547 | outputItems = append(outputItems, completedOutputItem{index: r.OutputIndex, raw: item}) |
| 525 | } | 548 | } |
| 526 | } | 549 | } |
| 527 | // Append message items in ascending index order | | |
| 528 | if len(st.MsgItemAdded) > 0 { | 550 | if len(st.MsgItemAdded) > 0 { |
| 529 | midxs := make([]int, 0, len(st.MsgItemAdded)) | | |
| 530 | for i := range st.MsgItemAdded { | 551 | for i := range st.MsgItemAdded { |
| 531 | midxs = append(midxs, i) | | |
| 532 | } | | |
| 533 | for i := 0; i < len(midxs); i++ { | | |
| 534 | for j := i + 1; j < len(midxs); j++ { | | |
| 535 | if midxs[j] < midxs[i] { | | |
| 536 | midxs[i], midxs[j] = midxs[j], midxs[i] | | |
| 537 | } | | |
| 538 | } | | |
| 539 | } | | |
| 540 | for _, i := range midxs { | | |
| 541 | txt := "" | 552 | txt := "" |
| 542 | if b := st.MsgTextBuf[i]; b != nil { | 553 | if b := st.MsgTextBuf[i]; b != nil { |
| 543 | txt = b.String() | 554 | txt = b.String() |
| @@ -545,37 +556,29 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context, · 1 unmodified line |
| 545 | item := []byte(`{"id":"","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":""}],"role":"assistant"}`) | 556 | item := []byte(`{"id":"","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":""}],"role":"assistant"}`) |
| 546 | item, _ = sjson.SetBytes(item, "id", fmt.Sprintf("msg_%s_%d", st.ResponseID, i)) | 557 | item, _ = sjson.SetBytes(item, "id", fmt.Sprintf("msg_%s_%d", st.ResponseID, i)) |
| 547 | item, _ = sjson.SetBytes(item, "content.0.text", txt) | 558 | item, _ = sjson.SetBytes(item, "content.0.text", txt) |
| 548 | outputsWrapper, _ = sjson.SetRawBytes(outputsWrapper, "arr.-1", item) | 559 | outputItems = append(outputItems, completedOutputItem{index: st.MsgOutputIx[i], raw: item}) |
| 549 | } | 560 | } |
| 550 | } | 561 | } |
| 551 | if len(st.FuncArgsBuf) > 0 { | 562 | if len(st.FuncArgsBuf) > 0 { |
| 552 | idxs := make([]int, 0, len(st.FuncArgsBuf)) | 563 | for key := range st.FuncArgsBuf { |
| 553 | for i := range st.FuncArgsBuf { | | |
| 554 | idxs = append(idxs, i) | | |
| 555 | } | | |
| 556 | // small-N sort without extra imports | | |
| 557 | for i := 0; i < len(idxs); i++ { | | |
| 558 | for j := i + 1; j < len(idxs); j++ { | | |
| 559 | if idxs[j] < idxs[i] { | | |
| 560 | idxs[i], idxs[j] = idxs[j], idxs[i] | | |
| 561 | } | | |
| 562 | } | | |
| 563 | } | | |
| 564 | for _, i := range idxs { | | |
| 565 | args := "" | 564 | args := "" |
| 566 | if b := st.FuncArgsBuf[i]; b != nil { | 565 | if b := st.FuncArgsBuf[key]; b != nil { |
| 567 | args = b.String() | 566 | args = b.String() |
| 568 | } | 567 | } |
| 569 | callID := st.FuncCallIDs[i] | 568 | callID := st.FuncCallIDs[key] |
| 570 | name := st.FuncNames[i] | 569 | name := st.FuncNames[key] |
| 571 | item := []byte(`{"id":"","type":"function_call","status":"completed","arguments":"","call_id":"","name":""}`) | 570 | item := []byte(`{"id":"","type":"function_call","status":"completed","arguments":"","call_id":"","name":""}`) |
| 572 | item, _ = sjson.SetBytes(item, "id", fmt.Sprintf("fc_%s", callID)) | 571 | item, _ = sjson.SetBytes(item, "id", fmt.Sprintf("fc_%s", callID)) |
| 573 | item, _ = sjson.SetBytes(item, "arguments", args) | 572 | item, _ = sjson.SetBytes(item, "arguments", args) |
| 574 | item, _ = sjson.SetBytes(item, "call_id", callID) | 573 | item, _ = sjson.SetBytes(item, "call_id", callID) |
| 575 | item, _ = sjson.SetBytes(item, "name", name) | 574 | item, _ = sjson.SetBytes(item, "name", name) |
| 576 | outputsWrapper, _ = sjson.SetRawBytes(outputsWrapper, "arr.-1", item) | 575 | outputItems = append(outputItems, completedOutputItem{index: st.FuncOutputIx[key], raw: item}) |
| 577 | } | 576 | } |
| 578 | } | 577 | } |
| | 578 | sort.Slice(outputItems, func(i, j int) bool { return outputItems[i].index < outputItems[j].index }) |
| | 579 | for _, item := range outputItems { |
| | 580 | outputsWrapper, _ = sjson.SetRawBytes(outputsWrapper, "arr.-1", item.raw) |
| | 581 | } |
| 579 | if gjson.GetBytes(outputsWrapper, "arr.#").Int() > 0 { | 582 | if gjson.GetBytes(outputsWrapper, "arr.#").Int() > 0 { |
| 580 | completed, _ = sjson.SetRawBytes(completed, "response.output", []byte(gjson.GetBytes(outputsWrapper, "arr").Raw)) | 583 | completed, _ = sjson.SetRawBytes(completed, "response.output", []byte(gjson.GetBytes(outputsWrapper, "arr").Raw)) |
| 581 | } | 584 | } |