From 86fba083d0c6c216734ebd8888c295bf42ed0a0a Mon Sep 17 00:00:00 2001 From: "Eason(G Ray)" <30045503+Eason0729@users.noreply.github.com> Date: Sat, 7 Jun 2025 14:37:48 +0800 Subject: [PATCH 1/3] add .prettierrc --- .prettierrc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..2c6154e76 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,25 @@ +{ + "semi": false, + "arrowParens": "avoid", + "printWidth": 120, + "trailingComma": "all", + "useTabs": false, + "quoteProps": "preserve", + "overrides": [ + { + "files": [".vscode/*.json"], + "options": { + "parser": "jsonc", + "quoteProps": "preserve", + "singleQuote": false, + "trailingComma": "all" + } + }, + { + "files": ["*.md"], + "options": { + "printWidth": 80 + } + } + ] +} From 67d2fed79a52db5d4070af9de97bf12f87c1fb78 Mon Sep 17 00:00:00 2001 From: "Eason(G Ray)" <30045503+Eason0729@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:05:59 +0800 Subject: [PATCH 2/3] Implement dynamic vision capability enablement #36 --- src/lib/api-config.ts | 6 ++-- .../copilot/create-chat-completions.ts | 30 +++++++++++++++++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/lib/api-config.ts b/src/lib/api-config.ts index 5b63e1a6b..8075145c9 100644 --- a/src/lib/api-config.ts +++ b/src/lib/api-config.ts @@ -15,7 +15,7 @@ const API_VERSION = "2025-04-01" export const copilotBaseUrl = (state: State) => `https://api.${state.accountType}.githubcopilot.com` -export const copilotHeaders = (state: State) => { +export const copilotHeaders = (state: State, vision: boolean = false) => { const headers: Record = { Authorization: `Bearer ${state.copilotToken}`, "content-type": standardHeaders()["content-type"], @@ -29,9 +29,7 @@ export const copilotHeaders = (state: State) => { "x-vscode-user-agent-library-version": "electron-fetch", } - if (state.visionEnabled) { - headers["copilot-vision-request"] = "true" - } + if (vision) headers["copilot-vision-request"] = "true" return headers } diff --git a/src/services/copilot/create-chat-completions.ts b/src/services/copilot/create-chat-completions.ts index e0bf6a49a..7d54d11f0 100644 --- a/src/services/copilot/create-chat-completions.ts +++ b/src/services/copilot/create-chat-completions.ts @@ -9,9 +9,19 @@ export const createChatCompletions = async ( ) => { if (!state.copilotToken) throw new Error("Copilot token not found") + for (const message of payload.messages) { + intoCopilotMessage(message) + } + + const visionEnable = payload.messages.some( + (x) => + typeof x.content !== "string" + && x.content.some((x) => x.type === "image_url"), + ) + const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, { method: "POST", - headers: copilotHeaders(state), + headers: copilotHeaders(state, visionEnable), body: JSON.stringify(payload), }) @@ -25,6 +35,14 @@ export const createChatCompletions = async ( return (await response.json()) as ChatCompletionResponse } +const intoCopilotMessage = (message: Message) => { + if (typeof message.content === "string") return false + + for (const part of message.content) { + if (part.type === "input_image") part.type = "image_url" + } +} + // Streaming types export interface ChatCompletionChunk { @@ -79,7 +97,15 @@ export interface ChatCompletionsPayload { export interface Message { role: "user" | "assistant" | "system" - content: string + content: string | Array } // https://platform.openai.com/docs/api-reference + +export interface ContentPart { + type: "input_image" | "input_text" | "image_url" + text?: string + image_url?: string +} +// https://platform.openai.com/docs/guides/images-vision#giving-a-model-images-as-input +// Note: copilot use "image_url", but openai use "input_image" From 8d457014aea74ba4fff1053d463eb4baa6737e55 Mon Sep 17 00:00:00 2001 From: "Eason(G Ray)" <30045503+Eason0729@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:14:34 +0800 Subject: [PATCH 3/3] Remove vision option and doc --- Dockerfile | 2 +- README.md | 4 ---- src/lib/state.ts | 2 -- src/main.ts | 13 ------------- 4 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9bd82bec1..4c5520b3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,4 +20,4 @@ EXPOSE 4141 ARG GH_TOKEN ENV GH_TOKEN=$GH_TOKEN -CMD bun run dist/main.js start -g $GH_TOKEN --vision +CMD bun run dist/main.js start -g $GH_TOKEN diff --git a/README.md b/README.md index 57ee50098..7a1490c15 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,6 @@ The following command line options are available for the `start` command: | --rate-limit | Rate limit in seconds between requests | none | -r | | --wait | Wait instead of error when rate limit is hit | false | -w | | --github-token | Provide GitHub token directly (must be generated using the `auth` subcommand) | none | -g | -| --vision | Enable vision capabilities | false | none | ### Auth Command Options @@ -116,9 +115,6 @@ npx copilot-api@latest start --rate-limit 30 --wait # Provide GitHub token directly npx copilot-api@latest start --github-token ghp_YOUR_TOKEN_HERE -# Enable vision capabilities -npx copilot-api@latest start --vision - # Run only the auth flow npx copilot-api@latest auth diff --git a/src/lib/state.ts b/src/lib/state.ts index ee5f51f63..78de99753 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -10,7 +10,6 @@ export interface State { manualApprove: boolean rateLimitWait: boolean - visionEnabled: boolean // Rate limiting configuration rateLimitSeconds?: number @@ -21,5 +20,4 @@ export const state: State = { accountType: "individual", manualApprove: false, rateLimitWait: false, - visionEnabled: false, } diff --git a/src/main.ts b/src/main.ts index c15a1afdf..bd47e239a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,7 +20,6 @@ interface RunServerOptions { rateLimit?: number rateLimitWait: boolean githubToken?: string - visionEnabled: boolean } export async function runServer(options: RunServerOptions): Promise { @@ -37,11 +36,6 @@ export async function runServer(options: RunServerOptions): Promise { state.manualApprove = options.manual state.rateLimitSeconds = options.rateLimit state.rateLimitWait = options.rateLimitWait - state.visionEnabled = options.visionEnabled - - if (options.visionEnabled) { - consola.info("Vision capability enabled") - } await ensurePaths() await cacheVSCodeVersion() @@ -111,12 +105,6 @@ const start = defineCommand({ description: "Provide GitHub token directly (must be generated using the `auth` subcommand)", }, - vision: { - type: "boolean", - default: false, - description: "Enable vision capabilities", - required: false, - }, }, run({ args }) { const rateLimitRaw = args["rate-limit"] @@ -134,7 +122,6 @@ const start = defineCommand({ rateLimit, rateLimitWait: Boolean(args.wait), githubToken: args["github-token"], - visionEnabled: args.vision, }) }, })