Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -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
}
}
]
}
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
6 changes: 2 additions & 4 deletions src/lib/api-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = {
Authorization: `Bearer ${state.copilotToken}`,
"content-type": standardHeaders()["content-type"],
Expand All @@ -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
}
Expand Down
2 changes: 0 additions & 2 deletions src/lib/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export interface State {

manualApprove: boolean
rateLimitWait: boolean
visionEnabled: boolean

// Rate limiting configuration
rateLimitSeconds?: number
Expand All @@ -21,5 +20,4 @@ export const state: State = {
accountType: "individual",
manualApprove: false,
rateLimitWait: false,
visionEnabled: false,
}
13 changes: 0 additions & 13 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ interface RunServerOptions {
rateLimit?: number
rateLimitWait: boolean
githubToken?: string
visionEnabled: boolean
}

export async function runServer(options: RunServerOptions): Promise<void> {
Expand All @@ -37,11 +36,6 @@ export async function runServer(options: RunServerOptions): Promise<void> {
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()
Expand Down Expand Up @@ -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"]
Expand All @@ -134,7 +122,6 @@ const start = defineCommand({
rateLimit,
rateLimitWait: Boolean(args.wait),
githubToken: args["github-token"],
visionEnabled: args.vision,
})
},
})
Expand Down
30 changes: 28 additions & 2 deletions src/services/copilot/create-chat-completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
})

Expand All @@ -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 {
Expand Down Expand Up @@ -79,7 +97,15 @@ export interface ChatCompletionsPayload {

export interface Message {
role: "user" | "assistant" | "system"
content: string
content: string | Array<ContentPart>
}

// 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"