| |
| |
| |
| |
| |
| |
| package claude |
|
|
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "strings" |
|
|
| "github.com/tidwall/gjson" |
| "github.com/tidwall/sjson" |
| ) |
|
|
| var ( |
| dataTag = []byte("data:") |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string { |
| if *param == nil { |
| hasToolCall := false |
| *param = &hasToolCall |
| } |
|
|
| |
| if !bytes.HasPrefix(rawJSON, dataTag) { |
| return []string{} |
| } |
| rawJSON = bytes.TrimSpace(rawJSON[5:]) |
|
|
| output := "" |
| rootResult := gjson.ParseBytes(rawJSON) |
| typeResult := rootResult.Get("type") |
| typeStr := typeResult.String() |
| template := "" |
| if typeStr == "response.created" { |
| template = `{"type":"message_start","message":{"id":"","type":"message","role":"assistant","model":"claude-opus-4-1-20250805","stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"content":[],"stop_reason":null}}` |
| template, _ = sjson.Set(template, "message.model", rootResult.Get("response.model").String()) |
| template, _ = sjson.Set(template, "message.id", rootResult.Get("response.id").String()) |
|
|
| output = "event: message_start\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } else if typeStr == "response.reasoning_summary_part.added" { |
| template = `{"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":""}}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
|
|
| output = "event: content_block_start\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } else if typeStr == "response.reasoning_summary_text.delta" { |
| template = `{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":""}}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
| template, _ = sjson.Set(template, "delta.thinking", rootResult.Get("delta").String()) |
|
|
| output = "event: content_block_delta\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } else if typeStr == "response.reasoning_summary_part.done" { |
| template = `{"type":"content_block_stop","index":0}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
|
|
| output = "event: content_block_stop\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } else if typeStr == "response.content_part.added" { |
| template = `{"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
|
|
| output = "event: content_block_start\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } else if typeStr == "response.output_text.delta" { |
| template = `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":""}}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
| template, _ = sjson.Set(template, "delta.text", rootResult.Get("delta").String()) |
|
|
| output = "event: content_block_delta\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } else if typeStr == "response.content_part.done" { |
| template = `{"type":"content_block_stop","index":0}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
|
|
| output = "event: content_block_stop\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } else if typeStr == "response.completed" { |
| template = `{"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}` |
| p := (*param).(*bool) |
| if *p { |
| template, _ = sjson.Set(template, "delta.stop_reason", "tool_use") |
| } else { |
| template, _ = sjson.Set(template, "delta.stop_reason", "end_turn") |
| } |
| template, _ = sjson.Set(template, "usage.input_tokens", rootResult.Get("response.usage.input_tokens").Int()) |
| template, _ = sjson.Set(template, "usage.output_tokens", rootResult.Get("response.usage.output_tokens").Int()) |
|
|
| output = "event: message_delta\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| output += "event: message_stop\n" |
| output += `data: {"type":"message_stop"}` |
| output += "\n\n" |
| } else if typeStr == "response.output_item.added" { |
| itemResult := rootResult.Get("item") |
| itemType := itemResult.Get("type").String() |
| if itemType == "function_call" { |
| p := true |
| *param = &p |
| template = `{"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"","name":"","input":{}}}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
| template, _ = sjson.Set(template, "content_block.id", itemResult.Get("call_id").String()) |
| { |
| |
| name := itemResult.Get("name").String() |
| rev := buildReverseMapFromClaudeOriginalShortToOriginal(originalRequestRawJSON) |
| if orig, ok := rev[name]; ok { |
| name = orig |
| } |
| template, _ = sjson.Set(template, "content_block.name", name) |
| } |
|
|
| output = "event: content_block_start\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
|
|
| template = `{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""}}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
|
|
| output += "event: content_block_delta\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } |
| } else if typeStr == "response.output_item.done" { |
| itemResult := rootResult.Get("item") |
| itemType := itemResult.Get("type").String() |
| if itemType == "function_call" { |
| template = `{"type":"content_block_stop","index":0}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
|
|
| output = "event: content_block_stop\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } |
| } else if typeStr == "response.function_call_arguments.delta" { |
| template = `{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""}}` |
| template, _ = sjson.Set(template, "index", rootResult.Get("output_index").Int()) |
| template, _ = sjson.Set(template, "delta.partial_json", rootResult.Get("delta").String()) |
|
|
| output += "event: content_block_delta\n" |
| output += fmt.Sprintf("data: %s\n\n", template) |
| } |
|
|
| return []string{output} |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func ConvertCodexResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, _ []byte, rawJSON []byte, _ *any) string { |
| revNames := buildReverseMapFromClaudeOriginalShortToOriginal(originalRequestRawJSON) |
|
|
| rootResult := gjson.ParseBytes(rawJSON) |
| if rootResult.Get("type").String() != "response.completed" { |
| return "" |
| } |
|
|
| responseData := rootResult.Get("response") |
| if !responseData.Exists() { |
| return "" |
| } |
|
|
| out := `{"id":"","type":"message","role":"assistant","model":"","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}` |
| out, _ = sjson.Set(out, "id", responseData.Get("id").String()) |
| out, _ = sjson.Set(out, "model", responseData.Get("model").String()) |
| out, _ = sjson.Set(out, "usage.input_tokens", responseData.Get("usage.input_tokens").Int()) |
| out, _ = sjson.Set(out, "usage.output_tokens", responseData.Get("usage.output_tokens").Int()) |
|
|
| hasToolCall := false |
|
|
| if output := responseData.Get("output"); output.Exists() && output.IsArray() { |
| output.ForEach(func(_, item gjson.Result) bool { |
| switch item.Get("type").String() { |
| case "reasoning": |
| thinkingBuilder := strings.Builder{} |
| if summary := item.Get("summary"); summary.Exists() { |
| if summary.IsArray() { |
| summary.ForEach(func(_, part gjson.Result) bool { |
| if txt := part.Get("text"); txt.Exists() { |
| thinkingBuilder.WriteString(txt.String()) |
| } else { |
| thinkingBuilder.WriteString(part.String()) |
| } |
| return true |
| }) |
| } else { |
| thinkingBuilder.WriteString(summary.String()) |
| } |
| } |
| if thinkingBuilder.Len() == 0 { |
| if content := item.Get("content"); content.Exists() { |
| if content.IsArray() { |
| content.ForEach(func(_, part gjson.Result) bool { |
| if txt := part.Get("text"); txt.Exists() { |
| thinkingBuilder.WriteString(txt.String()) |
| } else { |
| thinkingBuilder.WriteString(part.String()) |
| } |
| return true |
| }) |
| } else { |
| thinkingBuilder.WriteString(content.String()) |
| } |
| } |
| } |
| if thinkingBuilder.Len() > 0 { |
| block := `{"type":"thinking","thinking":""}` |
| block, _ = sjson.Set(block, "thinking", thinkingBuilder.String()) |
| out, _ = sjson.SetRaw(out, "content.-1", block) |
| } |
| case "message": |
| if content := item.Get("content"); content.Exists() { |
| if content.IsArray() { |
| content.ForEach(func(_, part gjson.Result) bool { |
| if part.Get("type").String() == "output_text" { |
| text := part.Get("text").String() |
| if text != "" { |
| block := `{"type":"text","text":""}` |
| block, _ = sjson.Set(block, "text", text) |
| out, _ = sjson.SetRaw(out, "content.-1", block) |
| } |
| } |
| return true |
| }) |
| } else { |
| text := content.String() |
| if text != "" { |
| block := `{"type":"text","text":""}` |
| block, _ = sjson.Set(block, "text", text) |
| out, _ = sjson.SetRaw(out, "content.-1", block) |
| } |
| } |
| } |
| case "function_call": |
| hasToolCall = true |
| name := item.Get("name").String() |
| if original, ok := revNames[name]; ok { |
| name = original |
| } |
|
|
| toolBlock := `{"type":"tool_use","id":"","name":"","input":{}}` |
| toolBlock, _ = sjson.Set(toolBlock, "id", item.Get("call_id").String()) |
| toolBlock, _ = sjson.Set(toolBlock, "name", name) |
| inputRaw := "{}" |
| if argsStr := item.Get("arguments").String(); argsStr != "" && gjson.Valid(argsStr) { |
| argsJSON := gjson.Parse(argsStr) |
| if argsJSON.IsObject() { |
| inputRaw = argsJSON.Raw |
| } |
| } |
| toolBlock, _ = sjson.SetRaw(toolBlock, "input", inputRaw) |
| out, _ = sjson.SetRaw(out, "content.-1", toolBlock) |
| } |
| return true |
| }) |
| } |
|
|
| if stopReason := responseData.Get("stop_reason"); stopReason.Exists() && stopReason.String() != "" { |
| out, _ = sjson.Set(out, "stop_reason", stopReason.String()) |
| } else if hasToolCall { |
| out, _ = sjson.Set(out, "stop_reason", "tool_use") |
| } else { |
| out, _ = sjson.Set(out, "stop_reason", "end_turn") |
| } |
|
|
| if stopSequence := responseData.Get("stop_sequence"); stopSequence.Exists() && stopSequence.String() != "" { |
| out, _ = sjson.SetRaw(out, "stop_sequence", stopSequence.Raw) |
| } |
|
|
| if responseData.Get("usage.input_tokens").Exists() || responseData.Get("usage.output_tokens").Exists() { |
| out, _ = sjson.Set(out, "usage.input_tokens", responseData.Get("usage.input_tokens").Int()) |
| out, _ = sjson.Set(out, "usage.output_tokens", responseData.Get("usage.output_tokens").Int()) |
| } |
|
|
| return out |
| } |
|
|
| |
| func buildReverseMapFromClaudeOriginalShortToOriginal(original []byte) map[string]string { |
| tools := gjson.GetBytes(original, "tools") |
| rev := map[string]string{} |
| if !tools.IsArray() { |
| return rev |
| } |
| var names []string |
| arr := tools.Array() |
| for i := 0; i < len(arr); i++ { |
| n := arr[i].Get("name").String() |
| if n != "" { |
| names = append(names, n) |
| } |
| } |
| if len(names) > 0 { |
| m := buildShortNameMap(names) |
| for orig, short := range m { |
| rev[short] = orig |
| } |
| } |
| return rev |
| } |
|
|
| func ClaudeTokenCount(ctx context.Context, count int64) string { |
| return fmt.Sprintf(`{"input_tokens":%d}`, count) |
| } |
|
|