Skip to content

Commit 6b80ec7

Browse files
authored
Merge pull request #61 from tinyc0der/fix/kiro-tool-results
fix(kiro): Handle tool results correctly in OpenAI format translation
2 parents d3f4783 + c169b32 commit 6b80ec7

File tree

3 files changed

+435
-28
lines changed

3 files changed

+435
-28
lines changed

internal/translator/kiro/common/message_merge.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
// MergeAdjacentMessages merges adjacent messages with the same role.
1111
// This reduces API call complexity and improves compatibility.
1212
// Based on AIClient-2-API implementation.
13+
// NOTE: Tool messages are NOT merged because each has a unique tool_call_id that must be preserved.
1314
func MergeAdjacentMessages(messages []gjson.Result) []gjson.Result {
1415
if len(messages) <= 1 {
1516
return messages
@@ -26,6 +27,12 @@ func MergeAdjacentMessages(messages []gjson.Result) []gjson.Result {
2627
currentRole := msg.Get("role").String()
2728
lastRole := lastMsg.Get("role").String()
2829

30+
// Don't merge tool messages - each has a unique tool_call_id
31+
if currentRole == "tool" || lastRole == "tool" {
32+
merged = append(merged, msg)
33+
continue
34+
}
35+
2936
if currentRole == lastRole {
3037
// Merge content from current message into last message
3138
mergedContent := mergeMessageContent(lastMsg, msg)

internal/translator/kiro/openai/kiro_openai_request.go

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -450,24 +450,10 @@ func processOpenAIMessages(messages gjson.Result, modelID, origin string) ([]Kir
450450
// Merge adjacent messages with the same role
451451
messagesArray := kirocommon.MergeAdjacentMessages(messages.Array())
452452

453-
// Build tool_call_id to name mapping from assistant messages
454-
toolCallIDToName := make(map[string]string)
455-
for _, msg := range messagesArray {
456-
if msg.Get("role").String() == "assistant" {
457-
toolCalls := msg.Get("tool_calls")
458-
if toolCalls.IsArray() {
459-
for _, tc := range toolCalls.Array() {
460-
if tc.Get("type").String() == "function" {
461-
id := tc.Get("id").String()
462-
name := tc.Get("function.name").String()
463-
if id != "" && name != "" {
464-
toolCallIDToName[id] = name
465-
}
466-
}
467-
}
468-
}
469-
}
470-
}
453+
// Track pending tool results that should be attached to the next user message
454+
// This is critical for LiteLLM-translated requests where tool results appear
455+
// as separate "tool" role messages between assistant and user messages
456+
var pendingToolResults []KiroToolResult
471457

472458
for i, msg := range messagesArray {
473459
role := msg.Get("role").String()
@@ -480,6 +466,10 @@ func processOpenAIMessages(messages gjson.Result, modelID, origin string) ([]Kir
480466

481467
case "user":
482468
userMsg, toolResults := buildUserMessageFromOpenAI(msg, modelID, origin)
469+
// Merge any pending tool results from preceding "tool" role messages
470+
toolResults = append(pendingToolResults, toolResults...)
471+
pendingToolResults = nil // Reset pending tool results
472+
483473
if isLastMessage {
484474
currentUserMsg = &userMsg
485475
currentToolResults = toolResults
@@ -505,6 +495,24 @@ func processOpenAIMessages(messages gjson.Result, modelID, origin string) ([]Kir
505495

506496
case "assistant":
507497
assistantMsg := buildAssistantMessageFromOpenAI(msg)
498+
499+
// If there are pending tool results, we need to insert a synthetic user message
500+
// before this assistant message to maintain proper conversation structure
501+
if len(pendingToolResults) > 0 {
502+
syntheticUserMsg := KiroUserInputMessage{
503+
Content: "Tool results provided.",
504+
ModelID: modelID,
505+
Origin: origin,
506+
UserInputMessageContext: &KiroUserInputMessageContext{
507+
ToolResults: pendingToolResults,
508+
},
509+
}
510+
history = append(history, KiroHistoryMessage{
511+
UserInputMessage: &syntheticUserMsg,
512+
})
513+
pendingToolResults = nil
514+
}
515+
508516
if isLastMessage {
509517
history = append(history, KiroHistoryMessage{
510518
AssistantResponseMessage: &assistantMsg,
@@ -524,7 +532,7 @@ func processOpenAIMessages(messages gjson.Result, modelID, origin string) ([]Kir
524532
case "tool":
525533
// Tool messages in OpenAI format provide results for tool_calls
526534
// These are typically followed by user or assistant messages
527-
// Process them and merge into the next user message's tool results
535+
// Collect them as pending and attach to the next user message
528536
toolCallID := msg.Get("tool_call_id").String()
529537
content := msg.Get("content").String()
530538

@@ -534,9 +542,21 @@ func processOpenAIMessages(messages gjson.Result, modelID, origin string) ([]Kir
534542
Content: []KiroTextContent{{Text: content}},
535543
Status: "success",
536544
}
537-
// Tool results should be included in the next user message
538-
// For now, collect them and they'll be handled when we build the current message
539-
currentToolResults = append(currentToolResults, toolResult)
545+
// Collect pending tool results to attach to the next user message
546+
pendingToolResults = append(pendingToolResults, toolResult)
547+
}
548+
}
549+
}
550+
551+
// Handle case where tool results are at the end with no following user message
552+
if len(pendingToolResults) > 0 {
553+
currentToolResults = append(currentToolResults, pendingToolResults...)
554+
// If there's no current user message, create a synthetic one for the tool results
555+
if currentUserMsg == nil {
556+
currentUserMsg = &KiroUserInputMessage{
557+
Content: "Tool results provided.",
558+
ModelID: modelID,
559+
Origin: origin,
540560
}
541561
}
542562
}
@@ -551,9 +571,6 @@ func buildUserMessageFromOpenAI(msg gjson.Result, modelID, origin string) (KiroU
551571
var toolResults []KiroToolResult
552572
var images []KiroImage
553573

554-
// Track seen toolCallIds to deduplicate
555-
seenToolCallIDs := make(map[string]bool)
556-
557574
if content.IsArray() {
558575
for _, part := range content.Array() {
559576
partType := part.Get("type").String()
@@ -589,9 +606,6 @@ func buildUserMessageFromOpenAI(msg gjson.Result, modelID, origin string) (KiroU
589606
contentBuilder.WriteString(content.String())
590607
}
591608

592-
// Check for tool_calls in the message (shouldn't be in user messages, but handle edge cases)
593-
_ = seenToolCallIDs // Used for deduplication if needed
594-
595609
userMsg := KiroUserInputMessage{
596610
Content: contentBuilder.String(),
597611
ModelID: modelID,

0 commit comments

Comments
 (0)