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
fix: use copilot-api subdomain for enterprise Copilot endpoints
- Update copilotBaseUrl to use copilot-api.{enterprise} for GHE
- Add 6 new tests for copilotBaseUrl with enterprise configuration
- Update CLAUDE.md with correct enterprise endpoint documentation
- Fixes 'Failed to get models' error for GitHub Enterprise users

🤖 Generated with [Claude Code](https://claude.com/claude-code)
  • Loading branch information
jkorsvik committed Oct 13, 2025
commit 32e948d5f64132a8284b3208d6f3e61c8d41c77c
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Important notes for Claude Code instances
- Local dev: Use "bun run dev" to iterate with file watching; use "bun run start" for a production-like run.
- Authentication: The application persists GitHub tokens under ~/.local/share/copilot-api (see src/lib/paths.ts:5-12). Avoid exposing tokens in commits or logs.
- GitHub Enterprise support: Use --enterprise-url flags for GitHub Enterprise Server/Cloud. The CLI will prompt interactively during auth if no enterprise URL is provided. Enterprise host is persisted for subsequent runs.
- Enterprise URLs: Stored in APP_DIR/enterprise_url and normalized (scheme/slash stripped) before persistence. OAuth flows use https://{enterprise} endpoints, Copilot API uses https://api.{enterprise} endpoints.
- Enterprise URLs: Stored in APP_DIR/enterprise_url and normalized (scheme/slash stripped) before persistence. OAuth flows use https://{enterprise} endpoints, Copilot API uses https://copilot-api.{enterprise} for models and chat endpoints, and https://api.{enterprise} for token/usage endpoints.
- Rate limiting & manual approval: These behaviours are controlled in start.ts and state.ts; tests or changes touching rate limiting should check src/lib/rate-limit.ts.

Files and areas to inspect for common tasks
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,7 @@ bun run start
- `--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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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
In README.md around lines 364 to 368 the example export command is malformed and
split across lines without a fenced code block; wrap the environment-variable
block in a proper fenced code block (```sh), place the export on one logical
command using backslashes for line continuations so each ANTHROPIC_* and flag is
on its own indented line, and add the closing ``` to restore correct formatting
and copy-pastability.

227 changes: 227 additions & 0 deletions TESTING_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# Enterprise Support Testing Plan

## Overview
Comprehensive test plan to validate GitHub Enterprise Server/Cloud support in copilot-api.

## Test Categories

### 1. Unit Tests (Automated)
- [x] URL normalization helpers
- [x] githubBaseUrl with/without enterprise
- [x] githubApiBaseUrl with/without enterprise
- [x] looksLikeHost validation

### 2. Integration Tests (Manual + Automated)

#### 2.1 File System Persistence
**Test**: Enterprise URL persistence
- [ ] Write enterprise URL to file
- [ ] Read enterprise URL from file
- [ ] Verify file permissions (0600)
- [ ] Handle empty/missing file gracefully

**Test**: Token and enterprise URL coordination
- [ ] Token persisted alongside enterprise URL
- [ ] Both files created in APP_DIR
- [ ] Files survive process restart

#### 2.2 OAuth Flow with Enterprise
**Test**: Device code endpoint
- [ ] Mock fetch to verify URL: `https://{enterprise}/login/device/code`
- [ ] Verify client_id and scope in request body
- [ ] Handle response correctly

**Test**: Access token endpoint
- [ ] Mock fetch to verify URL: `https://{enterprise}/login/oauth/access_token`
- [ ] Verify device_code and grant_type in request body
- [ ] Poll correctly with enterprise URL

**Test**: Without enterprise (backwards compatibility)
- [ ] Device code uses `https://github.com/login/device/code`
- [ ] Access token uses `https://github.com/login/oauth/access_token`

#### 2.3 Copilot API with Enterprise
**Test**: Copilot token fetch
- [ ] Mock fetch to verify URL: `https://api.{enterprise}/copilot_internal/v2/token`
- [ ] Verify authorization header with GitHub token
- [ ] Handle token response

**Test**: Copilot usage fetch
- [ ] Mock fetch to verify URL: `https://api.{enterprise}/copilot_internal/user`
- [ ] Verify headers and response handling

**Test**: Get GitHub user
- [ ] Mock fetch to verify URL: `https://api.{enterprise}/user`
- [ ] Verify authorization header

**Test**: Token refresh
- [ ] Verify refresh interval uses enterprise URL
- [ ] State.enterpriseUrl persists across refresh

**Test**: Without enterprise (backwards compatibility)
- [ ] Copilot token uses `https://api.github.com/copilot_internal/v2/token`
- [ ] Usage uses `https://api.github.com/copilot_internal/user`

#### 2.4 CLI Argument Parsing
**Test**: auth command with --enterprise-url
- [ ] Parse flag correctly
- [ ] Pass to setupGitHubToken
- [ ] Persist to file

**Test**: auth command without flag (interactive)
- [ ] Prompt: "Are you using GitHub Enterprise?"
- [ ] If yes, prompt for host
- [ ] Normalize and persist host

**Test**: start command with --enterprise-url
- [ ] Parse flag correctly
- [ ] Override persisted value if present
- [ ] Set state.enterpriseUrl

**Test**: start command without flag
- [ ] Load persisted enterprise URL if present
- [ ] Use github.com if not present

#### 2.5 End-to-End Scenarios

**Scenario 1**: Fresh auth with enterprise (interactive)
```bash
bun run dev auth
# User prompted: "Are you using GitHub Enterprise?" -> y
# User prompted: "Enter host:" -> ghe.example.com
# Expected: Writes ghe.example.com to enterprise_url file
# Expected: Device code flow uses https://ghe.example.com/...
```

**Scenario 2**: Fresh auth with enterprise (CLI flag)
```bash
bun run dev auth --enterprise-url https://ghe.company.com/
# Expected: Normalizes to ghe.company.com
# Expected: Writes to enterprise_url file
# Expected: Device code flow uses https://ghe.company.com/...
```

**Scenario 3**: Start server with persisted enterprise
```bash
# Assume enterprise_url file contains "ghe.example.com"
bun run dev start
# Expected: Loads ghe.example.com from file
# Expected: All API calls use enterprise endpoints
```

**Scenario 4**: Start server with override
```bash
# Assume enterprise_url file contains "old.ghe.com"
bun run dev start --enterprise-url new.ghe.com
# Expected: Uses new.ghe.com for this run
# Expected: Does NOT overwrite persisted file (only auth writes)
```

**Scenario 5**: Backwards compatibility (no enterprise)
```bash
# No enterprise_url file present
bun run dev start
# Expected: All endpoints use github.com/api.github.com
```

**Scenario 6**: URL normalization variations
- Input: `https://ghe.example.com/` -> `ghe.example.com`
- Input: `http://ghe.example.com` -> `ghe.example.com`
- Input: `ghe.example.com` -> `ghe.example.com`
- Input: `ghe.example.com/` -> `ghe.example.com`

### 3. Mock-Based Integration Tests (to add)

Create `tests/enterprise-integration.test.ts`:

```typescript
import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"
import { setupGitHubToken } from "../src/lib/token"
import { state } from "../src/lib/state"

describe("Enterprise OAuth Flow", () => {
beforeEach(() => {
// Reset state
state.enterpriseUrl = undefined
})

it("should use enterprise URL for device code", async () => {
const fetchMock = mock((url: string) => {
expect(url).toBe("https://ghe.example.com/login/device/code")
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
device_code: "test",
user_code: "ABCD-1234",
verification_uri: "https://ghe.example.com/login/device",
expires_in: 900,
interval: 5
})
})
})

global.fetch = fetchMock as any

state.enterpriseUrl = "ghe.example.com"
// Test device code fetch
// ...
})
})
```

## Test Execution Plan

### Phase 1: Automated Tests (Current)
- [x] Run existing unit tests: `bun test`
- [x] Verify all 38 tests pass

### Phase 2: Add Mock Integration Tests
- [ ] Create `tests/enterprise-integration.test.ts`
- [ ] Mock fetch for OAuth flows
- [ ] Mock fetch for Copilot API calls
- [ ] Verify correct URLs called
- [ ] Run: `bun test`

### Phase 3: Manual CLI Testing
- [ ] Test auth interactive flow
- [ ] Test auth with --enterprise-url flag
- [ ] Test start with persisted enterprise
- [ ] Test start with override
- [ ] Test backwards compatibility

### Phase 4: Real Enterprise Testing (if available)
- [ ] Test with actual GitHub Enterprise Server instance
- [ ] Complete full auth flow
- [ ] Verify Copilot token fetch
- [ ] Verify Copilot usage fetch
- [ ] Test server startup and API calls

## Test Results Log

### Automated Tests (Phase 1)
```
✅ bun test - 38 tests pass
✅ URL normalization tests pass
✅ Build succeeds
✅ Typecheck passes
```

### Integration Tests (Phase 2)
- Status: Pending

### Manual Tests (Phase 3)
- Status: Pending

### Real Enterprise Tests (Phase 4)
- Status: Pending (requires GHE instance)

## Known Limitations
- Cannot test against real GHE without access to instance
- Mock tests verify URL construction but not actual API compatibility
- Interactive prompt testing requires manual verification

## Next Steps
1. Create comprehensive mock-based integration tests
2. Add test for file system persistence
3. Manual CLI testing with mocked server responses
4. Document findings and any issues
Loading