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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ tests/
*.md

.eslintcache

web/node_modules
web/dist
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ node_modules/
.eslintcache

# build output
dist/
dist/

# IDE
.idea/
55 changes: 55 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

copilot-api is a reverse proxy that exposes GitHub Copilot as OpenAI and Anthropic-compatible API endpoints. Built with Bun, TypeScript, Hono, and citty (CLI framework).

## Commands

- **Build:** `bun run build` (tsdown bundler, outputs to `dist/`)
- **Dev:** `bun run dev` (watch mode via `bun run --watch`)
- **Start:** `bun run start` (production mode)
- **Lint:** `bun run lint` (ESLint with `@echristian/eslint-config`)
- **Typecheck:** `bun run typecheck` (tsc, no emit)
- **Test all:** `bun test`
- **Test single file:** `bun test tests/<filename>.test.ts`
- **Unused code detection:** `bun run knip`

## Architecture

### CLI Layer (`src/main.ts`)
Entry point uses citty to define subcommands: `start`, `auth`, `check-usage`, `debug`, `console`.

### Server (`src/server.ts`)
Hono app with routes mounted at both `/` and `/v1/` prefixes for compatibility:
- `POST /v1/chat/completions` — OpenAI-compatible chat completions
- `POST /v1/messages` — Anthropic-compatible messages API
- `POST /v1/messages/count_tokens` — Token counting
- `GET /v1/models` — Model listing
- `POST /v1/embeddings` — Embeddings
- `GET /usage`, `GET /token` — Monitoring endpoints

### Key Directories
- `src/routes/` — Route handlers, each in its own directory with `route.ts` + `handler.ts`
- `src/services/copilot/` — GitHub Copilot API calls (completions, embeddings, models)
- `src/services/github/` — GitHub API calls (auth, tokens, usage)
- `src/lib/` — Shared utilities (state, tokens, rate limiting, error handling, proxy)
- `src/console/` — Multi-account management mode with load balancing and web UI
- `web/` — React + Vite frontend for the console mode
- `tests/` — Bun test runner, files named `*.test.ts`

### Global State (`src/lib/state.ts`)
Mutable singleton `state` object holds runtime config: GitHub/Copilot tokens, account type, cached models, rate limit settings.

### Anthropic Translation Layer (`src/routes/messages/`)
Converts between Anthropic message format and Copilot's OpenAI-style API. Handles both streaming (`stream-translation.ts`) and non-streaming (`non-stream-translation.ts`) responses.

## Code Conventions

- ESM only, strict TypeScript — no `any`, no unused variables/imports
- Path alias: `~/*` maps to `src/*` (e.g., `import { state } from "~/lib/state"`)
- camelCase for variables/functions, PascalCase for types/classes
- Zod v4 for runtime validation
- Pre-commit hook runs lint-staged via simple-git-hooks
36 changes: 25 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
# Stage 1: Build frontend
FROM node:22-alpine AS web-builder
WORKDIR /app/web

COPY web/package.json ./
RUN npm install

COPY web/ ./
RUN npm run build

# Stage 2: Build backend
FROM oven/bun:1.2.19-alpine AS builder
WORKDIR /app

COPY ./package.json ./bun.lock ./
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun run build

FROM oven/bun:1.2.19-alpine AS runner
# Stage 3: Runtime
FROM oven/bun:1.2.19-alpine
WORKDIR /app

COPY ./package.json ./bun.lock ./
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production --ignore-scripts --no-cache

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/src ./src
COPY --from=builder /app/tsconfig.json ./
COPY --from=web-builder /app/web/dist ./web/dist

EXPOSE 3000 4141

EXPOSE 4141
VOLUME /root/.local/share/copilot-api

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --spider -q http://localhost:4141/ || exit 1
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget --spider -q http://localhost:3000/api/config || exit 1

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["bun", "run", "./src/main.ts", "console"]
CMD ["--web-port", "3000", "--proxy-port", "4141"]
39 changes: 39 additions & 0 deletions Dockerfile.console
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Stage 1: Build frontend
FROM node:22-alpine AS web-builder
WORKDIR /app/web

COPY web/package.json ./
RUN npm install

COPY web/ ./
RUN npm run build

# Stage 2: Build backend
FROM oven/bun:1.2.19-alpine AS builder
WORKDIR /app

COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY . .

# Stage 3: Runtime
FROM oven/bun:1.2.19-alpine
WORKDIR /app

COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production --ignore-scripts --no-cache

COPY --from=builder /app/src ./src
COPY --from=builder /app/tsconfig.json ./
COPY --from=web-builder /app/web/dist ./web/dist

EXPOSE 3000 4141

VOLUME /root/.local/share/copilot-api

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget --spider -q http://localhost:3000/api/config || exit 1

ENTRYPOINT ["bun", "run", "./src/main.ts", "console"]
CMD ["--web-port", "3000", "--proxy-port", "4141"]
116 changes: 115 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,46 @@ https://github.com/user-attachments/assets/7654b383-669d-4eb9-b23c-06d7aefee8c5

## Installation

To install dependencies, run:
### Global Installation (Recommended)

Install `copilot-api` globally so you can use it as a command anywhere:

```sh
# Using npm
npm install -g copilot-api

# Using bun
bun install -g copilot-api
```

Once installed, run it directly:

```sh
copilot-api start
```

### Using npx (No Install Required)

Run without installing using npx — always fetches the latest version:

```sh
npx copilot-api@latest start
```

### Installing from Source

Clone the repository and install dependencies:

```sh
bun install
```

Then link it globally so the `copilot-api` command is available system-wide:

```sh
npm link
```

## Using with Docker

Build image
Expand Down Expand Up @@ -342,10 +376,90 @@ bun run dev
bun run start
```

### Build from Source

To compile the project into `dist/`:

```sh
bun run build
```

Then run the compiled output:

```sh
bun dist/main.js start
```

## Console Mode (Multi-Account Management)

Console mode starts a web UI for managing multiple GitHub Copilot accounts with load balancing.

### 1. Build the Web UI (first time only)

```sh
cd web && bun install && bun run build
cd ..
```

### 2. Start Console Mode

```sh
bun run start console --web-port 3000 --proxy-port 4141
```

| Option | Description | Default |
| --- | --- | --- |
| `--web-port` / `-w` | Port for the web management console | 3000 |
| `--proxy-port` / `-p` | Port for the proxy API endpoints | 4141 |
| `--auto-start` | Auto-start enabled accounts on launch | true |
| `--verbose` / `-v` | Enable verbose logging | false |

### 3. Set Up Admin Account

Open `http://localhost:3000` in your browser. On first visit you will be prompted to create an admin username and password (minimum 6 characters).

### 4. Add Accounts

Click **Add Account** in the dashboard and authenticate via GitHub Device Flow, or paste a GitHub token directly.

### 5. Using the API Key

After adding and starting an account, each account has its own **API Key** shown on its card.

**Single-account mode** (use a specific account's key):

```
Base URL: http://localhost:4141/v1
API Key: <account API key from the dashboard>
```

**Pool mode** (load-balance across all enabled accounts):

Enable Pool in the dashboard to get a shared API Key. Choose a strategy:
- `round-robin` — distribute requests evenly across accounts
- `priority` — route to the highest-priority available account

```
Base URL: http://localhost:4141/v1
API Key: <pool API key from the dashboard>
```

Both keys are used as a standard Bearer token. They work with any OpenAI-compatible or Anthropic-compatible client — just point the client's base URL to `http://localhost:4141/v1` and set the API key accordingly.

## Usage Tips

- To avoid hitting GitHub Copilot's rate limits, you can use the following flags:
- `--manual`: Enables manual approval for each request, giving you full control over when requests are sent.
- `--rate-limit <seconds>`: Enforces a minimum time interval between requests. For example, `copilot-api start --rate-limit 30` will ensure there's at least a 30-second gap between requests.
- `--wait`: Use this with `--rate-limit`. It makes the server wait for the cooldown period to end instead of rejecting the request with an error. This is useful for clients that don't automatically retry on rate limit errors.
- If you have a GitHub business or enterprise plan account with Copilot, use the `--account-type` flag (e.g., `--account-type business`). See the [official documentation](https://docs.github.com/en/enterprise-cloud@latest/copilot/managing-copilot/managing-github-copilot-in-your-organization/managing-access-to-github-copilot-in-your-organization/managing-github-copilot-access-to-your-organizations-network#configuring-copilot-subscription-based-network-routing-for-your-enterprise-or-organization) for more details.


## Important commands with Claude code

curl -fsSL https://bun.sh/install | bash
cd web && bun install && bun run build
cd ..
bun run start console --web-port 3000 --proxy-port 4141
vi ~/.claude/settings.json
set api key in settings.json with the key copied in http://localhost:3000
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
services:
copilot-api:
image: copilot-api
ports:
- "3000:3000"
- "4141:4141"
volumes:
- copilot-api-data:/root/.local/share/copilot-api
restart: unless-stopped

volumes:
copilot-api-data:
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default config({
prettier: {
plugins: ["prettier-plugin-packagejson"],
},
ignores: ["web/**"],
})
Loading