diff --git a/internal/runtime/executor/github_copilot_executor.go b/internal/runtime/executor/github_copilot_executor.go index 147b32cd..ad93f488 100644 --- a/internal/runtime/executor/github_copilot_executor.go +++ b/internal/runtime/executor/github_copilot_executor.go @@ -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 {