diff --git a/.gitignore b/.gitignore index 577a4f199..fd28c37cf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,3 @@ node_modules/ # eslint cache .eslintcache - -# build output -dist/ \ No newline at end of file diff --git a/dist/main.js b/dist/main.js new file mode 100755 index 000000000..169f334e5 --- /dev/null +++ b/dist/main.js @@ -0,0 +1,1745 @@ +#!/usr/bin/env node +import { defineCommand, runMain } from "citty"; +import consola from "consola"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; +import clipboard from "clipboardy"; +import invariant from "tiny-invariant"; +import { getProxyForUrl } from "proxy-from-env"; +import { Agent, ProxyAgent, setGlobalDispatcher } from "undici"; +import { execSync } from "node:child_process"; +import process$1 from "node:process"; +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { logger } from "hono/logger"; +import { streamSSE } from "hono/streaming"; +import { events } from "fetch-event-stream"; + +//#region src/lib/paths.ts +const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api"); +const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token"); +const PATHS = { + APP_DIR, + GITHUB_TOKEN_PATH +}; +async function ensurePaths() { + await fs.mkdir(PATHS.APP_DIR, { recursive: true }); + await ensureFile(PATHS.GITHUB_TOKEN_PATH); +} +async function ensureFile(filePath) { + try { + await fs.access(filePath, fs.constants.W_OK); + } catch { + await fs.writeFile(filePath, ""); + await fs.chmod(filePath, 384); + } +} + +//#endregion +//#region src/lib/state.ts +const state = { + accountType: "individual", + manualApprove: false, + rateLimitWait: false, + showToken: false +}; + +//#endregion +//#region src/lib/api-config.ts +const standardHeaders = () => ({ + "content-type": "application/json", + accept: "application/json" +}); +const COPILOT_VERSION = "0.26.7"; +const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`; +const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`; +const API_VERSION = "2025-04-01"; +const copilotBaseUrl = (state$1) => state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`; +const copilotHeaders = (state$1, vision = false) => { + const headers = { + Authorization: `Bearer ${state$1.copilotToken}`, + "content-type": standardHeaders()["content-type"], + "copilot-integration-id": "vscode-chat", + "editor-version": `vscode/${state$1.vsCodeVersion}`, + "editor-plugin-version": EDITOR_PLUGIN_VERSION, + "user-agent": USER_AGENT, + "openai-intent": "conversation-panel", + "x-github-api-version": API_VERSION, + "x-request-id": randomUUID(), + "x-vscode-user-agent-library-version": "electron-fetch" + }; + if (vision) headers["copilot-vision-request"] = "true"; + return headers; +}; +const GITHUB_API_BASE_URL = "https://api.github.com"; +const githubHeaders = (state$1) => ({ + ...standardHeaders(), + authorization: `token ${state$1.githubToken}`, + "editor-version": `vscode/${state$1.vsCodeVersion}`, + "editor-plugin-version": EDITOR_PLUGIN_VERSION, + "user-agent": USER_AGENT, + "x-github-api-version": API_VERSION, + "x-vscode-user-agent-library-version": "electron-fetch" +}); +const GITHUB_BASE_URL = "https://github.com"; +const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98"; +const GITHUB_APP_SCOPES = ["read:user"].join(" "); + +//#endregion +//#region src/lib/error.ts +var HTTPError = class extends Error { + response; + constructor(message, response) { + super(message); + this.response = response; + } +}; +async function forwardError(c, error) { + consola.error("Error occurred:", error); + if (error instanceof HTTPError) { + const errorText = await error.response.text(); + let errorJson; + try { + errorJson = JSON.parse(errorText); + } catch { + errorJson = errorText; + } + consola.error("HTTP error:", errorJson); + return c.json({ error: { + message: errorText, + type: "error" + } }, error.response.status); + } + return c.json({ error: { + message: error.message, + type: "error" + } }, 500); +} + +//#endregion +//#region src/services/github/get-copilot-token.ts +const getCopilotToken = async () => { + const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, { headers: githubHeaders(state) }); + if (!response.ok) throw new HTTPError("Failed to get Copilot token", response); + return await response.json(); +}; + +//#endregion +//#region src/services/github/get-device-code.ts +async function getDeviceCode() { + const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, { + method: "POST", + headers: standardHeaders(), + body: JSON.stringify({ + client_id: GITHUB_CLIENT_ID, + scope: GITHUB_APP_SCOPES + }) + }); + if (!response.ok) throw new HTTPError("Failed to get device code", response); + return await response.json(); +} + +//#endregion +//#region src/services/github/get-user.ts +async function getGitHubUser() { + const response = await fetch(`${GITHUB_API_BASE_URL}/user`, { headers: { + authorization: `token ${state.githubToken}`, + ...standardHeaders() + } }); + if (!response.ok) throw new HTTPError("Failed to get GitHub user", response); + return await response.json(); +} + +//#endregion +//#region src/services/copilot/get-models.ts +const getModels = async () => { + const response = await fetch(`${copilotBaseUrl(state)}/models`, { headers: copilotHeaders(state) }); + if (!response.ok) throw new HTTPError("Failed to get models", response); + return await response.json(); +}; + +//#endregion +//#region src/services/get-vscode-version.ts +const FALLBACK = "1.104.3"; +async function getVSCodeVersion() { + const controller = new AbortController(); + const timeout = setTimeout(() => { + controller.abort(); + }, 5e3); + try { + const match = (await (await fetch("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin", { signal: controller.signal })).text()).match(/pkgver=([0-9.]+)/); + if (match) return match[1]; + return FALLBACK; + } catch { + return FALLBACK; + } finally { + clearTimeout(timeout); + } +} +await getVSCodeVersion(); + +//#endregion +//#region src/lib/utils.ts +const sleep = (ms) => new Promise((resolve) => { + setTimeout(resolve, ms); +}); +const isNullish = (value) => value === null || value === void 0; +async function cacheModels() { + state.models = await getModels(); +} +const cacheVSCodeVersion = async () => { + const response = await getVSCodeVersion(); + state.vsCodeVersion = response; + consola.info(`Using VSCode version: ${response}`); +}; + +//#endregion +//#region src/services/github/poll-access-token.ts +async function pollAccessToken(deviceCode) { + const sleepDuration = (deviceCode.interval + 1) * 1e3; + consola.debug(`Polling access token with interval of ${sleepDuration}ms`); + while (true) { + const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, { + method: "POST", + headers: standardHeaders(), + body: JSON.stringify({ + client_id: GITHUB_CLIENT_ID, + device_code: deviceCode.device_code, + grant_type: "urn:ietf:params:oauth:grant-type:device_code" + }) + }); + if (!response.ok) { + await sleep(sleepDuration); + consola.error("Failed to poll access token:", await response.text()); + continue; + } + const json = await response.json(); + consola.debug("Polling access token response:", json); + const { access_token } = json; + if (access_token) return access_token; + else await sleep(sleepDuration); + } +} + +//#endregion +//#region src/lib/token.ts +const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8"); +const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token); +const setupCopilotToken = async () => { + const { token, refresh_in } = await getCopilotToken(); + state.copilotToken = token; + consola.debug("GitHub Copilot Token fetched successfully!"); + if (state.showToken) consola.info("Copilot token:", token); + const refreshInterval = (refresh_in - 60) * 1e3; + setInterval(async () => { + consola.debug("Refreshing Copilot token"); + try { + const { token: token$1 } = await getCopilotToken(); + state.copilotToken = token$1; + consola.debug("Copilot token refreshed"); + if (state.showToken) consola.info("Refreshed Copilot token:", token$1); + } catch (error) { + consola.error("Failed to refresh Copilot token:", error); + throw error; + } + }, refreshInterval); +}; +async function setupGitHubToken(options) { + try { + const githubToken = await readGithubToken(); + if (githubToken && !options?.force) { + state.githubToken = githubToken; + if (state.showToken) consola.info("GitHub token:", githubToken); + await logUser(); + return; + } + consola.info("Not logged in, getting new access token"); + const response = await getDeviceCode(); + consola.debug("Device code response:", response); + consola.info(`Please enter the code "${response.user_code}" in ${response.verification_uri}`); + const token = await pollAccessToken(response); + await writeGithubToken(token); + state.githubToken = token; + if (state.showToken) consola.info("GitHub token:", token); + await logUser(); + } catch (error) { + if (error instanceof HTTPError) { + consola.error("Failed to get GitHub token:", await error.response.json()); + throw error; + } + consola.error("Failed to get GitHub token:", error); + throw error; + } +} +async function logUser() { + const user = await getGitHubUser(); + consola.info(`Logged in as ${user.login}`); +} + +//#endregion +//#region src/auth.ts +async function runAuth(options) { + if (options.verbose) { + consola.level = 5; + consola.info("Verbose logging enabled"); + } + state.showToken = options.showToken; + await ensurePaths(); + await setupGitHubToken({ force: true }); + consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH); +} +const auth = defineCommand({ + meta: { + name: "auth", + description: "Run GitHub auth flow without running the server" + }, + args: { + verbose: { + alias: "v", + type: "boolean", + default: false, + description: "Enable verbose logging" + }, + "show-token": { + type: "boolean", + default: false, + description: "Show GitHub token on auth" + } + }, + run({ args }) { + return runAuth({ + verbose: args.verbose, + showToken: args["show-token"] + }); + } +}); + +//#endregion +//#region src/services/github/get-copilot-usage.ts +const getCopilotUsage = async () => { + const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { headers: githubHeaders(state) }); + if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response); + return await response.json(); +}; + +//#endregion +//#region src/check-usage.ts +const checkUsage = defineCommand({ + meta: { + name: "check-usage", + description: "Show current GitHub Copilot usage/quota information" + }, + async run() { + await ensurePaths(); + await setupGitHubToken(); + try { + const usage = await getCopilotUsage(); + const premium = usage.quota_snapshots.premium_interactions; + const premiumTotal = premium.entitlement; + const premiumUsed = premiumTotal - premium.remaining; + const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0; + const premiumPercentRemaining = premium.percent_remaining; + function summarizeQuota(name, snap) { + if (!snap) return `${name}: N/A`; + const total = snap.entitlement; + const used = total - snap.remaining; + const percentUsed = total > 0 ? used / total * 100 : 0; + const percentRemaining = snap.percent_remaining; + return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`; + } + const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`; + const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat); + const completionsLine = summarizeQuota("Completions", usage.quota_snapshots.completions); + consola.box(`Copilot Usage (plan: ${usage.copilot_plan})\nQuota resets: ${usage.quota_reset_date}\n\nQuotas:\n ${premiumLine}\n ${chatLine}\n ${completionsLine}`); + } catch (err) { + consola.error("Failed to fetch Copilot usage:", err); + process.exit(1); + } + } +}); + +//#endregion +//#region src/debug.ts +async function getPackageVersion() { + try { + const packageJsonPath = new URL("../package.json", import.meta.url).pathname; + return JSON.parse(await fs.readFile(packageJsonPath)).version; + } catch { + return "unknown"; + } +} +function getRuntimeInfo() { + const isBun = typeof Bun !== "undefined"; + return { + name: isBun ? "bun" : "node", + version: isBun ? Bun.version : process.version.slice(1), + platform: os.platform(), + arch: os.arch() + }; +} +async function checkTokenExists() { + try { + if (!(await fs.stat(PATHS.GITHUB_TOKEN_PATH)).isFile()) return false; + return (await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")).trim().length > 0; + } catch { + return false; + } +} +async function getDebugInfo() { + const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]); + return { + version, + runtime: getRuntimeInfo(), + paths: { + APP_DIR: PATHS.APP_DIR, + GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH + }, + tokenExists + }; +} +function printDebugInfoPlain(info) { + consola.info(`copilot-api debug + +Version: ${info.version} +Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch}) + +Paths: +- APP_DIR: ${info.paths.APP_DIR} +- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH} + +Token exists: ${info.tokenExists ? "Yes" : "No"}`); +} +function printDebugInfoJson(info) { + console.log(JSON.stringify(info, null, 2)); +} +async function runDebug(options) { + const debugInfo = await getDebugInfo(); + if (options.json) printDebugInfoJson(debugInfo); + else printDebugInfoPlain(debugInfo); +} +const debug = defineCommand({ + meta: { + name: "debug", + description: "Print debug information about the application" + }, + args: { json: { + type: "boolean", + default: false, + description: "Output debug information as JSON" + } }, + run({ args }) { + return runDebug({ json: args.json }); + } +}); + +//#endregion +//#region src/lib/proxy.ts +function initProxyFromEnv() { + if (typeof Bun !== "undefined") return; + try { + const direct = new Agent(); + const proxies = /* @__PURE__ */ new Map(); + setGlobalDispatcher({ + dispatch(options, handler) { + try { + const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin; + const raw = getProxyForUrl(origin.toString()); + const proxyUrl = raw && raw.length > 0 ? raw : void 0; + if (!proxyUrl) { + consola.debug(`HTTP proxy bypass: ${origin.hostname}`); + return direct.dispatch(options, handler); + } + let agent = proxies.get(proxyUrl); + if (!agent) { + agent = new ProxyAgent(proxyUrl); + proxies.set(proxyUrl, agent); + } + let label = proxyUrl; + try { + const u = new URL(proxyUrl); + label = `${u.protocol}//${u.host}`; + } catch {} + consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`); + return agent.dispatch(options, handler); + } catch { + return direct.dispatch(options, handler); + } + }, + close() { + return direct.close(); + }, + destroy() { + return direct.destroy(); + } + }); + consola.debug("HTTP proxy configured from environment (per-URL)"); + } catch (err) { + consola.debug("Proxy setup skipped:", err); + } +} + +//#endregion +//#region src/lib/shell.ts +function getShell() { + const { platform, ppid, env } = process$1; + if (platform === "win32") { + try { + if (execSync(`wmic process get ParentProcessId,Name | findstr "${ppid}"`, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell"; + } catch { + return "cmd"; + } + return "cmd"; + } else { + const shellPath = env.SHELL; + if (shellPath) { + if (shellPath.endsWith("zsh")) return "zsh"; + if (shellPath.endsWith("fish")) return "fish"; + if (shellPath.endsWith("bash")) return "bash"; + } + return "sh"; + } +} +/** +* Generates a copy-pasteable script to set multiple environment variables +* and run a subsequent command. +* @param {EnvVars} envVars - An object of environment variables to set. +* @param {string} commandToRun - The command to run after setting the variables. +* @returns {string} The formatted script string. +*/ +function generateEnvScript(envVars, commandToRun = "") { + const shell = getShell(); + const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0); + let commandBlock; + switch (shell) { + case "powershell": + commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${value}`).join("; "); + break; + case "cmd": + commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & "); + break; + case "fish": + commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${value}`).join("; "); + break; + default: { + const assignments = filteredEnvVars.map(([key, value]) => `${key}=${value}`).join(" "); + commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : ""; + break; + } + } + if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : " && "}${commandToRun}`; + return commandBlock || commandToRun; +} + +//#endregion +//#region src/routes/responses/ws-proxy.ts +function handleResponsesWsUpgrade(req, bunServer) { + if (!state.copilotToken) { + consola.error("WS upgrade rejected: no Copilot token"); + return false; + } + return bunServer.upgrade(req, { data: { type: "responses-proxy" } }); +} +function isResponsesWsPath(url) { + const path$1 = new URL(url).pathname; + return path$1 === "/v1/responses" || path$1 === "/responses"; +} +const UNSUPPORTED_TOOL_TYPES = new Set(["image_generation"]); +function unwrapPayload(raw) { + let payload; + if (raw.type === "response.create") if (typeof raw.response === "object" && raw.response !== null) payload = { ...raw.response }; + else { + const { type: _,...rest } = raw; + payload = rest; + } + else payload = { ...raw }; + if (Array.isArray(payload.tools)) payload.tools = payload.tools.filter((t) => !UNSUPPORTED_TOOL_TYPES.has(t.type)); + payload.stream = true; + return payload; +} +async function handleRequest(ws, raw) { + const headers = { ...copilotHeaders(state) }; + const url = `${copilotBaseUrl(state)}/responses`; + const body = unwrapPayload(raw); + consola.debug("WS→HTTP POST:", url, "keys:", Object.keys(body).join(",")); + try { + const response = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify(body) + }); + consola.debug("Upstream status:", response.status, "content-type:", response.headers.get("content-type")); + if (!response.ok) { + const errText = await response.text(); + consola.error("Upstream error:", response.status, errText); + ws.send(JSON.stringify({ + type: "error", + error: { + message: errText, + code: response.status + } + })); + return; + } + if (!response.body) { + consola.error("No response body from upstream"); + return; + } + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + let currentEventType = ""; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) { + if (line.startsWith("event: ")) { + currentEventType = line.slice(7).trim(); + continue; + } + if (line.startsWith("data: ")) { + const data = line.slice(6); + if (data === "[DONE]") { + consola.debug("SSE stream done"); + continue; + } + let toSend = data; + if (currentEventType) try { + const parsed = JSON.parse(data); + if (!parsed.type) { + parsed.type = currentEventType; + toSend = JSON.stringify(parsed); + } + } catch {} + consola.debug("Relaying SSE→WS:", toSend.slice(0, 150)); + try { + ws.send(toSend); + } catch (e) { + consola.error("WS send failed:", e); + return; + } + currentEventType = ""; + } + } + } + } catch (e) { + consola.error("HTTP request failed:", e); + try { + ws.send(JSON.stringify({ + type: "error", + error: { message: e.message } + })); + } catch {} + } +} +const responsesWebSocket = { + open(ws) { + consola.debug("Client WS opened"); + }, + message(ws, message) { + const data = typeof message === "string" ? message : message.toString(); + consola.debug("Client WS msg:", data.slice(0, 300)); + try { + handleRequest(ws, JSON.parse(data)); + } catch (e) { + consola.error("Bad JSON from client:", e); + ws.send(JSON.stringify({ + type: "error", + error: { message: "Invalid JSON" } + })); + } + }, + close(ws, code, reason) { + consola.debug("Client WS closed:", code, reason); + } +}; + +//#endregion +//#region src/lib/approval.ts +const awaitApproval = async () => { + if (!await consola.prompt(`Accept incoming request?`, { type: "confirm" })) throw new HTTPError("Request rejected", Response.json({ message: "Request rejected" }, { status: 403 })); +}; + +//#endregion +//#region src/lib/rate-limit.ts +async function checkRateLimit(state$1) { + if (state$1.rateLimitSeconds === void 0) return; + const now = Date.now(); + if (!state$1.lastRequestTimestamp) { + state$1.lastRequestTimestamp = now; + return; + } + const elapsedSeconds = (now - state$1.lastRequestTimestamp) / 1e3; + if (elapsedSeconds > state$1.rateLimitSeconds) { + state$1.lastRequestTimestamp = now; + return; + } + const waitTimeSeconds = Math.ceil(state$1.rateLimitSeconds - elapsedSeconds); + if (!state$1.rateLimitWait) { + consola.warn(`Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`); + throw new HTTPError("Rate limit exceeded", Response.json({ message: "Rate limit exceeded" }, { status: 429 })); + } + const waitTimeMs = waitTimeSeconds * 1e3; + consola.warn(`Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`); + await sleep(waitTimeMs); + state$1.lastRequestTimestamp = now; + consola.info("Rate limit wait completed, proceeding with request"); +} + +//#endregion +//#region src/lib/tokenizer.ts +const ENCODING_MAP = { + o200k_base: () => import("gpt-tokenizer/encoding/o200k_base"), + cl100k_base: () => import("gpt-tokenizer/encoding/cl100k_base"), + p50k_base: () => import("gpt-tokenizer/encoding/p50k_base"), + p50k_edit: () => import("gpt-tokenizer/encoding/p50k_edit"), + r50k_base: () => import("gpt-tokenizer/encoding/r50k_base") +}; +const encodingCache = /* @__PURE__ */ new Map(); +/** +* Calculate tokens for tool calls +*/ +const calculateToolCallsTokens = (toolCalls, encoder, constants) => { + let tokens = 0; + for (const toolCall of toolCalls) { + tokens += constants.funcInit; + tokens += encoder.encode(JSON.stringify(toolCall)).length; + } + tokens += constants.funcEnd; + return tokens; +}; +/** +* Calculate tokens for content parts +*/ +const calculateContentPartsTokens = (contentParts, encoder) => { + let tokens = 0; + for (const part of contentParts) if (part.type === "image_url") tokens += encoder.encode(part.image_url.url).length + 85; + else if (part.text) tokens += encoder.encode(part.text).length; + return tokens; +}; +/** +* Calculate tokens for a single message +*/ +const calculateMessageTokens = (message, encoder, constants) => { + const tokensPerMessage = 3; + const tokensPerName = 1; + let tokens = tokensPerMessage; + for (const [key, value] of Object.entries(message)) { + if (typeof value === "string") tokens += encoder.encode(value).length; + if (key === "name") tokens += tokensPerName; + if (key === "tool_calls") tokens += calculateToolCallsTokens(value, encoder, constants); + if (key === "content" && Array.isArray(value)) tokens += calculateContentPartsTokens(value, encoder); + } + return tokens; +}; +/** +* Calculate tokens using custom algorithm +*/ +const calculateTokens = (messages, encoder, constants) => { + if (messages.length === 0) return 0; + let numTokens = 0; + for (const message of messages) numTokens += calculateMessageTokens(message, encoder, constants); + numTokens += 3; + return numTokens; +}; +/** +* Get the corresponding encoder module based on encoding type +*/ +const getEncodeChatFunction = async (encoding) => { + if (encodingCache.has(encoding)) { + const cached = encodingCache.get(encoding); + if (cached) return cached; + } + const supportedEncoding = encoding; + if (!(supportedEncoding in ENCODING_MAP)) { + const fallbackModule = await ENCODING_MAP.o200k_base(); + encodingCache.set(encoding, fallbackModule); + return fallbackModule; + } + const encodingModule = await ENCODING_MAP[supportedEncoding](); + encodingCache.set(encoding, encodingModule); + return encodingModule; +}; +/** +* Get tokenizer type from model information +*/ +const getTokenizerFromModel = (model) => { + return model.capabilities.tokenizer || "o200k_base"; +}; +/** +* Get model-specific constants for token calculation +*/ +const getModelConstants = (model) => { + return model.id === "gpt-3.5-turbo" || model.id === "gpt-4" ? { + funcInit: 10, + propInit: 3, + propKey: 3, + enumInit: -3, + enumItem: 3, + funcEnd: 12 + } : { + funcInit: 7, + propInit: 3, + propKey: 3, + enumInit: -3, + enumItem: 3, + funcEnd: 12 + }; +}; +/** +* Calculate tokens for a single parameter +*/ +const calculateParameterTokens = (key, prop, context) => { + const { encoder, constants } = context; + let tokens = constants.propKey; + if (typeof prop !== "object" || prop === null) return tokens; + const param = prop; + const paramName = key; + const paramType = param.type || "string"; + let paramDesc = param.description || ""; + if (param.enum && Array.isArray(param.enum)) { + tokens += constants.enumInit; + for (const item of param.enum) { + tokens += constants.enumItem; + tokens += encoder.encode(String(item)).length; + } + } + if (paramDesc.endsWith(".")) paramDesc = paramDesc.slice(0, -1); + const line = `${paramName}:${paramType}:${paramDesc}`; + tokens += encoder.encode(line).length; + const excludedKeys = new Set([ + "type", + "description", + "enum" + ]); + for (const propertyName of Object.keys(param)) if (!excludedKeys.has(propertyName)) { + const propertyValue = param[propertyName]; + const propertyText = typeof propertyValue === "string" ? propertyValue : JSON.stringify(propertyValue); + tokens += encoder.encode(`${propertyName}:${propertyText}`).length; + } + return tokens; +}; +/** +* Calculate tokens for function parameters +*/ +const calculateParametersTokens = (parameters, encoder, constants) => { + if (!parameters || typeof parameters !== "object") return 0; + const params = parameters; + let tokens = 0; + for (const [key, value] of Object.entries(params)) if (key === "properties") { + const properties = value; + if (Object.keys(properties).length > 0) { + tokens += constants.propInit; + for (const propKey of Object.keys(properties)) tokens += calculateParameterTokens(propKey, properties[propKey], { + encoder, + constants + }); + } + } else { + const paramText = typeof value === "string" ? value : JSON.stringify(value); + tokens += encoder.encode(`${key}:${paramText}`).length; + } + return tokens; +}; +/** +* Calculate tokens for a single tool +*/ +const calculateToolTokens = (tool, encoder, constants) => { + let tokens = constants.funcInit; + const func = tool.function; + const fName = func.name; + let fDesc = func.description || ""; + if (fDesc.endsWith(".")) fDesc = fDesc.slice(0, -1); + const line = fName + ":" + fDesc; + tokens += encoder.encode(line).length; + if (typeof func.parameters === "object" && func.parameters !== null) tokens += calculateParametersTokens(func.parameters, encoder, constants); + return tokens; +}; +/** +* Calculate token count for tools based on model +*/ +const numTokensForTools = (tools, encoder, constants) => { + let funcTokenCount = 0; + for (const tool of tools) funcTokenCount += calculateToolTokens(tool, encoder, constants); + funcTokenCount += constants.funcEnd; + return funcTokenCount; +}; +/** +* Calculate the token count of messages, supporting multiple GPT encoders +*/ +const getTokenCount = async (payload, model) => { + const encoder = await getEncodeChatFunction(getTokenizerFromModel(model)); + const simplifiedMessages = payload.messages; + const inputMessages = simplifiedMessages.filter((msg) => msg.role !== "assistant"); + const outputMessages = simplifiedMessages.filter((msg) => msg.role === "assistant"); + const constants = getModelConstants(model); + let inputTokens = calculateTokens(inputMessages, encoder, constants); + if (payload.tools && payload.tools.length > 0) inputTokens += numTokensForTools(payload.tools, encoder, constants); + const outputTokens = calculateTokens(outputMessages, encoder, constants); + return { + input: inputTokens, + output: outputTokens + }; +}; + +//#endregion +//#region src/services/copilot/create-chat-completions.ts +const createChatCompletions = async (payload) => { + if (!state.copilotToken) throw new Error("Copilot token not found"); + const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url")); + const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role)); + const headers = { + ...copilotHeaders(state, enableVision), + "X-Initiator": isAgentCall ? "agent" : "user" + }; + const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, { + method: "POST", + headers, + body: JSON.stringify(payload) + }); + if (!response.ok) { + consola.error("Failed to create chat completions", response); + throw new HTTPError("Failed to create chat completions", response); + } + if (payload.stream) return events(response); + return await response.json(); +}; + +//#endregion +//#region src/routes/chat-completions/handler.ts +async function handleCompletion$1(c) { + await checkRateLimit(state); + let payload = await c.req.json(); + consola.debug("Request payload:", JSON.stringify(payload).slice(-400)); + const selectedModel = state.models?.data.find((model) => model.id === payload.model); + try { + if (selectedModel) { + const tokenCount = await getTokenCount(payload, selectedModel); + consola.info("Current token count:", tokenCount); + } else consola.warn("No model selected, skipping token count calculation"); + } catch (error) { + consola.warn("Failed to calculate token count:", error); + } + if (state.manualApprove) await awaitApproval(); + if (isNullish(payload.max_tokens)) { + payload = { + ...payload, + max_tokens: selectedModel?.capabilities.limits.max_output_tokens + }; + consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens)); + } + const response = await createChatCompletions(payload); + if (isNonStreaming$2(response)) { + consola.debug("Non-streaming response:", JSON.stringify(response)); + return c.json(response); + } + consola.debug("Streaming response"); + return streamSSE(c, async (stream) => { + for await (const chunk of response) { + consola.debug("Streaming chunk:", JSON.stringify(chunk)); + await stream.writeSSE(chunk); + } + }); +} +const isNonStreaming$2 = (response) => Object.hasOwn(response, "choices"); + +//#endregion +//#region src/routes/chat-completions/route.ts +const completionRoutes = new Hono(); +completionRoutes.post("/", async (c) => { + try { + return await handleCompletion$1(c); + } catch (error) { + return await forwardError(c, error); + } +}); + +//#endregion +//#region src/services/copilot/create-embeddings.ts +const createEmbeddings = async (payload) => { + if (!state.copilotToken) throw new Error("Copilot token not found"); + const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, { + method: "POST", + headers: copilotHeaders(state), + body: JSON.stringify(payload) + }); + if (!response.ok) throw new HTTPError("Failed to create embeddings", response); + return await response.json(); +}; + +//#endregion +//#region src/routes/embeddings/route.ts +const embeddingRoutes = new Hono(); +embeddingRoutes.post("/", async (c) => { + try { + const response = await createEmbeddings(await c.req.json()); + return c.json(response); + } catch (error) { + return await forwardError(c, error); + } +}); + +//#endregion +//#region src/routes/messages/utils.ts +function mapOpenAIStopReasonToAnthropic(finishReason) { + if (finishReason === null) return null; + return { + stop: "end_turn", + length: "max_tokens", + tool_calls: "tool_use", + content_filter: "end_turn" + }[finishReason]; +} + +//#endregion +//#region src/routes/messages/non-stream-translation.ts +function translateToOpenAI(payload) { + return { + model: translateModelName(payload.model), + messages: translateAnthropicMessagesToOpenAI(payload.messages, payload.system), + max_tokens: payload.max_tokens, + stop: payload.stop_sequences, + stream: payload.stream, + temperature: payload.temperature, + top_p: payload.top_p, + user: payload.metadata?.user_id, + tools: translateAnthropicToolsToOpenAI(payload.tools), + tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice) + }; +} +function translateModelName(model) { + if (model.startsWith("claude-sonnet-4-")) return model.replace(/^claude-sonnet-4-.*/, "claude-sonnet-4"); + else if (model.startsWith("claude-opus-")) return model.replace(/^claude-opus-4-.*/, "claude-opus-4"); + return model; +} +function translateAnthropicMessagesToOpenAI(anthropicMessages, system) { + const systemMessages = handleSystemPrompt(system); + const otherMessages = anthropicMessages.flatMap((message) => message.role === "user" ? handleUserMessage(message) : handleAssistantMessage(message)); + return [...systemMessages, ...otherMessages]; +} +function handleSystemPrompt(system) { + if (!system) return []; + if (typeof system === "string") return [{ + role: "system", + content: system + }]; + else return [{ + role: "system", + content: system.map((block) => block.text).join("\n\n") + }]; +} +function handleUserMessage(message) { + const newMessages = []; + if (Array.isArray(message.content)) { + const toolResultBlocks = message.content.filter((block) => block.type === "tool_result"); + const otherBlocks = message.content.filter((block) => block.type !== "tool_result"); + for (const block of toolResultBlocks) newMessages.push({ + role: "tool", + tool_call_id: block.tool_use_id, + content: mapContent(block.content) + }); + if (otherBlocks.length > 0) newMessages.push({ + role: "user", + content: mapContent(otherBlocks) + }); + } else newMessages.push({ + role: "user", + content: mapContent(message.content) + }); + return newMessages; +} +function handleAssistantMessage(message) { + if (!Array.isArray(message.content)) return [{ + role: "assistant", + content: mapContent(message.content) + }]; + const toolUseBlocks = message.content.filter((block) => block.type === "tool_use"); + const textBlocks = message.content.filter((block) => block.type === "text"); + const thinkingBlocks = message.content.filter((block) => block.type === "thinking"); + const allTextContent = [...textBlocks.map((b) => b.text), ...thinkingBlocks.map((b) => b.thinking)].join("\n\n"); + return toolUseBlocks.length > 0 ? [{ + role: "assistant", + content: allTextContent || null, + tool_calls: toolUseBlocks.map((toolUse) => ({ + id: toolUse.id, + type: "function", + function: { + name: toolUse.name, + arguments: JSON.stringify(toolUse.input) + } + })) + }] : [{ + role: "assistant", + content: mapContent(message.content) + }]; +} +function mapContent(content) { + if (typeof content === "string") return content; + if (!Array.isArray(content)) return null; + if (!content.some((block) => block.type === "image")) return content.filter((block) => block.type === "text" || block.type === "thinking").map((block) => block.type === "text" ? block.text : block.thinking).join("\n\n"); + const contentParts = []; + for (const block of content) switch (block.type) { + case "text": + contentParts.push({ + type: "text", + text: block.text + }); + break; + case "thinking": + contentParts.push({ + type: "text", + text: block.thinking + }); + break; + case "image": + contentParts.push({ + type: "image_url", + image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` } + }); + break; + } + return contentParts; +} +function translateAnthropicToolsToOpenAI(anthropicTools) { + if (!anthropicTools) return; + return anthropicTools.map((tool) => ({ + type: "function", + function: { + name: tool.name, + description: tool.description, + parameters: tool.input_schema + } + })); +} +function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice) { + if (!anthropicToolChoice) return; + switch (anthropicToolChoice.type) { + case "auto": return "auto"; + case "any": return "required"; + case "tool": + if (anthropicToolChoice.name) return { + type: "function", + function: { name: anthropicToolChoice.name } + }; + return; + case "none": return "none"; + default: return; + } +} +function translateToAnthropic(response) { + const allTextBlocks = []; + const allToolUseBlocks = []; + let stopReason = null; + stopReason = response.choices[0]?.finish_reason ?? stopReason; + for (const choice of response.choices) { + const textBlocks = getAnthropicTextBlocks(choice.message.content); + const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls); + allTextBlocks.push(...textBlocks); + allToolUseBlocks.push(...toolUseBlocks); + if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason; + } + return { + id: response.id, + type: "message", + role: "assistant", + model: response.model, + content: [...allTextBlocks, ...allToolUseBlocks], + stop_reason: mapOpenAIStopReasonToAnthropic(stopReason), + stop_sequence: null, + usage: { + input_tokens: (response.usage?.prompt_tokens ?? 0) - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0), + output_tokens: response.usage?.completion_tokens ?? 0, + ...response.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.prompt_tokens_details.cached_tokens } + } + }; +} +function getAnthropicTextBlocks(messageContent) { + if (typeof messageContent === "string") return [{ + type: "text", + text: messageContent + }]; + if (Array.isArray(messageContent)) return messageContent.filter((part) => part.type === "text").map((part) => ({ + type: "text", + text: part.text + })); + return []; +} +function getAnthropicToolUseBlocks(toolCalls) { + if (!toolCalls) return []; + return toolCalls.map((toolCall) => ({ + type: "tool_use", + id: toolCall.id, + name: toolCall.function.name, + input: JSON.parse(toolCall.function.arguments) + })); +} + +//#endregion +//#region src/routes/messages/count-tokens-handler.ts +/** +* Handles token counting for Anthropic messages +*/ +async function handleCountTokens(c) { + try { + const anthropicBeta = c.req.header("anthropic-beta"); + const anthropicPayload = await c.req.json(); + const openAIPayload = translateToOpenAI(anthropicPayload); + const selectedModel = state.models?.data.find((model) => model.id === anthropicPayload.model); + if (!selectedModel) { + consola.warn("Model not found, returning default token count"); + return c.json({ input_tokens: 1 }); + } + const tokenCount = await getTokenCount(openAIPayload, selectedModel); + if (anthropicPayload.tools && anthropicPayload.tools.length > 0) { + let mcpToolExist = false; + if (anthropicBeta?.startsWith("claude-code")) mcpToolExist = anthropicPayload.tools.some((tool) => tool.name.startsWith("mcp__")); + if (!mcpToolExist) { + if (anthropicPayload.model.startsWith("claude")) tokenCount.input = tokenCount.input + 346; + else if (anthropicPayload.model.startsWith("grok")) tokenCount.input = tokenCount.input + 480; + } + } + let finalTokenCount = tokenCount.input + tokenCount.output; + if (anthropicPayload.model.startsWith("claude")) finalTokenCount = Math.round(finalTokenCount * 1.15); + else if (anthropicPayload.model.startsWith("grok")) finalTokenCount = Math.round(finalTokenCount * 1.03); + consola.info("Token count:", finalTokenCount); + return c.json({ input_tokens: finalTokenCount }); + } catch (error) { + consola.error("Error counting tokens:", error); + return c.json({ input_tokens: 1 }); + } +} + +//#endregion +//#region src/routes/messages/stream-translation.ts +function isToolBlockOpen(state$1) { + if (!state$1.contentBlockOpen) return false; + return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex); +} +function translateChunkToAnthropicEvents(chunk, state$1) { + const events$1 = []; + if (chunk.choices.length === 0) return events$1; + const choice = chunk.choices[0]; + const { delta } = choice; + if (!state$1.messageStartSent) { + events$1.push({ + type: "message_start", + message: { + id: chunk.id, + type: "message", + role: "assistant", + content: [], + model: chunk.model, + stop_reason: null, + stop_sequence: null, + usage: { + input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0), + output_tokens: 0, + ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens } + } + } + }); + state$1.messageStartSent = true; + } + if (delta.content) { + if (isToolBlockOpen(state$1)) { + events$1.push({ + type: "content_block_stop", + index: state$1.contentBlockIndex + }); + state$1.contentBlockIndex++; + state$1.contentBlockOpen = false; + } + if (!state$1.contentBlockOpen) { + events$1.push({ + type: "content_block_start", + index: state$1.contentBlockIndex, + content_block: { + type: "text", + text: "" + } + }); + state$1.contentBlockOpen = true; + } + events$1.push({ + type: "content_block_delta", + index: state$1.contentBlockIndex, + delta: { + type: "text_delta", + text: delta.content + } + }); + } + if (delta.tool_calls) for (const toolCall of delta.tool_calls) { + if (toolCall.id && toolCall.function?.name) { + if (state$1.contentBlockOpen) { + events$1.push({ + type: "content_block_stop", + index: state$1.contentBlockIndex + }); + state$1.contentBlockIndex++; + state$1.contentBlockOpen = false; + } + const anthropicBlockIndex = state$1.contentBlockIndex; + state$1.toolCalls[toolCall.index] = { + id: toolCall.id, + name: toolCall.function.name, + anthropicBlockIndex + }; + events$1.push({ + type: "content_block_start", + index: anthropicBlockIndex, + content_block: { + type: "tool_use", + id: toolCall.id, + name: toolCall.function.name, + input: {} + } + }); + state$1.contentBlockOpen = true; + } + if (toolCall.function?.arguments) { + const toolCallInfo = state$1.toolCalls[toolCall.index]; + if (toolCallInfo) events$1.push({ + type: "content_block_delta", + index: toolCallInfo.anthropicBlockIndex, + delta: { + type: "input_json_delta", + partial_json: toolCall.function.arguments + } + }); + } + } + if (choice.finish_reason) { + if (state$1.contentBlockOpen) { + events$1.push({ + type: "content_block_stop", + index: state$1.contentBlockIndex + }); + state$1.contentBlockOpen = false; + } + events$1.push({ + type: "message_delta", + delta: { + stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason), + stop_sequence: null + }, + usage: { + input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0), + output_tokens: chunk.usage?.completion_tokens ?? 0, + ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens } + } + }, { type: "message_stop" }); + } + return events$1; +} + +//#endregion +//#region src/routes/messages/handler.ts +async function handleCompletion(c) { + await checkRateLimit(state); + const anthropicPayload = await c.req.json(); + consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload)); + const openAIPayload = translateToOpenAI(anthropicPayload); + consola.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload)); + if (state.manualApprove) await awaitApproval(); + const response = await createChatCompletions(openAIPayload); + if (isNonStreaming$1(response)) { + consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400)); + const anthropicResponse = translateToAnthropic(response); + consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse)); + return c.json(anthropicResponse); + } + consola.debug("Streaming response from Copilot"); + return streamSSE(c, async (stream) => { + const streamState = { + messageStartSent: false, + contentBlockIndex: 0, + contentBlockOpen: false, + toolCalls: {} + }; + for await (const rawEvent of response) { + consola.debug("Copilot raw stream event:", JSON.stringify(rawEvent)); + if (rawEvent.data === "[DONE]") break; + if (!rawEvent.data) continue; + const events$1 = translateChunkToAnthropicEvents(JSON.parse(rawEvent.data), streamState); + for (const event of events$1) { + consola.debug("Translated Anthropic event:", JSON.stringify(event)); + await stream.writeSSE({ + event: event.type, + data: JSON.stringify(event) + }); + } + } + }); +} +const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices"); + +//#endregion +//#region src/routes/messages/route.ts +const messageRoutes = new Hono(); +messageRoutes.post("/", async (c) => { + try { + return await handleCompletion(c); + } catch (error) { + return await forwardError(c, error); + } +}); +messageRoutes.post("/count_tokens", async (c) => { + try { + return await handleCountTokens(c); + } catch (error) { + return await forwardError(c, error); + } +}); + +//#endregion +//#region src/routes/models/route.ts +const modelRoutes = new Hono(); +modelRoutes.get("/", async (c) => { + try { + if (!state.models) await cacheModels(); + const openaiModels = state.models?.data.map((model) => ({ + id: model.id, + object: "model", + type: "model", + created: 0, + created_at: (/* @__PURE__ */ new Date(0)).toISOString(), + owned_by: model.vendor, + display_name: model.name + })); + const codexModels = state.models?.data.map((model) => ({ + slug: model.id, + display_name: model.name, + description: "", + default_reasoning_level: "medium", + supported_reasoning_levels: [ + { + effort: "low", + description: "Fast responses" + }, + { + effort: "medium", + description: "Balanced" + }, + { + effort: "high", + description: "Deep reasoning" + }, + { + effort: "xhigh", + description: "Maximum reasoning" + } + ], + shell_type: "shell_command", + visibility: "list", + supported_in_api: true, + priority: 0, + additional_speed_tiers: [], + availability_nux: null, + upgrade: null + })); + return c.json({ + object: "list", + data: openaiModels, + models: codexModels, + has_more: false + }); + } catch (error) { + return await forwardError(c, error); + } +}); + +//#endregion +//#region src/services/copilot/create-responses.ts +const createResponses = async (payload) => { + if (!state.copilotToken) throw new Error("Copilot token not found"); + const headers = { + ...copilotHeaders(state), + "X-Initiator": "user" + }; + const response = await fetch(`${copilotBaseUrl(state)}/responses`, { + method: "POST", + headers, + body: JSON.stringify(payload) + }); + if (!response.ok) { + consola.error("Failed to create responses", response); + throw new HTTPError("Failed to create responses", response); + } + if (payload.stream) return events(response); + return await response.json(); +}; + +//#endregion +//#region src/routes/responses/handler.ts +async function handleResponses(c) { + await checkRateLimit(state); + const payload = await c.req.json(); + consola.debug("Responses API request payload:", JSON.stringify(payload).slice(-400)); + if (state.manualApprove) await awaitApproval(); + const response = await createResponses(payload); + if (isNonStreaming(response)) { + consola.debug("Non-streaming responses:", JSON.stringify(response).slice(-400)); + return c.json(response); + } + consola.debug("Streaming responses"); + return streamSSE(c, async (stream) => { + for await (const chunk of response) { + consola.debug("Responses stream chunk:", JSON.stringify(chunk)); + await stream.writeSSE(chunk); + } + }); +} +const isNonStreaming = (response) => Object.hasOwn(response, "output") || Object.hasOwn(response, "id"); + +//#endregion +//#region src/routes/responses/route.ts +const responsesRoutes = new Hono(); +responsesRoutes.post("/", async (c) => { + try { + return await handleResponses(c); + } catch (error) { + return await forwardError(c, error); + } +}); + +//#endregion +//#region src/routes/token/route.ts +const tokenRoute = new Hono(); +tokenRoute.get("/", (c) => { + try { + return c.json({ token: state.copilotToken }); + } catch (error) { + console.error("Error fetching token:", error); + return c.json({ + error: "Failed to fetch token", + token: null + }, 500); + } +}); + +//#endregion +//#region src/routes/usage/route.ts +const usageRoute = new Hono(); +usageRoute.get("/", async (c) => { + try { + const usage = await getCopilotUsage(); + return c.json(usage); + } catch (error) { + console.error("Error fetching Copilot usage:", error); + return c.json({ error: "Failed to fetch Copilot usage" }, 500); + } +}); + +//#endregion +//#region src/server.ts +const server = new Hono(); +server.use(logger()); +server.use(cors()); +server.get("/", (c) => c.text("Server running")); +server.route("/chat/completions", completionRoutes); +server.route("/responses", responsesRoutes); +server.route("/models", modelRoutes); +server.route("/embeddings", embeddingRoutes); +server.route("/usage", usageRoute); +server.route("/token", tokenRoute); +server.route("/v1/chat/completions", completionRoutes); +server.route("/v1/responses", responsesRoutes); +server.route("/v1/models", modelRoutes); +server.route("/v1/embeddings", embeddingRoutes); +server.route("/v1/messages", messageRoutes); + +//#endregion +//#region src/start.ts +async function runServer(options) { + if (options.proxyEnv) initProxyFromEnv(); + if (options.verbose) { + consola.level = 5; + consola.info("Verbose logging enabled"); + } + state.accountType = options.accountType; + if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`); + state.manualApprove = options.manual; + state.rateLimitSeconds = options.rateLimit; + state.rateLimitWait = options.rateLimitWait; + state.showToken = options.showToken; + await ensurePaths(); + await cacheVSCodeVersion(); + if (options.githubToken) { + state.githubToken = options.githubToken; + consola.info("Using provided GitHub token"); + } else await setupGitHubToken(); + await setupCopilotToken(); + await cacheModels(); + consola.info(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`); + const serverUrl = `http://localhost:${options.port}`; + if (options.claudeCode) { + invariant(state.models, "Models should be loaded by now"); + const selectedModel = await consola.prompt("Select a model to use with Claude Code", { + type: "select", + options: state.models.data.map((model) => model.id) + }); + const selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", { + type: "select", + options: state.models.data.map((model) => model.id) + }); + const command = generateEnvScript({ + ANTHROPIC_BASE_URL: serverUrl, + ANTHROPIC_AUTH_TOKEN: "dummy", + ANTHROPIC_MODEL: selectedModel, + ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel, + ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel, + ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel, + DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1", + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" + }, "claude"); + try { + clipboard.writeSync(command); + consola.success("Copied Claude Code command to clipboard!"); + } catch { + consola.warn("Failed to copy to clipboard. Here is the Claude Code command:"); + consola.log(command); + } + } + consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`); + if (typeof Bun !== "undefined") { + const bunServer = Bun.serve({ + port: options.port, + fetch(req, bunSrv) { + if (req.headers.get("upgrade")?.toLowerCase() === "websocket" && isResponsesWsPath(req.url)) { + if (handleResponsesWsUpgrade(req, bunSrv)) return void 0; + return new Response("WebSocket upgrade failed", { status: 500 }); + } + return server.fetch(req); + }, + websocket: responsesWebSocket + }); + consola.info(`Listening on: http://localhost:${bunServer.port}/`); + } else { + const { createServer } = await import("node:http"); + createServer(async (req, res) => { + const url = `http://localhost:${options.port}${req.url}`; + const headers = new Headers(); + for (const [key, val] of Object.entries(req.headers)) if (val) headers.set(key, Array.isArray(val) ? val.join(", ") : val); + const body = await new Promise((resolve) => { + const chunks = []; + req.on("data", (c) => chunks.push(c)); + req.on("end", () => resolve(Buffer.concat(chunks))); + }); + const request = new Request(url, { + method: req.method, + headers, + body: ["GET", "HEAD"].includes(req.method ?? "GET") ? void 0 : body + }); + const response = await server.fetch(request); + res.writeHead(response.status, Object.fromEntries(response.headers.entries())); + const buf = Buffer.from(await response.arrayBuffer()); + res.end(buf); + }).listen(options.port, () => { + consola.info(`Listening on: http://localhost:${options.port}/`); + }); + consola.warn("WebSocket support requires Bun runtime"); + } +} +const start = defineCommand({ + meta: { + name: "start", + description: "Start the Copilot API server" + }, + args: { + port: { + alias: "p", + type: "string", + default: "4141", + description: "Port to listen on" + }, + verbose: { + alias: "v", + type: "boolean", + default: false, + description: "Enable verbose logging" + }, + "account-type": { + alias: "a", + type: "string", + default: "individual", + description: "Account type to use (individual, business, enterprise)" + }, + manual: { + type: "boolean", + default: false, + description: "Enable manual request approval" + }, + "rate-limit": { + alias: "r", + type: "string", + description: "Rate limit in seconds between requests" + }, + wait: { + alias: "w", + type: "boolean", + default: false, + description: "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set" + }, + "github-token": { + alias: "g", + type: "string", + description: "Provide GitHub token directly (must be generated using the `auth` subcommand)" + }, + "claude-code": { + alias: "c", + type: "boolean", + default: false, + description: "Generate a command to launch Claude Code with Copilot API config" + }, + "show-token": { + type: "boolean", + default: false, + description: "Show GitHub and Copilot tokens on fetch and refresh" + }, + "proxy-env": { + type: "boolean", + default: false, + description: "Initialize proxy from environment variables" + } + }, + run({ args }) { + const rateLimitRaw = args["rate-limit"]; + const rateLimit = rateLimitRaw === void 0 ? void 0 : Number.parseInt(rateLimitRaw, 10); + return runServer({ + port: Number.parseInt(args.port, 10), + verbose: args.verbose, + accountType: args["account-type"], + manual: args.manual, + rateLimit, + rateLimitWait: args.wait, + githubToken: args["github-token"], + claudeCode: args["claude-code"], + showToken: args["show-token"], + proxyEnv: args["proxy-env"] + }); + } +}); + +//#endregion +//#region src/main.ts +await runMain(defineCommand({ + meta: { + name: "copilot-api", + description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools." + }, + subCommands: { + auth, + start, + "check-usage": checkUsage, + debug + } +})); + +//#endregion +export { }; +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/dist/main.js.map b/dist/main.js.map new file mode 100644 index 000000000..22af51a5d --- /dev/null +++ b/dist/main.js.map @@ -0,0 +1 @@ +{"version":3,"file":"main.js","names":["state: State","state","headers: Record","errorJson: unknown","token","process","commandBlock: string","path","payload: Record","headers: Record","state","x","headers: Record","handleCompletion","isNonStreaming","handleCompletion","newMessages: Array","contentParts: Array","allTextBlocks: Array","allToolUseBlocks: Array","stopReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null","state","events: Array","events","isNonStreaming","streamState: AnthropicStreamState","events","headers: Record","chunks: Buffer[]"],"sources":["../src/lib/paths.ts","../src/lib/state.ts","../src/lib/api-config.ts","../src/lib/error.ts","../src/services/github/get-copilot-token.ts","../src/services/github/get-device-code.ts","../src/services/github/get-user.ts","../src/services/copilot/get-models.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/poll-access-token.ts","../src/lib/token.ts","../src/auth.ts","../src/services/github/get-copilot-usage.ts","../src/check-usage.ts","../src/debug.ts","../src/lib/proxy.ts","../src/lib/shell.ts","../src/routes/responses/ws-proxy.ts","../src/lib/approval.ts","../src/lib/rate-limit.ts","../src/lib/tokenizer.ts","../src/services/copilot/create-chat-completions.ts","../src/routes/chat-completions/handler.ts","../src/routes/chat-completions/route.ts","../src/services/copilot/create-embeddings.ts","../src/routes/embeddings/route.ts","../src/routes/messages/utils.ts","../src/routes/messages/non-stream-translation.ts","../src/routes/messages/count-tokens-handler.ts","../src/routes/messages/stream-translation.ts","../src/routes/messages/handler.ts","../src/routes/messages/route.ts","../src/routes/models/route.ts","../src/services/copilot/create-responses.ts","../src/routes/responses/handler.ts","../src/routes/responses/route.ts","../src/routes/token/route.ts","../src/routes/usage/route.ts","../src/server.ts","../src/start.ts","../src/main.ts"],"sourcesContent":["import fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nconst APP_DIR = path.join(os.homedir(), \".local\", \"share\", \"copilot-api\")\n\nconst GITHUB_TOKEN_PATH = path.join(APP_DIR, \"github_token\")\n\nexport const PATHS = {\n APP_DIR,\n GITHUB_TOKEN_PATH,\n}\n\nexport async function ensurePaths(): Promise {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n}\n\nasync function ensureFile(filePath: string): Promise {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n","import type { ModelsResponse } from \"~/services/copilot/get-models\"\n\nexport interface State {\n githubToken?: string\n copilotToken?: string\n\n accountType: string\n models?: ModelsResponse\n vsCodeVersion?: string\n\n manualApprove: boolean\n rateLimitWait: boolean\n showToken: boolean\n\n // Rate limiting configuration\n rateLimitSeconds?: number\n lastRequestTimestamp?: number\n}\n\nexport const state: State = {\n accountType: \"individual\",\n manualApprove: false,\n rateLimitWait: false,\n showToken: false,\n}\n","import { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst COPILOT_VERSION = \"0.26.7\"\nconst EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`\nconst USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`\n\nconst API_VERSION = \"2025-04-01\"\n\nexport const copilotBaseUrl = (state: State) =>\n state.accountType === \"individual\" ?\n \"https://api.githubcopilot.com\"\n : `https://api.${state.accountType}.githubcopilot.com`\nexport const copilotHeaders = (state: State, vision: boolean = false) => {\n const headers: Record = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": \"vscode-chat\",\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"openai-intent\": \"conversation-panel\",\n \"x-github-api-version\": API_VERSION,\n \"x-request-id\": randomUUID(),\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL = \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"x-github-api-version\": API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n","import type { Context } from \"hono\"\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\"\n\nimport consola from \"consola\"\n\nexport class HTTPError extends Error {\n response: Response\n\n constructor(message: string, response: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport async function forwardError(c: Context, error: unknown) {\n consola.error(\"Error occurred:\", error)\n\n if (error instanceof HTTPError) {\n const errorText = await error.response.text()\n let errorJson: unknown\n try {\n errorJson = JSON.parse(errorText)\n } catch {\n errorJson = errorText\n }\n consola.error(\"HTTP error:\", errorJson)\n return c.json(\n {\n error: {\n message: errorText,\n type: \"error\",\n },\n },\n error.response.status as ContentfulStatusCode,\n )\n }\n\n return c.json(\n {\n error: {\n message: (error as Error).message,\n type: \"error\",\n },\n },\n 500,\n )\n}\n","import { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getCopilotToken = async () => {\n const response = await fetch(\n `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`,\n {\n headers: githubHeaders(state),\n },\n )\n\n if (!response.ok) throw new HTTPError(\"Failed to get Copilot token\", response)\n\n return (await response.json()) as GetCopilotTokenResponse\n}\n\n// Trimmed for the sake of simplicity\ninterface GetCopilotTokenResponse {\n expires_at: number\n refresh_in: number\n token: string\n}\n","import {\n GITHUB_APP_SCOPES,\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\n\nexport async function getDeviceCode(): Promise {\n const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n scope: GITHUB_APP_SCOPES,\n }),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get device code\", response)\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n expires_in: number\n interval: number\n}\n","import { GITHUB_API_BASE_URL, standardHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport async function getGitHubUser() {\n const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {\n headers: {\n authorization: `token ${state.githubToken}`,\n ...standardHeaders(),\n },\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get GitHub user\", response)\n\n return (await response.json()) as GithubUserResponse\n}\n\n// Trimmed for the sake of simplicity\ninterface GithubUserResponse {\n login: string\n}\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n const response = await fetch(`${copilotBaseUrl(state)}/models`, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get models\", response)\n\n return (await response.json()) as ModelsResponse\n}\n\nexport interface ModelsResponse {\n data: Array\n object: string\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_inputs?: number\n}\n\ninterface ModelSupports {\n tool_calls?: boolean\n parallel_tool_calls?: boolean\n dimensions?: boolean\n}\n\ninterface ModelCapabilities {\n family: string\n limits: ModelLimits\n object: string\n supports: ModelSupports\n tokenizer: string\n type: string\n}\n\nexport interface Model {\n capabilities: ModelCapabilities\n id: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n vendor: string\n version: string\n policy?: {\n state: string\n terms: string\n }\n}\n","const FALLBACK = \"1.104.3\"\n\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin\",\n {\n signal: controller.signal,\n },\n )\n\n const pkgbuild = await response.text()\n const pkgverRegex = /pkgver=([0-9.]+)/\n const match = pkgbuild.match(pkgverRegex)\n\n if (match) {\n return match[1]\n }\n\n return FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n\nawait getVSCodeVersion()\n","import consola from \"consola\"\n\nimport { getModels } from \"~/services/copilot/get-models\"\nimport { getVSCodeVersion } from \"~/services/get-vscode-version\"\n\nimport { state } from \"./state\"\n\nexport const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined\n\nexport async function cacheModels(): Promise {\n const models = await getModels()\n state.models = models\n}\n\nexport const cacheVSCodeVersion = async () => {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n\n consola.info(`Using VSCode version: ${response}`)\n}\n","import consola from \"consola\"\n\nimport {\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { sleep } from \"~/lib/utils\"\n\nimport type { DeviceCodeResponse } from \"./get-device-code\"\n\nexport async function pollAccessToken(\n deviceCode: DeviceCodeResponse,\n): Promise {\n // Interval is in seconds, we need to multiply by 1000 to get milliseconds\n // I'm also adding another second, just to be safe\n const sleepDuration = (deviceCode.interval + 1) * 1000\n consola.debug(`Polling access token with interval of ${sleepDuration}ms`)\n\n while (true) {\n const response = await fetch(\n `${GITHUB_BASE_URL}/login/oauth/access_token`,\n {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n },\n )\n\n if (!response.ok) {\n await sleep(sleepDuration)\n consola.error(\"Failed to poll access token:\", await response.text())\n\n continue\n }\n\n const json = await response.json()\n consola.debug(\"Polling access token response:\", json)\n\n const { access_token } = json as AccessTokenResponse\n\n if (access_token) {\n return access_token\n } else {\n await sleep(sleepDuration)\n }\n }\n}\n\ninterface AccessTokenResponse {\n access_token: string\n token_type: string\n scope: string\n}\n","import consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/paths\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getDeviceCode } from \"~/services/github/get-device-code\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\nimport { pollAccessToken } from \"~/services/github/poll-access-token\"\n\nimport { HTTPError } from \"./error\"\nimport { state } from \"./state\"\n\nconst readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n\nconst writeGithubToken = (token: string) =>\n fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)\n\nexport const setupCopilotToken = async () => {\n const { token, refresh_in } = await getCopilotToken()\n state.copilotToken = token\n\n // Display the Copilot token to the screen\n consola.debug(\"GitHub Copilot Token fetched successfully!\")\n if (state.showToken) {\n consola.info(\"Copilot token:\", token)\n }\n\n const refreshInterval = (refresh_in - 60) * 1000\n setInterval(async () => {\n consola.debug(\"Refreshing Copilot token\")\n try {\n const { token } = await getCopilotToken()\n state.copilotToken = token\n consola.debug(\"Copilot token refreshed\")\n if (state.showToken) {\n consola.info(\"Refreshed Copilot token:\", token)\n }\n } catch (error) {\n consola.error(\"Failed to refresh Copilot token:\", error)\n throw error\n }\n }, refreshInterval)\n}\n\ninterface SetupGitHubTokenOptions {\n force?: boolean\n}\n\nexport async function setupGitHubToken(\n options?: SetupGitHubTokenOptions,\n): Promise {\n try {\n const githubToken = await readGithubToken()\n\n if (githubToken && !options?.force) {\n state.githubToken = githubToken\n if (state.showToken) {\n consola.info(\"GitHub token:\", githubToken)\n }\n await logUser()\n\n return\n }\n\n consola.info(\"Not logged in, getting new access token\")\n const response = await getDeviceCode()\n consola.debug(\"Device code response:\", response)\n\n consola.info(\n `Please enter the code \"${response.user_code}\" in ${response.verification_uri}`,\n )\n\n const token = await pollAccessToken(response)\n await writeGithubToken(token)\n state.githubToken = token\n\n if (state.showToken) {\n consola.info(\"GitHub token:\", token)\n }\n await logUser()\n } catch (error) {\n if (error instanceof HTTPError) {\n consola.error(\"Failed to get GitHub token:\", await error.response.json())\n throw error\n }\n\n consola.error(\"Failed to get GitHub token:\", error)\n throw error\n }\n}\n\nasync function logUser() {\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"./lib/paths\"\nimport { state } from \"./lib/state\"\nimport { setupGitHubToken } from \"./lib/token\"\n\ninterface RunAuthOptions {\n verbose: boolean\n showToken: boolean\n}\n\nexport async function runAuth(options: RunAuthOptions): Promise {\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.showToken = options.showToken\n\n await ensurePaths()\n await setupGitHubToken({ force: true })\n consola.success(\"GitHub token written to\", PATHS.GITHUB_TOKEN_PATH)\n}\n\nexport const auth = defineCommand({\n meta: {\n name: \"auth\",\n description: \"Run GitHub auth flow without running the server\",\n },\n args: {\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"show-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub token on auth\",\n },\n },\n run({ args }) {\n return runAuth({\n verbose: args.verbose,\n showToken: args[\"show-token\"],\n })\n },\n})\n","import { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getCopilotUsage = async (): Promise => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {\n headers: githubHeaders(state),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get Copilot usage\", response)\n }\n\n return (await response.json()) as CopilotUsageResponse\n}\n\nexport interface QuotaDetail {\n entitlement: number\n overage_count: number\n overage_permitted: boolean\n percent_remaining: number\n quota_id: string\n quota_remaining: number\n remaining: number\n unlimited: boolean\n}\n\ninterface QuotaSnapshots {\n chat: QuotaDetail\n completions: QuotaDetail\n premium_interactions: QuotaDetail\n}\n\ninterface CopilotUsageResponse {\n access_type_sku: string\n analytics_tracking_id: string\n assigned_date: string\n can_signup_for_limited: boolean\n chat_enabled: boolean\n copilot_plan: string\n organization_login_list: Array\n organization_list: Array\n quota_reset_date: string\n quota_snapshots: QuotaSnapshots\n}\n","import { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { ensurePaths } from \"./lib/paths\"\nimport { setupGitHubToken } from \"./lib/token\"\nimport {\n getCopilotUsage,\n type QuotaDetail,\n} from \"./services/github/get-copilot-usage\"\n\nexport const checkUsage = defineCommand({\n meta: {\n name: \"check-usage\",\n description: \"Show current GitHub Copilot usage/quota information\",\n },\n async run() {\n await ensurePaths()\n await setupGitHubToken()\n try {\n const usage = await getCopilotUsage()\n const premium = usage.quota_snapshots.premium_interactions\n const premiumTotal = premium.entitlement\n const premiumUsed = premiumTotal - premium.remaining\n const premiumPercentUsed =\n premiumTotal > 0 ? (premiumUsed / premiumTotal) * 100 : 0\n const premiumPercentRemaining = premium.percent_remaining\n\n // Helper to summarize a quota snapshot\n function summarizeQuota(name: string, snap: QuotaDetail | undefined) {\n if (!snap) return `${name}: N/A`\n const total = snap.entitlement\n const used = total - snap.remaining\n const percentUsed = total > 0 ? (used / total) * 100 : 0\n const percentRemaining = snap.percent_remaining\n return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`\n }\n\n const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`\n const chatLine = summarizeQuota(\"Chat\", usage.quota_snapshots.chat)\n const completionsLine = summarizeQuota(\n \"Completions\",\n usage.quota_snapshots.completions,\n )\n\n consola.box(\n `Copilot Usage (plan: ${usage.copilot_plan})\\n`\n + `Quota resets: ${usage.quota_reset_date}\\n`\n + `\\nQuotas:\\n`\n + ` ${premiumLine}\\n`\n + ` ${chatLine}\\n`\n + ` ${completionsLine}`,\n )\n } catch (err) {\n consola.error(\"Failed to fetch Copilot usage:\", err)\n process.exit(1)\n }\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\n\nimport { PATHS } from \"./lib/paths\"\n\ninterface DebugInfo {\n version: string\n runtime: {\n name: string\n version: string\n platform: string\n arch: string\n }\n paths: {\n APP_DIR: string\n GITHUB_TOKEN_PATH: string\n }\n tokenExists: boolean\n}\n\ninterface RunDebugOptions {\n json: boolean\n}\n\nasync function getPackageVersion(): Promise {\n try {\n const packageJsonPath = new URL(\"../package.json\", import.meta.url).pathname\n // @ts-expect-error https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v59.0.1/docs/rules/prefer-json-parse-buffer.md\n // JSON.parse() can actually parse buffers\n const packageJson = JSON.parse(await fs.readFile(packageJsonPath)) as {\n version: string\n }\n return packageJson.version\n } catch {\n return \"unknown\"\n }\n}\n\nfunction getRuntimeInfo() {\n const isBun = typeof Bun !== \"undefined\"\n\n return {\n name: isBun ? \"bun\" : \"node\",\n version: isBun ? Bun.version : process.version.slice(1),\n platform: os.platform(),\n arch: os.arch(),\n }\n}\n\nasync function checkTokenExists(): Promise {\n try {\n const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH)\n if (!stats.isFile()) return false\n\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\nasync function getDebugInfo(): Promise {\n const [version, tokenExists] = await Promise.all([\n getPackageVersion(),\n checkTokenExists(),\n ])\n\n return {\n version,\n runtime: getRuntimeInfo(),\n paths: {\n APP_DIR: PATHS.APP_DIR,\n GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH,\n },\n tokenExists,\n }\n}\n\nfunction printDebugInfoPlain(info: DebugInfo): void {\n consola.info(`copilot-api debug\n\nVersion: ${info.version}\nRuntime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})\n\nPaths:\n- APP_DIR: ${info.paths.APP_DIR}\n- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}\n\nToken exists: ${info.tokenExists ? \"Yes\" : \"No\"}`)\n}\n\nfunction printDebugInfoJson(info: DebugInfo): void {\n console.log(JSON.stringify(info, null, 2))\n}\n\nexport async function runDebug(options: RunDebugOptions): Promise {\n const debugInfo = await getDebugInfo()\n\n if (options.json) {\n printDebugInfoJson(debugInfo)\n } else {\n printDebugInfoPlain(debugInfo)\n }\n}\n\nexport const debug = defineCommand({\n meta: {\n name: \"debug\",\n description: \"Print debug information about the application\",\n },\n args: {\n json: {\n type: \"boolean\",\n default: false,\n description: \"Output debug information as JSON\",\n },\n },\n run({ args }) {\n return runDebug({\n json: args.json,\n })\n },\n})\n","import consola from \"consola\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\nexport function initProxyFromEnv(): void {\n if (typeof Bun !== \"undefined\") return\n\n try {\n const direct = new Agent()\n const proxies = new Map()\n\n // We only need a minimal dispatcher that implements `dispatch` at runtime.\n // Typing the object as `Dispatcher` forces TypeScript to require many\n // additional methods. Instead, keep a plain object and cast when passing\n // to `setGlobalDispatcher`.\n const dispatcher = {\n dispatch(\n options: Dispatcher.DispatchOptions,\n handler: Dispatcher.DispatchHandler,\n ) {\n try {\n const origin =\n typeof options.origin === \"string\" ?\n new URL(options.origin)\n : (options.origin as URL)\n const get = getProxyForUrl as unknown as (\n u: string,\n ) => string | undefined\n const raw = get(origin.toString())\n const proxyUrl = raw && raw.length > 0 ? raw : undefined\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n let agent = proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n proxies.set(proxyUrl, agent)\n }\n let label = proxyUrl\n try {\n const u = new URL(proxyUrl)\n label = `${u.protocol}//${u.host}`\n } catch {\n /* noop */\n }\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`)\n return (agent as unknown as Dispatcher).dispatch(options, handler)\n } catch {\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n },\n close() {\n return direct.close()\n },\n destroy() {\n return direct.destroy()\n },\n }\n\n setGlobalDispatcher(dispatcher as unknown as Dispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n } catch (err) {\n consola.debug(\"Proxy setup skipped:\", err)\n }\n}\n","import { execSync } from \"node:child_process\"\nimport process from \"node:process\"\n\ntype ShellName = \"bash\" | \"zsh\" | \"fish\" | \"powershell\" | \"cmd\" | \"sh\"\ntype EnvVars = Record\n\nfunction getShell(): ShellName {\n const { platform, ppid, env } = process\n\n if (platform === \"win32\") {\n try {\n const command = `wmic process get ParentProcessId,Name | findstr \"${ppid}\"`\n const parentProcess = execSync(command, { stdio: \"pipe\" }).toString()\n\n if (parentProcess.toLowerCase().includes(\"powershell.exe\")) {\n return \"powershell\"\n }\n } catch {\n return \"cmd\"\n }\n\n return \"cmd\"\n } else {\n const shellPath = env.SHELL\n if (shellPath) {\n if (shellPath.endsWith(\"zsh\")) return \"zsh\"\n if (shellPath.endsWith(\"fish\")) return \"fish\"\n if (shellPath.endsWith(\"bash\")) return \"bash\"\n }\n\n return \"sh\"\n }\n}\n\n/**\n * Generates a copy-pasteable script to set multiple environment variables\n * and run a subsequent command.\n * @param {EnvVars} envVars - An object of environment variables to set.\n * @param {string} commandToRun - The command to run after setting the variables.\n * @returns {string} The formatted script string.\n */\nexport function generateEnvScript(\n envVars: EnvVars,\n commandToRun: string = \"\",\n): string {\n const shell = getShell()\n const filteredEnvVars = Object.entries(envVars).filter(\n ([, value]) => value !== undefined,\n ) as Array<[string, string]>\n\n let commandBlock: string\n\n switch (shell) {\n case \"powershell\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `$env:${key} = ${value}`)\n .join(\"; \")\n break\n }\n case \"cmd\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set ${key}=${value}`)\n .join(\" & \")\n break\n }\n case \"fish\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set -gx ${key} ${value}`)\n .join(\"; \")\n break\n }\n default: {\n // bash, zsh, sh\n const assignments = filteredEnvVars\n .map(([key, value]) => `${key}=${value}`)\n .join(\" \")\n commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : \"\"\n break\n }\n }\n\n if (commandBlock && commandToRun) {\n const separator = shell === \"cmd\" ? \" & \" : \" && \"\n return `${commandBlock}${separator}${commandToRun}`\n }\n\n return commandBlock || commandToRun\n}\n","import consola from \"consola\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { state } from \"~/lib/state\"\n\nexport function handleResponsesWsUpgrade(\n req: Request,\n bunServer: { upgrade: (req: Request, opts?: object) => boolean },\n): boolean {\n if (!state.copilotToken) {\n consola.error(\"WS upgrade rejected: no Copilot token\")\n return false\n }\n\n return bunServer.upgrade(req, {\n data: { type: \"responses-proxy\" },\n })\n}\n\nexport function isResponsesWsPath(url: string): boolean {\n const path = new URL(url).pathname\n return path === \"/v1/responses\" || path === \"/responses\"\n}\n\nconst UNSUPPORTED_TOOL_TYPES = new Set([\"image_generation\"])\n\nfunction unwrapPayload(raw: Record): Record {\n let payload: Record\n if (raw.type === \"response.create\") {\n if (typeof raw.response === \"object\" && raw.response !== null) {\n payload = { ...(raw.response as Record) }\n } else {\n const { type: _, ...rest } = raw\n payload = rest\n }\n } else {\n payload = { ...raw }\n }\n\n if (Array.isArray(payload.tools)) {\n payload.tools = (payload.tools as any[]).filter(\n (t) => !UNSUPPORTED_TOOL_TYPES.has(t.type),\n )\n }\n\n payload.stream = true\n return payload\n}\n\nasync function handleRequest(ws: any, raw: Record) {\n const headers: Record = {\n ...copilotHeaders(state),\n }\n\n const url = `${copilotBaseUrl(state)}/responses`\n const body = unwrapPayload(raw)\n\n consola.debug(\"WS→HTTP POST:\", url, \"keys:\", Object.keys(body).join(\",\"))\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n })\n\n consola.debug(\"Upstream status:\", response.status, \"content-type:\", response.headers.get(\"content-type\"))\n\n if (!response.ok) {\n const errText = await response.text()\n consola.error(\"Upstream error:\", response.status, errText)\n ws.send(JSON.stringify({\n type: \"error\",\n error: { message: errText, code: response.status },\n }))\n return\n }\n\n if (!response.body) {\n consola.error(\"No response body from upstream\")\n return\n }\n\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = \"\"\n let currentEventType = \"\"\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split(\"\\n\")\n buffer = lines.pop() ?? \"\"\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n currentEventType = line.slice(7).trim()\n continue\n }\n if (line.startsWith(\"data: \")) {\n const data = line.slice(6)\n if (data === \"[DONE]\") {\n consola.debug(\"SSE stream done\")\n continue\n }\n\n let toSend = data\n if (currentEventType) {\n try {\n const parsed = JSON.parse(data)\n if (!parsed.type) {\n parsed.type = currentEventType\n toSend = JSON.stringify(parsed)\n }\n } catch {}\n }\n\n consola.debug(\"Relaying SSE→WS:\", toSend.slice(0, 150))\n try {\n ws.send(toSend)\n } catch (e) {\n consola.error(\"WS send failed:\", e)\n return\n }\n currentEventType = \"\"\n }\n }\n }\n } catch (e) {\n consola.error(\"HTTP request failed:\", e)\n try {\n ws.send(JSON.stringify({\n type: \"error\",\n error: { message: (e as Error).message },\n }))\n } catch {}\n }\n}\n\nexport const responsesWebSocket = {\n open(ws: any) {\n consola.debug(\"Client WS opened\")\n },\n\n message(ws: any, message: string | Buffer) {\n const data = typeof message === \"string\" ? message : message.toString()\n consola.debug(\"Client WS msg:\", data.slice(0, 300))\n\n try {\n const parsed = JSON.parse(data)\n handleRequest(ws, parsed)\n } catch (e) {\n consola.error(\"Bad JSON from client:\", e)\n ws.send(JSON.stringify({ type: \"error\", error: { message: \"Invalid JSON\" } }))\n }\n },\n\n close(ws: any, code: number, reason: string) {\n consola.debug(\"Client WS closed:\", code, reason)\n },\n}\n","import consola from \"consola\"\n\nimport { HTTPError } from \"./error\"\n\nexport const awaitApproval = async () => {\n const response = await consola.prompt(`Accept incoming request?`, {\n type: \"confirm\",\n })\n\n if (!response)\n throw new HTTPError(\n \"Request rejected\",\n Response.json({ message: \"Request rejected\" }, { status: 403 }),\n )\n}\n","import consola from \"consola\"\n\nimport type { State } from \"./state\"\n\nimport { HTTPError } from \"./error\"\nimport { sleep } from \"./utils\"\n\nexport async function checkRateLimit(state: State) {\n if (state.rateLimitSeconds === undefined) return\n\n const now = Date.now()\n\n if (!state.lastRequestTimestamp) {\n state.lastRequestTimestamp = now\n return\n }\n\n const elapsedSeconds = (now - state.lastRequestTimestamp) / 1000\n\n if (elapsedSeconds > state.rateLimitSeconds) {\n state.lastRequestTimestamp = now\n return\n }\n\n const waitTimeSeconds = Math.ceil(state.rateLimitSeconds - elapsedSeconds)\n\n if (!state.rateLimitWait) {\n consola.warn(\n `Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`,\n )\n throw new HTTPError(\n \"Rate limit exceeded\",\n Response.json({ message: \"Rate limit exceeded\" }, { status: 429 }),\n )\n }\n\n const waitTimeMs = waitTimeSeconds * 1000\n consola.warn(\n `Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`,\n )\n await sleep(waitTimeMs)\n // eslint-disable-next-line require-atomic-updates\n state.lastRequestTimestamp = now\n consola.info(\"Rate limit wait completed, proceeding with request\")\n return\n}\n","import type {\n ChatCompletionsPayload,\n ContentPart,\n Message,\n Tool,\n ToolCall,\n} from \"~/services/copilot/create-chat-completions\"\nimport type { Model } from \"~/services/copilot/get-models\"\n\n// Encoder type mapping\nconst ENCODING_MAP = {\n o200k_base: () => import(\"gpt-tokenizer/encoding/o200k_base\"),\n cl100k_base: () => import(\"gpt-tokenizer/encoding/cl100k_base\"),\n p50k_base: () => import(\"gpt-tokenizer/encoding/p50k_base\"),\n p50k_edit: () => import(\"gpt-tokenizer/encoding/p50k_edit\"),\n r50k_base: () => import(\"gpt-tokenizer/encoding/r50k_base\"),\n} as const\n\ntype SupportedEncoding = keyof typeof ENCODING_MAP\n\n// Define encoder interface\ninterface Encoder {\n encode: (text: string) => Array\n}\n\n// Cache loaded encoders to avoid repeated imports\nconst encodingCache = new Map()\n\n/**\n * Calculate tokens for tool calls\n */\nconst calculateToolCallsTokens = (\n toolCalls: Array,\n encoder: Encoder,\n constants: ReturnType,\n): number => {\n let tokens = 0\n for (const toolCall of toolCalls) {\n tokens += constants.funcInit\n tokens += encoder.encode(JSON.stringify(toolCall)).length\n }\n tokens += constants.funcEnd\n return tokens\n}\n\n/**\n * Calculate tokens for content parts\n */\nconst calculateContentPartsTokens = (\n contentParts: Array,\n encoder: Encoder,\n): number => {\n let tokens = 0\n for (const part of contentParts) {\n if (part.type === \"image_url\") {\n tokens += encoder.encode(part.image_url.url).length + 85\n } else if (part.text) {\n tokens += encoder.encode(part.text).length\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens for a single message\n */\nconst calculateMessageTokens = (\n message: Message,\n encoder: Encoder,\n constants: ReturnType,\n): number => {\n const tokensPerMessage = 3\n const tokensPerName = 1\n let tokens = tokensPerMessage\n for (const [key, value] of Object.entries(message)) {\n if (typeof value === \"string\") {\n tokens += encoder.encode(value).length\n }\n if (key === \"name\") {\n tokens += tokensPerName\n }\n if (key === \"tool_calls\") {\n tokens += calculateToolCallsTokens(\n value as Array,\n encoder,\n constants,\n )\n }\n if (key === \"content\" && Array.isArray(value)) {\n tokens += calculateContentPartsTokens(\n value as Array,\n encoder,\n )\n }\n }\n return tokens\n}\n\n/**\n * Calculate tokens using custom algorithm\n */\nconst calculateTokens = (\n messages: Array,\n encoder: Encoder,\n constants: ReturnType,\n): number => {\n if (messages.length === 0) {\n return 0\n }\n let numTokens = 0\n for (const message of messages) {\n numTokens += calculateMessageTokens(message, encoder, constants)\n }\n // every reply is primed with <|start|>assistant<|message|>\n numTokens += 3\n return numTokens\n}\n\n/**\n * Get the corresponding encoder module based on encoding type\n */\nconst getEncodeChatFunction = async (encoding: string): Promise => {\n if (encodingCache.has(encoding)) {\n const cached = encodingCache.get(encoding)\n if (cached) {\n return cached\n }\n }\n\n const supportedEncoding = encoding as SupportedEncoding\n if (!(supportedEncoding in ENCODING_MAP)) {\n const fallbackModule = (await ENCODING_MAP.o200k_base()) as Encoder\n encodingCache.set(encoding, fallbackModule)\n return fallbackModule\n }\n\n const encodingModule = (await ENCODING_MAP[supportedEncoding]()) as Encoder\n encodingCache.set(encoding, encodingModule)\n return encodingModule\n}\n\n/**\n * Get tokenizer type from model information\n */\nexport const getTokenizerFromModel = (model: Model): string => {\n return model.capabilities.tokenizer || \"o200k_base\"\n}\n\n/**\n * Get model-specific constants for token calculation\n */\nconst getModelConstants = (model: Model) => {\n return model.id === \"gpt-3.5-turbo\" || model.id === \"gpt-4\" ?\n {\n funcInit: 10,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n : {\n funcInit: 7,\n propInit: 3,\n propKey: 3,\n enumInit: -3,\n enumItem: 3,\n funcEnd: 12,\n }\n}\n\n/**\n * Calculate tokens for a single parameter\n */\nconst calculateParameterTokens = (\n key: string,\n prop: unknown,\n context: {\n encoder: Encoder\n constants: ReturnType\n },\n): number => {\n const { encoder, constants } = context\n let tokens = constants.propKey\n\n // Early return if prop is not an object\n if (typeof prop !== \"object\" || prop === null) {\n return tokens\n }\n\n // Type assertion for parameter properties\n const param = prop as {\n type?: string\n description?: string\n enum?: Array\n [key: string]: unknown\n }\n\n const paramName = key\n const paramType = param.type || \"string\"\n let paramDesc = param.description || \"\"\n\n // Handle enum values\n if (param.enum && Array.isArray(param.enum)) {\n tokens += constants.enumInit\n for (const item of param.enum) {\n tokens += constants.enumItem\n tokens += encoder.encode(String(item)).length\n }\n }\n\n // Clean up description\n if (paramDesc.endsWith(\".\")) {\n paramDesc = paramDesc.slice(0, -1)\n }\n\n // Encode the main parameter line\n const line = `${paramName}:${paramType}:${paramDesc}`\n tokens += encoder.encode(line).length\n\n // Handle additional properties (excluding standard ones)\n const excludedKeys = new Set([\"type\", \"description\", \"enum\"])\n for (const propertyName of Object.keys(param)) {\n if (!excludedKeys.has(propertyName)) {\n const propertyValue = param[propertyName]\n const propertyText =\n typeof propertyValue === \"string\" ? propertyValue : (\n JSON.stringify(propertyValue)\n )\n tokens += encoder.encode(`${propertyName}:${propertyText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for function parameters\n */\nconst calculateParametersTokens = (\n parameters: unknown,\n encoder: Encoder,\n constants: ReturnType,\n): number => {\n if (!parameters || typeof parameters !== \"object\") {\n return 0\n }\n\n const params = parameters as Record\n let tokens = 0\n\n for (const [key, value] of Object.entries(params)) {\n if (key === \"properties\") {\n const properties = value as Record\n if (Object.keys(properties).length > 0) {\n tokens += constants.propInit\n for (const propKey of Object.keys(properties)) {\n tokens += calculateParameterTokens(propKey, properties[propKey], {\n encoder,\n constants,\n })\n }\n }\n } else {\n const paramText =\n typeof value === \"string\" ? value : JSON.stringify(value)\n tokens += encoder.encode(`${key}:${paramText}`).length\n }\n }\n\n return tokens\n}\n\n/**\n * Calculate tokens for a single tool\n */\nconst calculateToolTokens = (\n tool: Tool,\n encoder: Encoder,\n constants: ReturnType,\n): number => {\n let tokens = constants.funcInit\n const func = tool.function\n const fName = func.name\n let fDesc = func.description || \"\"\n if (fDesc.endsWith(\".\")) {\n fDesc = fDesc.slice(0, -1)\n }\n const line = fName + \":\" + fDesc\n tokens += encoder.encode(line).length\n if (\n typeof func.parameters === \"object\" // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n && func.parameters !== null\n ) {\n tokens += calculateParametersTokens(func.parameters, encoder, constants)\n }\n return tokens\n}\n\n/**\n * Calculate token count for tools based on model\n */\nexport const numTokensForTools = (\n tools: Array,\n encoder: Encoder,\n constants: ReturnType,\n): number => {\n let funcTokenCount = 0\n for (const tool of tools) {\n funcTokenCount += calculateToolTokens(tool, encoder, constants)\n }\n funcTokenCount += constants.funcEnd\n return funcTokenCount\n}\n\n/**\n * Calculate the token count of messages, supporting multiple GPT encoders\n */\nexport const getTokenCount = async (\n payload: ChatCompletionsPayload,\n model: Model,\n): Promise<{ input: number; output: number }> => {\n // Get tokenizer string\n const tokenizer = getTokenizerFromModel(model)\n\n // Get corresponding encoder module\n const encoder = await getEncodeChatFunction(tokenizer)\n\n const simplifiedMessages = payload.messages\n const inputMessages = simplifiedMessages.filter(\n (msg) => msg.role !== \"assistant\",\n )\n const outputMessages = simplifiedMessages.filter(\n (msg) => msg.role === \"assistant\",\n )\n\n const constants = getModelConstants(model)\n let inputTokens = calculateTokens(inputMessages, encoder, constants)\n if (payload.tools && payload.tools.length > 0) {\n inputTokens += numTokensForTools(payload.tools, encoder, constants)\n }\n const outputTokens = calculateTokens(outputMessages, encoder, constants)\n\n return {\n input: inputTokens,\n output: outputTokens,\n }\n}\n","import consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const createChatCompletions = async (\n payload: ChatCompletionsPayload,\n) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const enableVision = payload.messages.some(\n (x) =>\n typeof x.content !== \"string\"\n && x.content?.some((x) => x.type === \"image_url\"),\n )\n\n // Agent/user check for X-Initiator header\n // Determine if any message is from an agent (\"assistant\" or \"tool\")\n const isAgentCall = payload.messages.some((msg) =>\n [\"assistant\", \"tool\"].includes(msg.role),\n )\n\n // Build headers and add X-Initiator\n const headers: Record = {\n ...copilotHeaders(state, enableVision),\n \"X-Initiator\": isAgentCall ? \"agent\" : \"user\",\n }\n\n const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n consola.error(\"Failed to create chat completions\", response)\n throw new HTTPError(\"Failed to create chat completions\", response)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ChatCompletionResponse\n}\n\n// Streaming types\n\nexport interface ChatCompletionChunk {\n id: string\n object: \"chat.completion.chunk\"\n created: number\n model: string\n choices: Array\n system_fingerprint?: string\n usage?: {\n prompt_tokens: number\n completion_tokens: number\n total_tokens: number\n prompt_tokens_details?: {\n cached_tokens: number\n }\n completion_tokens_details?: {\n accepted_prediction_tokens: number\n rejected_prediction_tokens: number\n }\n }\n}\n\ninterface Delta {\n content?: string | null\n role?: \"user\" | \"assistant\" | \"system\" | \"tool\"\n tool_calls?: Array<{\n index: number\n id?: string\n type?: \"function\"\n function?: {\n name?: string\n arguments?: string\n }\n }>\n}\n\ninterface Choice {\n index: number\n delta: Delta\n finish_reason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null\n logprobs: object | null\n}\n\n// Non-streaming types\n\nexport interface ChatCompletionResponse {\n id: string\n object: \"chat.completion\"\n created: number\n model: string\n choices: Array\n system_fingerprint?: string\n usage?: {\n prompt_tokens: number\n completion_tokens: number\n total_tokens: number\n prompt_tokens_details?: {\n cached_tokens: number\n }\n }\n}\n\ninterface ResponseMessage {\n role: \"assistant\"\n content: string | null\n tool_calls?: Array\n}\n\ninterface ChoiceNonStreaming {\n index: number\n message: ResponseMessage\n logprobs: object | null\n finish_reason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\"\n}\n\n// Payload types\n\nexport interface ChatCompletionsPayload {\n messages: Array\n model: string\n temperature?: number | null\n top_p?: number | null\n max_tokens?: number | null\n stop?: string | Array | null\n n?: number | null\n stream?: boolean | null\n\n frequency_penalty?: number | null\n presence_penalty?: number | null\n logit_bias?: Record | null\n logprobs?: boolean | null\n response_format?: { type: \"json_object\" } | null\n seed?: number | null\n tools?: Array | null\n tool_choice?:\n | \"none\"\n | \"auto\"\n | \"required\"\n | { type: \"function\"; function: { name: string } }\n | null\n user?: string | null\n}\n\nexport interface Tool {\n type: \"function\"\n function: {\n name: string\n description?: string\n parameters: Record\n }\n}\n\nexport interface Message {\n role: \"user\" | \"assistant\" | \"system\" | \"tool\" | \"developer\"\n content: string | Array | null\n\n name?: string\n tool_calls?: Array\n tool_call_id?: string\n}\n\nexport interface ToolCall {\n id: string\n type: \"function\"\n function: {\n name: string\n arguments: string\n }\n}\n\nexport type ContentPart = TextPart | ImagePart\n\nexport interface TextPart {\n type: \"text\"\n text: string\n}\n\nexport interface ImagePart {\n type: \"image_url\"\n image_url: {\n url: string\n detail?: \"low\" | \"high\" | \"auto\"\n }\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { streamSSE, type SSEMessage } from \"hono/streaming\"\n\nimport { awaitApproval } from \"~/lib/approval\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { state } from \"~/lib/state\"\nimport { getTokenCount } from \"~/lib/tokenizer\"\nimport { isNullish } from \"~/lib/utils\"\nimport {\n createChatCompletions,\n type ChatCompletionResponse,\n type ChatCompletionsPayload,\n} from \"~/services/copilot/create-chat-completions\"\n\nexport async function handleCompletion(c: Context) {\n await checkRateLimit(state)\n\n let payload = await c.req.json()\n consola.debug(\"Request payload:\", JSON.stringify(payload).slice(-400))\n\n // Find the selected model\n const selectedModel = state.models?.data.find(\n (model) => model.id === payload.model,\n )\n\n // Calculate and display token count\n try {\n if (selectedModel) {\n const tokenCount = await getTokenCount(payload, selectedModel)\n consola.info(\"Current token count:\", tokenCount)\n } else {\n consola.warn(\"No model selected, skipping token count calculation\")\n }\n } catch (error) {\n consola.warn(\"Failed to calculate token count:\", error)\n }\n\n if (state.manualApprove) await awaitApproval()\n\n if (isNullish(payload.max_tokens)) {\n payload = {\n ...payload,\n max_tokens: selectedModel?.capabilities.limits.max_output_tokens,\n }\n consola.debug(\"Set max_tokens to:\", JSON.stringify(payload.max_tokens))\n }\n\n const response = await createChatCompletions(payload)\n\n if (isNonStreaming(response)) {\n consola.debug(\"Non-streaming response:\", JSON.stringify(response))\n return c.json(response)\n }\n\n consola.debug(\"Streaming response\")\n return streamSSE(c, async (stream) => {\n for await (const chunk of response) {\n consola.debug(\"Streaming chunk:\", JSON.stringify(chunk))\n await stream.writeSSE(chunk as SSEMessage)\n }\n })\n}\n\nconst isNonStreaming = (\n response: Awaited>,\n): response is ChatCompletionResponse => Object.hasOwn(response, \"choices\")\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleCompletion } from \"./handler\"\n\nexport const completionRoutes = new Hono()\n\ncompletionRoutes.post(\"/\", async (c) => {\n try {\n return await handleCompletion(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const createEmbeddings = async (payload: EmbeddingRequest) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, {\n method: \"POST\",\n headers: copilotHeaders(state),\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to create embeddings\", response)\n\n return (await response.json()) as EmbeddingResponse\n}\n\nexport interface EmbeddingRequest {\n input: string | Array\n model: string\n}\n\nexport interface Embedding {\n object: string\n embedding: Array\n index: number\n}\n\nexport interface EmbeddingResponse {\n object: string\n data: Array\n model: string\n usage: {\n prompt_tokens: number\n total_tokens: number\n }\n}\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport {\n createEmbeddings,\n type EmbeddingRequest,\n} from \"~/services/copilot/create-embeddings\"\n\nexport const embeddingRoutes = new Hono()\n\nembeddingRoutes.post(\"/\", async (c) => {\n try {\n const paylod = await c.req.json()\n const response = await createEmbeddings(paylod)\n\n return c.json(response)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { type AnthropicResponse } from \"./anthropic-types\"\n\nexport function mapOpenAIStopReasonToAnthropic(\n finishReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null,\n): AnthropicResponse[\"stop_reason\"] {\n if (finishReason === null) {\n return null\n }\n const stopReasonMap = {\n stop: \"end_turn\",\n length: \"max_tokens\",\n tool_calls: \"tool_use\",\n content_filter: \"end_turn\",\n } as const\n return stopReasonMap[finishReason]\n}\n","import {\n type ChatCompletionResponse,\n type ChatCompletionsPayload,\n type ContentPart,\n type Message,\n type TextPart,\n type Tool,\n type ToolCall,\n} from \"~/services/copilot/create-chat-completions\"\n\nimport {\n type AnthropicAssistantContentBlock,\n type AnthropicAssistantMessage,\n type AnthropicMessage,\n type AnthropicMessagesPayload,\n type AnthropicResponse,\n type AnthropicTextBlock,\n type AnthropicThinkingBlock,\n type AnthropicTool,\n type AnthropicToolResultBlock,\n type AnthropicToolUseBlock,\n type AnthropicUserContentBlock,\n type AnthropicUserMessage,\n} from \"./anthropic-types\"\nimport { mapOpenAIStopReasonToAnthropic } from \"./utils\"\n\n// Payload translation\n\nexport function translateToOpenAI(\n payload: AnthropicMessagesPayload,\n): ChatCompletionsPayload {\n return {\n model: translateModelName(payload.model),\n messages: translateAnthropicMessagesToOpenAI(\n payload.messages,\n payload.system,\n ),\n max_tokens: payload.max_tokens,\n stop: payload.stop_sequences,\n stream: payload.stream,\n temperature: payload.temperature,\n top_p: payload.top_p,\n user: payload.metadata?.user_id,\n tools: translateAnthropicToolsToOpenAI(payload.tools),\n tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice),\n }\n}\n\nfunction translateModelName(model: string): string {\n // Subagent requests use a specific model number which Copilot doesn't support\n if (model.startsWith(\"claude-sonnet-4-\")) {\n return model.replace(/^claude-sonnet-4-.*/, \"claude-sonnet-4\")\n } else if (model.startsWith(\"claude-opus-\")) {\n return model.replace(/^claude-opus-4-.*/, \"claude-opus-4\")\n }\n return model\n}\n\nfunction translateAnthropicMessagesToOpenAI(\n anthropicMessages: Array,\n system: string | Array | undefined,\n): Array {\n const systemMessages = handleSystemPrompt(system)\n\n const otherMessages = anthropicMessages.flatMap((message) =>\n message.role === \"user\" ?\n handleUserMessage(message)\n : handleAssistantMessage(message),\n )\n\n return [...systemMessages, ...otherMessages]\n}\n\nfunction handleSystemPrompt(\n system: string | Array | undefined,\n): Array {\n if (!system) {\n return []\n }\n\n if (typeof system === \"string\") {\n return [{ role: \"system\", content: system }]\n } else {\n const systemText = system.map((block) => block.text).join(\"\\n\\n\")\n return [{ role: \"system\", content: systemText }]\n }\n}\n\nfunction handleUserMessage(message: AnthropicUserMessage): Array {\n const newMessages: Array = []\n\n if (Array.isArray(message.content)) {\n const toolResultBlocks = message.content.filter(\n (block): block is AnthropicToolResultBlock =>\n block.type === \"tool_result\",\n )\n const otherBlocks = message.content.filter(\n (block) => block.type !== \"tool_result\",\n )\n\n // Tool results must come first to maintain protocol: tool_use -> tool_result -> user\n for (const block of toolResultBlocks) {\n newMessages.push({\n role: \"tool\",\n tool_call_id: block.tool_use_id,\n content: mapContent(block.content),\n })\n }\n\n if (otherBlocks.length > 0) {\n newMessages.push({\n role: \"user\",\n content: mapContent(otherBlocks),\n })\n }\n } else {\n newMessages.push({\n role: \"user\",\n content: mapContent(message.content),\n })\n }\n\n return newMessages\n}\n\nfunction handleAssistantMessage(\n message: AnthropicAssistantMessage,\n): Array {\n if (!Array.isArray(message.content)) {\n return [\n {\n role: \"assistant\",\n content: mapContent(message.content),\n },\n ]\n }\n\n const toolUseBlocks = message.content.filter(\n (block): block is AnthropicToolUseBlock => block.type === \"tool_use\",\n )\n\n const textBlocks = message.content.filter(\n (block): block is AnthropicTextBlock => block.type === \"text\",\n )\n\n const thinkingBlocks = message.content.filter(\n (block): block is AnthropicThinkingBlock => block.type === \"thinking\",\n )\n\n // Combine text and thinking blocks, as OpenAI doesn't have separate thinking blocks\n const allTextContent = [\n ...textBlocks.map((b) => b.text),\n ...thinkingBlocks.map((b) => b.thinking),\n ].join(\"\\n\\n\")\n\n return toolUseBlocks.length > 0 ?\n [\n {\n role: \"assistant\",\n content: allTextContent || null,\n tool_calls: toolUseBlocks.map((toolUse) => ({\n id: toolUse.id,\n type: \"function\",\n function: {\n name: toolUse.name,\n arguments: JSON.stringify(toolUse.input),\n },\n })),\n },\n ]\n : [\n {\n role: \"assistant\",\n content: mapContent(message.content),\n },\n ]\n}\n\nfunction mapContent(\n content:\n | string\n | Array,\n): string | Array | null {\n if (typeof content === \"string\") {\n return content\n }\n if (!Array.isArray(content)) {\n return null\n }\n\n const hasImage = content.some((block) => block.type === \"image\")\n if (!hasImage) {\n return content\n .filter(\n (block): block is AnthropicTextBlock | AnthropicThinkingBlock =>\n block.type === \"text\" || block.type === \"thinking\",\n )\n .map((block) => (block.type === \"text\" ? block.text : block.thinking))\n .join(\"\\n\\n\")\n }\n\n const contentParts: Array = []\n for (const block of content) {\n switch (block.type) {\n case \"text\": {\n contentParts.push({ type: \"text\", text: block.text })\n\n break\n }\n case \"thinking\": {\n contentParts.push({ type: \"text\", text: block.thinking })\n\n break\n }\n case \"image\": {\n contentParts.push({\n type: \"image_url\",\n image_url: {\n url: `data:${block.source.media_type};base64,${block.source.data}`,\n },\n })\n\n break\n }\n // No default\n }\n }\n return contentParts\n}\n\nfunction translateAnthropicToolsToOpenAI(\n anthropicTools: Array | undefined,\n): Array | undefined {\n if (!anthropicTools) {\n return undefined\n }\n return anthropicTools.map((tool) => ({\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input_schema,\n },\n }))\n}\n\nfunction translateAnthropicToolChoiceToOpenAI(\n anthropicToolChoice: AnthropicMessagesPayload[\"tool_choice\"],\n): ChatCompletionsPayload[\"tool_choice\"] {\n if (!anthropicToolChoice) {\n return undefined\n }\n\n switch (anthropicToolChoice.type) {\n case \"auto\": {\n return \"auto\"\n }\n case \"any\": {\n return \"required\"\n }\n case \"tool\": {\n if (anthropicToolChoice.name) {\n return {\n type: \"function\",\n function: { name: anthropicToolChoice.name },\n }\n }\n return undefined\n }\n case \"none\": {\n return \"none\"\n }\n default: {\n return undefined\n }\n }\n}\n\n// Response translation\n\nexport function translateToAnthropic(\n response: ChatCompletionResponse,\n): AnthropicResponse {\n // Merge content from all choices\n const allTextBlocks: Array = []\n const allToolUseBlocks: Array = []\n let stopReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null =\n null // default\n stopReason = response.choices[0]?.finish_reason ?? stopReason\n\n // Process all choices to extract text and tool use blocks\n for (const choice of response.choices) {\n const textBlocks = getAnthropicTextBlocks(choice.message.content)\n const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls)\n\n allTextBlocks.push(...textBlocks)\n allToolUseBlocks.push(...toolUseBlocks)\n\n // Use the finish_reason from the first choice, or prioritize tool_calls\n if (choice.finish_reason === \"tool_calls\" || stopReason === \"stop\") {\n stopReason = choice.finish_reason\n }\n }\n\n // Note: GitHub Copilot doesn't generate thinking blocks, so we don't include them in responses\n\n return {\n id: response.id,\n type: \"message\",\n role: \"assistant\",\n model: response.model,\n content: [...allTextBlocks, ...allToolUseBlocks],\n stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),\n stop_sequence: null,\n usage: {\n input_tokens:\n (response.usage?.prompt_tokens ?? 0)\n - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: response.usage?.completion_tokens ?? 0,\n ...(response.usage?.prompt_tokens_details?.cached_tokens\n !== undefined && {\n cache_read_input_tokens:\n response.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n }\n}\n\nfunction getAnthropicTextBlocks(\n messageContent: Message[\"content\"],\n): Array {\n if (typeof messageContent === \"string\") {\n return [{ type: \"text\", text: messageContent }]\n }\n\n if (Array.isArray(messageContent)) {\n return messageContent\n .filter((part): part is TextPart => part.type === \"text\")\n .map((part) => ({ type: \"text\", text: part.text }))\n }\n\n return []\n}\n\nfunction getAnthropicToolUseBlocks(\n toolCalls: Array | undefined,\n): Array {\n if (!toolCalls) {\n return []\n }\n return toolCalls.map((toolCall) => ({\n type: \"tool_use\",\n id: toolCall.id,\n name: toolCall.function.name,\n input: JSON.parse(toolCall.function.arguments) as Record,\n }))\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\n\nimport { state } from \"~/lib/state\"\nimport { getTokenCount } from \"~/lib/tokenizer\"\n\nimport { type AnthropicMessagesPayload } from \"./anthropic-types\"\nimport { translateToOpenAI } from \"./non-stream-translation\"\n\n/**\n * Handles token counting for Anthropic messages\n */\nexport async function handleCountTokens(c: Context) {\n try {\n const anthropicBeta = c.req.header(\"anthropic-beta\")\n\n const anthropicPayload = await c.req.json()\n\n const openAIPayload = translateToOpenAI(anthropicPayload)\n\n const selectedModel = state.models?.data.find(\n (model) => model.id === anthropicPayload.model,\n )\n\n if (!selectedModel) {\n consola.warn(\"Model not found, returning default token count\")\n return c.json({\n input_tokens: 1,\n })\n }\n\n const tokenCount = await getTokenCount(openAIPayload, selectedModel)\n\n if (anthropicPayload.tools && anthropicPayload.tools.length > 0) {\n let mcpToolExist = false\n if (anthropicBeta?.startsWith(\"claude-code\")) {\n mcpToolExist = anthropicPayload.tools.some((tool) =>\n tool.name.startsWith(\"mcp__\"),\n )\n }\n if (!mcpToolExist) {\n if (anthropicPayload.model.startsWith(\"claude\")) {\n // https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview#pricing\n tokenCount.input = tokenCount.input + 346\n } else if (anthropicPayload.model.startsWith(\"grok\")) {\n tokenCount.input = tokenCount.input + 480\n }\n }\n }\n\n let finalTokenCount = tokenCount.input + tokenCount.output\n if (anthropicPayload.model.startsWith(\"claude\")) {\n finalTokenCount = Math.round(finalTokenCount * 1.15)\n } else if (anthropicPayload.model.startsWith(\"grok\")) {\n finalTokenCount = Math.round(finalTokenCount * 1.03)\n }\n\n consola.info(\"Token count:\", finalTokenCount)\n\n return c.json({\n input_tokens: finalTokenCount,\n })\n } catch (error) {\n consola.error(\"Error counting tokens:\", error)\n return c.json({\n input_tokens: 1,\n })\n }\n}\n","import { type ChatCompletionChunk } from \"~/services/copilot/create-chat-completions\"\n\nimport {\n type AnthropicStreamEventData,\n type AnthropicStreamState,\n} from \"./anthropic-types\"\nimport { mapOpenAIStopReasonToAnthropic } from \"./utils\"\n\nfunction isToolBlockOpen(state: AnthropicStreamState): boolean {\n if (!state.contentBlockOpen) {\n return false\n }\n // Check if the current block index corresponds to any known tool call\n return Object.values(state.toolCalls).some(\n (tc) => tc.anthropicBlockIndex === state.contentBlockIndex,\n )\n}\n\n// eslint-disable-next-line max-lines-per-function, complexity\nexport function translateChunkToAnthropicEvents(\n chunk: ChatCompletionChunk,\n state: AnthropicStreamState,\n): Array {\n const events: Array = []\n\n if (chunk.choices.length === 0) {\n return events\n }\n\n const choice = chunk.choices[0]\n const { delta } = choice\n\n if (!state.messageStartSent) {\n events.push({\n type: \"message_start\",\n message: {\n id: chunk.id,\n type: \"message\",\n role: \"assistant\",\n content: [],\n model: chunk.model,\n stop_reason: null,\n stop_sequence: null,\n usage: {\n input_tokens:\n (chunk.usage?.prompt_tokens ?? 0)\n - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: 0, // Will be updated in message_delta when finished\n ...(chunk.usage?.prompt_tokens_details?.cached_tokens\n !== undefined && {\n cache_read_input_tokens:\n chunk.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n },\n })\n state.messageStartSent = true\n }\n\n if (delta.content) {\n if (isToolBlockOpen(state)) {\n // A tool block was open, so close it before starting a text block.\n events.push({\n type: \"content_block_stop\",\n index: state.contentBlockIndex,\n })\n state.contentBlockIndex++\n state.contentBlockOpen = false\n }\n\n if (!state.contentBlockOpen) {\n events.push({\n type: \"content_block_start\",\n index: state.contentBlockIndex,\n content_block: {\n type: \"text\",\n text: \"\",\n },\n })\n state.contentBlockOpen = true\n }\n\n events.push({\n type: \"content_block_delta\",\n index: state.contentBlockIndex,\n delta: {\n type: \"text_delta\",\n text: delta.content,\n },\n })\n }\n\n if (delta.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n if (toolCall.id && toolCall.function?.name) {\n // New tool call starting.\n if (state.contentBlockOpen) {\n // Close any previously open block.\n events.push({\n type: \"content_block_stop\",\n index: state.contentBlockIndex,\n })\n state.contentBlockIndex++\n state.contentBlockOpen = false\n }\n\n const anthropicBlockIndex = state.contentBlockIndex\n state.toolCalls[toolCall.index] = {\n id: toolCall.id,\n name: toolCall.function.name,\n anthropicBlockIndex,\n }\n\n events.push({\n type: \"content_block_start\",\n index: anthropicBlockIndex,\n content_block: {\n type: \"tool_use\",\n id: toolCall.id,\n name: toolCall.function.name,\n input: {},\n },\n })\n state.contentBlockOpen = true\n }\n\n if (toolCall.function?.arguments) {\n const toolCallInfo = state.toolCalls[toolCall.index]\n // Tool call can still be empty\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (toolCallInfo) {\n events.push({\n type: \"content_block_delta\",\n index: toolCallInfo.anthropicBlockIndex,\n delta: {\n type: \"input_json_delta\",\n partial_json: toolCall.function.arguments,\n },\n })\n }\n }\n }\n }\n\n if (choice.finish_reason) {\n if (state.contentBlockOpen) {\n events.push({\n type: \"content_block_stop\",\n index: state.contentBlockIndex,\n })\n state.contentBlockOpen = false\n }\n\n events.push(\n {\n type: \"message_delta\",\n delta: {\n stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason),\n stop_sequence: null,\n },\n usage: {\n input_tokens:\n (chunk.usage?.prompt_tokens ?? 0)\n - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),\n output_tokens: chunk.usage?.completion_tokens ?? 0,\n ...(chunk.usage?.prompt_tokens_details?.cached_tokens\n !== undefined && {\n cache_read_input_tokens:\n chunk.usage.prompt_tokens_details.cached_tokens,\n }),\n },\n },\n {\n type: \"message_stop\",\n },\n )\n }\n\n return events\n}\n\nexport function translateErrorToAnthropicErrorEvent(): AnthropicStreamEventData {\n return {\n type: \"error\",\n error: {\n type: \"api_error\",\n message: \"An unexpected error occurred during streaming.\",\n },\n }\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { streamSSE } from \"hono/streaming\"\n\nimport { awaitApproval } from \"~/lib/approval\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { state } from \"~/lib/state\"\nimport {\n createChatCompletions,\n type ChatCompletionChunk,\n type ChatCompletionResponse,\n} from \"~/services/copilot/create-chat-completions\"\n\nimport {\n type AnthropicMessagesPayload,\n type AnthropicStreamState,\n} from \"./anthropic-types\"\nimport {\n translateToAnthropic,\n translateToOpenAI,\n} from \"./non-stream-translation\"\nimport { translateChunkToAnthropicEvents } from \"./stream-translation\"\n\nexport async function handleCompletion(c: Context) {\n await checkRateLimit(state)\n\n const anthropicPayload = await c.req.json()\n consola.debug(\"Anthropic request payload:\", JSON.stringify(anthropicPayload))\n\n const openAIPayload = translateToOpenAI(anthropicPayload)\n consola.debug(\n \"Translated OpenAI request payload:\",\n JSON.stringify(openAIPayload),\n )\n\n if (state.manualApprove) {\n await awaitApproval()\n }\n\n const response = await createChatCompletions(openAIPayload)\n\n if (isNonStreaming(response)) {\n consola.debug(\n \"Non-streaming response from Copilot:\",\n JSON.stringify(response).slice(-400),\n )\n const anthropicResponse = translateToAnthropic(response)\n consola.debug(\n \"Translated Anthropic response:\",\n JSON.stringify(anthropicResponse),\n )\n return c.json(anthropicResponse)\n }\n\n consola.debug(\"Streaming response from Copilot\")\n return streamSSE(c, async (stream) => {\n const streamState: AnthropicStreamState = {\n messageStartSent: false,\n contentBlockIndex: 0,\n contentBlockOpen: false,\n toolCalls: {},\n }\n\n for await (const rawEvent of response) {\n consola.debug(\"Copilot raw stream event:\", JSON.stringify(rawEvent))\n if (rawEvent.data === \"[DONE]\") {\n break\n }\n\n if (!rawEvent.data) {\n continue\n }\n\n const chunk = JSON.parse(rawEvent.data) as ChatCompletionChunk\n const events = translateChunkToAnthropicEvents(chunk, streamState)\n\n for (const event of events) {\n consola.debug(\"Translated Anthropic event:\", JSON.stringify(event))\n await stream.writeSSE({\n event: event.type,\n data: JSON.stringify(event),\n })\n }\n }\n })\n}\n\nconst isNonStreaming = (\n response: Awaited>,\n): response is ChatCompletionResponse => Object.hasOwn(response, \"choices\")\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleCountTokens } from \"./count-tokens-handler\"\nimport { handleCompletion } from \"./handler\"\n\nexport const messageRoutes = new Hono()\n\nmessageRoutes.post(\"/\", async (c) => {\n try {\n return await handleCompletion(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n\nmessageRoutes.post(\"/count_tokens\", async (c) => {\n try {\n return await handleCountTokens(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\nimport { cacheModels } from \"~/lib/utils\"\n\nexport const modelRoutes = new Hono()\n\nmodelRoutes.get(\"/\", async (c) => {\n try {\n if (!state.models) {\n // This should be handled by startup logic, but as a fallback.\n await cacheModels()\n }\n\n const openaiModels = state.models?.data.map((model) => ({\n id: model.id,\n object: \"model\",\n type: \"model\",\n created: 0,\n created_at: new Date(0).toISOString(),\n owned_by: model.vendor,\n display_name: model.name,\n }))\n\n const codexModels = state.models?.data.map((model) => ({\n slug: model.id,\n display_name: model.name,\n description: \"\",\n default_reasoning_level: \"medium\",\n supported_reasoning_levels: [\n { effort: \"low\", description: \"Fast responses\" },\n { effort: \"medium\", description: \"Balanced\" },\n { effort: \"high\", description: \"Deep reasoning\" },\n { effort: \"xhigh\", description: \"Maximum reasoning\" },\n ],\n shell_type: \"shell_command\",\n visibility: \"list\",\n supported_in_api: true,\n priority: 0,\n additional_speed_tiers: [],\n availability_nux: null,\n upgrade: null,\n }))\n\n return c.json({\n object: \"list\",\n data: openaiModels,\n models: codexModels,\n has_more: false,\n })\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import consola from \"consola\"\nimport { events } from \"fetch-event-stream\"\n\nimport { copilotHeaders, copilotBaseUrl } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const createResponses = async (payload: ResponsesPayload) => {\n if (!state.copilotToken) throw new Error(\"Copilot token not found\")\n\n const headers: Record = {\n ...copilotHeaders(state),\n \"X-Initiator\": \"user\",\n }\n\n const response = await fetch(`${copilotBaseUrl(state)}/responses`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n consola.error(\"Failed to create responses\", response)\n throw new HTTPError(\"Failed to create responses\", response)\n }\n\n if (payload.stream) {\n return events(response)\n }\n\n return (await response.json()) as ResponsesResponse\n}\n\n// Payload types\n\nexport interface ResponsesInputMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"developer\"\n content: string | Array\n}\n\nexport interface ResponsesContentPart {\n type: string\n text?: string\n [key: string]: unknown\n}\n\nexport interface ResponsesTool {\n name: string\n description?: string\n parameters?: Record\n type: \"function\"\n strict?: boolean\n}\n\nexport interface ResponsesReasoning {\n effort?: \"low\" | \"medium\" | \"high\" | \"xhigh\"\n summary?: \"auto\" | \"detailed\" | \"none\"\n}\n\nexport interface ResponsesPayload {\n model: string\n input: Array | string\n stream?: boolean\n tools?: Array\n max_output_tokens?: number\n store?: boolean\n truncation?: string\n reasoning?: ResponsesReasoning\n include?: Array\n temperature?: number\n top_p?: number\n [key: string]: unknown\n}\n\n// Response types\n\nexport interface ResponsesResponse {\n id: string\n object: string\n model: string\n output: Array>\n usage?: {\n input_tokens: number\n output_tokens: number\n total_tokens: number\n }\n [key: string]: unknown\n}\n","import type { Context } from \"hono\"\n\nimport consola from \"consola\"\nimport { streamSSE, type SSEMessage } from \"hono/streaming\"\n\nimport { awaitApproval } from \"~/lib/approval\"\nimport { checkRateLimit } from \"~/lib/rate-limit\"\nimport { state } from \"~/lib/state\"\nimport {\n createResponses,\n type ResponsesPayload,\n type ResponsesResponse,\n} from \"~/services/copilot/create-responses\"\n\nexport async function handleResponses(c: Context) {\n await checkRateLimit(state)\n\n const payload = await c.req.json()\n consola.debug(\"Responses API request payload:\", JSON.stringify(payload).slice(-400))\n\n if (state.manualApprove) await awaitApproval()\n\n const response = await createResponses(payload)\n\n if (isNonStreaming(response)) {\n consola.debug(\"Non-streaming responses:\", JSON.stringify(response).slice(-400))\n return c.json(response)\n }\n\n consola.debug(\"Streaming responses\")\n return streamSSE(c, async (stream) => {\n for await (const chunk of response) {\n consola.debug(\"Responses stream chunk:\", JSON.stringify(chunk))\n await stream.writeSSE(chunk as SSEMessage)\n }\n })\n}\n\nconst isNonStreaming = (\n response: Awaited>,\n): response is ResponsesResponse =>\n Object.hasOwn(response, \"output\") || Object.hasOwn(response, \"id\")\n","import { Hono } from \"hono\"\n\nimport { forwardError } from \"~/lib/error\"\n\nimport { handleResponses } from \"./handler\"\n\nexport const responsesRoutes = new Hono()\n\nresponsesRoutes.post(\"/\", async (c) => {\n try {\n return await handleResponses(c)\n } catch (error) {\n return await forwardError(c, error)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { state } from \"~/lib/state\"\n\nexport const tokenRoute = new Hono()\n\ntokenRoute.get(\"/\", (c) => {\n try {\n return c.json({\n token: state.copilotToken,\n })\n } catch (error) {\n console.error(\"Error fetching token:\", error)\n return c.json({ error: \"Failed to fetch token\", token: null }, 500)\n }\n})\n","import { Hono } from \"hono\"\n\nimport { getCopilotUsage } from \"~/services/github/get-copilot-usage\"\n\nexport const usageRoute = new Hono()\n\nusageRoute.get(\"/\", async (c) => {\n try {\n const usage = await getCopilotUsage()\n return c.json(usage)\n } catch (error) {\n console.error(\"Error fetching Copilot usage:\", error)\n return c.json({ error: \"Failed to fetch Copilot usage\" }, 500)\n }\n})\n","import { Hono } from \"hono\"\nimport { cors } from \"hono/cors\"\nimport { logger } from \"hono/logger\"\n\nimport { completionRoutes } from \"./routes/chat-completions/route\"\nimport { embeddingRoutes } from \"./routes/embeddings/route\"\nimport { messageRoutes } from \"./routes/messages/route\"\nimport { modelRoutes } from \"./routes/models/route\"\nimport { responsesRoutes } from \"./routes/responses/route\"\nimport { tokenRoute } from \"./routes/token/route\"\nimport { usageRoute } from \"./routes/usage/route\"\n\nexport const server = new Hono()\n\nserver.use(logger())\nserver.use(cors())\n\nserver.get(\"/\", (c) => c.text(\"Server running\"))\n\nserver.route(\"/chat/completions\", completionRoutes)\nserver.route(\"/responses\", responsesRoutes)\nserver.route(\"/models\", modelRoutes)\nserver.route(\"/embeddings\", embeddingRoutes)\nserver.route(\"/usage\", usageRoute)\nserver.route(\"/token\", tokenRoute)\n\n// Compatibility with tools that expect v1/ prefix\nserver.route(\"/v1/chat/completions\", completionRoutes)\nserver.route(\"/v1/responses\", responsesRoutes)\nserver.route(\"/v1/models\", modelRoutes)\nserver.route(\"/v1/embeddings\", embeddingRoutes)\n\n// Anthropic compatible endpoints\nserver.route(\"/v1/messages\", messageRoutes)\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport clipboard from \"clipboardy\"\nimport consola from \"consola\"\nimport invariant from \"tiny-invariant\"\n\nimport { ensurePaths } from \"./lib/paths\"\nimport { initProxyFromEnv } from \"./lib/proxy\"\nimport { generateEnvScript } from \"./lib/shell\"\nimport { state } from \"./lib/state\"\nimport { setupCopilotToken, setupGitHubToken } from \"./lib/token\"\nimport { cacheModels, cacheVSCodeVersion } from \"./lib/utils\"\nimport {\n handleResponsesWsUpgrade,\n isResponsesWsPath,\n responsesWebSocket,\n} from \"./routes/responses/ws-proxy\"\nimport { server } from \"./server\"\n\ninterface RunServerOptions {\n port: number\n verbose: boolean\n accountType: string\n manual: boolean\n rateLimit?: number\n rateLimitWait: boolean\n githubToken?: string\n claudeCode: boolean\n showToken: boolean\n proxyEnv: boolean\n}\n\nexport async function runServer(options: RunServerOptions): Promise {\n if (options.proxyEnv) {\n initProxyFromEnv()\n }\n\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.accountType = options.accountType\n if (options.accountType !== \"individual\") {\n consola.info(`Using ${options.accountType} plan GitHub account`)\n }\n\n state.manualApprove = options.manual\n state.rateLimitSeconds = options.rateLimit\n state.rateLimitWait = options.rateLimitWait\n state.showToken = options.showToken\n\n await ensurePaths()\n await cacheVSCodeVersion()\n\n if (options.githubToken) {\n state.githubToken = options.githubToken\n consola.info(\"Using provided GitHub token\")\n } else {\n await setupGitHubToken()\n }\n\n await setupCopilotToken()\n await cacheModels()\n\n consola.info(\n `Available models: \\n${state.models?.data.map((model) => `- ${model.id}`).join(\"\\n\")}`,\n )\n\n const serverUrl = `http://localhost:${options.port}`\n\n if (options.claudeCode) {\n invariant(state.models, \"Models should be loaded by now\")\n\n const selectedModel = await consola.prompt(\n \"Select a model to use with Claude Code\",\n {\n type: \"select\",\n options: state.models.data.map((model) => model.id),\n },\n )\n\n const selectedSmallModel = await consola.prompt(\n \"Select a small model to use with Claude Code\",\n {\n type: \"select\",\n options: state.models.data.map((model) => model.id),\n },\n )\n\n const command = generateEnvScript(\n {\n ANTHROPIC_BASE_URL: serverUrl,\n ANTHROPIC_AUTH_TOKEN: \"dummy\",\n ANTHROPIC_MODEL: selectedModel,\n ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,\n ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,\n ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,\n DISABLE_NON_ESSENTIAL_MODEL_CALLS: \"1\",\n CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: \"1\",\n },\n \"claude\",\n )\n\n try {\n clipboard.writeSync(command)\n consola.success(\"Copied Claude Code command to clipboard!\")\n } catch {\n consola.warn(\n \"Failed to copy to clipboard. Here is the Claude Code command:\",\n )\n consola.log(command)\n }\n }\n\n consola.box(\n `🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`,\n )\n\n const isBun = typeof Bun !== \"undefined\"\n\n if (isBun) {\n const bunServer = Bun.serve({\n port: options.port,\n fetch(req: Request, bunSrv: any) {\n if (\n req.headers.get(\"upgrade\")?.toLowerCase() === \"websocket\" &&\n isResponsesWsPath(req.url)\n ) {\n const ok = handleResponsesWsUpgrade(req, bunSrv)\n if (ok) return undefined as any\n return new Response(\"WebSocket upgrade failed\", { status: 500 })\n }\n return server.fetch(req)\n },\n websocket: responsesWebSocket,\n })\n consola.info(`Listening on: http://localhost:${bunServer.port}/`)\n } else {\n const { createServer } = await import(\"node:http\")\n const nodeServer = createServer(async (req, res) => {\n const url = `http://localhost:${options.port}${req.url}`\n const headers = new Headers()\n for (const [key, val] of Object.entries(req.headers)) {\n if (val) headers.set(key, Array.isArray(val) ? val.join(\", \") : val)\n }\n const body = await new Promise((resolve) => {\n const chunks: Buffer[] = []\n req.on(\"data\", (c: Buffer) => chunks.push(c))\n req.on(\"end\", () => resolve(Buffer.concat(chunks)))\n })\n const request = new Request(url, {\n method: req.method,\n headers,\n body: [\"GET\", \"HEAD\"].includes(req.method ?? \"GET\") ? undefined : body,\n })\n const response = await server.fetch(request)\n res.writeHead(response.status, Object.fromEntries(response.headers.entries()))\n const buf = Buffer.from(await response.arrayBuffer())\n res.end(buf)\n })\n nodeServer.listen(options.port, () => {\n consola.info(`Listening on: http://localhost:${options.port}/`)\n })\n consola.warn(\"WebSocket support requires Bun runtime\")\n }\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the Copilot API server\",\n },\n args: {\n port: {\n alias: \"p\",\n type: \"string\",\n default: \"4141\",\n description: \"Port to listen on\",\n },\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"account-type\": {\n alias: \"a\",\n type: \"string\",\n default: \"individual\",\n description: \"Account type to use (individual, business, enterprise)\",\n },\n manual: {\n type: \"boolean\",\n default: false,\n description: \"Enable manual request approval\",\n },\n \"rate-limit\": {\n alias: \"r\",\n type: \"string\",\n description: \"Rate limit in seconds between requests\",\n },\n wait: {\n alias: \"w\",\n type: \"boolean\",\n default: false,\n description:\n \"Wait instead of error when rate limit is hit. Has no effect if rate limit is not set\",\n },\n \"github-token\": {\n alias: \"g\",\n type: \"string\",\n description:\n \"Provide GitHub token directly (must be generated using the `auth` subcommand)\",\n },\n \"claude-code\": {\n alias: \"c\",\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Claude Code with Copilot API config\",\n },\n \"show-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub and Copilot tokens on fetch and refresh\",\n },\n \"proxy-env\": {\n type: \"boolean\",\n default: false,\n description: \"Initialize proxy from environment variables\",\n },\n },\n run({ args }) {\n const rateLimitRaw = args[\"rate-limit\"]\n const rateLimit =\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n rateLimitRaw === undefined ? undefined : Number.parseInt(rateLimitRaw, 10)\n\n return runServer({\n port: Number.parseInt(args.port, 10),\n verbose: args.verbose,\n accountType: args[\"account-type\"],\n manual: args.manual,\n rateLimit,\n rateLimitWait: args.wait,\n githubToken: args[\"github-token\"],\n claudeCode: args[\"claude-code\"],\n showToken: args[\"show-token\"],\n proxyEnv: args[\"proxy-env\"],\n })\n },\n})\n","#!/usr/bin/env node\n\nimport { defineCommand, runMain } from \"citty\"\n\nimport { auth } from \"./auth\"\nimport { checkUsage } from \"./check-usage\"\nimport { debug } from \"./debug\"\nimport { start } from \"./start\"\n\nconst main = defineCommand({\n meta: {\n name: \"copilot-api\",\n description:\n \"A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools.\",\n },\n subCommands: { auth, start, \"check-usage\": checkUsage, debug },\n})\n\nawait runMain(main)\n"],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index a5adbb8e7..7dd51c5b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "copilot-api", - "version": "0.7.0", + "version": "0.8.0", "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!", "keywords": [ "proxy", diff --git a/src/routes/models/route.ts b/src/routes/models/route.ts index 5254e2af7..0d746643b 100644 --- a/src/routes/models/route.ts +++ b/src/routes/models/route.ts @@ -13,19 +13,40 @@ modelRoutes.get("/", async (c) => { await cacheModels() } - const models = state.models?.data.map((model) => ({ + const openaiModels = state.models?.data.map((model) => ({ id: model.id, object: "model", type: "model", - created: 0, // No date available from source - created_at: new Date(0).toISOString(), // No date available from source + created: 0, + created_at: new Date(0).toISOString(), owned_by: model.vendor, display_name: model.name, })) + const codexModels = state.models?.data.map((model) => ({ + slug: model.id, + display_name: model.name, + description: "", + default_reasoning_level: "medium", + supported_reasoning_levels: [ + { effort: "low", description: "Fast responses" }, + { effort: "medium", description: "Balanced" }, + { effort: "high", description: "Deep reasoning" }, + { effort: "xhigh", description: "Maximum reasoning" }, + ], + shell_type: "shell_command", + visibility: "list", + supported_in_api: true, + priority: 0, + additional_speed_tiers: [], + availability_nux: null, + upgrade: null, + })) + return c.json({ object: "list", - data: models, + data: openaiModels, + models: codexModels, has_more: false, }) } catch (error) { diff --git a/src/routes/responses/handler.ts b/src/routes/responses/handler.ts new file mode 100644 index 000000000..76280376c --- /dev/null +++ b/src/routes/responses/handler.ts @@ -0,0 +1,42 @@ +import type { Context } from "hono" + +import consola from "consola" +import { streamSSE, type SSEMessage } from "hono/streaming" + +import { awaitApproval } from "~/lib/approval" +import { checkRateLimit } from "~/lib/rate-limit" +import { state } from "~/lib/state" +import { + createResponses, + type ResponsesPayload, + type ResponsesResponse, +} from "~/services/copilot/create-responses" + +export async function handleResponses(c: Context) { + await checkRateLimit(state) + + const payload = await c.req.json() + consola.debug("Responses API request payload:", JSON.stringify(payload).slice(-400)) + + if (state.manualApprove) await awaitApproval() + + const response = await createResponses(payload) + + if (isNonStreaming(response)) { + consola.debug("Non-streaming responses:", JSON.stringify(response).slice(-400)) + return c.json(response) + } + + consola.debug("Streaming responses") + return streamSSE(c, async (stream) => { + for await (const chunk of response) { + consola.debug("Responses stream chunk:", JSON.stringify(chunk)) + await stream.writeSSE(chunk as SSEMessage) + } + }) +} + +const isNonStreaming = ( + response: Awaited>, +): response is ResponsesResponse => + Object.hasOwn(response, "output") || Object.hasOwn(response, "id") diff --git a/src/routes/responses/route.ts b/src/routes/responses/route.ts new file mode 100644 index 000000000..af2423427 --- /dev/null +++ b/src/routes/responses/route.ts @@ -0,0 +1,15 @@ +import { Hono } from "hono" + +import { forwardError } from "~/lib/error" + +import { handleResponses } from "./handler" + +export const responsesRoutes = new Hono() + +responsesRoutes.post("/", async (c) => { + try { + return await handleResponses(c) + } catch (error) { + return await forwardError(c, error) + } +}) diff --git a/src/routes/responses/ws-proxy.ts b/src/routes/responses/ws-proxy.ts new file mode 100644 index 000000000..e54919d02 --- /dev/null +++ b/src/routes/responses/ws-proxy.ts @@ -0,0 +1,163 @@ +import consola from "consola" + +import { copilotHeaders, copilotBaseUrl } from "~/lib/api-config" +import { state } from "~/lib/state" + +export function handleResponsesWsUpgrade( + req: Request, + bunServer: { upgrade: (req: Request, opts?: object) => boolean }, +): boolean { + if (!state.copilotToken) { + consola.error("WS upgrade rejected: no Copilot token") + return false + } + + return bunServer.upgrade(req, { + data: { type: "responses-proxy" }, + }) +} + +export function isResponsesWsPath(url: string): boolean { + const path = new URL(url).pathname + return path === "/v1/responses" || path === "/responses" +} + +const UNSUPPORTED_TOOL_TYPES = new Set(["image_generation"]) + +function unwrapPayload(raw: Record): Record { + let payload: Record + if (raw.type === "response.create") { + if (typeof raw.response === "object" && raw.response !== null) { + payload = { ...(raw.response as Record) } + } else { + const { type: _, ...rest } = raw + payload = rest + } + } else { + payload = { ...raw } + } + + if (Array.isArray(payload.tools)) { + payload.tools = (payload.tools as any[]).filter( + (t) => !UNSUPPORTED_TOOL_TYPES.has(t.type), + ) + } + + payload.stream = true + return payload +} + +async function handleRequest(ws: any, raw: Record) { + const headers: Record = { + ...copilotHeaders(state), + } + + const url = `${copilotBaseUrl(state)}/responses` + const body = unwrapPayload(raw) + + consola.debug("WS→HTTP POST:", url, "keys:", Object.keys(body).join(",")) + + try { + const response = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify(body), + }) + + consola.debug("Upstream status:", response.status, "content-type:", response.headers.get("content-type")) + + if (!response.ok) { + const errText = await response.text() + consola.error("Upstream error:", response.status, errText) + ws.send(JSON.stringify({ + type: "error", + error: { message: errText, code: response.status }, + })) + return + } + + if (!response.body) { + consola.error("No response body from upstream") + return + } + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let buffer = "" + let currentEventType = "" + + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split("\n") + buffer = lines.pop() ?? "" + + for (const line of lines) { + if (line.startsWith("event: ")) { + currentEventType = line.slice(7).trim() + continue + } + if (line.startsWith("data: ")) { + const data = line.slice(6) + if (data === "[DONE]") { + consola.debug("SSE stream done") + continue + } + + let toSend = data + if (currentEventType) { + try { + const parsed = JSON.parse(data) + if (!parsed.type) { + parsed.type = currentEventType + toSend = JSON.stringify(parsed) + } + } catch {} + } + + consola.debug("Relaying SSE→WS:", toSend.slice(0, 150)) + try { + ws.send(toSend) + } catch (e) { + consola.error("WS send failed:", e) + return + } + currentEventType = "" + } + } + } + } catch (e) { + consola.error("HTTP request failed:", e) + try { + ws.send(JSON.stringify({ + type: "error", + error: { message: (e as Error).message }, + })) + } catch {} + } +} + +export const responsesWebSocket = { + open(ws: any) { + consola.debug("Client WS opened") + }, + + message(ws: any, message: string | Buffer) { + const data = typeof message === "string" ? message : message.toString() + consola.debug("Client WS msg:", data.slice(0, 300)) + + try { + const parsed = JSON.parse(data) + handleRequest(ws, parsed) + } catch (e) { + consola.error("Bad JSON from client:", e) + ws.send(JSON.stringify({ type: "error", error: { message: "Invalid JSON" } })) + } + }, + + close(ws: any, code: number, reason: string) { + consola.debug("Client WS closed:", code, reason) + }, +} diff --git a/src/server.ts b/src/server.ts index 462a278f3..51fda1988 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,6 +6,7 @@ import { completionRoutes } from "./routes/chat-completions/route" import { embeddingRoutes } from "./routes/embeddings/route" import { messageRoutes } from "./routes/messages/route" import { modelRoutes } from "./routes/models/route" +import { responsesRoutes } from "./routes/responses/route" import { tokenRoute } from "./routes/token/route" import { usageRoute } from "./routes/usage/route" @@ -17,6 +18,7 @@ server.use(cors()) server.get("/", (c) => c.text("Server running")) server.route("/chat/completions", completionRoutes) +server.route("/responses", responsesRoutes) server.route("/models", modelRoutes) server.route("/embeddings", embeddingRoutes) server.route("/usage", usageRoute) @@ -24,6 +26,7 @@ server.route("/token", tokenRoute) // Compatibility with tools that expect v1/ prefix server.route("/v1/chat/completions", completionRoutes) +server.route("/v1/responses", responsesRoutes) server.route("/v1/models", modelRoutes) server.route("/v1/embeddings", embeddingRoutes) diff --git a/src/services/copilot/create-responses.ts b/src/services/copilot/create-responses.ts new file mode 100644 index 000000000..b1511a133 --- /dev/null +++ b/src/services/copilot/create-responses.ts @@ -0,0 +1,88 @@ +import consola from "consola" +import { events } from "fetch-event-stream" + +import { copilotHeaders, copilotBaseUrl } from "~/lib/api-config" +import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" + +export const createResponses = async (payload: ResponsesPayload) => { + if (!state.copilotToken) throw new Error("Copilot token not found") + + const headers: Record = { + ...copilotHeaders(state), + "X-Initiator": "user", + } + + const response = await fetch(`${copilotBaseUrl(state)}/responses`, { + method: "POST", + headers, + body: JSON.stringify(payload), + }) + + if (!response.ok) { + consola.error("Failed to create responses", response) + throw new HTTPError("Failed to create responses", response) + } + + if (payload.stream) { + return events(response) + } + + return (await response.json()) as ResponsesResponse +} + +// Payload types + +export interface ResponsesInputMessage { + role: "system" | "user" | "assistant" | "developer" + content: string | Array +} + +export interface ResponsesContentPart { + type: string + text?: string + [key: string]: unknown +} + +export interface ResponsesTool { + name: string + description?: string + parameters?: Record + type: "function" + strict?: boolean +} + +export interface ResponsesReasoning { + effort?: "low" | "medium" | "high" | "xhigh" + summary?: "auto" | "detailed" | "none" +} + +export interface ResponsesPayload { + model: string + input: Array | string + stream?: boolean + tools?: Array + max_output_tokens?: number + store?: boolean + truncation?: string + reasoning?: ResponsesReasoning + include?: Array + temperature?: number + top_p?: number + [key: string]: unknown +} + +// Response types + +export interface ResponsesResponse { + id: string + object: string + model: string + output: Array> + usage?: { + input_tokens: number + output_tokens: number + total_tokens: number + } + [key: string]: unknown +} diff --git a/src/start.ts b/src/start.ts index 14abbbdff..13a543e89 100644 --- a/src/start.ts +++ b/src/start.ts @@ -3,7 +3,6 @@ import { defineCommand } from "citty" import clipboard from "clipboardy" import consola from "consola" -import { serve, type ServerHandler } from "srvx" import invariant from "tiny-invariant" import { ensurePaths } from "./lib/paths" @@ -12,6 +11,11 @@ import { generateEnvScript } from "./lib/shell" import { state } from "./lib/state" import { setupCopilotToken, setupGitHubToken } from "./lib/token" import { cacheModels, cacheVSCodeVersion } from "./lib/utils" +import { + handleResponsesWsUpgrade, + isResponsesWsPath, + responsesWebSocket, +} from "./routes/responses/ws-proxy" import { server } from "./server" interface RunServerOptions { @@ -114,10 +118,53 @@ export async function runServer(options: RunServerOptions): Promise { `🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`, ) - serve({ - fetch: server.fetch as ServerHandler, - port: options.port, - }) + const isBun = typeof Bun !== "undefined" + + if (isBun) { + const bunServer = Bun.serve({ + port: options.port, + fetch(req: Request, bunSrv: any) { + if ( + req.headers.get("upgrade")?.toLowerCase() === "websocket" && + isResponsesWsPath(req.url) + ) { + const ok = handleResponsesWsUpgrade(req, bunSrv) + if (ok) return undefined as any + return new Response("WebSocket upgrade failed", { status: 500 }) + } + return server.fetch(req) + }, + websocket: responsesWebSocket, + }) + consola.info(`Listening on: http://localhost:${bunServer.port}/`) + } else { + const { createServer } = await import("node:http") + const nodeServer = createServer(async (req, res) => { + const url = `http://localhost:${options.port}${req.url}` + const headers = new Headers() + for (const [key, val] of Object.entries(req.headers)) { + if (val) headers.set(key, Array.isArray(val) ? val.join(", ") : val) + } + const body = await new Promise((resolve) => { + const chunks: Buffer[] = [] + req.on("data", (c: Buffer) => chunks.push(c)) + req.on("end", () => resolve(Buffer.concat(chunks))) + }) + const request = new Request(url, { + method: req.method, + headers, + body: ["GET", "HEAD"].includes(req.method ?? "GET") ? undefined : body, + }) + const response = await server.fetch(request) + res.writeHead(response.status, Object.fromEntries(response.headers.entries())) + const buf = Buffer.from(await response.arrayBuffer()) + res.end(buf) + }) + nodeServer.listen(options.port, () => { + consola.info(`Listening on: http://localhost:${options.port}/`) + }) + consola.warn("WebSocket support requires Bun runtime") + } } export const start = defineCommand({