Skip to content

Commit 4d13325

Browse files
authored
Merge pull request ericc-ch#40 from Eason0729/master
Implement dynamic vision capability enablement
2 parents 156b7e9 + 8d45701 commit 4d13325

7 files changed

Lines changed: 56 additions & 26 deletions

File tree

.prettierrc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"semi": false,
3+
"arrowParens": "avoid",
4+
"printWidth": 120,
5+
"trailingComma": "all",
6+
"useTabs": false,
7+
"quoteProps": "preserve",
8+
"overrides": [
9+
{
10+
"files": [".vscode/*.json"],
11+
"options": {
12+
"parser": "jsonc",
13+
"quoteProps": "preserve",
14+
"singleQuote": false,
15+
"trailingComma": "all"
16+
}
17+
},
18+
{
19+
"files": ["*.md"],
20+
"options": {
21+
"printWidth": 80
22+
}
23+
}
24+
]
25+
}

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ EXPOSE 4141
2020
ARG GH_TOKEN
2121
ENV GH_TOKEN=$GH_TOKEN
2222

23-
CMD bun run dist/main.js start -g $GH_TOKEN --vision
23+
CMD bun run dist/main.js start -g $GH_TOKEN

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ The following command line options are available for the `start` command:
8282
| --rate-limit | Rate limit in seconds between requests | none | -r |
8383
| --wait | Wait instead of error when rate limit is hit | false | -w |
8484
| --github-token | Provide GitHub token directly (must be generated using the `auth` subcommand) | none | -g |
85-
| --vision | Enable vision capabilities | false | none |
8685

8786
### Auth Command Options
8887

@@ -116,9 +115,6 @@ npx copilot-api@latest start --rate-limit 30 --wait
116115
# Provide GitHub token directly
117116
npx copilot-api@latest start --github-token ghp_YOUR_TOKEN_HERE
118117

119-
# Enable vision capabilities
120-
npx copilot-api@latest start --vision
121-
122118
# Run only the auth flow
123119
npx copilot-api@latest auth
124120

src/lib/api-config.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const API_VERSION = "2025-04-01"
1515

1616
export const copilotBaseUrl = (state: State) =>
1717
`https://api.${state.accountType}.githubcopilot.com`
18-
export const copilotHeaders = (state: State) => {
18+
export const copilotHeaders = (state: State, vision: boolean = false) => {
1919
const headers: Record<string, string> = {
2020
Authorization: `Bearer ${state.copilotToken}`,
2121
"content-type": standardHeaders()["content-type"],
@@ -29,9 +29,7 @@ export const copilotHeaders = (state: State) => {
2929
"x-vscode-user-agent-library-version": "electron-fetch",
3030
}
3131

32-
if (state.visionEnabled) {
33-
headers["copilot-vision-request"] = "true"
34-
}
32+
if (vision) headers["copilot-vision-request"] = "true"
3533

3634
return headers
3735
}

src/lib/state.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ export interface State {
1010

1111
manualApprove: boolean
1212
rateLimitWait: boolean
13-
visionEnabled: boolean
1413

1514
// Rate limiting configuration
1615
rateLimitSeconds?: number
@@ -21,5 +20,4 @@ export const state: State = {
2120
accountType: "individual",
2221
manualApprove: false,
2322
rateLimitWait: false,
24-
visionEnabled: false,
2523
}

src/main.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ interface RunServerOptions {
2020
rateLimit?: number
2121
rateLimitWait: boolean
2222
githubToken?: string
23-
visionEnabled: boolean
2423
}
2524

2625
export async function runServer(options: RunServerOptions): Promise<void> {
@@ -37,11 +36,6 @@ export async function runServer(options: RunServerOptions): Promise<void> {
3736
state.manualApprove = options.manual
3837
state.rateLimitSeconds = options.rateLimit
3938
state.rateLimitWait = options.rateLimitWait
40-
state.visionEnabled = options.visionEnabled
41-
42-
if (options.visionEnabled) {
43-
consola.info("Vision capability enabled")
44-
}
4539

4640
await ensurePaths()
4741
await cacheVSCodeVersion()
@@ -111,12 +105,6 @@ const start = defineCommand({
111105
description:
112106
"Provide GitHub token directly (must be generated using the `auth` subcommand)",
113107
},
114-
vision: {
115-
type: "boolean",
116-
default: false,
117-
description: "Enable vision capabilities",
118-
required: false,
119-
},
120108
},
121109
run({ args }) {
122110
const rateLimitRaw = args["rate-limit"]
@@ -134,7 +122,6 @@ const start = defineCommand({
134122
rateLimit,
135123
rateLimitWait: Boolean(args.wait),
136124
githubToken: args["github-token"],
137-
visionEnabled: args.vision,
138125
})
139126
},
140127
})

src/services/copilot/create-chat-completions.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,19 @@ export const createChatCompletions = async (
99
) => {
1010
if (!state.copilotToken) throw new Error("Copilot token not found")
1111

12+
for (const message of payload.messages) {
13+
intoCopilotMessage(message)
14+
}
15+
16+
const visionEnable = payload.messages.some(
17+
(x) =>
18+
typeof x.content !== "string"
19+
&& x.content.some((x) => x.type === "image_url"),
20+
)
21+
1222
const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
1323
method: "POST",
14-
headers: copilotHeaders(state),
24+
headers: copilotHeaders(state, visionEnable),
1525
body: JSON.stringify(payload),
1626
})
1727

@@ -25,6 +35,14 @@ export const createChatCompletions = async (
2535
return (await response.json()) as ChatCompletionResponse
2636
}
2737

38+
const intoCopilotMessage = (message: Message) => {
39+
if (typeof message.content === "string") return false
40+
41+
for (const part of message.content) {
42+
if (part.type === "input_image") part.type = "image_url"
43+
}
44+
}
45+
2846
// Streaming types
2947

3048
export interface ChatCompletionChunk {
@@ -79,7 +97,15 @@ export interface ChatCompletionsPayload {
7997

8098
export interface Message {
8199
role: "user" | "assistant" | "system"
82-
content: string
100+
content: string | Array<ContentPart>
83101
}
84102

85103
// https://platform.openai.com/docs/api-reference
104+
105+
export interface ContentPart {
106+
type: "input_image" | "input_text" | "image_url"
107+
text?: string
108+
image_url?: string
109+
}
110+
// https://platform.openai.com/docs/guides/images-vision#giving-a-model-images-as-input
111+
// Note: copilot use "image_url", but openai use "input_image"

0 commit comments

Comments
 (0)