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 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"}
@@ -0,0 +1,4 @@
1
+ import "./paths-Ch0ixSo2.js";
2
+ import { clearZenAuth, getZenAuthPath, loadZenAuth, saveZenAuth, setupZenApiKey } from "./auth-C5zV8JbW.js";
3
+
4
+ export { loadZenAuth, setupZenApiKey };
@@ -0,0 +1,3 @@
1
+ import { HTTPError, forwardError } from "./error-CvU5otz-.js";
2
+
3
+ export { HTTPError };
@@ -32,4 +32,4 @@ async function forwardError(c, error) {
32
32
 
33
33
  //#endregion
34
34
  export { HTTPError, forwardError };
35
- //# sourceMappingURL=error-Cmeg4mmB.js.map
35
+ //# sourceMappingURL=error-CvU5otz-.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-Cmeg4mmB.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"}
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 { HTTPError } from "./error-Cmeg4mmB.js";
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, state };
66
- //# sourceMappingURL=get-user-DalX7epg.js.map
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-DalX7epg.js","names":["state: State","state","headers: Record<string, string>"],"sources":["../src/lib/state.ts","../src/lib/api-config.ts","../src/services/github/get-user.ts"],"sourcesContent":["import type { ModelsResponse } from \"~/services/copilot/get-models\"\n\nexport interface State {\n githubToken?: string\n copilotToken?: string\n\n accountType: string\n models?: ModelsResponse\n vsCodeVersion?: string\n\n manualApprove: boolean\n rateLimitWait: boolean\n showToken: boolean\n\n // Rate limiting configuration\n rateLimitSeconds?: number\n lastRequestTimestamp?: number\n\n // API key authentication\n apiKeys?: Array<string>\n}\n\nexport const state: State = {\n accountType: \"individual\",\n manualApprove: false,\n rateLimitWait: false,\n showToken: false,\n}\n","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":";;;;AAsBA,MAAaA,QAAe;CAC1B,aAAa;CACb,eAAe;CACf,eAAe;CACf,WAAW;CACZ;;;;ACvBD,MAAa,yBAAyB;CACpC,gBAAgB;CAChB,QAAQ;CACT;AAED,MAAM,kBAAkB;AACxB,MAAM,wBAAwB,gBAAgB;AAC9C,MAAM,aAAa,qBAAqB;AAExC,MAAM,cAAc;AAEpB,MAAa,kBAAkB,YAC7BC,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"}
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"}
@@ -0,0 +1,5 @@
1
+ import "./state-DAw5jMjc.js";
2
+ import { getGitHubUser } from "./get-user-DgPgvnrS.js";
3
+ import "./error-CvU5otz-.js";
4
+
5
+ export { getGitHubUser };