Spaces:
Running
Running
| 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" | |
| import { initProxyFromEnv } from "./lib/proxy" | |
| import { generateEnvScript } from "./lib/shell" | |
| import { state } from "./lib/state" | |
| import { setupCopilotToken, setupGitHubToken } from "./lib/token" | |
| import { cacheModels, cacheVSCodeVersion } from "./lib/utils" | |
| import { server } from "./server" | |
| interface RunServerOptions { | |
| port: number | |
| verbose: boolean | |
| accountType: string | |
| manual: boolean | |
| rateLimit?: number | |
| rateLimitWait: boolean | |
| githubToken?: string | |
| claudeCode: boolean | |
| showToken: boolean | |
| proxyEnv: boolean | |
| } | |
| export async function runServer(options: RunServerOptions): Promise<void> { | |
| 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`, | |
| ) | |
| serve({ | |
| fetch: server.fetch as ServerHandler, | |
| port: options.port, | |
| }) | |
| } | |
| export const start = defineCommand({ | |
| meta: { | |
| name: "start", | |
| description: "Start the Copilot API server", | |
| }, | |
| args: { | |
| port: { | |
| alias: "p", | |
| type: "string", | |
| default: "7860", | |
| 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 = | |
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | |
| rateLimitRaw === undefined ? undefined : 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"], | |
| }) | |
| }, | |
| }) | |