mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-21 16:40:22 +00:00
fix(kiro): fix translator format mismatch for OpenAI protocol
Amp-Thread-ID: https://ampcode.com/threads/T-019b092b-f2de-72a1-b428-72511c0de628 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
// Package claude provides translation between Kiro and Claude formats.
|
||||
// Since Kiro uses Claude-compatible format internally, translations are mostly pass-through.
|
||||
// However, SSE events require proper "event: <type>" prefix for Claude clients.
|
||||
// Since Kiro executor generates Claude-compatible SSE format internally (with event: prefix),
|
||||
// translations are pass-through.
|
||||
package claude
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// ConvertClaudeRequestToKiro converts Claude request to Kiro format.
|
||||
@@ -18,52 +15,10 @@ func ConvertClaudeRequestToKiro(modelName string, inputRawJSON []byte, stream bo
|
||||
}
|
||||
|
||||
// ConvertKiroResponseToClaude converts Kiro streaming response to Claude format.
|
||||
// It adds the required "event: <type>" prefix for SSE compliance with Claude clients.
|
||||
// Input format: "data: {\"type\":\"message_start\",...}"
|
||||
// Output format: "event: message_start\ndata: {\"type\":\"message_start\",...}"
|
||||
// Kiro executor already generates complete SSE format with "event:" prefix,
|
||||
// so this is a simple pass-through.
|
||||
func ConvertKiroResponseToClaude(ctx context.Context, model string, originalRequest, request, rawResponse []byte, param *any) []string {
|
||||
raw := string(rawResponse)
|
||||
|
||||
// Handle multiple data blocks (e.g., message_delta + message_stop)
|
||||
lines := strings.Split(raw, "\n\n")
|
||||
var results []string
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract event type from JSON and add "event:" prefix
|
||||
formatted := addEventPrefix(line)
|
||||
if formatted != "" {
|
||||
results = append(results, formatted)
|
||||
}
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return []string{raw}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// addEventPrefix extracts the event type from the data line and adds the event: prefix.
|
||||
// Input: "data: {\"type\":\"message_start\",...}"
|
||||
// Output: "event: message_start\ndata: {\"type\":\"message_start\",...}"
|
||||
func addEventPrefix(dataLine string) string {
|
||||
if !strings.HasPrefix(dataLine, "data: ") {
|
||||
return dataLine
|
||||
}
|
||||
|
||||
jsonPart := strings.TrimPrefix(dataLine, "data: ")
|
||||
eventType := gjson.Get(jsonPart, "type").String()
|
||||
|
||||
if eventType == "" {
|
||||
return dataLine
|
||||
}
|
||||
|
||||
return "event: " + eventType + "\n" + dataLine
|
||||
return []string{string(rawResponse)}
|
||||
}
|
||||
|
||||
// ConvertKiroResponseToClaudeNonStream converts Kiro non-streaming response to Claude format.
|
||||
|
||||
@@ -4,6 +4,7 @@ package chat_completions
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -13,15 +14,58 @@ import (
|
||||
// ConvertKiroResponseToOpenAI converts Kiro streaming response to OpenAI SSE format.
|
||||
// Handles Claude SSE events: content_block_start, content_block_delta, input_json_delta,
|
||||
// content_block_stop, message_delta, and message_stop.
|
||||
// Input may be in SSE format: "event: xxx\ndata: {...}" or raw JSON.
|
||||
func ConvertKiroResponseToOpenAI(ctx context.Context, model string, originalRequest, request, rawResponse []byte, param *any) []string {
|
||||
root := gjson.ParseBytes(rawResponse)
|
||||
raw := string(rawResponse)
|
||||
var results []string
|
||||
|
||||
// Handle SSE format: extract JSON from "data: " lines
|
||||
// Input format: "event: message_start\ndata: {...}"
|
||||
lines := strings.Split(raw, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "data: ") {
|
||||
jsonPart := strings.TrimPrefix(line, "data: ")
|
||||
chunks := convertClaudeEventToOpenAI(jsonPart, model)
|
||||
results = append(results, chunks...)
|
||||
} else if strings.HasPrefix(line, "{") {
|
||||
// Raw JSON (backward compatibility)
|
||||
chunks := convertClaudeEventToOpenAI(line, model)
|
||||
results = append(results, chunks...)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// convertClaudeEventToOpenAI converts a single Claude JSON event to OpenAI format
|
||||
func convertClaudeEventToOpenAI(jsonStr string, model string) []string {
|
||||
root := gjson.Parse(jsonStr)
|
||||
var results []string
|
||||
|
||||
eventType := root.Get("type").String()
|
||||
|
||||
switch eventType {
|
||||
case "message_start":
|
||||
// Initial message event - could emit initial chunk if needed
|
||||
// Initial message event - emit initial chunk with role
|
||||
response := map[string]interface{}{
|
||||
"id": "chatcmpl-" + uuid.New().String()[:24],
|
||||
"object": "chat.completion.chunk",
|
||||
"created": time.Now().Unix(),
|
||||
"model": model,
|
||||
"choices": []map[string]interface{}{
|
||||
{
|
||||
"index": 0,
|
||||
"delta": map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
},
|
||||
"finish_reason": nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
result, _ := json.Marshal(response)
|
||||
results = append(results, string(result))
|
||||
return results
|
||||
|
||||
case "content_block_start":
|
||||
|
||||
Reference in New Issue
Block a user