fix(claude): prevent final events when no content streamed
Unified · Split
| @@ -35,6 +35,7 @@ type Params struct { · 34 unmodified lines | |||
| 35 | TotalTokenCount int64 // Cached total token count from usage metadata | 35 | TotalTokenCount int64 // Cached total token count from usage metadata |
| 36 | HasSentFinalEvents bool // Indicates if final content/message events have been sent | 36 | HasSentFinalEvents bool // Indicates if final content/message events have been sent |
| 37 | HasToolUse bool // Indicates if tool use was observed in the stream | 37 | HasToolUse bool // Indicates if tool use was observed in the stream |
| 38 | HasContent bool // Tracks whether any content (text, thinking, or tool use) has been output | ||
| 38 | } | 39 | } |
| 39 | 40 | ||
| 40 | // toolUseIDCounter provides a process-wide unique counter for tool use identifiers. | 41 | // toolUseIDCounter provides a process-wide unique counter for tool use identifiers. |
| @@ -69,11 +70,14 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq · 28 unmodified lines | |||
| 69 | 70 | ||
| 70 | if bytes.Equal(rawJSON, []byte("[DONE]")) { | 71 | if bytes.Equal(rawJSON, []byte("[DONE]")) { |
| 71 | output := "" | 72 | output := "" |
| 72 | appendFinalEvents(params, &output, true) | 73 | // Only send final events if we have actually output content |
| 73 | 74 | if params.HasContent { | |
| 74 | return []string{ | 75 | appendFinalEvents(params, &output, true) |
| 75 | output + "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n\n", | 76 | return []string{ |
| 77 | output + "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n\n", | ||
| 78 | } | ||
| 76 | } | 79 | } |
| 80 | return []string{} | ||
| 77 | } | 81 | } |
| 78 | 82 | ||
| 79 | output := "" | 83 | output := "" |
| @@ -119,10 +123,12 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq · 39 unmodified lines | |||
| 119 | output = output + "event: content_block_delta\n" | 123 | output = output + "event: content_block_delta\n" |
| 120 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"signature_delta","signature":""}}`, params.ResponseIndex), "delta.signature", thoughtSignature.String()) | 124 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"signature_delta","signature":""}}`, params.ResponseIndex), "delta.signature", thoughtSignature.String()) |
| 121 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 125 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 126 | params.HasContent = true | ||
| 122 | } else if params.ResponseType == 2 { // Continue existing thinking block if already in thinking state | 127 | } else if params.ResponseType == 2 { // Continue existing thinking block if already in thinking state |
| 123 | output = output + "event: content_block_delta\n" | 128 | output = output + "event: content_block_delta\n" |
| 124 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, params.ResponseIndex), "delta.thinking", partTextResult.String()) | 129 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, params.ResponseIndex), "delta.thinking", partTextResult.String()) |
| 125 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 130 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 131 | params.HasContent = true | ||
| 126 | } else { | 132 | } else { |
| 127 | // Transition from another state to thinking | 133 | // Transition from another state to thinking |
| 128 | // First, close any existing content block | 134 | // First, close any existing content block |
| @@ -146,6 +152,7 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq · 17 unmodified lines | |||
| 146 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, params.ResponseIndex), "delta.thinking", partTextResult.String()) | 152 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, params.ResponseIndex), "delta.thinking", partTextResult.String()) |
| 147 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 153 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 148 | params.ResponseType = 2 // Set state to thinking | 154 | params.ResponseType = 2 // Set state to thinking |
| 155 | params.HasContent = true | ||
| 149 | } | 156 | } |
| 150 | } else { | 157 | } else { |
| 151 | finishReasonResult := gjson.GetBytes(rawJSON, "response.candidates.0.finishReason") | 158 | finishReasonResult := gjson.GetBytes(rawJSON, "response.candidates.0.finishReason") |
| @@ -156,6 +163,7 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq · 4 unmodified lines | |||
| 156 | output = output + "event: content_block_delta\n" | 163 | output = output + "event: content_block_delta\n" |
| 157 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, params.ResponseIndex), "delta.text", partTextResult.String()) | 164 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, params.ResponseIndex), "delta.text", partTextResult.String()) |
| 158 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 165 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 166 | params.HasContent = true | ||
| 159 | } else { | 167 | } else { |
| 160 | // Transition from another state to text content | 168 | // Transition from another state to text content |
| 161 | // First, close any existing content block | 169 | // First, close any existing content block |
| @@ -179,6 +187,7 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq · 17 unmodified lines | |||
| 179 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, params.ResponseIndex), "delta.text", partTextResult.String()) | 187 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, params.ResponseIndex), "delta.text", partTextResult.String()) |
| 180 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 188 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 181 | params.ResponseType = 1 // Set state to content | 189 | params.ResponseType = 1 // Set state to content |
| 190 | params.HasContent = true | ||
| 182 | } | 191 | } |
| 183 | } | 192 | } |
| 184 | } | 193 | } |
| @@ -230,6 +239,7 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq · 45 unmodified lines | |||
| 230 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 239 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 231 | } | 240 | } |
| 232 | params.ResponseType = 3 | 241 | params.ResponseType = 3 |
| 242 | params.HasContent = true | ||
| 233 | } | 243 | } |
| 234 | } | 244 | } |
| 235 | } | 245 | } |
| @@ -269,6 +279,11 @@ func appendFinalEvents(params *Params, output *string, force bool) { · 33 unmodified lines | |||
| 269 | return | 279 | return |
| 270 | } | 280 | } |
| 271 | 281 | ||
| 282 | // Only send final events if we have actually output content | ||
| 283 | if !params.HasContent { | ||
| 284 | return | ||
| 285 | } | ||
| 286 | |||
| 272 | if params.ResponseType != 0 { | 287 | if params.ResponseType != 0 { |
| 273 | *output = *output + "event: content_block_stop\n" | 288 | *output = *output + "event: content_block_stop\n" |
| 274 | *output = *output + fmt.Sprintf(`data: {"type":"content_block_stop","index":%d}`, params.ResponseIndex) | 289 | *output = *output + fmt.Sprintf(`data: {"type":"content_block_stop","index":%d}`, params.ResponseIndex) |
| @@ -26,6 +26,7 @@ type Params struct { · 25 unmodified lines | |||
| 26 | HasFirstResponse bool // Indicates if the initial message_start event has been sent | 26 | HasFirstResponse bool // Indicates if the initial message_start event has been sent |
| 27 | ResponseType int // Current response type: 0=none, 1=content, 2=thinking, 3=function | 27 | ResponseType int // Current response type: 0=none, 1=content, 2=thinking, 3=function |
| 28 | ResponseIndex int // Index counter for content blocks in the streaming response | 28 | ResponseIndex int // Index counter for content blocks in the streaming response |
| 29 | HasContent bool // Tracks whether any content (text, thinking, or tool use) has been output | ||
| 29 | } | 30 | } |
| 30 | 31 | ||
| 31 | // toolUseIDCounter provides a process-wide unique counter for tool use identifiers. | 32 | // toolUseIDCounter provides a process-wide unique counter for tool use identifiers. |
| @@ -57,9 +58,13 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque · 25 unmodified lines | |||
| 57 | } | 58 | } |
| 58 | 59 | ||
| 59 | if bytes.Equal(rawJSON, []byte("[DONE]")) { | 60 | if bytes.Equal(rawJSON, []byte("[DONE]")) { |
| 60 | return []string{ | 61 | // Only send message_stop if we have actually output content |
| 61 | "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n\n", | 62 | if (*param).(*Params).HasContent { |
| 63 | return []string{ | ||
| 64 | "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n\n", | ||
| 65 | } | ||
| 62 | } | 66 | } |
| 67 | return []string{} | ||
| 63 | } | 68 | } |
| 64 | 69 | ||
| 65 | // Track whether tools are being used in this response chunk | 70 | // Track whether tools are being used in this response chunk |
| @@ -108,6 +113,7 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque · 42 unmodified lines | |||
| 108 | output = output + "event: content_block_delta\n" | 113 | output = output + "event: content_block_delta\n" |
| 109 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, (*param).(*Params).ResponseIndex), "delta.thinking", partTextResult.String()) | 114 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, (*param).(*Params).ResponseIndex), "delta.thinking", partTextResult.String()) |
| 110 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 115 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 116 | (*param).(*Params).HasContent = true | ||
| 111 | } else { | 117 | } else { |
| 112 | // Transition from another state to thinking | 118 | // Transition from another state to thinking |
| 113 | // First, close any existing content block | 119 | // First, close any existing content block |
| @@ -131,6 +137,7 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque · 17 unmodified lines | |||
| 131 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, (*param).(*Params).ResponseIndex), "delta.thinking", partTextResult.String()) | 137 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, (*param).(*Params).ResponseIndex), "delta.thinking", partTextResult.String()) |
| 132 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 138 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 133 | (*param).(*Params).ResponseType = 2 // Set state to thinking | 139 | (*param).(*Params).ResponseType = 2 // Set state to thinking |
| 140 | (*param).(*Params).HasContent = true | ||
| 134 | } | 141 | } |
| 135 | } else { | 142 | } else { |
| 136 | // Process regular text content (user-visible output) | 143 | // Process regular text content (user-visible output) |
| @@ -139,6 +146,7 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque · 2 unmodified lines | |||
| 139 | output = output + "event: content_block_delta\n" | 146 | output = output + "event: content_block_delta\n" |
| 140 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, (*param).(*Params).ResponseIndex), "delta.text", partTextResult.String()) | 147 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, (*param).(*Params).ResponseIndex), "delta.text", partTextResult.String()) |
| 141 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 148 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 149 | (*param).(*Params).HasContent = true | ||
| 142 | } else { | 150 | } else { |
| 143 | // Transition from another state to text content | 151 | // Transition from another state to text content |
| 144 | // First, close any existing content block | 152 | // First, close any existing content block |
| @@ -162,6 +170,7 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque · 17 unmodified lines | |||
| 162 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, (*param).(*Params).ResponseIndex), "delta.text", partTextResult.String()) | 170 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, (*param).(*Params).ResponseIndex), "delta.text", partTextResult.String()) |
| 163 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 171 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 164 | (*param).(*Params).ResponseType = 1 // Set state to content | 172 | (*param).(*Params).ResponseType = 1 // Set state to content |
| 173 | (*param).(*Params).HasContent = true | ||
| 165 | } | 174 | } |
| 166 | } | 175 | } |
| 167 | } else if functionCallResult.Exists() { | 176 | } else if functionCallResult.Exists() { |
| @@ -211,6 +220,7 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque · 43 unmodified lines | |||
| 211 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 220 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 212 | } | 221 | } |
| 213 | (*param).(*Params).ResponseType = 3 | 222 | (*param).(*Params).ResponseType = 3 |
| 223 | (*param).(*Params).HasContent = true | ||
| 214 | } | 224 | } |
| 215 | } | 225 | } |
| 216 | } | 226 | } |
| @@ -219,28 +229,31 @@ func ConvertGeminiCLIResponseToClaude(_ context.Context, _ string, originalReque · 2 unmodified lines | |||
| 219 | // Process usage metadata and finish reason when present in the response | 229 | // Process usage metadata and finish reason when present in the response |
| 220 | if usageResult.Exists() && bytes.Contains(rawJSON, []byte(`"finishReason"`)) { | 230 | if usageResult.Exists() && bytes.Contains(rawJSON, []byte(`"finishReason"`)) { |
| 221 | if candidatesTokenCountResult := usageResult.Get("candidatesTokenCount"); candidatesTokenCountResult.Exists() { | 231 | if candidatesTokenCountResult := usageResult.Get("candidatesTokenCount"); candidatesTokenCountResult.Exists() { |
| 222 | // Close the final content block | 232 | // Only send final events if we have actually output content |
| 223 | output = output + "event: content_block_stop\n" | 233 | if (*param).(*Params).HasContent { |
| 224 | output = output + fmt.Sprintf(`data: {"type":"content_block_stop","index":%d}`, (*param).(*Params).ResponseIndex) | 234 | // Close the final content block |
| 225 | output = output + "\n\n\n" | 235 | output = output + "event: content_block_stop\n" |
| 226 | 236 | output = output + fmt.Sprintf(`data: {"type":"content_block_stop","index":%d}`, (*param).(*Params).ResponseIndex) | |
| 227 | // Send the final message delta with usage information and stop reason | 237 | output = output + "\n\n\n" |
| 228 | output = output + "event: message_delta\n" | 238 | |
| 229 | output = output + `data: ` | 239 | // Send the final message delta with usage information and stop reason |
| 230 | 240 | output = output + "event: message_delta\n" | |
| 231 | // Create the message delta template with appropriate stop reason | 241 | output = output + `data: ` |
| 232 | template := `{"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` | 242 | |
| 233 | // Set tool_use stop reason if tools were used in this response | 243 | // Create the message delta template with appropriate stop reason |
| 234 | if usedTool { | 244 | template := `{"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` |
| 235 | template = `{"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` | 245 | // Set tool_use stop reason if tools were used in this response |
| 236 | } | 246 | if usedTool { |
| 247 | template = `{"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` | ||
| 248 | } | ||
| 237 | 249 | ||
| 238 | // Include thinking tokens in output token count if present | 250 | // Include thinking tokens in output token count if present |
| 239 | thoughtsTokenCount := usageResult.Get("thoughtsTokenCount").Int() | 251 | thoughtsTokenCount := usageResult.Get("thoughtsTokenCount").Int() |
| 240 | template, _ = sjson.Set(template, "usage.output_tokens", candidatesTokenCountResult.Int()+thoughtsTokenCount) | 252 | template, _ = sjson.Set(template, "usage.output_tokens", candidatesTokenCountResult.Int()+thoughtsTokenCount) |
| 241 | template, _ = sjson.Set(template, "usage.input_tokens", usageResult.Get("promptTokenCount").Int()) | 253 | template, _ = sjson.Set(template, "usage.input_tokens", usageResult.Get("promptTokenCount").Int()) |
| 242 | 254 | ||
| 243 | output = output + template + "\n\n\n" | 255 | output = output + template + "\n\n\n" |
| 256 | } | ||
| 244 | } | 257 | } |
| 245 | } | 258 | } |
| 246 | 259 | ||
| @@ -25,6 +25,7 @@ type Params struct { · 24 unmodified lines | |||
| 25 | HasFirstResponse bool | 25 | HasFirstResponse bool |
| 26 | ResponseType int | 26 | ResponseType int |
| 27 | ResponseIndex int | 27 | ResponseIndex int |
| 28 | HasContent bool // Tracks whether any content (text, thinking, or tool use) has been output | ||
| 28 | } | 29 | } |
| 29 | 30 | ||
| 30 | // toolUseIDCounter provides a process-wide unique counter for tool use identifiers. | 31 | // toolUseIDCounter provides a process-wide unique counter for tool use identifiers. |
| @@ -57,9 +58,13 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR · 26 unmodified lines | |||
| 57 | } | 58 | } |
| 58 | 59 | ||
| 59 | if bytes.Equal(rawJSON, []byte("[DONE]")) { | 60 | if bytes.Equal(rawJSON, []byte("[DONE]")) { |
| 60 | return []string{ | 61 | // Only send message_stop if we have actually output content |
| 61 | "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n\n", | 62 | if (*param).(*Params).HasContent { |
| 63 | return []string{ | ||
| 64 | "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n\n", | ||
| 65 | } | ||
| 62 | } | 66 | } |
| 67 | return []string{} | ||
| 63 | } | 68 | } |
| 64 | 69 | ||
| 65 | // Track whether tools are being used in this response chunk | 70 | // Track whether tools are being used in this response chunk |
| @@ -108,6 +113,7 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR · 42 unmodified lines | |||
| 108 | output = output + "event: content_block_delta\n" | 113 | output = output + "event: content_block_delta\n" |
| 109 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, (*param).(*Params).ResponseIndex), "delta.thinking", partTextResult.String()) | 114 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, (*param).(*Params).ResponseIndex), "delta.thinking", partTextResult.String()) |
| 110 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 115 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 116 | (*param).(*Params).HasContent = true | ||
| 111 | } else { | 117 | } else { |
| 112 | // Transition from another state to thinking | 118 | // Transition from another state to thinking |
| 113 | // First, close any existing content block | 119 | // First, close any existing content block |
| @@ -131,6 +137,7 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR · 17 unmodified lines | |||
| 131 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, (*param).(*Params).ResponseIndex), "delta.thinking", partTextResult.String()) | 137 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"thinking_delta","thinking":""}}`, (*param).(*Params).ResponseIndex), "delta.thinking", partTextResult.String()) |
| 132 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 138 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 133 | (*param).(*Params).ResponseType = 2 // Set state to thinking | 139 | (*param).(*Params).ResponseType = 2 // Set state to thinking |
| 140 | (*param).(*Params).HasContent = true | ||
| 134 | } | 141 | } |
| 135 | } else { | 142 | } else { |
| 136 | // Process regular text content (user-visible output) | 143 | // Process regular text content (user-visible output) |
| @@ -139,6 +146,7 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR · 2 unmodified lines | |||
| 139 | output = output + "event: content_block_delta\n" | 146 | output = output + "event: content_block_delta\n" |
| 140 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, (*param).(*Params).ResponseIndex), "delta.text", partTextResult.String()) | 147 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, (*param).(*Params).ResponseIndex), "delta.text", partTextResult.String()) |
| 141 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 148 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 149 | (*param).(*Params).HasContent = true | ||
| 142 | } else { | 150 | } else { |
| 143 | // Transition from another state to text content | 151 | // Transition from another state to text content |
| 144 | // First, close any existing content block | 152 | // First, close any existing content block |
| @@ -162,6 +170,7 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR · 17 unmodified lines | |||
| 162 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, (*param).(*Params).ResponseIndex), "delta.text", partTextResult.String()) | 170 | data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"text_delta","text":""}}`, (*param).(*Params).ResponseIndex), "delta.text", partTextResult.String()) |
| 163 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 171 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 164 | (*param).(*Params).ResponseType = 1 // Set state to content | 172 | (*param).(*Params).ResponseType = 1 // Set state to content |
| 173 | (*param).(*Params).HasContent = true | ||
| 165 | } | 174 | } |
| 166 | } | 175 | } |
| 167 | } else if functionCallResult.Exists() { | 176 | } else if functionCallResult.Exists() { |
| @@ -211,6 +220,7 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR · 43 unmodified lines | |||
| 211 | output = output + fmt.Sprintf("data: %s\n\n\n", data) | 220 | output = output + fmt.Sprintf("data: %s\n\n\n", data) |
| 212 | } | 221 | } |
| 213 | (*param).(*Params).ResponseType = 3 | 222 | (*param).(*Params).ResponseType = 3 |
| 223 | (*param).(*Params).HasContent = true | ||
| 214 | } | 224 | } |
| 215 | } | 225 | } |
| 216 | } | 226 | } |
| @@ -218,23 +228,26 @@ func ConvertGeminiResponseToClaude(_ context.Context, _ string, originalRequestR · 1 unmodified line | |||
| 218 | usageResult := gjson.GetBytes(rawJSON, "usageMetadata") | 228 | usageResult := gjson.GetBytes(rawJSON, "usageMetadata") |
| 219 | if usageResult.Exists() && bytes.Contains(rawJSON, []byte(`"finishReason"`)) { | 229 | if usageResult.Exists() && bytes.Contains(rawJSON, []byte(`"finishReason"`)) { |
| 220 | if candidatesTokenCountResult := usageResult.Get("candidatesTokenCount"); candidatesTokenCountResult.Exists() { | 230 | if candidatesTokenCountResult := usageResult.Get("candidatesTokenCount"); candidatesTokenCountResult.Exists() { |
| 221 | output = output + "event: content_block_stop\n" | 231 | // Only send final events if we have actually output content |
| 222 | output = output + fmt.Sprintf(`data: {"type":"content_block_stop","index":%d}`, (*param).(*Params).ResponseIndex) | 232 | if (*param).(*Params).HasContent { |
| 223 | output = output + "\n\n\n" | 233 | output = output + "event: content_block_stop\n" |
| 234 | output = output + fmt.Sprintf(`data: {"type":"content_block_stop","index":%d}`, (*param).(*Params).ResponseIndex) | ||
| 235 | output = output + "\n\n\n" | ||
| 236 | |||
| 237 | output = output + "event: message_delta\n" | ||
| 238 | output = output + `data: ` | ||
| 239 | |||
| 240 | template := `{"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` | ||
| 241 | if usedTool { | ||
| 242 | template = `{"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` | ||
| 243 | } | ||
| 224 | 244 | ||
| 225 | output = output + "event: message_delta\n" | 245 | thoughtsTokenCount := usageResult.Get("thoughtsTokenCount").Int() |
| 226 | output = output + `data: ` | 246 | template, _ = sjson.Set(template, "usage.output_tokens", candidatesTokenCountResult.Int()+thoughtsTokenCount) |
| 247 | template, _ = sjson.Set(template, "usage.input_tokens", usageResult.Get("promptTokenCount").Int()) | ||
| 227 | 248 | ||
| 228 | template := `{"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` | 249 | output = output + template + "\n\n\n" |
| 229 | if usedTool { | ||
| 230 | template = `{"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` | ||
| 231 | } | 250 | } |
| 232 | |||
| 233 | thoughtsTokenCount := usageResult.Get("thoughtsTokenCount").Int() | ||
| 234 | template, _ = sjson.Set(template, "usage.output_tokens", candidatesTokenCountResult.Int()+thoughtsTokenCount) | ||
| 235 | template, _ = sjson.Set(template, "usage.input_tokens", usageResult.Get("promptTokenCount").Int()) | ||
| 236 | |||
| 237 | output = output + template + "\n\n\n" | ||
| 238 | } | 251 | } |
| 239 | } | 252 | } |
| 240 | 253 | ||