copilot-api-plus 1.0.6 → 1.0.8

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,7 @@
1
1
  # Copilot API Plus
2
2
 
3
+
4
+
3
5
  > **Fork of [ericc-ch/copilot-api](https://github.com/ericc-ch/copilot-api)** with bug fixes and improvements.
4
6
 
5
7
  > [!WARNING]
@@ -169,6 +171,8 @@ The following command line options are available for the `start` command:
169
171
  | --claude-code | Generate a command to launch Claude Code with Copilot API config | false | -c |
170
172
  | --show-token | Show GitHub and Copilot tokens on fetch and refresh | false | none |
171
173
  | --proxy-env | Initialize proxy from environment variables | false | none |
174
+ | --zen | Enable OpenCode Zen mode (proxy to Zen instead of GitHub Copilot) | false | -z |
175
+ | --zen-api-key | OpenCode Zen API key (get from https://opencode.ai/zen) | none | none |
172
176
 
173
177
  ### Auth Command Options
174
178
 
@@ -350,6 +354,141 @@ The dashboard provides a user-friendly interface to view your Copilot usage data
350
354
  - **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
355
  `https://imbuxiangnan-cyber.github.io/copilot-api-plus?endpoint=http://your-api-server/usage`
352
356
 
357
+ ## Using with OpenCode Zen
358
+
359
+
360
+ ### OpenCode Zen 使用指南
361
+
362
+ OpenCode Zen 是由 opencode.ai 提供的多模型 API 服务,支持 Claude、GPT-5、Gemini、Qwen 等主流大模型,适合代码生成、AI 助手等场景。Copilot API Plus 支持将 Zen 转换为 OpenAI/Anthropic 兼容 API,方便与 Claude Code、opencode 等工具无缝集成。
363
+
364
+ #### 1. 获取 Zen API Key
365
+ 1. 访问 [https://opencode.ai/zen](https://opencode.ai/zen)
366
+ 2. 注册并登录,进入“API Keys”页面,创建你的 API Key
367
+
368
+ #### 2. 启动 Zen 模式
369
+ 首次运行会自动提示输入 API Key,并保存到本地。也可直接指定:
370
+ ```sh
371
+ npx copilot-api-plus@latest start --zen --zen-api-key <你的APIKey>
372
+ ```
373
+ 或交互式:
374
+ ```sh
375
+ npx copilot-api-plus@latest start --zen
376
+ ```
377
+
378
+ #### 3. 与 Claude Code 配合
379
+ Zen 支持多种 Claude 模型,推荐用法:
380
+ ```sh
381
+ npx copilot-api-plus@latest start --zen --claude-code
382
+ ```
383
+ 会自动生成 Claude Code 启动命令,支持模型选择。
384
+
385
+ #### 4. 支持的模型(部分示例)
386
+ | 名称 | ID | 说明 |
387
+ |---------------------|----------------------|---------------------------|
388
+ | Claude Sonnet 4.5 | claude-sonnet-4-5 | Anthropic Claude 200K |
389
+ | Claude Opus 4.5 | claude-opus-4-5 | Anthropic Claude 200K |
390
+ | GPT-5 Codex | gpt-5-codex | OpenAI Responses API |
391
+ | Gemini 3 Pro | gemini-3-pro | Google Gemini |
392
+ | Qwen3 Coder 480B | qwen3-coder | Alibaba Qwen |
393
+ | Kimi K2 | kimi-k2 | Moonshot |
394
+ | Grok Code Fast 1 | grok-code | xAI |
395
+ 更多模型请见 [Zen 官网](https://opencode.ai/zen)。
396
+
397
+ #### 5. API 路径
398
+ Zen 模式下,所有 OpenAI/Anthropic 兼容路径均可用:
399
+ - `POST /v1/chat/completions` (OpenAI 格式)
400
+ - `POST /v1/messages` (Anthropic 格式)
401
+ - `GET /v1/models` (模型列表)
402
+ Zen 专属路径(始终可用):
403
+ - `POST /zen/v1/chat/completions`
404
+ - `POST /zen/v1/messages`
405
+ - `GET /zen/v1/models`
406
+
407
+ #### 6. 常见问题
408
+ - **API Key 存储位置**:`~/.local/share/copilot-api-plus/zen-auth.json`
409
+ - **切换/清除 API Key**:
410
+ - 只清除 Zen:`npx copilot-api-plus@latest logout --zen`
411
+ - 全部清除:`npx copilot-api-plus@latest logout --all`
412
+ - **模型选择**:启动时会自动显示可用模型列表
413
+ - **与 opencode 配合**:在 `opencode.json` 里设置 `baseURL` 为 `http://127.0.0.1:4141/v1`,或用环境变量 `OPENAI_BASE_URL`
414
+
415
+ #### 7. 使用技巧
416
+ - 推荐用 `--claude-code` 生成 Claude Code 启动命令
417
+ - 支持所有 Zen 公开模型,按需选择
418
+ - 支持 Docker 部署,API Key 持久化
419
+
420
+ 如需详细配置示例、opencode 配置、更多高级用法,请继续阅读下方文档。
421
+
422
+ ### Why Use Zen Mode?
423
+
424
+ - **Access to many models**: Claude Sonnet 4.5, Claude Opus 4.5, GPT-5 Codex, Gemini 3 Pro, Qwen3 Coder, and more
425
+ - **Transparent pricing**: Pay per request with zero markups
426
+ - **Tested & verified**: All models are tested by the OpenCode team for coding tasks
427
+ - **Single API key**: One key for all Zen models
428
+
429
+ ### Getting Started with Zen
430
+
431
+ 1. **Get your API key**: Go to [opencode.ai/zen](https://opencode.ai/zen), sign up, and create an API key.
432
+
433
+ 2. **Start the server in Zen mode**:
434
+ ```sh
435
+ npx copilot-api-plus@latest start --zen
436
+ ```
437
+
438
+ You will be prompted to enter your Zen API key on first run. The key will be saved for future use.
439
+
440
+ 3. **Or provide the API key directly**:
441
+ ```sh
442
+ npx copilot-api-plus@latest start --zen --zen-api-key your_api_key_here
443
+ ```
444
+
445
+ ### Using Zen with Claude Code
446
+
447
+ Start the server with both `--zen` and `--claude-code` flags:
448
+
449
+ ```sh
450
+ npx copilot-api-plus@latest start --zen --claude-code
451
+ ```
452
+
453
+ This will:
454
+ 1. Connect to OpenCode Zen instead of GitHub Copilot
455
+ 2. Show you available Zen models to choose from
456
+ 3. Generate a command to launch Claude Code with the selected model
457
+
458
+ ### Available Zen Models
459
+
460
+ When using Zen mode, you can access models like:
461
+
462
+ | Model | ID | Description |
463
+ | ------------------ | ----------------- | ------------------------------ |
464
+ | Claude Sonnet 4.5 | claude-sonnet-4-5 | Anthropic Claude (200K context)|
465
+ | Claude Opus 4.5 | claude-opus-4-5 | Anthropic Claude (200K context)|
466
+ | GPT 5 Codex | gpt-5-codex | OpenAI (Responses API) |
467
+ | Gemini 3 Pro | gemini-3-pro | Google Gemini |
468
+ | Qwen3 Coder 480B | qwen3-coder | Alibaba Qwen |
469
+ | Kimi K2 | kimi-k2 | Moonshot |
470
+ | Grok Code Fast 1 | grok-code | xAI |
471
+
472
+ For the full list, visit [opencode.ai/zen](https://opencode.ai/zen).
473
+
474
+ ### Zen API Endpoints
475
+
476
+ The server exposes the same endpoints in Zen mode:
477
+
478
+ | Endpoint | Description |
479
+ | --------------------------- | ------------------------------------ |
480
+ | `POST /v1/chat/completions` | OpenAI-compatible chat completions |
481
+ | `POST /v1/messages` | Anthropic-compatible messages |
482
+ | `GET /v1/models` | List available Zen models |
483
+
484
+ You can also access dedicated Zen routes (always available):
485
+
486
+ | Endpoint | Description |
487
+ | -------------------------------- | ------------------------ |
488
+ | `POST /zen/v1/chat/completions` | Zen chat completions |
489
+ | `POST /zen/v1/messages` | Zen messages |
490
+ | `GET /zen/v1/models` | Zen models |
491
+
353
492
  ## Using with Claude Code
354
493
 
355
494
  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 +537,125 @@ You can find more options here: [Claude Code settings](https://docs.anthropic.co
398
537
 
399
538
  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
539
 
540
+ ## Using with opencode
541
+
542
+ [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.
543
+
544
+ ### Configuration
545
+
546
+ Create or edit `opencode.json` in your project root directory:
547
+
548
+ ```json
549
+ {
550
+ "$schema": "https://opencode.ai/config.json",
551
+ "provider": {
552
+ "copilot-api-plus": {
553
+ "api": "openai-compatible",
554
+ "name": "Copilot API Plus",
555
+ "options": {
556
+ "baseURL": "http://127.0.0.1:4141/v1"
557
+ },
558
+ "models": {
559
+ "claude-sonnet-4": {
560
+ "name": "Claude Sonnet 4",
561
+ "id": "claude-sonnet-4",
562
+ "max_tokens": 64000,
563
+ "default_tokens": 16000,
564
+ "profile": "coder",
565
+ "attachment": true,
566
+ "limit": {
567
+ "context": 200000
568
+ }
569
+ },
570
+ "claude-sonnet-4.5": {
571
+ "name": "Claude Sonnet 4.5",
572
+ "id": "claude-sonnet-4.5",
573
+ "max_tokens": 64000,
574
+ "default_tokens": 16000,
575
+ "profile": "coder",
576
+ "attachment": true,
577
+ "limit": {
578
+ "context": 200000
579
+ }
580
+ },
581
+ "gpt-4.1": {
582
+ "name": "GPT-4.1",
583
+ "id": "gpt-4.1",
584
+ "max_tokens": 32768,
585
+ "default_tokens": 16000,
586
+ "profile": "coder",
587
+ "attachment": true,
588
+ "limit": {
589
+ "context": 1047576
590
+ }
591
+ },
592
+ "o4-mini": {
593
+ "name": "o4-mini",
594
+ "id": "o4-mini",
595
+ "max_tokens": 100000,
596
+ "default_tokens": 16000,
597
+ "profile": "coder",
598
+ "attachment": true,
599
+ "limit": {
600
+ "context": 200000
601
+ }
602
+ },
603
+ "gemini-2.5-pro": {
604
+ "name": "Gemini 2.5 Pro",
605
+ "id": "gemini-2.5-pro",
606
+ "max_tokens": 65536,
607
+ "default_tokens": 16000,
608
+ "profile": "coder",
609
+ "attachment": true,
610
+ "limit": {
611
+ "context": 1048576
612
+ }
613
+ }
614
+ }
615
+ }
616
+ }
617
+ }
618
+ ```
619
+
620
+ ### Usage
621
+
622
+ 1. Start copilot-api-plus:
623
+ ```sh
624
+ npx copilot-api-plus@latest start --proxy-env
625
+ ```
626
+
627
+ 2. Run opencode in the same project directory:
628
+ ```sh
629
+ npx opencode@latest
630
+ ```
631
+
632
+ 3. Select `copilot-api-plus` as your provider when prompted, or use the model switcher to change providers.
633
+
634
+ ### Available Models
635
+
636
+ When using copilot-api-plus with opencode, you can access all GitHub Copilot models:
637
+
638
+ | Model | Description |
639
+ | ----------------- | ------------------------------------ |
640
+ | `claude-sonnet-4` | Claude Sonnet 4 (200K context) |
641
+ | `claude-sonnet-4.5` | Claude Sonnet 4.5 (200K context) |
642
+ | `gpt-4.1` | GPT-4.1 (1M context) |
643
+ | `o4-mini` | OpenAI o4-mini reasoning model |
644
+ | `gemini-2.5-pro` | Google Gemini 2.5 Pro (1M context) |
645
+
646
+ ### Environment Variables for opencode
647
+
648
+ If you prefer not to create a config file, you can also set environment variables:
649
+
650
+ ```sh
651
+ # Set the base URL for opencode to use
652
+ export OPENAI_BASE_URL=http://127.0.0.1:4141/v1
653
+ export OPENAI_API_KEY=dummy
654
+
655
+ # Start opencode
656
+ npx opencode@latest
657
+ ```
658
+
401
659
  ## Running from Source
402
660
 
403
661
  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 };