-
-
Notifications
You must be signed in to change notification settings - Fork 304
Feat/enterprise and reasoning #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
8f6c468
32e948d
6f07c56
ee4670a
151646e
29668ce
a2467d3
58f7a45
3fa5519
dfb40d2
7657d87
968ff12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Changelog | ||
|
|
||
| All notable changes to this project will be documented in this file. | ||
|
|
||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
|
||
| ## [0.8.0] - 2025-01-31 | ||
|
|
||
| ### Added | ||
| - GitHub Enterprise Server/Cloud support | ||
| - New `--enterprise-url` flag for `auth` and `start` commands | ||
| - Interactive prompt during auth for enterprise configuration | ||
| - Persistent enterprise URL storage in `~/.local/share/copilot-api/enterprise_url` | ||
| - Automatic endpoint routing for enterprise instances | ||
| - Comprehensive test suite with 41 new tests covering enterprise functionality | ||
| - URL normalization utilities for enterprise host configuration | ||
|
|
||
| ### Changed | ||
| - OAuth endpoints now use enterprise URLs when configured | ||
| - Copilot API endpoints route to `copilot-api.{enterprise}` for enterprise users | ||
| - GitHub API endpoints route to `api.{enterprise}` for enterprise users | ||
|
|
||
| ### Technical Details | ||
| - Enterprise endpoint structure: | ||
| - OAuth: `https://{enterprise}/login/...` | ||
| - GitHub API: `https://api.{enterprise}/...` | ||
| - Copilot API: `https://copilot-api.{enterprise}/...` | ||
| - 100% backwards compatible - defaults to github.com when no enterprise configured | ||
|
|
||
| ## [0.7.0] - Previous Release | ||
| - See git history for details | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code when working with this repository. | ||
|
|
||
| ## Overview | ||
|
|
||
| TypeScript CLI application using Bun runtime that exposes GitHub Copilot as OpenAI/Anthropic-compatible APIs. | ||
|
|
||
| ## Commands | ||
|
|
||
| - `bun install` - Install dependencies | ||
| - `bun run build` - Build distribution | ||
| - `bun run dev` - Development with watch mode | ||
| - `bun run start` - Production start | ||
| - `bun run lint` - Lint code | ||
| - `bun run typecheck` - Type checking | ||
| - `bun test` - Run tests | ||
|
|
||
| ## Architecture | ||
|
|
||
| - **Entry**: `src/main.ts` - CLI definition with subcommands (auth, start, check-usage, debug) | ||
| - **Server**: `src/start.ts` - Orchestrates initialization and HTTP server | ||
| - **Routes**: `src/routes/` - OpenAI & Anthropic compatible endpoints | ||
| - **Services**: | ||
| - `src/services/github/` - GitHub OAuth and token management | ||
| - `src/services/copilot/` - Copilot API interactions | ||
| - **Utilities**: `src/lib/` - State management, paths, token handling | ||
|
|
||
| ## GitHub Enterprise Support | ||
|
|
||
| - Use `--enterprise-url` flag for GHE Server/Cloud | ||
| - Interactive prompt during auth if no flag provided | ||
| - Enterprise URL persisted in `~/.local/share/copilot-api/enterprise_url` | ||
| - Endpoints: `https://{enterprise}/...` for OAuth, `https://api.{enterprise}/...` for GitHub API, `https://copilot-api.{enterprise}/...` for Copilot | ||
|
|
||
| ## Key Files | ||
|
|
||
| - CLI flags: `src/start.ts:123`, `src/main.ts:10` | ||
| - Routes: `src/server.ts`, `src/routes/*` | ||
| - Models: `src/services/copilot/get-models.ts` | ||
| - Token storage: `src/lib/paths.ts`, `src/lib/token.ts` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ A reverse-engineered proxy for the GitHub Copilot API that exposes it as an Open | |
| - **Token Visibility**: Option to display GitHub and Copilot tokens during authentication and refresh for debugging (`--show-token`). | ||
| - **Flexible Authentication**: Authenticate interactively or provide a GitHub token directly, suitable for CI/CD environments. | ||
| - **Support for Different Account Types**: Works with individual, business, and enterprise GitHub Copilot plans. | ||
| - **GitHub Enterprise Support**: Compatible with GitHub Enterprise Server and GitHub Enterprise Cloud installations. | ||
|
|
||
| ## Demo | ||
|
|
||
|
|
@@ -163,13 +164,15 @@ The following command line options are available for the `start` command: | |
| | --claude-code | Generate a command to launch Claude Code with Copilot API config | false | -c | | ||
| | --show-token | Show GitHub and Copilot tokens on fetch and refresh | false | none | | ||
| | --proxy-env | Initialize proxy from environment variables | false | none | | ||
| | --enterprise-url | GitHub Enterprise host to use (eg. https://ghe.example.com) | none | none | | ||
|
|
||
| ### Auth Command Options | ||
|
|
||
| | Option | Description | Default | Alias | | ||
| | ------------ | ------------------------- | ------- | ----- | | ||
| | --verbose | Enable verbose logging | false | -v | | ||
| | --show-token | Show GitHub token on auth | false | none | | ||
| | --enterprise-url | GitHub Enterprise host (eg. https://ghe.example.com) | none | none | | ||
|
|
||
| ### Debug Command Options | ||
|
|
||
|
|
@@ -255,6 +258,15 @@ npx copilot-api@latest debug --json | |
|
|
||
| # Initialize proxy from environment variables (HTTP_PROXY, HTTPS_PROXY, etc.) | ||
| npx copilot-api@latest start --proxy-env | ||
|
|
||
| # Use GitHub Enterprise / GitHub Enterprise Server | ||
| npx copilot-api@latest start --account-type enterprise --enterprise-url https://ghe.example.com | ||
|
|
||
| # Authenticate with GitHub Enterprise interactively (will prompt for enterprise host) | ||
| npx copilot-api@latest auth | ||
|
|
||
| # Authenticate with GitHub Enterprise using CLI flag (for scripting) | ||
| npx copilot-api@latest auth --enterprise-url ghe.example.com | ||
| ``` | ||
|
|
||
| ## Using the Usage Viewer | ||
|
|
@@ -349,3 +361,8 @@ bun run start | |
| - `--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. | ||
| - For GitHub Enterprise Server/Cloud users: Use `--enterprise-url` to specify your enterprise host (e.g., `--enterprise-url https://ghe.example.com`). The interactive auth command (`copilot-api auth`) will prompt you for your enterprise host if you don't provide it via the CLI flag. | ||
|
|
||
|
|
||
|
|
||
| export ANTHROPIC_BASE_URL=http://localhost:4141 ANTHROPIC_AUTH_TOKEN=dummy ANTHROPIC_MODEL=claude-sonnet-4.5 ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4.5 ANTHROPIC_SMALL_FAST_MODEL=gpt-5-mini ANTHROPIC_DEFAULT_HAIKU_MODEL=gpt-5-mini DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 && claude | ||
|
Comment on lines
+364
to
+368
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix malformed export command example. Lines 365-368 appear to be improperly formatted. The export command is split across lines without proper markdown code block formatting, making it difficult to read and copy. Apply this diff to properly format the export command: - For GitHub Enterprise Server/Cloud users: Use `--enterprise-url` to specify your enterprise host (e.g., `--enterprise-url https://ghe.example.com`). The interactive auth command (`copilot-api auth`) will prompt you for your enterprise host if you don't provide it via the CLI flag.
-
-
-
-export ANTHROPIC_BASE_URL=http://localhost:4141 ANTHROPIC_AUTH_TOKEN=dummy ANTHROPIC_MODEL=claude-sonnet-4.5 ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4.5 ANTHROPIC_SMALL_FAST_MODEL=gpt-5-mini ANTHROPIC_DEFAULT_HAIKU_MODEL=gpt-5-mini DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 && claude
+
+Example command to launch Claude with Copilot API:
+
+```sh
+export ANTHROPIC_BASE_URL=http://localhost:4141 \
+ ANTHROPIC_AUTH_TOKEN=dummy \
+ ANTHROPIC_MODEL=claude-sonnet-4.5 \
+ ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-4.5 \
+ ANTHROPIC_SMALL_FAST_MODEL=gpt-5-mini \
+ ANTHROPIC_DEFAULT_HAIKU_MODEL=gpt-5-mini \
+ DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 \
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 && claude
+```🧰 Tools🪛 markdownlint-cli2 (0.18.1)368-368: Bare URL used (MD034, no-bare-urls) 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,9 @@ import { randomUUID } from "node:crypto" | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { State } from "./state" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { state } from "./state" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { githubBaseUrl, githubApiBaseUrl } from "./url" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const standardHeaders = () => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content-type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| accept: "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -13,10 +16,17 @@ const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}` | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const API_VERSION = "2025-04-01" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const copilotBaseUrl = (state: State) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.accountType === "individual" ? | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "https://api.githubcopilot.com" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| : `https://api.${state.accountType}.githubcopilot.com` | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const copilotBaseUrl = (state: State) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If enterprise URL is configured, use enterprise Copilot API endpoint | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (state.enterpriseUrl) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `https://copilot-api.${state.enterpriseUrl}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Otherwise use standard GitHub Copilot endpoints based on account type | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return state.accountType === "individual" ? | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "https://api.githubcopilot.com" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| : `https://api.${state.accountType}.githubcopilot.com` | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enterprise URL not normalized in copilotBaseUrl. The Other functions use +import { normalizeDomain, githubBaseUrl, githubApiBaseUrl } from "./url"
+
export const copilotBaseUrl = (state: State) => {
// If enterprise URL is configured, use enterprise Copilot API endpoint
if (state.enterpriseUrl) {
- return `https://copilot-api.${state.enterpriseUrl}`
+ const domain = normalizeDomain(state.enterpriseUrl)
+ return `https://copilot-api.${domain}`
}
// Otherwise use standard GitHub Copilot endpoints based on account type
return state.accountType === "individual" ?
"https://api.githubcopilot.com"
: `https://api.${state.accountType}.githubcopilot.com`
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const copilotHeaders = (state: State, vision: boolean = false) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headers: Record<string, string> = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Authorization: `Bearer ${state.copilotToken}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -36,7 +46,7 @@ export const copilotHeaders = (state: State, vision: boolean = false) => { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| return headers | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const GITHUB_API_BASE_URL = "https://api.github.com" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const GITHUB_API_BASE_URL = () => githubApiBaseUrl(state.enterpriseUrl) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const githubHeaders = (state: State) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...standardHeaders(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| authorization: `token ${state.githubToken}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -47,6 +57,6 @@ export const githubHeaders = (state: State) => ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||
| "x-vscode-user-agent-library-version": "electron-fetch", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const GITHUB_BASE_URL = "https://github.com" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const GITHUB_BASE_URL = () => githubBaseUrl(state.enterpriseUrl) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const GITHUB_APP_SCOPES = ["read:user"].join(" ") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,6 +73,9 @@ const calculateMessageTokens = ( | |
| const tokensPerName = 1 | ||
| let tokens = tokensPerMessage | ||
| for (const [key, value] of Object.entries(message)) { | ||
| if (key === "reasoning_opaque") { | ||
| continue | ||
| } | ||
|
Comment on lines
+76
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify token counting for reasoning fields. The code excludes Run the following script to check how both reasoning fields are used across the codebase: 🏁 Script executed: #!/bin/bash
# Search for reasoning_text and reasoning_opaque usage patterns
echo "=== Searching for reasoning_text usage ==="
rg -nC3 'reasoning_text' --type=ts
echo -e "\n=== Searching for reasoning_opaque usage ==="
rg -nC3 'reasoning_opaque' --type=tsLength of output: 11191 I need to examine the full tokenizer logic to confirm whether Now let me check the full tokenizer implementation to confirm whether Let me check the complete tokenizer implementation to see if Exclude The Anthropic API automatically ignores thinking blocks from previous turns and they are not included when calculating context usage. Both Add a check to also skip 🤖 Prompt for AI Agents |
||
| if (typeof value === "string") { | ||
| tokens += encoder.encode(value).length | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| export function normalizeDomain(url: string | undefined): string | undefined { | ||
| if (!url) return undefined | ||
| return url.replace(/^https?:\/\//, "").replace(/\/+$/, "") | ||
| } | ||
|
|
||
| export function githubBaseUrl(enterprise?: string): string { | ||
| if (!enterprise) return "https://github.com" | ||
| const domain = normalizeDomain(enterprise) | ||
| return `https://${domain}` | ||
| } | ||
|
|
||
| export function githubApiBaseUrl(enterprise?: string): string { | ||
| if (!enterprise) return "https://api.github.com" | ||
| const domain = normalizeDomain(enterprise) | ||
| return `https://api.${domain}` | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the release date.
The changelog shows release date as
2025-01-31, but this PR was created on2025-11-25. Please update to the correct release date or useUnreleasedif the version hasn't been released yet.Apply this diff to use standard unreleased format:
📝 Committable suggestion
🤖 Prompt for AI Agents