Skip to content

Commit 4bf7b23

Browse files
committed
feat: add CLAUDE.md for project guidance and update token counting logic in translation functions
1 parent 09fdf2d commit 4bf7b23

4 files changed

Lines changed: 96 additions & 9 deletions

File tree

CLAUDE.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Development Commands
6+
7+
- **Build**: `bun run build` (uses tsdown)
8+
- **Development**: `bun run dev` (with file watching)
9+
- **Production**: `bun run start` (sets NODE_ENV=production)
10+
- **Lint**: `bun run lint` (uses @echristian/eslint-config with cache)
11+
- **Lint fix**: `bunx lint-staged` (fixes staged files)
12+
- **Typecheck**: `bun run typecheck` (runs TypeScript compiler)
13+
- **Test all**: `bun test`
14+
- **Test single file**: `bun test tests/[filename].test.ts`
15+
- **Package**: `bun run prepack` (builds before packaging)
16+
17+
## Project Architecture
18+
19+
### High-Level Structure
20+
This is a GitHub Copilot API proxy server that exposes Copilot as both OpenAI-compatible and Anthropic-compatible APIs. The server is built with Hono framework and uses Bun as the runtime.
21+
22+
### Core Architecture Components
23+
24+
**API Translation Layer** (`src/routes/messages/`):
25+
- Translates between Anthropic Messages API format and OpenAI Chat Completions format
26+
- Handles both streaming and non-streaming responses
27+
- Key files: `handler.ts`, `anthropic-types.ts`, `stream-translation.ts`, `non-stream-translation.ts`
28+
29+
**Token Counting for Anthropic Models** (`src/lib/tokenizer.ts`):
30+
- Uses `gpt-tokenizer/model/gpt-4o` for token counting
31+
- Separates input tokens (all messages except last assistant message) from output tokens (last assistant message)
32+
- Filters out tool messages and extracts text content from multipart messages
33+
- Used by `/v1/messages/count_tokens` endpoint for Anthropic compatibility
34+
35+
**GitHub Copilot Integration** (`src/services/`):
36+
- Authentication flow using device code OAuth
37+
- Token management and refresh
38+
- API requests to GitHub Copilot endpoints
39+
- Usage monitoring and quota tracking
40+
41+
**Rate Limiting & Controls** (`src/lib/`):
42+
- Rate limiting between requests (`rate-limit.ts`)
43+
- Manual approval system for requests (`approval.ts`)
44+
- State management for server configuration (`state.ts`)
45+
46+
### API Endpoints Structure
47+
48+
**OpenAI Compatible**:
49+
- `/v1/chat/completions` - Chat completions
50+
- `/v1/models` - Available models
51+
- `/v1/embeddings` - Text embeddings
52+
53+
**Anthropic Compatible**:
54+
- `/v1/messages` - Message completions (translates to/from OpenAI format)
55+
- `/v1/messages/count_tokens` - Token counting for Anthropic format
56+
57+
**Monitoring**:
58+
- `/usage` - GitHub Copilot usage dashboard
59+
- `/token` - Current Copilot token info
60+
61+
### Key Implementation Details
62+
63+
**Anthropic Token Counting**:
64+
The `getTokenCount()` function in `src/lib/tokenizer.ts` implements token counting specifically for Anthropic compatibility:
65+
- Converts multipart content to text-only for counting
66+
- Splits messages into input (all except last assistant) and output (last assistant message only)
67+
- Uses GPT-4o tokenizer as the underlying counting mechanism
68+
- Returns `{input: number, output: number}` format
69+
70+
**Message Translation**:
71+
- OpenAI → Anthropic: Converts chat completion responses to Anthropic message format
72+
- Anthropic → OpenAI: Converts Anthropic message requests to OpenAI chat completion format
73+
- Handles tool calls, system messages, and content blocks appropriately
74+
75+
**Streaming Translation**:
76+
Real-time conversion of OpenAI SSE chunks to Anthropic streaming events, maintaining state for proper message reconstruction.
77+
78+
## Code Style & Conventions
79+
80+
- **TypeScript**: Strict mode enabled, avoid `any` types
81+
- **Imports**: Use `~/*` path aliases for `src/*` imports
82+
- **Error Handling**: Use explicit error classes from `src/lib/error.ts`
83+
- **Testing**: Place tests in `tests/` directory with `*.test.ts` naming
84+
- **Formatting**: Prettier with package.json plugin
85+
- **Linting**: @echristian/eslint-config with strict rules
86+
87+
## Important Notes
88+
89+
- Server uses GitHub Copilot as the underlying LLM provider
90+
- Rate limiting and manual approval features help avoid GitHub abuse detection
91+
- Token counting uses GPT-4o tokenizer regardless of the actual model being proxied
92+
- All API translations maintain compatibility with both OpenAI and Anthropic client libraries

src/routes/messages/non-stream-translation.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,7 @@ export function translateToAnthropic(
313313
stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
314314
stop_sequence: null,
315315
usage: {
316-
input_tokens:
317-
(response.usage?.prompt_tokens ?? 0)
318-
- (response.usage?.prompt_tokens_details?.cached_tokens ?? 0),
316+
input_tokens: response.usage?.prompt_tokens ?? 0,
319317
output_tokens: response.usage?.completion_tokens ?? 0,
320318
...(response.usage?.prompt_tokens_details?.cached_tokens
321319
!== undefined && {

src/routes/messages/stream-translation.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ export function translateChunkToAnthropicEvents(
4242
stop_reason: null,
4343
stop_sequence: null,
4444
usage: {
45-
input_tokens:
46-
(chunk.usage?.prompt_tokens ?? 0)
47-
- (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
45+
input_tokens: chunk.usage?.prompt_tokens ?? 0,
4846
output_tokens: 0, // Will be updated in message_delta when finished
4947
...(chunk.usage?.prompt_tokens_details?.cached_tokens
5048
!== undefined && {
@@ -159,9 +157,7 @@ export function translateChunkToAnthropicEvents(
159157
stop_sequence: null,
160158
},
161159
usage: {
162-
input_tokens:
163-
(chunk.usage?.prompt_tokens ?? 0)
164-
- (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
160+
input_tokens: chunk.usage?.prompt_tokens ?? 0,
165161
output_tokens: chunk.usage?.completion_tokens ?? 0,
166162
...(chunk.usage?.prompt_tokens_details?.cached_tokens
167163
!== undefined && {

src/routes/models/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ modelRoutes.get("/", async (c) => {
2121
created_at: new Date(0).toISOString(), // No date available from source
2222
owned_by: model.vendor,
2323
display_name: model.name,
24+
capabilities: model.capabilities, // Include full capabilities from GitHub Copilot
2425
}))
2526

2627
return c.json({

0 commit comments

Comments
 (0)