copilot-api-plus 1.0.6 → 1.0.7
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.md +271 -0
- package/dist/auth-C5zV8JbW.js +73 -0
- package/dist/auth-C5zV8JbW.js.map +1 -0
- package/dist/auth-KlL1W4gV.js +4 -0
- package/dist/error-CsShqJjE.js +3 -0
- package/dist/{error-Cmeg4mmB.js → error-CvU5otz-.js} +1 -1
- package/dist/{error-Cmeg4mmB.js.map → error-CvU5otz-.js.map} +1 -1
- package/dist/get-models-Hlxa1hWY.js +33 -0
- package/dist/get-models-Hlxa1hWY.js.map +1 -0
- package/dist/{get-user-DalX7epg.js → get-user-DgPgvnrS.js} +4 -12
- package/dist/{get-user-DalX7epg.js.map → get-user-DgPgvnrS.js.map} +1 -1
- package/dist/get-user-M3sQS0U8.js +5 -0
- package/dist/main.js +281 -41
- package/dist/main.js.map +1 -1
- package/dist/paths-Ch0ixSo2.js +28 -0
- package/dist/paths-Ch0ixSo2.js.map +1 -0
- package/dist/state-DAw5jMjc.js +12 -0
- package/dist/state-DAw5jMjc.js.map +1 -0
- package/dist/token-BssxOyqn.js +7 -0
- package/dist/{token-CRn8c1A7.js → token-DYeOMeid.js} +6 -27
- package/dist/token-DYeOMeid.js.map +1 -0
- package/package.json +5 -3
- package/dist/error-Ba4dbGYj.js +0 -3
- package/dist/get-user-BQgLIPYd.js +0 -4
- package/dist/token-CRn8c1A7.js.map +0 -1
- package/dist/token-DP4tvNfm.js +0 -5
package/README.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# Copilot API Plus
|
|
2
2
|
|
|
3
|
+
## OpenCode Zen 使用指南
|
|
4
|
+
|
|
5
|
+
OpenCode Zen 是由 opencode.ai 提供的多模型 API 服务,支持 Claude、GPT-5、Gemini、Qwen 等主流大模型,适合代码生成、AI 助手等场景。Copilot API Plus 支持将 Zen 转换为 OpenAI/Anthropic 兼容 API,方便与 Claude Code、opencode 等工具无缝集成。
|
|
6
|
+
|
|
7
|
+
### 1. 获取 Zen API Key
|
|
8
|
+
|
|
9
|
+
1. 访问 [https://opencode.ai/zen](https://opencode.ai/zen)
|
|
10
|
+
2. 注册并登录,进入“API Keys”页面,创建你的 API Key
|
|
11
|
+
|
|
12
|
+
### 2. 启动 Zen 模式
|
|
13
|
+
|
|
14
|
+
首次运行会自动提示输入 API Key,并保存到本地。也可直接指定:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npx copilot-api-plus@latest start --zen --zen-api-key <你的APIKey>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
或交互式:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npx copilot-api-plus@latest start --zen
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 3. 与 Claude Code 配合
|
|
27
|
+
|
|
28
|
+
Zen 支持多种 Claude 模型,推荐用法:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npx copilot-api-plus@latest start --zen --claude-code
|
|
32
|
+
```
|
|
33
|
+
会自动生成 Claude Code 启动命令,支持模型选择。
|
|
34
|
+
|
|
35
|
+
### 4. 支持的模型(部分示例)
|
|
36
|
+
|
|
37
|
+
| 名称 | ID | 说明 |
|
|
38
|
+
|---------------------|----------------------|---------------------------|
|
|
39
|
+
| Claude Sonnet 4.5 | claude-sonnet-4-5 | Anthropic Claude 200K |
|
|
40
|
+
| Claude Opus 4.5 | claude-opus-4-5 | Anthropic Claude 200K |
|
|
41
|
+
| GPT-5 Codex | gpt-5-codex | OpenAI Responses API |
|
|
42
|
+
| Gemini 3 Pro | gemini-3-pro | Google Gemini |
|
|
43
|
+
| Qwen3 Coder 480B | qwen3-coder | Alibaba Qwen |
|
|
44
|
+
| Kimi K2 | kimi-k2 | Moonshot |
|
|
45
|
+
| Grok Code Fast 1 | grok-code | xAI |
|
|
46
|
+
|
|
47
|
+
更多模型请见 [Zen 官网](https://opencode.ai/zen)。
|
|
48
|
+
|
|
49
|
+
### 5. API 路径
|
|
50
|
+
|
|
51
|
+
Zen 模式下,所有 OpenAI/Anthropic 兼容路径均可用:
|
|
52
|
+
|
|
53
|
+
- `POST /v1/chat/completions` (OpenAI 格式)
|
|
54
|
+
- `POST /v1/messages` (Anthropic 格式)
|
|
55
|
+
- `GET /v1/models` (模型列表)
|
|
56
|
+
|
|
57
|
+
Zen 专属路径(始终可用):
|
|
58
|
+
- `POST /zen/v1/chat/completions`
|
|
59
|
+
- `POST /zen/v1/messages`
|
|
60
|
+
- `GET /zen/v1/models`
|
|
61
|
+
|
|
62
|
+
### 6. 常见问题
|
|
63
|
+
|
|
64
|
+
- **API Key 存储位置**:`~/.local/share/copilot-api-plus/zen-auth.json`
|
|
65
|
+
- **切换/清除 API Key**:
|
|
66
|
+
- 只清除 Zen:`npx copilot-api-plus@latest logout --zen`
|
|
67
|
+
- 全部清除:`npx copilot-api-plus@latest logout --all`
|
|
68
|
+
- **模型选择**:启动时会自动显示可用模型列表
|
|
69
|
+
- **与 opencode 配合**:在 `opencode.json` 里设置 `baseURL` 为 `http://127.0.0.1:4141/v1`,或用环境变量 `OPENAI_BASE_URL`
|
|
70
|
+
|
|
71
|
+
### 7. 使用技巧
|
|
72
|
+
|
|
73
|
+
- 推荐用 `--claude-code` 生成 Claude Code 启动命令
|
|
74
|
+
- 支持所有 Zen 公开模型,按需选择
|
|
75
|
+
- 支持 Docker 部署,API Key 持久化
|
|
76
|
+
|
|
77
|
+
如需详细配置示例、opencode 配置、更多高级用法,请继续阅读下方文档。
|
|
78
|
+
|
|
3
79
|
> **Fork of [ericc-ch/copilot-api](https://github.com/ericc-ch/copilot-api)** with bug fixes and improvements.
|
|
4
80
|
|
|
5
81
|
> [!WARNING]
|
|
@@ -169,6 +245,8 @@ The following command line options are available for the `start` command:
|
|
|
169
245
|
| --claude-code | Generate a command to launch Claude Code with Copilot API config | false | -c |
|
|
170
246
|
| --show-token | Show GitHub and Copilot tokens on fetch and refresh | false | none |
|
|
171
247
|
| --proxy-env | Initialize proxy from environment variables | false | none |
|
|
248
|
+
| --zen | Enable OpenCode Zen mode (proxy to Zen instead of GitHub Copilot) | false | -z |
|
|
249
|
+
| --zen-api-key | OpenCode Zen API key (get from https://opencode.ai/zen) | none | none |
|
|
172
250
|
|
|
173
251
|
### Auth Command Options
|
|
174
252
|
|
|
@@ -350,6 +428,80 @@ The dashboard provides a user-friendly interface to view your Copilot usage data
|
|
|
350
428
|
- **URL-based Configuration**: You can also specify the API endpoint directly in the URL using a query parameter. This is useful for bookmarks or sharing links. For example:
|
|
351
429
|
`https://imbuxiangnan-cyber.github.io/copilot-api-plus?endpoint=http://your-api-server/usage`
|
|
352
430
|
|
|
431
|
+
## Using with OpenCode Zen
|
|
432
|
+
|
|
433
|
+
[OpenCode Zen](https://opencode.ai/zen) is a curated list of tested and verified models provided by the OpenCode team. This proxy can convert OpenCode Zen into an OpenAI/Anthropic compatible API server, allowing you to use Zen models with Claude Code and other tools!
|
|
434
|
+
|
|
435
|
+
### Why Use Zen Mode?
|
|
436
|
+
|
|
437
|
+
- **Access to many models**: Claude Sonnet 4.5, Claude Opus 4.5, GPT-5 Codex, Gemini 3 Pro, Qwen3 Coder, and more
|
|
438
|
+
- **Transparent pricing**: Pay per request with zero markups
|
|
439
|
+
- **Tested & verified**: All models are tested by the OpenCode team for coding tasks
|
|
440
|
+
- **Single API key**: One key for all Zen models
|
|
441
|
+
|
|
442
|
+
### Getting Started with Zen
|
|
443
|
+
|
|
444
|
+
1. **Get your API key**: Go to [opencode.ai/zen](https://opencode.ai/zen), sign up, and create an API key.
|
|
445
|
+
|
|
446
|
+
2. **Start the server in Zen mode**:
|
|
447
|
+
```sh
|
|
448
|
+
npx copilot-api-plus@latest start --zen
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
You will be prompted to enter your Zen API key on first run. The key will be saved for future use.
|
|
452
|
+
|
|
453
|
+
3. **Or provide the API key directly**:
|
|
454
|
+
```sh
|
|
455
|
+
npx copilot-api-plus@latest start --zen --zen-api-key your_api_key_here
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Using Zen with Claude Code
|
|
459
|
+
|
|
460
|
+
Start the server with both `--zen` and `--claude-code` flags:
|
|
461
|
+
|
|
462
|
+
```sh
|
|
463
|
+
npx copilot-api-plus@latest start --zen --claude-code
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
This will:
|
|
467
|
+
1. Connect to OpenCode Zen instead of GitHub Copilot
|
|
468
|
+
2. Show you available Zen models to choose from
|
|
469
|
+
3. Generate a command to launch Claude Code with the selected model
|
|
470
|
+
|
|
471
|
+
### Available Zen Models
|
|
472
|
+
|
|
473
|
+
When using Zen mode, you can access models like:
|
|
474
|
+
|
|
475
|
+
| Model | ID | Description |
|
|
476
|
+
| ------------------ | ----------------- | ------------------------------ |
|
|
477
|
+
| Claude Sonnet 4.5 | claude-sonnet-4-5 | Anthropic Claude (200K context)|
|
|
478
|
+
| Claude Opus 4.5 | claude-opus-4-5 | Anthropic Claude (200K context)|
|
|
479
|
+
| GPT 5 Codex | gpt-5-codex | OpenAI (Responses API) |
|
|
480
|
+
| Gemini 3 Pro | gemini-3-pro | Google Gemini |
|
|
481
|
+
| Qwen3 Coder 480B | qwen3-coder | Alibaba Qwen |
|
|
482
|
+
| Kimi K2 | kimi-k2 | Moonshot |
|
|
483
|
+
| Grok Code Fast 1 | grok-code | xAI |
|
|
484
|
+
|
|
485
|
+
For the full list, visit [opencode.ai/zen](https://opencode.ai/zen).
|
|
486
|
+
|
|
487
|
+
### Zen API Endpoints
|
|
488
|
+
|
|
489
|
+
The server exposes the same endpoints in Zen mode:
|
|
490
|
+
|
|
491
|
+
| Endpoint | Description |
|
|
492
|
+
| --------------------------- | ------------------------------------ |
|
|
493
|
+
| `POST /v1/chat/completions` | OpenAI-compatible chat completions |
|
|
494
|
+
| `POST /v1/messages` | Anthropic-compatible messages |
|
|
495
|
+
| `GET /v1/models` | List available Zen models |
|
|
496
|
+
|
|
497
|
+
You can also access dedicated Zen routes (always available):
|
|
498
|
+
|
|
499
|
+
| Endpoint | Description |
|
|
500
|
+
| -------------------------------- | ------------------------ |
|
|
501
|
+
| `POST /zen/v1/chat/completions` | Zen chat completions |
|
|
502
|
+
| `POST /zen/v1/messages` | Zen messages |
|
|
503
|
+
| `GET /zen/v1/models` | Zen models |
|
|
504
|
+
|
|
353
505
|
## Using with Claude Code
|
|
354
506
|
|
|
355
507
|
This proxy can be used to power [Claude Code](https://docs.anthropic.com/en/claude-code), an experimental conversational AI assistant for developers from Anthropic.
|
|
@@ -398,6 +550,125 @@ You can find more options here: [Claude Code settings](https://docs.anthropic.co
|
|
|
398
550
|
|
|
399
551
|
You can also read more about IDE integration here: [Add Claude Code to your IDE](https://docs.anthropic.com/en/docs/claude-code/ide-integrations)
|
|
400
552
|
|
|
553
|
+
## Using with opencode
|
|
554
|
+
|
|
555
|
+
[opencode](https://github.com/sst/opencode) is a modern AI coding assistant that supports multiple providers. You can use copilot-api-plus as a custom provider for opencode.
|
|
556
|
+
|
|
557
|
+
### Configuration
|
|
558
|
+
|
|
559
|
+
Create or edit `opencode.json` in your project root directory:
|
|
560
|
+
|
|
561
|
+
```json
|
|
562
|
+
{
|
|
563
|
+
"$schema": "https://opencode.ai/config.json",
|
|
564
|
+
"provider": {
|
|
565
|
+
"copilot-api-plus": {
|
|
566
|
+
"api": "openai-compatible",
|
|
567
|
+
"name": "Copilot API Plus",
|
|
568
|
+
"options": {
|
|
569
|
+
"baseURL": "http://127.0.0.1:4141/v1"
|
|
570
|
+
},
|
|
571
|
+
"models": {
|
|
572
|
+
"claude-sonnet-4": {
|
|
573
|
+
"name": "Claude Sonnet 4",
|
|
574
|
+
"id": "claude-sonnet-4",
|
|
575
|
+
"max_tokens": 64000,
|
|
576
|
+
"default_tokens": 16000,
|
|
577
|
+
"profile": "coder",
|
|
578
|
+
"attachment": true,
|
|
579
|
+
"limit": {
|
|
580
|
+
"context": 200000
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
"claude-sonnet-4.5": {
|
|
584
|
+
"name": "Claude Sonnet 4.5",
|
|
585
|
+
"id": "claude-sonnet-4.5",
|
|
586
|
+
"max_tokens": 64000,
|
|
587
|
+
"default_tokens": 16000,
|
|
588
|
+
"profile": "coder",
|
|
589
|
+
"attachment": true,
|
|
590
|
+
"limit": {
|
|
591
|
+
"context": 200000
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
"gpt-4.1": {
|
|
595
|
+
"name": "GPT-4.1",
|
|
596
|
+
"id": "gpt-4.1",
|
|
597
|
+
"max_tokens": 32768,
|
|
598
|
+
"default_tokens": 16000,
|
|
599
|
+
"profile": "coder",
|
|
600
|
+
"attachment": true,
|
|
601
|
+
"limit": {
|
|
602
|
+
"context": 1047576
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
"o4-mini": {
|
|
606
|
+
"name": "o4-mini",
|
|
607
|
+
"id": "o4-mini",
|
|
608
|
+
"max_tokens": 100000,
|
|
609
|
+
"default_tokens": 16000,
|
|
610
|
+
"profile": "coder",
|
|
611
|
+
"attachment": true,
|
|
612
|
+
"limit": {
|
|
613
|
+
"context": 200000
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
"gemini-2.5-pro": {
|
|
617
|
+
"name": "Gemini 2.5 Pro",
|
|
618
|
+
"id": "gemini-2.5-pro",
|
|
619
|
+
"max_tokens": 65536,
|
|
620
|
+
"default_tokens": 16000,
|
|
621
|
+
"profile": "coder",
|
|
622
|
+
"attachment": true,
|
|
623
|
+
"limit": {
|
|
624
|
+
"context": 1048576
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Usage
|
|
634
|
+
|
|
635
|
+
1. Start copilot-api-plus:
|
|
636
|
+
```sh
|
|
637
|
+
npx copilot-api-plus@latest start --proxy-env
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
2. Run opencode in the same project directory:
|
|
641
|
+
```sh
|
|
642
|
+
npx opencode@latest
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
3. Select `copilot-api-plus` as your provider when prompted, or use the model switcher to change providers.
|
|
646
|
+
|
|
647
|
+
### Available Models
|
|
648
|
+
|
|
649
|
+
When using copilot-api-plus with opencode, you can access all GitHub Copilot models:
|
|
650
|
+
|
|
651
|
+
| Model | Description |
|
|
652
|
+
| ----------------- | ------------------------------------ |
|
|
653
|
+
| `claude-sonnet-4` | Claude Sonnet 4 (200K context) |
|
|
654
|
+
| `claude-sonnet-4.5` | Claude Sonnet 4.5 (200K context) |
|
|
655
|
+
| `gpt-4.1` | GPT-4.1 (1M context) |
|
|
656
|
+
| `o4-mini` | OpenAI o4-mini reasoning model |
|
|
657
|
+
| `gemini-2.5-pro` | Google Gemini 2.5 Pro (1M context) |
|
|
658
|
+
|
|
659
|
+
### Environment Variables for opencode
|
|
660
|
+
|
|
661
|
+
If you prefer not to create a config file, you can also set environment variables:
|
|
662
|
+
|
|
663
|
+
```sh
|
|
664
|
+
# Set the base URL for opencode to use
|
|
665
|
+
export OPENAI_BASE_URL=http://127.0.0.1:4141/v1
|
|
666
|
+
export OPENAI_API_KEY=dummy
|
|
667
|
+
|
|
668
|
+
# Start opencode
|
|
669
|
+
npx opencode@latest
|
|
670
|
+
```
|
|
671
|
+
|
|
401
672
|
## Running from Source
|
|
402
673
|
|
|
403
674
|
The project can be run from source in several ways:
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PATHS, ensurePaths } from "./paths-Ch0ixSo2.js";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
|
|
4
|
+
//#region src/services/zen/auth.ts
|
|
5
|
+
const ZEN_AUTH_FILENAME = "zen-auth.json";
|
|
6
|
+
/**
|
|
7
|
+
* Get the path to the Zen auth file
|
|
8
|
+
*/
|
|
9
|
+
function getZenAuthPath() {
|
|
10
|
+
return `${PATHS.DATA_DIR}/${ZEN_AUTH_FILENAME}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Save Zen API key to file
|
|
14
|
+
*/
|
|
15
|
+
async function saveZenAuth(auth) {
|
|
16
|
+
await ensurePaths();
|
|
17
|
+
const authPath = getZenAuthPath();
|
|
18
|
+
await Bun.write(authPath, JSON.stringify(auth, null, 2));
|
|
19
|
+
consola.success("Zen API key saved to", authPath);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load Zen API key from file
|
|
23
|
+
*/
|
|
24
|
+
async function loadZenAuth() {
|
|
25
|
+
try {
|
|
26
|
+
const authPath = getZenAuthPath();
|
|
27
|
+
const file = Bun.file(authPath);
|
|
28
|
+
if (!await file.exists()) return null;
|
|
29
|
+
const content = await file.text();
|
|
30
|
+
return JSON.parse(content);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Clear Zen API key
|
|
37
|
+
*/
|
|
38
|
+
async function clearZenAuth() {
|
|
39
|
+
try {
|
|
40
|
+
const authPath = getZenAuthPath();
|
|
41
|
+
await (await import("node:fs/promises")).unlink(authPath);
|
|
42
|
+
consola.success("Zen API key cleared");
|
|
43
|
+
} catch {}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Setup Zen API key interactively
|
|
47
|
+
*/
|
|
48
|
+
async function setupZenApiKey(force = false) {
|
|
49
|
+
const existingAuth = await loadZenAuth();
|
|
50
|
+
if (existingAuth && !force) {
|
|
51
|
+
consola.info("Using existing Zen API key");
|
|
52
|
+
return existingAuth.apiKey;
|
|
53
|
+
}
|
|
54
|
+
consola.info("OpenCode Zen gives you access to all the best coding models");
|
|
55
|
+
consola.info("Get your API key at: https://opencode.ai/zen");
|
|
56
|
+
consola.info("");
|
|
57
|
+
const apiKey = await consola.prompt("Enter your OpenCode Zen API key:", { type: "text" });
|
|
58
|
+
if (!apiKey || typeof apiKey !== "string") throw new Error("API key is required");
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch("https://opencode.ai/zen/v1/models", { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
61
|
+
if (!response.ok) throw new Error(`Invalid API key: ${response.status} ${response.statusText}`);
|
|
62
|
+
consola.success("API key validated successfully");
|
|
63
|
+
} catch (error) {
|
|
64
|
+
consola.error("Failed to validate API key:", error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
await saveZenAuth({ apiKey });
|
|
68
|
+
return apiKey;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { clearZenAuth, getZenAuthPath, loadZenAuth, saveZenAuth, setupZenApiKey };
|
|
73
|
+
//# sourceMappingURL=auth-C5zV8JbW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-C5zV8JbW.js","names":[],"sources":["../src/services/zen/auth.ts"],"sourcesContent":["/**\r\n * OpenCode Zen Authentication\r\n *\r\n * Handles API key authentication for OpenCode Zen.\r\n * API keys are created at https://opencode.ai/zen\r\n */\r\n\r\nimport consola from \"consola\"\r\nimport { PATHS, ensurePaths } from \"~/lib/paths\"\r\n\r\nexport interface ZenAuth {\r\n apiKey: string\r\n}\r\n\r\nconst ZEN_AUTH_FILENAME = \"zen-auth.json\"\r\n\r\n/**\r\n * Get the path to the Zen auth file\r\n */\r\nexport function getZenAuthPath(): string {\r\n return `${PATHS.DATA_DIR}/${ZEN_AUTH_FILENAME}`\r\n}\r\n\r\n/**\r\n * Save Zen API key to file\r\n */\r\nexport async function saveZenAuth(auth: ZenAuth): Promise<void> {\r\n await ensurePaths()\r\n const authPath = getZenAuthPath()\r\n await Bun.write(authPath, JSON.stringify(auth, null, 2))\r\n consola.success(\"Zen API key saved to\", authPath)\r\n}\r\n\r\n/**\r\n * Load Zen API key from file\r\n */\r\nexport async function loadZenAuth(): Promise<ZenAuth | null> {\r\n try {\r\n const authPath = getZenAuthPath()\r\n const file = Bun.file(authPath)\r\n\r\n if (!(await file.exists())) {\r\n return null\r\n }\r\n\r\n const content = await file.text()\r\n return JSON.parse(content) as ZenAuth\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\n/**\r\n * Clear Zen API key\r\n */\r\nexport async function clearZenAuth(): Promise<void> {\r\n try {\r\n const authPath = getZenAuthPath()\r\n const fs = await import(\"node:fs/promises\")\r\n await fs.unlink(authPath)\r\n consola.success(\"Zen API key cleared\")\r\n } catch {\r\n // File might not exist, ignore\r\n }\r\n}\r\n\r\n/**\r\n * Setup Zen API key interactively\r\n */\r\nexport async function setupZenApiKey(force = false): Promise<string> {\r\n const existingAuth = await loadZenAuth()\r\n\r\n if (existingAuth && !force) {\r\n consola.info(\"Using existing Zen API key\")\r\n return existingAuth.apiKey\r\n }\r\n\r\n consola.info(\"OpenCode Zen gives you access to all the best coding models\")\r\n consola.info(\"Get your API key at: https://opencode.ai/zen\")\r\n consola.info(\"\")\r\n\r\n const apiKey = await consola.prompt(\"Enter your OpenCode Zen API key:\", {\r\n type: \"text\",\r\n })\r\n\r\n if (!apiKey || typeof apiKey !== \"string\") {\r\n throw new Error(\"API key is required\")\r\n }\r\n\r\n // Validate the API key by fetching models\r\n try {\r\n const response = await fetch(\"https://opencode.ai/zen/v1/models\", {\r\n headers: {\r\n Authorization: `Bearer ${apiKey}`,\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error(`Invalid API key: ${response.status} ${response.statusText}`)\r\n }\r\n\r\n consola.success(\"API key validated successfully\")\r\n } catch (error) {\r\n consola.error(\"Failed to validate API key:\", error)\r\n throw error\r\n }\r\n\r\n await saveZenAuth({ apiKey })\r\n return apiKey\r\n}\r\n"],"mappings":";;;;AAcA,MAAM,oBAAoB;;;;AAK1B,SAAgB,iBAAyB;AACvC,QAAO,GAAG,MAAM,SAAS,GAAG;;;;;AAM9B,eAAsB,YAAY,MAA8B;AAC9D,OAAM,aAAa;CACnB,MAAM,WAAW,gBAAgB;AACjC,OAAM,IAAI,MAAM,UAAU,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;AACxD,SAAQ,QAAQ,wBAAwB,SAAS;;;;;AAMnD,eAAsB,cAAuC;AAC3D,KAAI;EACF,MAAM,WAAW,gBAAgB;EACjC,MAAM,OAAO,IAAI,KAAK,SAAS;AAE/B,MAAI,CAAE,MAAM,KAAK,QAAQ,CACvB,QAAO;EAGT,MAAM,UAAU,MAAM,KAAK,MAAM;AACjC,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO;;;;;;AAOX,eAAsB,eAA8B;AAClD,KAAI;EACF,MAAM,WAAW,gBAAgB;AAEjC,SADW,MAAM,OAAO,qBACf,OAAO,SAAS;AACzB,UAAQ,QAAQ,sBAAsB;SAChC;;;;;AAQV,eAAsB,eAAe,QAAQ,OAAwB;CACnE,MAAM,eAAe,MAAM,aAAa;AAExC,KAAI,gBAAgB,CAAC,OAAO;AAC1B,UAAQ,KAAK,6BAA6B;AAC1C,SAAO,aAAa;;AAGtB,SAAQ,KAAK,8DAA8D;AAC3E,SAAQ,KAAK,+CAA+C;AAC5D,SAAQ,KAAK,GAAG;CAEhB,MAAM,SAAS,MAAM,QAAQ,OAAO,oCAAoC,EACtE,MAAM,QACP,CAAC;AAEF,KAAI,CAAC,UAAU,OAAO,WAAW,SAC/B,OAAM,IAAI,MAAM,sBAAsB;AAIxC,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,qCAAqC,EAChE,SAAS,EACP,eAAe,UAAU,UAC1B,EACF,CAAC;AAEF,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,oBAAoB,SAAS,OAAO,GAAG,SAAS,aAAa;AAG/E,UAAQ,QAAQ,iCAAiC;UAC1C,OAAO;AACd,UAAQ,MAAM,+BAA+B,MAAM;AACnD,QAAM;;AAGR,OAAM,YAAY,EAAE,QAAQ,CAAC;AAC7B,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-
|
|
1
|
+
{"version":3,"file":"error-CvU5otz-.js","names":["errorJson: unknown"],"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 consola.error(\"Error occurred:\", error)\n\n if (error instanceof HTTPError) {\n const errorText = await error.response.text()\n let errorJson: unknown\n try {\n errorJson = JSON.parse(errorText)\n } catch {\n errorJson = errorText\n }\n consola.error(\"HTTP error:\", errorJson)\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 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,SAAQ,MAAM,mBAAmB,MAAM;AAEvC,KAAI,iBAAiB,WAAW;EAC9B,MAAM,YAAY,MAAM,MAAM,SAAS,MAAM;EAC7C,IAAIA;AACJ,MAAI;AACF,eAAY,KAAK,MAAM,UAAU;UAC3B;AACN,eAAY;;AAEd,UAAQ,MAAM,eAAe,UAAU;AACvC,SAAO,EAAE,KACP,EACE,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,EACD,MAAM,SAAS,OAChB;;AAGH,QAAO,EAAE,KACP,EACE,OAAO;EACL,SAAU,MAAgB;EAC1B,MAAM;EACP,EACF,EACD,IACD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { state } from "./state-DAw5jMjc.js";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
|
|
4
|
+
//#region src/services/zen/get-models.ts
|
|
5
|
+
/**
|
|
6
|
+
* Fetch available models from OpenCode Zen
|
|
7
|
+
*/
|
|
8
|
+
async function getZenModels() {
|
|
9
|
+
const apiKey = state.zenApiKey;
|
|
10
|
+
if (!apiKey) throw new Error("Zen API key not configured");
|
|
11
|
+
const response = await fetch("https://opencode.ai/zen/v1/models", { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
12
|
+
if (!response.ok) throw new Error(`Failed to fetch Zen models: ${response.status} ${response.statusText}`);
|
|
13
|
+
const data = await response.json();
|
|
14
|
+
consola.debug(`Fetched ${data.data.length} models from Zen`);
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Cache Zen models in state
|
|
19
|
+
*/
|
|
20
|
+
async function cacheZenModels() {
|
|
21
|
+
try {
|
|
22
|
+
const models = await getZenModels();
|
|
23
|
+
state.zenModels = models;
|
|
24
|
+
consola.info(`Loaded ${models.data.length} Zen models`);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
consola.error("Failed to load Zen models:", error);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { cacheZenModels, getZenModels };
|
|
33
|
+
//# sourceMappingURL=get-models-Hlxa1hWY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-models-Hlxa1hWY.js","names":[],"sources":["../src/services/zen/get-models.ts"],"sourcesContent":["/**\r\n * OpenCode Zen Models\r\n *\r\n * Fetches available models from OpenCode Zen.\r\n */\r\n\r\nimport consola from \"consola\"\r\nimport { state } from \"~/lib/state\"\r\n\r\nexport interface ZenModel {\r\n id: string\r\n object: string\r\n created: number\r\n owned_by: string\r\n}\r\n\r\nexport interface ZenModelsResponse {\r\n object: string\r\n data: ZenModel[]\r\n}\r\n\r\n/**\r\n * Fetch available models from OpenCode Zen\r\n */\r\nexport async function getZenModels(): Promise<ZenModelsResponse> {\r\n const apiKey = state.zenApiKey\r\n\r\n if (!apiKey) {\r\n throw new Error(\"Zen API key not configured\")\r\n }\r\n\r\n const response = await fetch(\"https://opencode.ai/zen/v1/models\", {\r\n headers: {\r\n Authorization: `Bearer ${apiKey}`,\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch Zen models: ${response.status} ${response.statusText}`)\r\n }\r\n\r\n const data = (await response.json()) as ZenModelsResponse\r\n consola.debug(`Fetched ${data.data.length} models from Zen`)\r\n\r\n return data\r\n}\r\n\r\n/**\r\n * Cache Zen models in state\r\n */\r\nexport async function cacheZenModels(): Promise<void> {\r\n try {\r\n const models = await getZenModels()\r\n state.zenModels = models\r\n consola.info(`Loaded ${models.data.length} Zen models`)\r\n } catch (error) {\r\n consola.error(\"Failed to load Zen models:\", error)\r\n throw error\r\n }\r\n}\r\n"],"mappings":";;;;;;;AAwBA,eAAsB,eAA2C;CAC/D,MAAM,SAAS,MAAM;AAErB,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,6BAA6B;CAG/C,MAAM,WAAW,MAAM,MAAM,qCAAqC,EAChE,SAAS,EACP,eAAe,UAAU,UAC1B,EACF,CAAC;AAEF,KAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,+BAA+B,SAAS,OAAO,GAAG,SAAS,aAAa;CAG1F,MAAM,OAAQ,MAAM,SAAS,MAAM;AACnC,SAAQ,MAAM,WAAW,KAAK,KAAK,OAAO,kBAAkB;AAE5D,QAAO;;;;;AAMT,eAAsB,iBAAgC;AACpD,KAAI;EACF,MAAM,SAAS,MAAM,cAAc;AACnC,QAAM,YAAY;AAClB,UAAQ,KAAK,UAAU,OAAO,KAAK,OAAO,aAAa;UAChD,OAAO;AACd,UAAQ,MAAM,8BAA8B,MAAM;AAClD,QAAM"}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { state } from "./state-DAw5jMjc.js";
|
|
2
|
+
import { HTTPError } from "./error-CvU5otz-.js";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
|
|
4
|
-
//#region src/lib/state.ts
|
|
5
|
-
const state = {
|
|
6
|
-
accountType: "individual",
|
|
7
|
-
manualApprove: false,
|
|
8
|
-
rateLimitWait: false,
|
|
9
|
-
showToken: false
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
//#endregion
|
|
13
5
|
//#region src/lib/api-config.ts
|
|
14
6
|
const standardHeaders = () => ({
|
|
15
7
|
"content-type": "application/json",
|
|
@@ -62,5 +54,5 @@ async function getGitHubUser() {
|
|
|
62
54
|
}
|
|
63
55
|
|
|
64
56
|
//#endregion
|
|
65
|
-
export { GITHUB_API_BASE_URL, GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, copilotBaseUrl, copilotHeaders, getGitHubUser, githubHeaders, standardHeaders
|
|
66
|
-
//# sourceMappingURL=get-user-
|
|
57
|
+
export { GITHUB_API_BASE_URL, GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, copilotBaseUrl, copilotHeaders, getGitHubUser, githubHeaders, standardHeaders };
|
|
58
|
+
//# sourceMappingURL=get-user-DgPgvnrS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-user-
|
|
1
|
+
{"version":3,"file":"get-user-DgPgvnrS.js","names":["state","headers: Record<string, string>"],"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\nconst API_VERSION = \"2025-04-01\"\n\nexport const copilotBaseUrl = (state: State) =>\n state.accountType === \"individual\" ?\n \"https://api.githubcopilot.com\"\n : `https://api.${state.accountType}.githubcopilot.com`\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;AAExC,MAAM,cAAc;AAEpB,MAAa,kBAAkB,YAC7BA,QAAM,gBAAgB,eACpB,kCACA,eAAeA,QAAM,YAAY;AACrC,MAAa,kBAAkB,SAAc,SAAkB,UAAU;CACvE,MAAMC,UAAkC;EACtC,eAAe,UAAUD,QAAM;EAC/B,gBAAgB,iBAAiB,CAAC;EAClC,0BAA0B;EAC1B,kBAAkB,UAAUA,QAAM;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,aAAkB;CAC9C,GAAG,iBAAiB;CACpB,eAAe,SAASA,QAAM;CAC9B,kBAAkB,UAAUA,QAAM;CAClC,yBAAyB;CACzB,cAAc;CACd,wBAAwB;CACxB,uCAAuC;CACxC;AAED,MAAa,kBAAkB;AAC/B,MAAa,mBAAmB;AAChC,MAAa,oBAAoB,CAAC,YAAY,CAAC,KAAK,IAAI;;;;AC/CxD,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"}
|