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 ADDED
@@ -0,0 +1,839 @@
1
+ # Copilot API Plus
2
+
3
+ [![npm version](https://img.shields.io/npm/v/copilot-api-plus.svg)](https://www.npmjs.com/package/copilot-api-plus)
4
+ [![license](https://img.shields.io/npm/l/copilot-api-plus.svg)](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
  [![npm version](https://img.shields.io/npm/v/copilot-api-plus.svg)](https://www.npmjs.com/package/copilot-api-plus)
4
4
  [![license](https://img.shields.io/npm/l/copilot-api-plus.svg)](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
- consola.error("Error occurred:", error);
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-BYsVGb6T.js.map
45
+ //# sourceMappingURL=error-CvvAyU1E.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-BYsVGb6T.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 consola.error(\"Error occurred:\", error)\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;;AAGH,SAAQ,MAAM,mBAAmB,MAAM;AACvC,QAAO,EAAE,KACP,EACE,OAAO;EACL,SAAU,MAAgB;EAC1B,MAAM;EACP,EACF,EACD,IACD"}
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"}
@@ -0,0 +1,2 @@
1
+ import { t as HTTPError } from "./error-CvvAyU1E.js";
2
+ export { HTTPError };
@@ -0,0 +1,3 @@
1
+ import { t as getGitHubUser } from "./get-user-DOv07Myc.js";
2
+ import "./error-CvvAyU1E.js";
3
+ export { getGitHubUser };
@@ -1,5 +1,5 @@
1
1
  import { t as state } from "./state-CRpaW-qc.js";
2
- import { t as HTTPError } from "./error-BYsVGb6T.js";
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-wlP5uMaW.js.map
59
+ //# sourceMappingURL=get-user-DOv07Myc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-user-wlP5uMaW.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"}
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-wlP5uMaW.js";
5
- import { n as forwardError, t as HTTPError } from "./error-BYsVGb6T.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-Bg5qiNBd.js";
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-CtEiwKow.js");
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-4DW6q2Mo.js");
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-B_0VZjlS.js");
3930
+ const { clearGithubToken } = await import("./token-CoKq3Guw.js");
3931
3931
  await clearGithubToken();
3932
3932
  consola.info("Please restart to re-authenticate");
3933
3933
  }
@@ -0,0 +1,5 @@
1
+ import "./paths-BdbyVdad.js";
2
+ import "./get-user-DOv07Myc.js";
3
+ import "./error-CvvAyU1E.js";
4
+ import { t as clearGithubToken } from "./token-D8U-wBLK.js";
5
+ export { clearGithubToken };
@@ -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-wlP5uMaW.js";
4
- import { t as HTTPError } from "./error-BYsVGb6T.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-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-Bg5qiNBd.js.map
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.55",
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",
@@ -1,2 +0,0 @@
1
- import { t as HTTPError } from "./error-BYsVGb6T.js";
2
- export { HTTPError };
@@ -1,3 +0,0 @@
1
- import { t as getGitHubUser } from "./get-user-wlP5uMaW.js";
2
- import "./error-BYsVGb6T.js";
3
- export { getGitHubUser };
@@ -1,5 +0,0 @@
1
- import "./paths-BdbyVdad.js";
2
- import "./get-user-wlP5uMaW.js";
3
- import "./error-BYsVGb6T.js";
4
- import { t as clearGithubToken } from "./token-Bg5qiNBd.js";
5
- export { clearGithubToken };