fix: add Copilot-Vision-Request header for vision requests

**Problem:**
GitHub Copilot API returns 400 error "missing required Copilot-Vision-Request
header for vision requests" when requests contain image content blocks, even
though the requests are valid Claude API calls.

**Root Cause:**
The GitHub Copilot executor was not detecting vision content in requests and
did not add the required `Copilot-Vision-Request: true` header.

**Solution:**
- Added `detectVisionContent()` function to check for image_url/image content blocks
- Automatically add `Copilot-Vision-Request: true` header when vision content is detected
- Applied fix to both `Execute()` and `ExecuteStream()` methods

**Testing:**
- Tested with Claude Code IDE requests containing code context screenshots
- Vision requests now succeed instead of failing with 400 errors
- Non-vision requests remain unchanged

Fixes issue where GitHub Copilot executor fails all vision-enabled requests,
causing unnecessary fallback to other providers and 0% utilization.

Co-Authored-By: Claude (claude-sonnet-4.5) <noreply@anthropic.com>
This commit is contained in:
cybit
2026-01-27 15:13:54 +08:00
parent fe8c7a62aa
commit 33ab3a99f0

View File

@@ -17,6 +17,7 @@ import (
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
@@ -134,6 +135,11 @@ func (e *GitHubCopilotExecutor) Execute(ctx context.Context, auth *cliproxyauth.
}
e.applyHeaders(httpReq, apiToken)
// Add Copilot-Vision-Request header if the request contains vision content
if detectVisionContent(body) {
httpReq.Header.Set("Copilot-Vision-Request", "true")
}
var authID, authLabel, authType, authValue string
if auth != nil {
authID = auth.ID
@@ -238,6 +244,11 @@ func (e *GitHubCopilotExecutor) ExecuteStream(ctx context.Context, auth *cliprox
}
e.applyHeaders(httpReq, apiToken)
// Add Copilot-Vision-Request header if the request contains vision content
if detectVisionContent(body) {
httpReq.Header.Set("Copilot-Vision-Request", "true")
}
var authID, authLabel, authType, authValue string
if auth != nil {
authID = auth.ID
@@ -415,6 +426,34 @@ func (e *GitHubCopilotExecutor) applyHeaders(r *http.Request, apiToken string) {
r.Header.Set("X-Request-Id", uuid.NewString())
}
// detectVisionContent checks if the request body contains vision/image content.
// Returns true if the request includes image_url or image type content blocks.
func detectVisionContent(body []byte) bool {
// Parse messages array
messagesResult := gjson.GetBytes(body, "messages")
if !messagesResult.Exists() || !messagesResult.IsArray() {
return false
}
// Check each message for vision content
for _, message := range messagesResult.Array() {
content := message.Get("content")
// If content is an array, check each content block
if content.IsArray() {
for _, block := range content.Array() {
blockType := block.Get("type").String()
// Check for image_url or image type
if blockType == "image_url" || blockType == "image" {
return true
}
}
}
}
return false
}
// normalizeModel is a no-op as GitHub Copilot accepts model names directly.
// Model mapping should be done at the registry level if needed.
func (e *GitHubCopilotExecutor) normalizeModel(_ string, body []byte) []byte {