mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-22 00:50:26 +00:00
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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user