Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
完善功能
  • Loading branch information
StarryKira committed Feb 16, 2026
commit 486bab857af078b10ddf58e3d1e5a3c113efbef2
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"]
30 changes: 30 additions & 0 deletions src/console/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,36 @@ consoleApi.get("/accounts", async (c) => {
return c.json(result)
Comment on lines +105 to +122
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/accounts (and similar endpoints) return the full persisted account object via { ...account, ... }, which includes githubToken. This exposes GitHub access tokens to the browser unnecessarily (the UI doesn’t use them) and increases impact of any client-side compromise. Consider omitting githubToken (and any other secrets) from API responses and adjusting the web Account type accordingly.

Copilot uses AI. Check for mistakes.
})

// Batch usage for all running accounts
consoleApi.get("/accounts/usage", async (c) => {
const accounts = await getAccounts()
const results = await Promise.all(
accounts.map(async (account) => {
const status = getInstanceStatus(account.id)
if (status !== "running") {
return {
accountId: account.id,
name: account.name,
status,
usage: null,
}
}
try {
const usage = await getInstanceUsage(account.id)
return { accountId: account.id, name: account.name, status, usage }
} catch {
return {
accountId: account.id,
name: account.name,
status,
usage: null,
}
}
}),
)
return c.json(results)
})

// Get single account
consoleApi.get("/accounts/:id", async (c) => {
const account = await getAccount(c.req.param("id"))
Expand Down
5 changes: 1 addition & 4 deletions src/console/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,7 @@ function mountProxyRoutes(app: Hono): void {
}

async function mountStaticFiles(app: Hono): Promise<void> {
const webDistPath = path.resolve(
new URL(".", import.meta.url).pathname,
"../../web/dist",
)
const webDistPath = path.resolve(import.meta.dirname, "../../web/dist")

let hasWebDist = false
try {
Expand Down
Loading