copilot-api-plus 1.0.55 → 1.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +839 -0
- package/README.md +2 -0
- package/dist/{error-BYsVGb6T.js → error-CvvAyU1E.js} +5 -2
- package/dist/{error-BYsVGb6T.js.map → error-CvvAyU1E.js.map} +1 -1
- package/dist/error-Djpro28X.js +2 -0
- package/dist/get-user-CGhBmkXO.js +3 -0
- package/dist/{get-user-wlP5uMaW.js → get-user-DOv07Myc.js} +2 -2
- package/dist/{get-user-wlP5uMaW.js.map → get-user-DOv07Myc.js.map} +1 -1
- package/dist/main.js +6 -6
- package/dist/token-CoKq3Guw.js +5 -0
- package/dist/{token-Bg5qiNBd.js → token-D8U-wBLK.js} +3 -3
- package/dist/{token-Bg5qiNBd.js.map → token-D8U-wBLK.js.map} +1 -1
- package/package.json +1 -1
- package/dist/error-4DW6q2Mo.js +0 -2
- package/dist/get-user-CtEiwKow.js +0 -3
- package/dist/token-B_0VZjlS.js +0 -5
package/README.en.md
ADDED
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
# Copilot API Plus
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/copilot-api-plus)
|
|
4
|
+
[](https://github.com/imbuxiangnan-cyber/copilot-api-plus/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
English | [简体中文](README.md)
|
|
7
|
+
|
|
8
|
+
> A proxy that converts GitHub Copilot, OpenCode Zen, and Google Antigravity into OpenAI & Anthropic compatible APIs. Works with Claude Code, opencode, and more.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 📋 Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Features](#-features)
|
|
15
|
+
- [Quick Start](#-quick-start)
|
|
16
|
+
- [Usage Guide](#-usage-guide)
|
|
17
|
+
- [GitHub Copilot Mode](#1-github-copilot-mode-default)
|
|
18
|
+
- [OpenCode Zen Mode](#2-opencode-zen-mode)
|
|
19
|
+
- [Google Antigravity Mode](#3-google-antigravity-mode)
|
|
20
|
+
- [Proxy Configuration](#-proxy-configuration)
|
|
21
|
+
- [Claude Code Integration](#-claude-code-integration)
|
|
22
|
+
- [opencode Integration](#-opencode-integration)
|
|
23
|
+
- [API Endpoints](#-api-endpoints)
|
|
24
|
+
- [API Key Authentication](#-api-key-authentication)
|
|
25
|
+
- [Technical Details](#-technical-details)
|
|
26
|
+
- [CLI Reference](#️-cli-reference)
|
|
27
|
+
- [Docker Deployment](#-docker-deployment)
|
|
28
|
+
- [FAQ](#-faq)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ✨ Features
|
|
33
|
+
|
|
34
|
+
| Feature | Description |
|
|
35
|
+
|---------|-------------|
|
|
36
|
+
| 🔌 **Multiple Backends** | Choose from GitHub Copilot, OpenCode Zen, or Google Antigravity |
|
|
37
|
+
| 🤖 **Dual Protocol** | Supports both OpenAI Chat Completions API and Anthropic Messages API |
|
|
38
|
+
| 💻 **Claude Code Integration** | One-command Claude Code setup (`--claude-code`) |
|
|
39
|
+
| 📊 **Usage Monitoring** | Real-time API usage dashboard |
|
|
40
|
+
| 🔄 **Auto Authentication** | Automatic token refresh, no manual intervention needed |
|
|
41
|
+
| ⚡ **Rate Limiting** | Built-in request rate control to avoid hitting limits |
|
|
42
|
+
| 🌐 **Proxy Support** | HTTP/HTTPS proxy with persistent configuration |
|
|
43
|
+
| 🐳 **Docker Support** | Full Docker deployment solution |
|
|
44
|
+
| 🔑 **API Key Auth** | Optional API key authentication for public deployments |
|
|
45
|
+
| ✂️ **Context Passthrough** | Full context passthrough to upstream API; clients (e.g. Claude Code) manage compression |
|
|
46
|
+
| 🔍 **Smart Model Matching** | Handles model name format differences (date suffixes, dash/dot versions, etc.) |
|
|
47
|
+
| 🔁 **Antigravity Failover** | Dual-endpoint auto-switching with per-model-family rate tracking and exponential backoff |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🚀 Quick Start
|
|
52
|
+
|
|
53
|
+
### Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Global install
|
|
57
|
+
npm install -g copilot-api-plus
|
|
58
|
+
|
|
59
|
+
# Or run directly with npx (recommended)
|
|
60
|
+
npx copilot-api-plus@latest start
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Basic Usage
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Start server (defaults to GitHub Copilot)
|
|
67
|
+
npx copilot-api-plus@latest start
|
|
68
|
+
|
|
69
|
+
# Use OpenCode Zen
|
|
70
|
+
npx copilot-api-plus@latest start --zen
|
|
71
|
+
|
|
72
|
+
# Use Google Antigravity
|
|
73
|
+
npx copilot-api-plus@latest start --antigravity
|
|
74
|
+
|
|
75
|
+
# Use with Claude Code
|
|
76
|
+
npx copilot-api-plus@latest start --claude-code
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The server listens on `http://localhost:4141` by default.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 📖 Usage Guide
|
|
84
|
+
|
|
85
|
+
### 1. GitHub Copilot Mode (Default)
|
|
86
|
+
|
|
87
|
+
Access AI models using your GitHub Copilot subscription.
|
|
88
|
+
|
|
89
|
+
#### Prerequisites
|
|
90
|
+
- GitHub account
|
|
91
|
+
- Active Copilot subscription (Individual / Business / Enterprise)
|
|
92
|
+
|
|
93
|
+
#### Getting Started
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx copilot-api-plus@latest start
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**First run** will guide you through GitHub OAuth authentication:
|
|
100
|
+
|
|
101
|
+
1. A device code appears in the terminal, e.g.: `XXXX-XXXX`
|
|
102
|
+
2. Open your browser and visit: https://github.com/login/device
|
|
103
|
+
3. Enter the device code and authorize
|
|
104
|
+
4. Return to the terminal and wait for authentication to complete
|
|
105
|
+
|
|
106
|
+
Once authenticated, the token is saved locally. No re-authentication needed on subsequent runs.
|
|
107
|
+
|
|
108
|
+
#### Business / Enterprise Accounts
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Business plan
|
|
112
|
+
npx copilot-api-plus@latest start --account-type business
|
|
113
|
+
|
|
114
|
+
# Enterprise plan
|
|
115
|
+
npx copilot-api-plus@latest start --account-type enterprise
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Available Models
|
|
119
|
+
|
|
120
|
+
| Model | ID | Context Length |
|
|
121
|
+
|-------|-----|---------------|
|
|
122
|
+
| Claude Sonnet 4 | `claude-sonnet-4` | 200K |
|
|
123
|
+
| Claude Sonnet 4.5 | `claude-sonnet-4.5` | 200K |
|
|
124
|
+
| GPT-4.1 | `gpt-4.1` | 1M |
|
|
125
|
+
| o4-mini | `o4-mini` | 200K |
|
|
126
|
+
| Gemini 2.5 Pro | `gemini-2.5-pro` | 1M |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### 2. OpenCode Zen Mode
|
|
131
|
+
|
|
132
|
+
Use [OpenCode Zen](https://opencode.ai/zen)'s multi-model API service, supporting GPT-5, Claude, Gemini, and other top coding models.
|
|
133
|
+
|
|
134
|
+
#### Prerequisites
|
|
135
|
+
1. Visit https://opencode.ai/zen
|
|
136
|
+
2. Register and create an API Key
|
|
137
|
+
|
|
138
|
+
#### Getting Started
|
|
139
|
+
|
|
140
|
+
**Option 1: Interactive setup**
|
|
141
|
+
```bash
|
|
142
|
+
npx copilot-api-plus@latest start --zen
|
|
143
|
+
```
|
|
144
|
+
First run will prompt for an API Key, which is saved for future use.
|
|
145
|
+
|
|
146
|
+
**Option 2: Provide API Key directly**
|
|
147
|
+
```bash
|
|
148
|
+
npx copilot-api-plus@latest start --zen --zen-api-key YOUR_API_KEY
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Available Models
|
|
152
|
+
|
|
153
|
+
| Model | ID | Description |
|
|
154
|
+
|-------|-----|-------------|
|
|
155
|
+
| GPT-5.2 | `gpt-5.2` | OpenAI latest |
|
|
156
|
+
| GPT-5.1 Codex Max | `gpt-5.1-codex-max` | Code-optimized |
|
|
157
|
+
| GPT-5.1 Codex | `gpt-5.1-codex` | Code-focused |
|
|
158
|
+
| GPT-5 Codex | `gpt-5-codex` | OpenAI Responses API |
|
|
159
|
+
| Claude Opus 4.5 | `claude-opus-4-5` | Anthropic Claude (200K) |
|
|
160
|
+
| Claude Sonnet 4.5 | `claude-sonnet-4-5` | Anthropic Claude (200K) |
|
|
161
|
+
| Claude Sonnet 4 | `claude-sonnet-4` | Anthropic Claude |
|
|
162
|
+
| Gemini 3 Pro | `gemini-3-pro` | Google Gemini |
|
|
163
|
+
| Qwen3 Coder | `qwen3-coder` | Alibaba Qwen |
|
|
164
|
+
| Kimi K2 | `kimi-k2` | Moonshot |
|
|
165
|
+
| Grok Code Fast 1 | `grok-code-fast-1` | xAI |
|
|
166
|
+
|
|
167
|
+
More models at [opencode.ai/zen](https://opencode.ai/zen)
|
|
168
|
+
|
|
169
|
+
#### API Endpoints
|
|
170
|
+
|
|
171
|
+
Zen mode supports the following endpoints:
|
|
172
|
+
|
|
173
|
+
| Endpoint | Description |
|
|
174
|
+
|----------|-------------|
|
|
175
|
+
| `/v1/chat/completions` | OpenAI-compatible Chat API |
|
|
176
|
+
| `/v1/messages` | Anthropic-compatible Messages API |
|
|
177
|
+
| `/v1/responses` | OpenAI Responses API (GPT-5 series) |
|
|
178
|
+
| `/v1/models` | List available models |
|
|
179
|
+
|
|
180
|
+
Dedicated endpoints (accessible without `--zen` flag):
|
|
181
|
+
- `/zen/v1/chat/completions`
|
|
182
|
+
- `/zen/v1/messages`
|
|
183
|
+
- `/zen/v1/responses`
|
|
184
|
+
- `/zen/v1/models`
|
|
185
|
+
|
|
186
|
+
#### Manage API Key
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# View/change API Key (clearing it will prompt for a new one on next start)
|
|
190
|
+
npx copilot-api-plus@latest logout --zen
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### 3. Google Antigravity Mode
|
|
196
|
+
|
|
197
|
+
Use Google Antigravity API service, supporting Gemini and Claude models.
|
|
198
|
+
|
|
199
|
+
#### Prerequisites
|
|
200
|
+
- Google account
|
|
201
|
+
|
|
202
|
+
#### Authentication Methods
|
|
203
|
+
|
|
204
|
+
**Option 1: API Key (Recommended - Simplest)**
|
|
205
|
+
|
|
206
|
+
1. Get an API Key at https://aistudio.google.com/apikey
|
|
207
|
+
2. Start with environment variable:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Linux/macOS
|
|
211
|
+
GEMINI_API_KEY=your_api_key npx copilot-api-plus@latest start --antigravity
|
|
212
|
+
|
|
213
|
+
# Windows PowerShell
|
|
214
|
+
$env:GEMINI_API_KEY = "your_api_key"
|
|
215
|
+
npx copilot-api-plus@latest start --antigravity
|
|
216
|
+
|
|
217
|
+
# Windows CMD
|
|
218
|
+
set GEMINI_API_KEY=your_api_key
|
|
219
|
+
npx copilot-api-plus@latest start --antigravity
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Option 2: OAuth Web Login (Recommended)**
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npx copilot-api-plus@latest start --antigravity
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
First run will prompt you to choose a login method:
|
|
229
|
+
- **Web (Recommended)**: Opens browser for Google login, automatically captures the callback
|
|
230
|
+
- **Manual**: Manually copy the callback URL to the terminal
|
|
231
|
+
|
|
232
|
+
**Option 3: Custom OAuth Credentials**
|
|
233
|
+
|
|
234
|
+
If you encounter an `invalid_client` error, create your own OAuth app:
|
|
235
|
+
|
|
236
|
+
1. Visit https://console.cloud.google.com/apis/credentials
|
|
237
|
+
2. Create an OAuth 2.0 Client ID (select "Desktop application" type)
|
|
238
|
+
3. Add redirect URI: `http://localhost:8046/callback`
|
|
239
|
+
4. Use environment variables or CLI arguments:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Environment variables
|
|
243
|
+
ANTIGRAVITY_CLIENT_ID=your_client_id ANTIGRAVITY_CLIENT_SECRET=your_secret \
|
|
244
|
+
npx copilot-api-plus@latest start --antigravity
|
|
245
|
+
|
|
246
|
+
# CLI arguments
|
|
247
|
+
npx copilot-api-plus@latest start --antigravity \
|
|
248
|
+
--antigravity-client-id your_client_id \
|
|
249
|
+
--antigravity-client-secret your_secret
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### Available Models
|
|
253
|
+
|
|
254
|
+
| Model | ID | Description |
|
|
255
|
+
|-------|-----|-------------|
|
|
256
|
+
| Gemini 2.5 Pro | `gemini-2.5-pro-exp-03-25` | Google Gemini |
|
|
257
|
+
| Gemini 2.5 Pro Preview | `gemini-2.5-pro-preview-05-06` | Google Gemini |
|
|
258
|
+
| Gemini 2.0 Flash | `gemini-2.0-flash-exp` | Fast responses |
|
|
259
|
+
| Gemini 2.0 Flash Thinking | `gemini-2.0-flash-thinking-exp` | Chain-of-thought |
|
|
260
|
+
| Claude Opus 4.5 | `claude-opus-4-5` | Anthropic Claude |
|
|
261
|
+
| Claude Sonnet 4.5 | `claude-sonnet-4-5` | Anthropic Claude |
|
|
262
|
+
|
|
263
|
+
#### Features
|
|
264
|
+
- ✅ Automatic token refresh
|
|
265
|
+
- ✅ Multi-account support with auto-rotation
|
|
266
|
+
- ✅ Auto-switch on quota exhaustion
|
|
267
|
+
- ✅ Thinking model support (chain-of-thought output)
|
|
268
|
+
|
|
269
|
+
#### Multi-Account Management
|
|
270
|
+
|
|
271
|
+
Add multiple Google accounts; the system auto-switches when quota is exhausted:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Add new account
|
|
275
|
+
npx copilot-api-plus@latest antigravity add
|
|
276
|
+
|
|
277
|
+
# List all accounts
|
|
278
|
+
npx copilot-api-plus@latest antigravity list
|
|
279
|
+
|
|
280
|
+
# Remove account by index
|
|
281
|
+
npx copilot-api-plus@latest antigravity remove 0
|
|
282
|
+
|
|
283
|
+
# Clear all accounts
|
|
284
|
+
npx copilot-api-plus@latest antigravity clear
|
|
285
|
+
# Or use logout
|
|
286
|
+
npx copilot-api-plus@latest logout --antigravity
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 🌐 Proxy Configuration
|
|
292
|
+
|
|
293
|
+
Two ways to configure a proxy:
|
|
294
|
+
|
|
295
|
+
### Option 1: Persistent Configuration (Recommended)
|
|
296
|
+
|
|
297
|
+
Configure once, automatically used on every startup.
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
# Interactive setup
|
|
301
|
+
npx copilot-api-plus@latest proxy --set
|
|
302
|
+
|
|
303
|
+
# Or set directly
|
|
304
|
+
npx copilot-api-plus@latest proxy --http-proxy http://127.0.0.1:7890
|
|
305
|
+
|
|
306
|
+
# Set both HTTP and HTTPS proxy
|
|
307
|
+
npx copilot-api-plus@latest proxy --http-proxy http://127.0.0.1:7890 --https-proxy http://127.0.0.1:7890
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Proxy Management Commands
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
# View current proxy settings
|
|
314
|
+
npx copilot-api-plus@latest proxy
|
|
315
|
+
|
|
316
|
+
# Enable proxy
|
|
317
|
+
npx copilot-api-plus@latest proxy --enable
|
|
318
|
+
|
|
319
|
+
# Disable proxy (keeps settings)
|
|
320
|
+
npx copilot-api-plus@latest proxy --disable
|
|
321
|
+
|
|
322
|
+
# Clear proxy settings
|
|
323
|
+
npx copilot-api-plus@latest proxy --clear
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### Example: Configure Clash Proxy
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# Clash default port 7890
|
|
330
|
+
npx copilot-api-plus@latest proxy --http-proxy http://127.0.0.1:7890
|
|
331
|
+
|
|
332
|
+
# Verify configuration
|
|
333
|
+
npx copilot-api-plus@latest proxy
|
|
334
|
+
# Output:
|
|
335
|
+
# Current proxy configuration:
|
|
336
|
+
# Status: ✅ Enabled
|
|
337
|
+
# HTTP_PROXY: http://127.0.0.1:7890
|
|
338
|
+
# HTTPS_PROXY: http://127.0.0.1:7890
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Option 2: Environment Variables (Temporary)
|
|
342
|
+
|
|
343
|
+
Only effective for the current session:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# Linux/macOS
|
|
347
|
+
export HTTP_PROXY=http://127.0.0.1:7890
|
|
348
|
+
export HTTPS_PROXY=http://127.0.0.1:7890
|
|
349
|
+
npx copilot-api-plus@latest start --proxy-env
|
|
350
|
+
|
|
351
|
+
# Windows PowerShell
|
|
352
|
+
$env:HTTP_PROXY = "http://127.0.0.1:7890"
|
|
353
|
+
$env:HTTPS_PROXY = "http://127.0.0.1:7890"
|
|
354
|
+
npx copilot-api-plus@latest start --proxy-env
|
|
355
|
+
|
|
356
|
+
# Windows CMD
|
|
357
|
+
set HTTP_PROXY=http://127.0.0.1:7890
|
|
358
|
+
set HTTPS_PROXY=http://127.0.0.1:7890
|
|
359
|
+
npx copilot-api-plus@latest start --proxy-env
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Proxy Priority
|
|
363
|
+
|
|
364
|
+
1. `--proxy-env` flag (reads from environment variables)
|
|
365
|
+
2. Persistent configuration (set via `proxy --set`)
|
|
366
|
+
3. No proxy
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 💻 Claude Code Integration
|
|
371
|
+
|
|
372
|
+
[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) is Anthropic's AI coding assistant.
|
|
373
|
+
|
|
374
|
+
### Auto Configuration (Recommended)
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
# Using GitHub Copilot as backend
|
|
378
|
+
npx copilot-api-plus@latest start --claude-code
|
|
379
|
+
|
|
380
|
+
# Using OpenCode Zen as backend
|
|
381
|
+
npx copilot-api-plus@latest start --zen --claude-code
|
|
382
|
+
|
|
383
|
+
# Using Google Antigravity as backend
|
|
384
|
+
npx copilot-api-plus@latest start --antigravity --claude-code
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
After running:
|
|
388
|
+
1. Select main model (for code generation)
|
|
389
|
+
2. Select fast model (for background tasks)
|
|
390
|
+
3. The launch command is automatically copied to clipboard
|
|
391
|
+
4. **Open a new terminal**, paste and run to start Claude Code
|
|
392
|
+
|
|
393
|
+
### Manual Configuration
|
|
394
|
+
|
|
395
|
+
Create `.claude/settings.json` in your project root:
|
|
396
|
+
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"env": {
|
|
400
|
+
"ANTHROPIC_BASE_URL": "http://localhost:4141",
|
|
401
|
+
"ANTHROPIC_AUTH_TOKEN": "dummy",
|
|
402
|
+
"ANTHROPIC_MODEL": "claude-sonnet-4",
|
|
403
|
+
"ANTHROPIC_SMALL_FAST_MODEL": "gpt-4.1",
|
|
404
|
+
"DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1"
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Then start the copilot-api-plus server and run `claude` in that project directory.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 🔧 opencode Integration
|
|
414
|
+
|
|
415
|
+
[opencode](https://github.com/sst/opencode) is a modern AI coding assistant.
|
|
416
|
+
|
|
417
|
+
### Setup
|
|
418
|
+
|
|
419
|
+
1. Create `opencode.json` in your project root:
|
|
420
|
+
|
|
421
|
+
```json
|
|
422
|
+
{
|
|
423
|
+
"$schema": "https://opencode.ai/config.json",
|
|
424
|
+
"provider": {
|
|
425
|
+
"copilot-api-plus": {
|
|
426
|
+
"api": "openai-compatible",
|
|
427
|
+
"name": "Copilot API Plus",
|
|
428
|
+
"options": {
|
|
429
|
+
"baseURL": "http://127.0.0.1:4141/v1"
|
|
430
|
+
},
|
|
431
|
+
"models": {
|
|
432
|
+
"claude-sonnet-4": {
|
|
433
|
+
"name": "Claude Sonnet 4",
|
|
434
|
+
"id": "claude-sonnet-4",
|
|
435
|
+
"max_tokens": 64000,
|
|
436
|
+
"profile": "coder",
|
|
437
|
+
"limit": { "context": 200000 }
|
|
438
|
+
},
|
|
439
|
+
"gpt-4.1": {
|
|
440
|
+
"name": "GPT-4.1",
|
|
441
|
+
"id": "gpt-4.1",
|
|
442
|
+
"max_tokens": 32768,
|
|
443
|
+
"profile": "coder",
|
|
444
|
+
"limit": { "context": 1047576 }
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
2. Start copilot-api-plus:
|
|
453
|
+
```bash
|
|
454
|
+
npx copilot-api-plus@latest start
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
3. Run opencode in the same directory:
|
|
458
|
+
```bash
|
|
459
|
+
npx opencode@latest
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
4. Select `copilot-api-plus` as the provider
|
|
463
|
+
|
|
464
|
+
### Shortcut: Environment Variables
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
# Set environment variables
|
|
468
|
+
export OPENAI_BASE_URL=http://127.0.0.1:4141/v1
|
|
469
|
+
export OPENAI_API_KEY=dummy
|
|
470
|
+
|
|
471
|
+
# Run opencode
|
|
472
|
+
npx opencode@latest
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## 📡 API Endpoints
|
|
478
|
+
|
|
479
|
+
The server listens on `http://localhost:4141` by default.
|
|
480
|
+
|
|
481
|
+
### OpenAI-Compatible Endpoints
|
|
482
|
+
|
|
483
|
+
| Endpoint | Method | Description |
|
|
484
|
+
|----------|--------|-------------|
|
|
485
|
+
| `/v1/chat/completions` | POST | Chat completions (streaming supported) |
|
|
486
|
+
| `/v1/models` | GET | Model list |
|
|
487
|
+
| `/v1/embeddings` | POST | Text embeddings (Copilot only) |
|
|
488
|
+
|
|
489
|
+
### Anthropic-Compatible Endpoints
|
|
490
|
+
|
|
491
|
+
| Endpoint | Method | Description |
|
|
492
|
+
|----------|--------|-------------|
|
|
493
|
+
| `/v1/messages` | POST | Messages API (streaming supported) |
|
|
494
|
+
| `/v1/messages/count_tokens` | POST | Token counting |
|
|
495
|
+
|
|
496
|
+
### Dedicated Endpoints
|
|
497
|
+
|
|
498
|
+
Each backend has its own dedicated routes, accessible regardless of the default backend:
|
|
499
|
+
|
|
500
|
+
| Route Prefix | Description |
|
|
501
|
+
|--------------|-------------|
|
|
502
|
+
| `/copilot/v1/*` | GitHub Copilot |
|
|
503
|
+
| `/zen/v1/*` | OpenCode Zen |
|
|
504
|
+
| `/antigravity/v1/*` | Google Antigravity |
|
|
505
|
+
|
|
506
|
+
### Monitoring Endpoints
|
|
507
|
+
|
|
508
|
+
| Endpoint | Method | Description |
|
|
509
|
+
|----------|--------|-------------|
|
|
510
|
+
| `/usage` | GET | Usage statistics (Copilot only) |
|
|
511
|
+
| `/token` | GET | Current token info |
|
|
512
|
+
|
|
513
|
+
### Examples
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
# OpenAI format
|
|
517
|
+
curl http://localhost:4141/v1/chat/completions \
|
|
518
|
+
-H "Content-Type: application/json" \
|
|
519
|
+
-d '{
|
|
520
|
+
"model": "claude-sonnet-4",
|
|
521
|
+
"messages": [{"role": "user", "content": "Hello!"}]
|
|
522
|
+
}'
|
|
523
|
+
|
|
524
|
+
# Anthropic format
|
|
525
|
+
curl http://localhost:4141/v1/messages \
|
|
526
|
+
-H "Content-Type: application/json" \
|
|
527
|
+
-H "x-api-key: dummy" \
|
|
528
|
+
-d '{
|
|
529
|
+
"model": "claude-sonnet-4",
|
|
530
|
+
"max_tokens": 1024,
|
|
531
|
+
"messages": [{"role": "user", "content": "Hello!"}]
|
|
532
|
+
}'
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## 🔑 API Key Authentication
|
|
538
|
+
|
|
539
|
+
To protect your service when exposed publicly, enable API key authentication:
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
# Single key
|
|
543
|
+
npx copilot-api-plus@latest start --api-key my-secret-key
|
|
544
|
+
|
|
545
|
+
# Multiple keys
|
|
546
|
+
npx copilot-api-plus@latest start --api-key key1 --api-key key2
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Once enabled, all requests must include an API key:
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
# OpenAI format - via Authorization header
|
|
553
|
+
curl http://localhost:4141/v1/chat/completions \
|
|
554
|
+
-H "Authorization: Bearer my-secret-key" \
|
|
555
|
+
-H "Content-Type: application/json" \
|
|
556
|
+
-d '{"model": "claude-sonnet-4", "messages": [{"role": "user", "content": "Hello"}]}'
|
|
557
|
+
|
|
558
|
+
# Anthropic format - via x-api-key header
|
|
559
|
+
curl http://localhost:4141/v1/messages \
|
|
560
|
+
-H "x-api-key: my-secret-key" \
|
|
561
|
+
-H "Content-Type: application/json" \
|
|
562
|
+
-d '{"model": "claude-sonnet-4", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}]}'
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
When using with Claude Code, set `ANTHROPIC_AUTH_TOKEN` to your API key.
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## 🔧 Technical Details
|
|
570
|
+
|
|
571
|
+
### Context Management
|
|
572
|
+
|
|
573
|
+
The proxy does not truncate context. All messages are passed through to the upstream API as-is. Context compression is handled by the client:
|
|
574
|
+
|
|
575
|
+
- **Claude Code**: Uses `/count_tokens` to get the current token count, and automatically triggers `/compact` when approaching the limit
|
|
576
|
+
- **Other clients**: If the upstream API returns 400 (token limit exceeded), the client handles retry logic
|
|
577
|
+
|
|
578
|
+
### Smart Model Name Matching
|
|
579
|
+
|
|
580
|
+
Anthropic-format model names (e.g. `claude-opus-4-6`) may differ from Copilot's model list IDs. The proxy uses multi-strategy matching:
|
|
581
|
+
|
|
582
|
+
| Strategy | Example |
|
|
583
|
+
|----------|---------|
|
|
584
|
+
| Exact match | `claude-opus-4-6` → `claude-opus-4-6` |
|
|
585
|
+
| Strip date suffix | `claude-opus-4-6-20251101` → `claude-opus-4-6` |
|
|
586
|
+
| Dash → Dot | `claude-opus-4-5` → `claude-opus-4.5` |
|
|
587
|
+
| Dot → Dash | `claude-opus-4.5` → `claude-opus-4-5` |
|
|
588
|
+
|
|
589
|
+
For Anthropic endpoints (`/v1/messages`), `translateModelName` also handles legacy format conversion (e.g. `claude-3-5-sonnet` → `claude-sonnet-4.5`) before applying the above strategies.
|
|
590
|
+
|
|
591
|
+
### Antigravity Endpoint Failover
|
|
592
|
+
|
|
593
|
+
Google Antigravity mode has built-in reliability features:
|
|
594
|
+
|
|
595
|
+
- **Dual-endpoint auto-switching**: Daily sandbox and production endpoints; automatically switches on failure
|
|
596
|
+
- **Per-model-family rate tracking**: Separate rate limit tracking for Gemini and Claude model families
|
|
597
|
+
- **Exponential backoff retry**: Auto-retry on 429/503 errors; short intervals stay on the same endpoint, longer intervals switch endpoints
|
|
598
|
+
|
|
599
|
+
### Request Logging
|
|
600
|
+
|
|
601
|
+
Each API request outputs a log line with model name, status code, and duration:
|
|
602
|
+
|
|
603
|
+
```log
|
|
604
|
+
[claude-opus-4-6] 13:13:39 <-- POST /v1/messages?beta=true
|
|
605
|
+
[claude-opus-4-6] 13:13:59 --> POST /v1/messages?beta=true 200 20.1s
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Network Retry
|
|
609
|
+
|
|
610
|
+
Built-in retry for transient network errors (TLS disconnect, connection reset, etc.):
|
|
611
|
+
|
|
612
|
+
- Up to 2 retries (3 total attempts)
|
|
613
|
+
- Backoff intervals: 1s, 2s
|
|
614
|
+
- Only retries network-layer errors; HTTP error codes (e.g. 400/500) are not retried
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## ⚙️ CLI Reference
|
|
619
|
+
|
|
620
|
+
### Commands
|
|
621
|
+
|
|
622
|
+
| Command | Description |
|
|
623
|
+
|---------|-------------|
|
|
624
|
+
| `start` | Start the API server |
|
|
625
|
+
| `auth` | Run GitHub authentication only |
|
|
626
|
+
| `logout` | Clear saved credentials |
|
|
627
|
+
| `proxy` | Configure proxy settings |
|
|
628
|
+
| `antigravity` | Manage Google Antigravity accounts |
|
|
629
|
+
| `check-usage` | View Copilot usage |
|
|
630
|
+
| `debug` | Show debug information |
|
|
631
|
+
|
|
632
|
+
### start Options
|
|
633
|
+
|
|
634
|
+
| Option | Alias | Default | Description |
|
|
635
|
+
|--------|-------|---------|-------------|
|
|
636
|
+
| `--port` | `-p` | 4141 | Listen port |
|
|
637
|
+
| `--verbose` | `-v` | false | Verbose logging |
|
|
638
|
+
| `--account-type` | `-a` | individual | Account type (individual/business/enterprise) |
|
|
639
|
+
| `--claude-code` | `-c` | false | Generate Claude Code launch command |
|
|
640
|
+
| `--zen` | `-z` | false | Enable OpenCode Zen mode |
|
|
641
|
+
| `--zen-api-key` | - | - | Zen API Key |
|
|
642
|
+
| `--antigravity` | - | false | Enable Google Antigravity mode |
|
|
643
|
+
| `--antigravity-client-id` | - | - | Antigravity OAuth Client ID |
|
|
644
|
+
| `--antigravity-client-secret` | - | - | Antigravity OAuth Client Secret |
|
|
645
|
+
| `--rate-limit` | `-r` | - | Request interval (seconds) |
|
|
646
|
+
| `--wait` | `-w` | false | Wait instead of error when rate limited |
|
|
647
|
+
| `--manual` | - | false | Manually approve each request |
|
|
648
|
+
| `--github-token` | `-g` | - | Provide GitHub Token directly |
|
|
649
|
+
| `--show-token` | - | false | Show token info |
|
|
650
|
+
| `--proxy-env` | - | false | Read proxy from environment variables |
|
|
651
|
+
| `--api-key` | - | - | API key auth (can be specified multiple times) |
|
|
652
|
+
|
|
653
|
+
### proxy Options
|
|
654
|
+
|
|
655
|
+
| Option | Description |
|
|
656
|
+
|--------|-------------|
|
|
657
|
+
| `--set` | Interactive proxy setup |
|
|
658
|
+
| `--enable` | Enable saved proxy |
|
|
659
|
+
| `--disable` | Disable proxy (keeps settings) |
|
|
660
|
+
| `--clear` | Clear proxy settings |
|
|
661
|
+
| `--show` | Show current settings |
|
|
662
|
+
| `--http-proxy` | HTTP proxy URL |
|
|
663
|
+
| `--https-proxy` | HTTPS proxy URL |
|
|
664
|
+
| `--no-proxy` | Hosts to bypass proxy |
|
|
665
|
+
|
|
666
|
+
### logout Options
|
|
667
|
+
|
|
668
|
+
| Option | Alias | Description |
|
|
669
|
+
|--------|-------|-------------|
|
|
670
|
+
| `--github` | `-g` | Clear GitHub Copilot credentials only |
|
|
671
|
+
| `--zen` | `-z` | Clear Zen credentials only |
|
|
672
|
+
| `--antigravity` | - | Clear Antigravity credentials only |
|
|
673
|
+
| `--all` | `-a` | Clear all credentials |
|
|
674
|
+
|
|
675
|
+
> **Tip**: Running `logout` without arguments shows an interactive menu.
|
|
676
|
+
|
|
677
|
+
### antigravity Subcommands
|
|
678
|
+
|
|
679
|
+
| Subcommand | Description |
|
|
680
|
+
|------------|-------------|
|
|
681
|
+
| `add` | Add a new Antigravity account (OAuth login) |
|
|
682
|
+
| `list` | List all configured accounts and their status |
|
|
683
|
+
| `remove <index>` | Remove account by index |
|
|
684
|
+
| `clear` | Clear all Antigravity accounts (requires confirmation) |
|
|
685
|
+
|
|
686
|
+
```bash
|
|
687
|
+
# Examples
|
|
688
|
+
npx copilot-api-plus@latest antigravity add # Add account
|
|
689
|
+
npx copilot-api-plus@latest antigravity list # List accounts
|
|
690
|
+
npx copilot-api-plus@latest antigravity remove 0 # Remove account at index 0
|
|
691
|
+
npx copilot-api-plus@latest antigravity clear # Clear all accounts
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## 🐳 Docker Deployment
|
|
697
|
+
|
|
698
|
+
### Quick Start
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
# Using pre-built image
|
|
702
|
+
docker run -p 4141:4141 \
|
|
703
|
+
-v ./copilot-data:/root/.local/share/copilot-api-plus \
|
|
704
|
+
ghcr.io/imbuxiangnan-cyber/copilot-api-plus
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Build from Source
|
|
708
|
+
|
|
709
|
+
```bash
|
|
710
|
+
# Build image
|
|
711
|
+
docker build -t copilot-api-plus .
|
|
712
|
+
|
|
713
|
+
# Run container
|
|
714
|
+
docker run -p 4141:4141 \
|
|
715
|
+
-v ./copilot-data:/root/.local/share/copilot-api-plus \
|
|
716
|
+
copilot-api-plus
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### Docker Compose
|
|
720
|
+
|
|
721
|
+
```yaml
|
|
722
|
+
version: "3.8"
|
|
723
|
+
services:
|
|
724
|
+
copilot-api-plus:
|
|
725
|
+
build: .
|
|
726
|
+
ports:
|
|
727
|
+
- "4141:4141"
|
|
728
|
+
volumes:
|
|
729
|
+
- ./copilot-data:/root/.local/share/copilot-api-plus
|
|
730
|
+
environment:
|
|
731
|
+
- GH_TOKEN=your_github_token # Optional
|
|
732
|
+
restart: unless-stopped
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Using a Proxy
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
docker run -p 4141:4141 \
|
|
739
|
+
-e HTTP_PROXY=http://host.docker.internal:7890 \
|
|
740
|
+
-e HTTPS_PROXY=http://host.docker.internal:7890 \
|
|
741
|
+
-v ./copilot-data:/root/.local/share/copilot-api-plus \
|
|
742
|
+
copilot-api-plus start --proxy-env
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
## ❓ FAQ
|
|
748
|
+
|
|
749
|
+
### Data Storage Location
|
|
750
|
+
|
|
751
|
+
All data is stored in `~/.local/share/copilot-api-plus/`:
|
|
752
|
+
|
|
753
|
+
| File | Description |
|
|
754
|
+
|------|-------------|
|
|
755
|
+
| `github_token` | GitHub Token |
|
|
756
|
+
| `zen-auth.json` | Zen API Key |
|
|
757
|
+
| `antigravity-accounts.json` | Antigravity accounts |
|
|
758
|
+
| `config.json` | Proxy and other settings |
|
|
759
|
+
|
|
760
|
+
### Switching Accounts
|
|
761
|
+
|
|
762
|
+
```bash
|
|
763
|
+
# Interactive credential selection
|
|
764
|
+
npx copilot-api-plus@latest logout
|
|
765
|
+
|
|
766
|
+
# Clear GitHub Copilot credentials only
|
|
767
|
+
npx copilot-api-plus@latest logout --github
|
|
768
|
+
# Or shorthand
|
|
769
|
+
npx copilot-api-plus@latest logout -g
|
|
770
|
+
|
|
771
|
+
# Clear Zen credentials
|
|
772
|
+
npx copilot-api-plus@latest logout --zen
|
|
773
|
+
|
|
774
|
+
# Clear Antigravity credentials
|
|
775
|
+
npx copilot-api-plus@latest logout --antigravity
|
|
776
|
+
|
|
777
|
+
# Clear all credentials
|
|
778
|
+
npx copilot-api-plus@latest logout --all
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### View Usage
|
|
782
|
+
|
|
783
|
+
```bash
|
|
784
|
+
# CLI (Copilot only)
|
|
785
|
+
npx copilot-api-plus@latest check-usage
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
After starting the server, you can also access the web dashboard:
|
|
789
|
+
```
|
|
790
|
+
https://imbuxiangnan-cyber.github.io/copilot-api-plus?endpoint=http://localhost:4141/usage
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### Debugging
|
|
794
|
+
|
|
795
|
+
```bash
|
|
796
|
+
# Show debug info
|
|
797
|
+
npx copilot-api-plus@latest debug
|
|
798
|
+
|
|
799
|
+
# JSON output
|
|
800
|
+
npx copilot-api-plus@latest debug --json
|
|
801
|
+
|
|
802
|
+
# Enable verbose logging
|
|
803
|
+
npx copilot-api-plus@latest start --verbose
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Rate Limiting
|
|
807
|
+
|
|
808
|
+
To avoid triggering GitHub's abuse detection:
|
|
809
|
+
|
|
810
|
+
```bash
|
|
811
|
+
# Set 30-second request interval
|
|
812
|
+
npx copilot-api-plus@latest start --rate-limit 30
|
|
813
|
+
|
|
814
|
+
# Wait instead of error when rate limited
|
|
815
|
+
npx copilot-api-plus@latest start --rate-limit 30 --wait
|
|
816
|
+
|
|
817
|
+
# Manually approve each request
|
|
818
|
+
npx copilot-api-plus@latest start --manual
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
## ⚠️ Disclaimer
|
|
824
|
+
|
|
825
|
+
> [!WARNING]
|
|
826
|
+
> This is a reverse-engineered proxy for the GitHub Copilot API. **Not officially supported by GitHub** and may stop working at any time. Use at your own risk.
|
|
827
|
+
|
|
828
|
+
> [!WARNING]
|
|
829
|
+
> **GitHub Safety Notice**: Excessive automated or scripted use of Copilot may trigger GitHub's abuse detection systems, resulting in Copilot access suspension. Please use responsibly.
|
|
830
|
+
>
|
|
831
|
+
> Related policies:
|
|
832
|
+
> - [GitHub Acceptable Use Policies](https://docs.github.com/site-policy/acceptable-use-policies/github-acceptable-use-policies)
|
|
833
|
+
> - [GitHub Copilot Terms](https://docs.github.com/site-policy/github-terms/github-terms-for-additional-products-and-features#github-copilot)
|
|
834
|
+
|
|
835
|
+
---
|
|
836
|
+
|
|
837
|
+
## 📄 License
|
|
838
|
+
|
|
839
|
+
MIT License
|
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/copilot-api-plus)
|
|
4
4
|
[](https://github.com/imbuxiangnan-cyber/copilot-api-plus/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
+
[English](README.en.md) | 简体中文
|
|
7
|
+
|
|
6
8
|
> A proxy that converts GitHub Copilot, OpenCode Zen, and Google Antigravity into OpenAI & Anthropic compatible APIs. Works with Claude Code, opencode, and more.
|
|
7
9
|
|
|
8
10
|
将 GitHub Copilot、OpenCode Zen、Google Antigravity 等 AI 服务转换为 **OpenAI** 和 **Anthropic** 兼容 API,支持与 [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview)、[opencode](https://github.com/sst/opencode) 等工具无缝集成。
|
|
@@ -30,7 +30,10 @@ async function forwardError(c, error) {
|
|
|
30
30
|
type: "error"
|
|
31
31
|
} }, error.response.status);
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
const message = error.message || String(error);
|
|
34
|
+
const cause = error.cause;
|
|
35
|
+
if (cause) consola.error(`${message}: ${cause.message}`);
|
|
36
|
+
else consola.error(message);
|
|
34
37
|
return c.json({ error: {
|
|
35
38
|
message: error.message,
|
|
36
39
|
type: "error"
|
|
@@ -39,4 +42,4 @@ async function forwardError(c, error) {
|
|
|
39
42
|
//#endregion
|
|
40
43
|
export { forwardError as n, HTTPError as t };
|
|
41
44
|
|
|
42
|
-
//# sourceMappingURL=error-
|
|
45
|
+
//# sourceMappingURL=error-CvvAyU1E.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-
|
|
1
|
+
{"version":3,"file":"error-CvvAyU1E.js","names":[],"sources":["../src/lib/error.ts"],"sourcesContent":["import type { Context } from \"hono\"\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\"\n\nimport consola from \"consola\"\n\nexport class HTTPError extends Error {\n response: Response\n\n constructor(message: string, response: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport async function forwardError(c: Context, error: unknown) {\n if (error instanceof HTTPError) {\n // Try to read error body, but it may already be consumed by the caller\n let errorText: string\n try {\n errorText = await error.response.text()\n } catch {\n // Body already read — fall back to the error message\n errorText = error.message\n }\n\n // 400 errors: concise log, already detailed upstream\n if (error.response.status === 400) {\n // no extra logging, upstream already printed details\n } else {\n let errorJson: unknown\n try {\n errorJson = JSON.parse(errorText)\n } catch {\n errorJson = errorText\n }\n consola.error(\"Error occurred:\", error)\n consola.error(\"HTTP error:\", errorJson)\n }\n\n return c.json(\n {\n error: {\n message: errorText,\n type: \"error\",\n },\n },\n error.response.status as ContentfulStatusCode,\n )\n }\n\n // Network errors (fetch failed, TLS disconnect, etc.) — concise log\n const message = (error as Error).message || String(error)\n const cause = (error as { cause?: Error }).cause\n if (cause) {\n consola.error(`${message}: ${cause.message}`)\n } else {\n consola.error(message)\n }\n return c.json(\n {\n error: {\n message: (error as Error).message,\n type: \"error\",\n },\n },\n 500,\n )\n}\n"],"mappings":";;AAKA,IAAa,YAAb,cAA+B,MAAM;CACnC;CAEA,YAAY,SAAiB,UAAoB;AAC/C,QAAM,QAAQ;AACd,OAAK,WAAW;;;AAIpB,eAAsB,aAAa,GAAY,OAAgB;AAC7D,KAAI,iBAAiB,WAAW;EAE9B,IAAI;AACJ,MAAI;AACF,eAAY,MAAM,MAAM,SAAS,MAAM;UACjC;AAEN,eAAY,MAAM;;AAIpB,MAAI,MAAM,SAAS,WAAW,KAAK,QAE5B;GACL,IAAI;AACJ,OAAI;AACF,gBAAY,KAAK,MAAM,UAAU;WAC3B;AACN,gBAAY;;AAEd,WAAQ,MAAM,mBAAmB,MAAM;AACvC,WAAQ,MAAM,eAAe,UAAU;;AAGzC,SAAO,EAAE,KACP,EACE,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,EACD,MAAM,SAAS,OAChB;;CAIH,MAAM,UAAW,MAAgB,WAAW,OAAO,MAAM;CACzD,MAAM,QAAS,MAA4B;AAC3C,KAAI,MACF,SAAQ,MAAM,GAAG,QAAQ,IAAI,MAAM,UAAU;KAE7C,SAAQ,MAAM,QAAQ;AAExB,QAAO,EAAE,KACP,EACE,OAAO;EACL,SAAU,MAAgB;EAC1B,MAAM;EACP,EACF,EACD,IACD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as state } from "./state-CRpaW-qc.js";
|
|
2
|
-
import { t as HTTPError } from "./error-
|
|
2
|
+
import { t as HTTPError } from "./error-CvvAyU1E.js";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/lib/api-config.ts
|
|
5
5
|
const standardHeaders = () => ({
|
|
@@ -56,4 +56,4 @@ async function getGitHubUser() {
|
|
|
56
56
|
//#endregion
|
|
57
57
|
export { GITHUB_CLIENT_ID as a, githubHeaders as c, GITHUB_BASE_URL as i, standardHeaders as l, GITHUB_API_BASE_URL as n, copilotBaseUrl as o, GITHUB_APP_SCOPES as r, copilotHeaders as s, getGitHubUser as t };
|
|
58
58
|
|
|
59
|
-
//# sourceMappingURL=get-user-
|
|
59
|
+
//# sourceMappingURL=get-user-DOv07Myc.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-user-
|
|
1
|
+
{"version":3,"file":"get-user-DOv07Myc.js","names":[],"sources":["../src/lib/api-config.ts","../src/services/github/get-user.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst COPILOT_VERSION = \"0.26.7\"\nconst EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`\nconst USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`\n\n// Updated to match latest Zed implementation - 2025-05-01 returns Claude models\nconst API_VERSION = \"2025-05-01\"\n\n// Use the API endpoint from token response if available, otherwise fall back to default\nexport const copilotBaseUrl = (state: State) => {\n if (state.copilotApiEndpoint) {\n return state.copilotApiEndpoint\n }\n return state.accountType === \"individual\" ?\n \"https://api.githubcopilot.com\"\n : `https://api.${state.accountType}.githubcopilot.com`\n}\nexport const copilotHeaders = (state: State, vision: boolean = false) => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": \"vscode-chat\",\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"openai-intent\": \"conversation-panel\",\n \"x-github-api-version\": API_VERSION,\n \"x-request-id\": randomUUID(),\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL = \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"x-github-api-version\": API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n","import { GITHUB_API_BASE_URL, standardHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport async function getGitHubUser() {\n const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {\n headers: {\n authorization: `token ${state.githubToken}`,\n ...standardHeaders(),\n },\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get GitHub user\", response)\n\n return (await response.json()) as GithubUserResponse\n}\n\n// Trimmed for the sake of simplicity\ninterface GithubUserResponse {\n login: string\n}\n"],"mappings":";;;;AAIA,MAAa,yBAAyB;CACpC,gBAAgB;CAChB,QAAQ;CACT;AAED,MAAM,kBAAkB;AACxB,MAAM,wBAAwB,gBAAgB;AAC9C,MAAM,aAAa,qBAAqB;AAGxC,MAAM,cAAc;AAGpB,MAAa,kBAAkB,UAAiB;AAC9C,KAAI,MAAM,mBACR,QAAO,MAAM;AAEf,QAAO,MAAM,gBAAgB,eACzB,kCACA,eAAe,MAAM,YAAY;;AAEvC,MAAa,kBAAkB,OAAc,SAAkB,UAAU;CACvE,MAAM,UAAkC;EACtC,eAAe,UAAU,MAAM;EAC/B,gBAAgB,iBAAiB,CAAC;EAClC,0BAA0B;EAC1B,kBAAkB,UAAU,MAAM;EAClC,yBAAyB;EACzB,cAAc;EACd,iBAAiB;EACjB,wBAAwB;EACxB,gBAAgB,YAAY;EAC5B,uCAAuC;EACxC;AAED,KAAI,OAAQ,SAAQ,4BAA4B;AAEhD,QAAO;;AAGT,MAAa,sBAAsB;AACnC,MAAa,iBAAiB,WAAkB;CAC9C,GAAG,iBAAiB;CACpB,eAAe,SAAS,MAAM;CAC9B,kBAAkB,UAAU,MAAM;CAClC,yBAAyB;CACzB,cAAc;CACd,wBAAwB;CACxB,uCAAuC;CACxC;AAED,MAAa,kBAAkB;AAC/B,MAAa,mBAAmB;AAChC,MAAa,oBAAoB,CAAC,YAAY,CAAC,KAAK,IAAI;;;ACrDxD,eAAsB,gBAAgB;CACpC,MAAM,WAAW,MAAM,MAAM,GAAG,oBAAoB,QAAQ,EAC1D,SAAS;EACP,eAAe,SAAS,MAAM;EAC9B,GAAG,iBAAiB;EACrB,EACF,CAAC;AAEF,KAAI,CAAC,SAAS,GAAI,OAAM,IAAI,UAAU,6BAA6B,SAAS;AAE5E,QAAQ,MAAM,SAAS,MAAM"}
|
package/dist/main.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as ensurePaths, t as PATHS } from "./paths-BdbyVdad.js";
|
|
3
3
|
import { t as state } from "./state-CRpaW-qc.js";
|
|
4
|
-
import { c as githubHeaders, n as GITHUB_API_BASE_URL, o as copilotBaseUrl, s as copilotHeaders } from "./get-user-
|
|
5
|
-
import { n as forwardError, t as HTTPError } from "./error-
|
|
6
|
-
import { a as cacheModels, c as isNullish, i as setupGitHubToken, l as sleep, n as refreshCopilotToken, o as cacheVSCodeVersion, r as setupCopilotToken, s as findModel, t as clearGithubToken } from "./token-
|
|
4
|
+
import { c as githubHeaders, n as GITHUB_API_BASE_URL, o as copilotBaseUrl, s as copilotHeaders } from "./get-user-DOv07Myc.js";
|
|
5
|
+
import { n as forwardError, t as HTTPError } from "./error-CvvAyU1E.js";
|
|
6
|
+
import { a as cacheModels, c as isNullish, i as setupGitHubToken, l as sleep, n as refreshCopilotToken, o as cacheVSCodeVersion, r as setupCopilotToken, s as findModel, t as clearGithubToken } from "./token-D8U-wBLK.js";
|
|
7
7
|
import { d as getValidAccessToken, g as rotateAccount, l as getCurrentProjectId, n as clearAntigravityAuth, o as getAntigravityAuthPath, r as disableCurrentAccount, s as getApiKey } from "./auth-D_mymhYC.js";
|
|
8
8
|
import { n as getZenAuthPath, t as clearZenAuth } from "./auth-DIDShcrD.js";
|
|
9
9
|
import { n as getAntigravityUsage, r as isThinkingModel, t as getAntigravityModels } from "./get-models-DhYpjJVG.js";
|
|
@@ -3913,7 +3913,7 @@ async function runServer(options) {
|
|
|
3913
3913
|
state.githubToken = options.githubToken;
|
|
3914
3914
|
consola.info("Using provided GitHub token");
|
|
3915
3915
|
try {
|
|
3916
|
-
const { getGitHubUser } = await import("./get-user-
|
|
3916
|
+
const { getGitHubUser } = await import("./get-user-CGhBmkXO.js");
|
|
3917
3917
|
const user = await getGitHubUser();
|
|
3918
3918
|
consola.info(`Logged in as ${user.login}`);
|
|
3919
3919
|
} catch (error) {
|
|
@@ -3924,10 +3924,10 @@ async function runServer(options) {
|
|
|
3924
3924
|
try {
|
|
3925
3925
|
await setupCopilotToken();
|
|
3926
3926
|
} catch (error) {
|
|
3927
|
-
const { HTTPError } = await import("./error-
|
|
3927
|
+
const { HTTPError } = await import("./error-Djpro28X.js");
|
|
3928
3928
|
if (error instanceof HTTPError && error.response.status === 401) {
|
|
3929
3929
|
consola.error("Failed to get Copilot token - GitHub token may be invalid or Copilot access revoked");
|
|
3930
|
-
const { clearGithubToken } = await import("./token-
|
|
3930
|
+
const { clearGithubToken } = await import("./token-CoKq3Guw.js");
|
|
3931
3931
|
await clearGithubToken();
|
|
3932
3932
|
consola.info("Please restart to re-authenticate");
|
|
3933
3933
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as PATHS } from "./paths-BdbyVdad.js";
|
|
2
2
|
import { t as state } from "./state-CRpaW-qc.js";
|
|
3
|
-
import { a as GITHUB_CLIENT_ID, c as githubHeaders, i as GITHUB_BASE_URL, l as standardHeaders, n as GITHUB_API_BASE_URL, o as copilotBaseUrl, r as GITHUB_APP_SCOPES, s as copilotHeaders, t as getGitHubUser } from "./get-user-
|
|
4
|
-
import { t as HTTPError } from "./error-
|
|
3
|
+
import { a as GITHUB_CLIENT_ID, c as githubHeaders, i as GITHUB_BASE_URL, l as standardHeaders, n as GITHUB_API_BASE_URL, o as copilotBaseUrl, r as GITHUB_APP_SCOPES, s as copilotHeaders, t as getGitHubUser } from "./get-user-DOv07Myc.js";
|
|
4
|
+
import { t as HTTPError } from "./error-CvvAyU1E.js";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
7
|
//#region src/services/github/get-copilot-token.ts
|
|
@@ -252,4 +252,4 @@ async function logUser() {
|
|
|
252
252
|
//#endregion
|
|
253
253
|
export { cacheModels as a, isNullish as c, setupGitHubToken as i, sleep as l, refreshCopilotToken as n, cacheVSCodeVersion as o, setupCopilotToken as r, findModel as s, clearGithubToken as t };
|
|
254
254
|
|
|
255
|
-
//# sourceMappingURL=token-
|
|
255
|
+
//# sourceMappingURL=token-D8U-wBLK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-Bg5qiNBd.js","names":[],"sources":["../src/services/github/get-copilot-token.ts","../src/services/github/get-device-code.ts","../src/services/copilot/get-models.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/poll-access-token.ts","../src/lib/token.ts"],"sourcesContent":["import consola from \"consola\"\n\nimport { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getCopilotToken = async () => {\n const url = `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`\n const fetchOptions: RequestInit = {\n headers: githubHeaders(state),\n }\n\n // Retry on transient network errors (TLS disconnect, connection timeout, etc.)\n const maxRetries = 2\n let lastError: unknown\n let response: Response | undefined\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n response = await fetch(url, fetchOptions)\n break\n } catch (error: unknown) {\n lastError = error\n if (attempt < maxRetries) {\n const delay = 1000 * (attempt + 1)\n consola.warn(\n `Token fetch error on attempt ${attempt + 1}/${maxRetries + 1}, retrying in ${delay}ms:`,\n error instanceof Error ? error.message : error,\n )\n await new Promise((r) => setTimeout(r, delay))\n }\n }\n }\n\n if (!response) {\n throw lastError\n }\n\n if (!response.ok) throw new HTTPError(\"Failed to get Copilot token\", response)\n\n const data = (await response.json()) as GetCopilotTokenResponse\n\n // Store the API endpoint if available\n if (data.endpoints?.api) {\n // eslint-disable-next-line require-atomic-updates\n state.copilotApiEndpoint = data.endpoints.api\n }\n\n return data\n}\n\n// Full interface matching Zed's implementation\ninterface GetCopilotTokenResponse {\n expires_at: number\n refresh_in: number\n token: string\n endpoints?: {\n api: string\n \"origin-tracker\"?: string\n proxy?: string\n telemetry?: string\n }\n annotations_enabled?: boolean\n chat_enabled?: boolean\n chat_jetbrains_enabled?: boolean\n code_quote_enabled?: boolean\n codesearch?: boolean\n copilot_ide_agent_chat_gpt4_small_prompt?: boolean\n copilotignore_enabled?: boolean\n individual?: boolean\n sku?: string\n tracking_id?: string\n limited_user_quotas?: unknown // Premium request quotas\n}\n","import {\n GITHUB_APP_SCOPES,\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\n\nexport async function getDeviceCode(): Promise<DeviceCodeResponse> {\n const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n scope: GITHUB_APP_SCOPES,\n }),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get device code\", response)\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n expires_in: number\n interval: number\n}\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n const url = `${copilotBaseUrl(state)}/models`\n\n const response = await fetch(url, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get models\", response)\n\n const data = (await response.json()) as ModelsResponse\n\n return data\n}\n\nexport interface ModelsResponse {\n data: Array<Model>\n object: string\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_inputs?: number\n}\n\ninterface ModelSupports {\n tool_calls?: boolean\n parallel_tool_calls?: boolean\n dimensions?: boolean\n}\n\ninterface ModelCapabilities {\n family: string\n limits: ModelLimits\n object: string\n supports: ModelSupports\n tokenizer: string\n type: string\n}\n\nexport interface Model {\n capabilities: ModelCapabilities\n id: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n vendor: string\n version: string\n policy?: {\n state: string\n terms: string\n }\n billing?: {\n is_premium: boolean\n multiplier: number\n restricted_to?: Array<string>\n }\n}\n","const FALLBACK = \"1.104.3\"\n\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin\",\n {\n signal: controller.signal,\n },\n )\n\n const pkgbuild = await response.text()\n const pkgverRegex = /pkgver=([0-9.]+)/\n const match = pkgbuild.match(pkgverRegex)\n\n if (match) {\n return match[1]\n }\n\n return FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n\nawait getVSCodeVersion()\n","import consola from \"consola\"\n\nimport type { Model } from \"~/services/copilot/get-models\"\n\nimport { getModels } from \"~/services/copilot/get-models\"\nimport { getVSCodeVersion } from \"~/services/get-vscode-version\"\n\nimport { state } from \"./state\"\n\nexport const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined\n\nexport async function cacheModels(): Promise<void> {\n const models = await getModels()\n state.models = models\n\n const claudeModels = models.data.filter(\n (m) =>\n m.id.includes(\"claude\")\n || m.vendor.toLowerCase().includes(\"anthropic\")\n || m.name.toLowerCase().includes(\"claude\"),\n )\n if (claudeModels.length > 0) {\n consola.info(\n \"Found Claude models:\",\n claudeModels.map((m) => ({ id: m.id, policy: m.policy })),\n )\n } else {\n consola.warn(\"No Claude models found in API response\")\n }\n}\n\nexport const cacheVSCodeVersion = async () => {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n\n consola.info(`Using VSCode version: ${response}`)\n}\n\n/**\n * Find a model in state.models using multi-strategy exact matching.\n *\n * Strategies (in order):\n * 1. Exact match on model ID\n * 2. Strip date suffix (claude-opus-4-6-20251101 → claude-opus-4-6)\n * 3. Dash to dot version (claude-opus-4-5 → claude-opus-4.5)\n * 4. Dot to dash version (claude-opus-4.5 → claude-opus-4-5)\n *\n * No fuzzy/family matching — all strategies produce deterministic exact IDs.\n */\nexport function findModel(modelName: string): Model | undefined {\n const models = state.models?.data\n if (!models || models.length === 0) {\n return undefined\n }\n\n // 1. Exact match\n const exact = models.find((m) => m.id === modelName)\n if (exact) return exact\n\n // 2. Strip date suffix\n const base = modelName.replace(/-\\d{8}$/, \"\")\n if (base !== modelName) {\n const baseMatch = models.find((m) => m.id === base)\n if (baseMatch) return baseMatch\n }\n\n // 3. Dash to dot version (4-5 → 4.5)\n const withDot = base.replace(/-(\\d+)-(\\d+)$/, \"-$1.$2\")\n if (withDot !== base) {\n const dotMatch = models.find((m) => m.id === withDot)\n if (dotMatch) return dotMatch\n }\n\n // 4. Dot to dash version (4.5 → 4-5)\n const withDash = modelName.replace(/(\\d+)\\.(\\d+)/, \"$1-$2\")\n if (withDash !== modelName) {\n const dashMatch = models.find((m) => m.id === withDash)\n if (dashMatch) return dashMatch\n }\n\n return undefined\n}\n","import consola from \"consola\"\n\nimport {\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { sleep } from \"~/lib/utils\"\n\nimport type { DeviceCodeResponse } from \"./get-device-code\"\n\nexport async function pollAccessToken(\n deviceCode: DeviceCodeResponse,\n): Promise<string> {\n // Interval is in seconds, we need to multiply by 1000 to get milliseconds\n // I'm also adding another second, just to be safe\n const sleepDuration = (deviceCode.interval + 1) * 1000\n consola.debug(`Polling access token with interval of ${sleepDuration}ms`)\n\n // Calculate expiration time - device code expires after expires_in seconds\n const expirationTime = Date.now() + deviceCode.expires_in * 1000\n\n while (Date.now() < expirationTime) {\n const response = await fetch(\n `${GITHUB_BASE_URL}/login/oauth/access_token`,\n {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n },\n )\n\n if (!response.ok) {\n await sleep(sleepDuration)\n consola.error(\"Failed to poll access token:\", await response.text())\n\n continue\n }\n\n const json = (await response.json()) as\n | AccessTokenResponse\n | AccessTokenErrorResponse\n consola.debug(\"Polling access token response:\", json)\n\n if (\"access_token\" in json && json.access_token) {\n return json.access_token\n }\n\n // Handle specific error cases\n if (\"error\" in json) {\n switch (json.error) {\n case \"authorization_pending\": {\n // User hasn't authorized yet, continue polling\n await sleep(sleepDuration)\n continue\n\n break\n }\n case \"slow_down\": {\n // We're polling too fast, increase interval\n await sleep(sleepDuration * 2)\n continue\n\n break\n }\n case \"expired_token\": {\n throw new Error(\"Device code expired. Please try again.\")\n }\n case \"access_denied\": {\n throw new Error(\"Authorization was denied by the user.\")\n }\n default: {\n throw new Error(\n `Authentication failed: ${json.error_description || json.error}`,\n )\n }\n }\n }\n\n await sleep(sleepDuration)\n }\n\n throw new Error(\"Device code expired. Please try again.\")\n}\n\ninterface AccessTokenResponse {\n access_token: string\n token_type: string\n scope: string\n}\n\ninterface AccessTokenErrorResponse {\n error: string\n error_description?: string\n error_uri?: string\n}\n","import consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/paths\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getDeviceCode } from \"~/services/github/get-device-code\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\nimport { pollAccessToken } from \"~/services/github/poll-access-token\"\n\nimport { HTTPError } from \"./error\"\nimport { state } from \"./state\"\n\nconst readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n\nconst writeGithubToken = (token: string) =>\n fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)\n\n/**\n * Clear the stored GitHub token from disk and state.\n * This allows the user to logout or re-authenticate.\n */\nexport async function clearGithubToken(): Promise<void> {\n state.githubToken = undefined\n state.copilotToken = undefined\n await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, \"\")\n consola.info(\"GitHub token cleared\")\n}\n\nexport const setupCopilotToken = async () => {\n const { token, refresh_in } = await getCopilotToken()\n state.copilotToken = token\n\n // Display the Copilot token to the screen\n consola.debug(\"GitHub Copilot Token fetched successfully!\")\n if (state.showToken) {\n consola.info(\"Copilot token:\", token)\n }\n\n const refreshInterval = (refresh_in - 60) * 1000\n setInterval(async () => {\n consola.debug(\"Refreshing Copilot token\")\n try {\n await refreshCopilotToken()\n } catch (error) {\n consola.error(\"Failed to refresh Copilot token:\", error)\n\n // If we get a 401, the GitHub token might be invalid\n // Log the error but don't crash - the next API request will fail\n // and the user can restart with valid credentials\n if (error instanceof HTTPError && error.response.status === 401) {\n consola.warn(\n \"GitHub token may have been revoked. Please restart and re-authenticate.\",\n )\n state.copilotToken = undefined\n }\n // Don't throw here - it would cause an unhandled rejection in setInterval\n }\n }, refreshInterval)\n}\n\n/**\n * Refresh the Copilot token on demand (e.g. after a 401 error).\n */\nexport async function refreshCopilotToken(): Promise<void> {\n const { token } = await getCopilotToken()\n state.copilotToken = token\n consola.debug(\"Copilot token refreshed\")\n if (state.showToken) {\n consola.info(\"Refreshed Copilot token:\", token)\n }\n}\n\ninterface SetupGitHubTokenOptions {\n force?: boolean\n}\n\n/**\n * Perform a fresh GitHub authentication flow.\n * Gets a device code and polls for the access token.\n */\nasync function performFreshAuthentication(): Promise<void> {\n consola.info(\"Starting GitHub authentication flow...\")\n const response = await getDeviceCode()\n consola.debug(\"Device code response:\", response)\n\n consola.info(\n `Please enter the code \"${response.user_code}\" in ${response.verification_uri}`,\n )\n\n const token = await pollAccessToken(response)\n await writeGithubToken(token)\n state.githubToken = token\n\n if (state.showToken) {\n consola.info(\"GitHub token:\", token)\n }\n await logUser()\n}\n\nexport async function setupGitHubToken(\n options?: SetupGitHubTokenOptions,\n): Promise<void> {\n try {\n const githubToken = await readGithubToken()\n\n if (githubToken && !options?.force) {\n state.githubToken = githubToken\n if (state.showToken) {\n consola.info(\"GitHub token:\", githubToken)\n }\n\n // Validate the token by checking if we can get the user\n try {\n await logUser()\n return\n } catch (error) {\n // Token is invalid or expired, clear it and re-authenticate\n if (error instanceof HTTPError && error.response.status === 401) {\n consola.warn(\n \"Stored GitHub token is invalid or expired, clearing and re-authenticating...\",\n )\n await clearGithubToken()\n // Fall through to perform fresh authentication\n } else {\n throw error\n }\n }\n }\n\n consola.info(\"Not logged in, getting new access token\")\n await performFreshAuthentication()\n } catch (error) {\n if (error instanceof HTTPError) {\n consola.error(\"Failed to get GitHub token:\", await error.response.json())\n throw error\n }\n\n consola.error(\"Failed to get GitHub token:\", error)\n throw error\n }\n}\n\nasync function logUser() {\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"token-D8U-wBLK.js","names":[],"sources":["../src/services/github/get-copilot-token.ts","../src/services/github/get-device-code.ts","../src/services/copilot/get-models.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/poll-access-token.ts","../src/lib/token.ts"],"sourcesContent":["import consola from \"consola\"\n\nimport { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getCopilotToken = async () => {\n const url = `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`\n const fetchOptions: RequestInit = {\n headers: githubHeaders(state),\n }\n\n // Retry on transient network errors (TLS disconnect, connection timeout, etc.)\n const maxRetries = 2\n let lastError: unknown\n let response: Response | undefined\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n response = await fetch(url, fetchOptions)\n break\n } catch (error: unknown) {\n lastError = error\n if (attempt < maxRetries) {\n const delay = 1000 * (attempt + 1)\n consola.warn(\n `Token fetch error on attempt ${attempt + 1}/${maxRetries + 1}, retrying in ${delay}ms:`,\n error instanceof Error ? error.message : error,\n )\n await new Promise((r) => setTimeout(r, delay))\n }\n }\n }\n\n if (!response) {\n throw lastError\n }\n\n if (!response.ok) throw new HTTPError(\"Failed to get Copilot token\", response)\n\n const data = (await response.json()) as GetCopilotTokenResponse\n\n // Store the API endpoint if available\n if (data.endpoints?.api) {\n // eslint-disable-next-line require-atomic-updates\n state.copilotApiEndpoint = data.endpoints.api\n }\n\n return data\n}\n\n// Full interface matching Zed's implementation\ninterface GetCopilotTokenResponse {\n expires_at: number\n refresh_in: number\n token: string\n endpoints?: {\n api: string\n \"origin-tracker\"?: string\n proxy?: string\n telemetry?: string\n }\n annotations_enabled?: boolean\n chat_enabled?: boolean\n chat_jetbrains_enabled?: boolean\n code_quote_enabled?: boolean\n codesearch?: boolean\n copilot_ide_agent_chat_gpt4_small_prompt?: boolean\n copilotignore_enabled?: boolean\n individual?: boolean\n sku?: string\n tracking_id?: string\n limited_user_quotas?: unknown // Premium request quotas\n}\n","import {\n GITHUB_APP_SCOPES,\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\n\nexport async function getDeviceCode(): Promise<DeviceCodeResponse> {\n const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n scope: GITHUB_APP_SCOPES,\n }),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get device code\", response)\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n expires_in: number\n interval: number\n}\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n const url = `${copilotBaseUrl(state)}/models`\n\n const response = await fetch(url, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get models\", response)\n\n const data = (await response.json()) as ModelsResponse\n\n return data\n}\n\nexport interface ModelsResponse {\n data: Array<Model>\n object: string\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_inputs?: number\n}\n\ninterface ModelSupports {\n tool_calls?: boolean\n parallel_tool_calls?: boolean\n dimensions?: boolean\n}\n\ninterface ModelCapabilities {\n family: string\n limits: ModelLimits\n object: string\n supports: ModelSupports\n tokenizer: string\n type: string\n}\n\nexport interface Model {\n capabilities: ModelCapabilities\n id: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n vendor: string\n version: string\n policy?: {\n state: string\n terms: string\n }\n billing?: {\n is_premium: boolean\n multiplier: number\n restricted_to?: Array<string>\n }\n}\n","const FALLBACK = \"1.104.3\"\n\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin\",\n {\n signal: controller.signal,\n },\n )\n\n const pkgbuild = await response.text()\n const pkgverRegex = /pkgver=([0-9.]+)/\n const match = pkgbuild.match(pkgverRegex)\n\n if (match) {\n return match[1]\n }\n\n return FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n\nawait getVSCodeVersion()\n","import consola from \"consola\"\n\nimport type { Model } from \"~/services/copilot/get-models\"\n\nimport { getModels } from \"~/services/copilot/get-models\"\nimport { getVSCodeVersion } from \"~/services/get-vscode-version\"\n\nimport { state } from \"./state\"\n\nexport const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined\n\nexport async function cacheModels(): Promise<void> {\n const models = await getModels()\n state.models = models\n\n const claudeModels = models.data.filter(\n (m) =>\n m.id.includes(\"claude\")\n || m.vendor.toLowerCase().includes(\"anthropic\")\n || m.name.toLowerCase().includes(\"claude\"),\n )\n if (claudeModels.length > 0) {\n consola.info(\n \"Found Claude models:\",\n claudeModels.map((m) => ({ id: m.id, policy: m.policy })),\n )\n } else {\n consola.warn(\"No Claude models found in API response\")\n }\n}\n\nexport const cacheVSCodeVersion = async () => {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n\n consola.info(`Using VSCode version: ${response}`)\n}\n\n/**\n * Find a model in state.models using multi-strategy exact matching.\n *\n * Strategies (in order):\n * 1. Exact match on model ID\n * 2. Strip date suffix (claude-opus-4-6-20251101 → claude-opus-4-6)\n * 3. Dash to dot version (claude-opus-4-5 → claude-opus-4.5)\n * 4. Dot to dash version (claude-opus-4.5 → claude-opus-4-5)\n *\n * No fuzzy/family matching — all strategies produce deterministic exact IDs.\n */\nexport function findModel(modelName: string): Model | undefined {\n const models = state.models?.data\n if (!models || models.length === 0) {\n return undefined\n }\n\n // 1. Exact match\n const exact = models.find((m) => m.id === modelName)\n if (exact) return exact\n\n // 2. Strip date suffix\n const base = modelName.replace(/-\\d{8}$/, \"\")\n if (base !== modelName) {\n const baseMatch = models.find((m) => m.id === base)\n if (baseMatch) return baseMatch\n }\n\n // 3. Dash to dot version (4-5 → 4.5)\n const withDot = base.replace(/-(\\d+)-(\\d+)$/, \"-$1.$2\")\n if (withDot !== base) {\n const dotMatch = models.find((m) => m.id === withDot)\n if (dotMatch) return dotMatch\n }\n\n // 4. Dot to dash version (4.5 → 4-5)\n const withDash = modelName.replace(/(\\d+)\\.(\\d+)/, \"$1-$2\")\n if (withDash !== modelName) {\n const dashMatch = models.find((m) => m.id === withDash)\n if (dashMatch) return dashMatch\n }\n\n return undefined\n}\n","import consola from \"consola\"\n\nimport {\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { sleep } from \"~/lib/utils\"\n\nimport type { DeviceCodeResponse } from \"./get-device-code\"\n\nexport async function pollAccessToken(\n deviceCode: DeviceCodeResponse,\n): Promise<string> {\n // Interval is in seconds, we need to multiply by 1000 to get milliseconds\n // I'm also adding another second, just to be safe\n const sleepDuration = (deviceCode.interval + 1) * 1000\n consola.debug(`Polling access token with interval of ${sleepDuration}ms`)\n\n // Calculate expiration time - device code expires after expires_in seconds\n const expirationTime = Date.now() + deviceCode.expires_in * 1000\n\n while (Date.now() < expirationTime) {\n const response = await fetch(\n `${GITHUB_BASE_URL}/login/oauth/access_token`,\n {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n },\n )\n\n if (!response.ok) {\n await sleep(sleepDuration)\n consola.error(\"Failed to poll access token:\", await response.text())\n\n continue\n }\n\n const json = (await response.json()) as\n | AccessTokenResponse\n | AccessTokenErrorResponse\n consola.debug(\"Polling access token response:\", json)\n\n if (\"access_token\" in json && json.access_token) {\n return json.access_token\n }\n\n // Handle specific error cases\n if (\"error\" in json) {\n switch (json.error) {\n case \"authorization_pending\": {\n // User hasn't authorized yet, continue polling\n await sleep(sleepDuration)\n continue\n\n break\n }\n case \"slow_down\": {\n // We're polling too fast, increase interval\n await sleep(sleepDuration * 2)\n continue\n\n break\n }\n case \"expired_token\": {\n throw new Error(\"Device code expired. Please try again.\")\n }\n case \"access_denied\": {\n throw new Error(\"Authorization was denied by the user.\")\n }\n default: {\n throw new Error(\n `Authentication failed: ${json.error_description || json.error}`,\n )\n }\n }\n }\n\n await sleep(sleepDuration)\n }\n\n throw new Error(\"Device code expired. Please try again.\")\n}\n\ninterface AccessTokenResponse {\n access_token: string\n token_type: string\n scope: string\n}\n\ninterface AccessTokenErrorResponse {\n error: string\n error_description?: string\n error_uri?: string\n}\n","import consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/paths\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getDeviceCode } from \"~/services/github/get-device-code\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\nimport { pollAccessToken } from \"~/services/github/poll-access-token\"\n\nimport { HTTPError } from \"./error\"\nimport { state } from \"./state\"\n\nconst readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n\nconst writeGithubToken = (token: string) =>\n fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)\n\n/**\n * Clear the stored GitHub token from disk and state.\n * This allows the user to logout or re-authenticate.\n */\nexport async function clearGithubToken(): Promise<void> {\n state.githubToken = undefined\n state.copilotToken = undefined\n await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, \"\")\n consola.info(\"GitHub token cleared\")\n}\n\nexport const setupCopilotToken = async () => {\n const { token, refresh_in } = await getCopilotToken()\n state.copilotToken = token\n\n // Display the Copilot token to the screen\n consola.debug(\"GitHub Copilot Token fetched successfully!\")\n if (state.showToken) {\n consola.info(\"Copilot token:\", token)\n }\n\n const refreshInterval = (refresh_in - 60) * 1000\n setInterval(async () => {\n consola.debug(\"Refreshing Copilot token\")\n try {\n await refreshCopilotToken()\n } catch (error) {\n consola.error(\"Failed to refresh Copilot token:\", error)\n\n // If we get a 401, the GitHub token might be invalid\n // Log the error but don't crash - the next API request will fail\n // and the user can restart with valid credentials\n if (error instanceof HTTPError && error.response.status === 401) {\n consola.warn(\n \"GitHub token may have been revoked. Please restart and re-authenticate.\",\n )\n state.copilotToken = undefined\n }\n // Don't throw here - it would cause an unhandled rejection in setInterval\n }\n }, refreshInterval)\n}\n\n/**\n * Refresh the Copilot token on demand (e.g. after a 401 error).\n */\nexport async function refreshCopilotToken(): Promise<void> {\n const { token } = await getCopilotToken()\n state.copilotToken = token\n consola.debug(\"Copilot token refreshed\")\n if (state.showToken) {\n consola.info(\"Refreshed Copilot token:\", token)\n }\n}\n\ninterface SetupGitHubTokenOptions {\n force?: boolean\n}\n\n/**\n * Perform a fresh GitHub authentication flow.\n * Gets a device code and polls for the access token.\n */\nasync function performFreshAuthentication(): Promise<void> {\n consola.info(\"Starting GitHub authentication flow...\")\n const response = await getDeviceCode()\n consola.debug(\"Device code response:\", response)\n\n consola.info(\n `Please enter the code \"${response.user_code}\" in ${response.verification_uri}`,\n )\n\n const token = await pollAccessToken(response)\n await writeGithubToken(token)\n state.githubToken = token\n\n if (state.showToken) {\n consola.info(\"GitHub token:\", token)\n }\n await logUser()\n}\n\nexport async function setupGitHubToken(\n options?: SetupGitHubTokenOptions,\n): Promise<void> {\n try {\n const githubToken = await readGithubToken()\n\n if (githubToken && !options?.force) {\n state.githubToken = githubToken\n if (state.showToken) {\n consola.info(\"GitHub token:\", githubToken)\n }\n\n // Validate the token by checking if we can get the user\n try {\n await logUser()\n return\n } catch (error) {\n // Token is invalid or expired, clear it and re-authenticate\n if (error instanceof HTTPError && error.response.status === 401) {\n consola.warn(\n \"Stored GitHub token is invalid or expired, clearing and re-authenticating...\",\n )\n await clearGithubToken()\n // Fall through to perform fresh authentication\n } else {\n throw error\n }\n }\n }\n\n consola.info(\"Not logged in, getting new access token\")\n await performFreshAuthentication()\n } catch (error) {\n if (error instanceof HTTPError) {\n consola.error(\"Failed to get GitHub token:\", await error.response.json())\n throw error\n }\n\n consola.error(\"Failed to get GitHub token:\", error)\n throw error\n }\n}\n\nasync function logUser() {\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "copilot-api-plus",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.56",
|
|
4
4
|
"description": "Turn GitHub Copilot, OpenCode Zen, or Google Antigravity into OpenAI/Anthropic API compatible server. Features: smart model matching, dual-endpoint failover, API key auth, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"proxy",
|
package/dist/error-4DW6q2Mo.js
DELETED