Merge branch 'pr-454'

# Conflicts:
#	cmd/server/main.go
#	internal/translator/claude/openai/chat-completions/claude_openai_response.go
This commit is contained in:
Luis Pater
2026-03-22 22:52:46 +08:00
87 changed files with 3259 additions and 3117 deletions

View File

@@ -221,13 +221,13 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth.
}
var param any
converted := ""
var converted []byte
if useResponses && from.String() == "claude" {
converted = translateGitHubCopilotResponsesNonStreamToClaude(data)
} else {
converted = sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, &param)
}
resp = cliproxyexecutor.Response{Payload: []byte(converted)}
resp = cliproxyexecutor.Response{Payload: converted}
reporter.ensurePublished(ctx)
return resp, nil
}
@@ -374,14 +374,14 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox
}
}
var chunks []string
var chunks [][]byte
if useResponses && from.String() == "claude" {
chunks = translateGitHubCopilotResponsesStreamToClaude(bytes.Clone(line), &param)
} else {
chunks = sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), &param)
}
for i := range chunks {
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
out <- cliproxyexecutor.StreamChunk{Payload: bytes.Clone(chunks[i])}
}
}
@@ -977,7 +977,7 @@ type githubCopilotResponsesStreamState struct {
ItemIDToTool map[string]*githubCopilotResponsesStreamToolState
}
func translateGitHubCopilotResponsesNonStreamToClaude(data []byte) string {
func translateGitHubCopilotResponsesNonStreamToClaude(data []byte) []byte {
root := gjson.ParseBytes(data)
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", root.Get("id").String())
@@ -1067,10 +1067,10 @@ func translateGitHubCopilotResponsesNonStreamToClaude(data []byte) string {
} else {
out, _ = sjson.Set(out, "stop_reason", "end_turn")
}
return out
return []byte(out)
}
func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []string {
func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) [][]byte {
if *param == nil {
*param = &githubCopilotResponsesStreamState{
TextBlockIndex: -1,
@@ -1092,7 +1092,10 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
}
event := gjson.GetBytes(payload, "type").String()
results := make([]string, 0, 4)
results := make([][]byte, 0, 4)
appendResult := func(chunk string) {
results = append(results, []byte(chunk))
}
ensureMessageStart := func() {
if state.MessageStarted {
return
@@ -1100,7 +1103,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
messageStart := `{"type":"message_start","message":{"id":"","type":"message","role":"assistant","model":"","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}}`
messageStart, _ = sjson.Set(messageStart, "message.id", gjson.GetBytes(payload, "response.id").String())
messageStart, _ = sjson.Set(messageStart, "message.model", gjson.GetBytes(payload, "response.model").String())
results = append(results, "event: message_start\ndata: "+messageStart+"\n\n")
appendResult("event: message_start\ndata: " + messageStart + "\n\n")
state.MessageStarted = true
}
startTextBlockIfNeeded := func() {
@@ -1113,7 +1116,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
}
contentBlockStart := `{"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}`
contentBlockStart, _ = sjson.Set(contentBlockStart, "index", state.TextBlockIndex)
results = append(results, "event: content_block_start\ndata: "+contentBlockStart+"\n\n")
appendResult("event: content_block_start\ndata: " + contentBlockStart + "\n\n")
state.TextBlockStarted = true
}
stopTextBlockIfNeeded := func() {
@@ -1122,7 +1125,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
}
contentBlockStop := `{"type":"content_block_stop","index":0}`
contentBlockStop, _ = sjson.Set(contentBlockStop, "index", state.TextBlockIndex)
results = append(results, "event: content_block_stop\ndata: "+contentBlockStop+"\n\n")
appendResult("event: content_block_stop\ndata: " + contentBlockStop + "\n\n")
state.TextBlockStarted = false
state.TextBlockIndex = -1
}
@@ -1152,7 +1155,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
contentDelta := `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":""}}`
contentDelta, _ = sjson.Set(contentDelta, "index", state.TextBlockIndex)
contentDelta, _ = sjson.Set(contentDelta, "delta.text", delta)
results = append(results, "event: content_block_delta\ndata: "+contentDelta+"\n\n")
appendResult("event: content_block_delta\ndata: " + contentDelta + "\n\n")
}
case "response.reasoning_summary_part.added":
ensureMessageStart()
@@ -1161,7 +1164,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
state.NextContentIndex++
thinkingStart := `{"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":""}}`
thinkingStart, _ = sjson.Set(thinkingStart, "index", state.ReasoningIndex)
results = append(results, "event: content_block_start\ndata: "+thinkingStart+"\n\n")
appendResult("event: content_block_start\ndata: " + thinkingStart + "\n\n")
case "response.reasoning_summary_text.delta":
if state.ReasoningActive {
delta := gjson.GetBytes(payload, "delta").String()
@@ -1169,14 +1172,14 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
thinkingDelta := `{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":""}}`
thinkingDelta, _ = sjson.Set(thinkingDelta, "index", state.ReasoningIndex)
thinkingDelta, _ = sjson.Set(thinkingDelta, "delta.thinking", delta)
results = append(results, "event: content_block_delta\ndata: "+thinkingDelta+"\n\n")
appendResult("event: content_block_delta\ndata: " + thinkingDelta + "\n\n")
}
}
case "response.reasoning_summary_part.done":
if state.ReasoningActive {
thinkingStop := `{"type":"content_block_stop","index":0}`
thinkingStop, _ = sjson.Set(thinkingStop, "index", state.ReasoningIndex)
results = append(results, "event: content_block_stop\ndata: "+thinkingStop+"\n\n")
appendResult("event: content_block_stop\ndata: " + thinkingStop + "\n\n")
state.ReasoningActive = false
}
case "response.output_item.added":
@@ -1204,7 +1207,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
contentBlockStart, _ = sjson.Set(contentBlockStart, "index", tool.Index)
contentBlockStart, _ = sjson.Set(contentBlockStart, "content_block.id", tool.ID)
contentBlockStart, _ = sjson.Set(contentBlockStart, "content_block.name", tool.Name)
results = append(results, "event: content_block_start\ndata: "+contentBlockStart+"\n\n")
appendResult("event: content_block_start\ndata: " + contentBlockStart + "\n\n")
case "response.output_item.delta":
item := gjson.GetBytes(payload, "item")
if item.Get("type").String() != "function_call" {
@@ -1224,7 +1227,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
inputDelta := `{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""}}`
inputDelta, _ = sjson.Set(inputDelta, "index", tool.Index)
inputDelta, _ = sjson.Set(inputDelta, "delta.partial_json", partial)
results = append(results, "event: content_block_delta\ndata: "+inputDelta+"\n\n")
appendResult("event: content_block_delta\ndata: " + inputDelta + "\n\n")
case "response.function_call_arguments.delta":
// Copilot sends tool call arguments via this event type (not response.output_item.delta).
// Data format: {"delta":"...", "item_id":"...", "output_index":N, ...}
@@ -1241,7 +1244,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
inputDelta := `{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""}}`
inputDelta, _ = sjson.Set(inputDelta, "index", tool.Index)
inputDelta, _ = sjson.Set(inputDelta, "delta.partial_json", partial)
results = append(results, "event: content_block_delta\ndata: "+inputDelta+"\n\n")
appendResult("event: content_block_delta\ndata: " + inputDelta + "\n\n")
case "response.output_item.done":
if gjson.GetBytes(payload, "item.type").String() != "function_call" {
break
@@ -1252,7 +1255,7 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
}
contentBlockStop := `{"type":"content_block_stop","index":0}`
contentBlockStop, _ = sjson.Set(contentBlockStop, "index", tool.Index)
results = append(results, "event: content_block_stop\ndata: "+contentBlockStop+"\n\n")
appendResult("event: content_block_stop\ndata: " + contentBlockStop + "\n\n")
case "response.completed":
ensureMessageStart()
stopTextBlockIfNeeded()
@@ -1276,8 +1279,8 @@ func translateGitHubCopilotResponsesStreamToClaude(line []byte, param *any) []st
if cachedTokens > 0 {
messageDelta, _ = sjson.Set(messageDelta, "usage.cache_read_input_tokens", cachedTokens)
}
results = append(results, "event: message_delta\ndata: "+messageDelta+"\n\n")
results = append(results, "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n")
appendResult("event: message_delta\ndata: " + messageDelta + "\n\n")
appendResult("event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n")
state.MessageStopSent = true
}
}