@jeffreycao/copilot-api 1.10.1 → 1.10.2
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 +28 -5
- package/README.zh-CN.md +28 -5
- package/dist/{auth-D7YCTWpx.js → auth-BHa2OHXf.js} +3 -3
- package/dist/{auth-D7YCTWpx.js.map → auth-BHa2OHXf.js.map} +1 -1
- package/dist/{check-usage-Dgg0nNEw.js → check-usage-BdXGp1Wr.js} +3 -3
- package/dist/{check-usage-Dgg0nNEw.js.map → check-usage-BdXGp1Wr.js.map} +1 -1
- package/dist/main.js +3 -3
- package/dist/{proxy-De0Po8kG.js → proxy-DQLzdeq3.js} +106 -6
- package/dist/proxy-DQLzdeq3.js.map +1 -0
- package/dist/{server-DpVPS3zt.js → server-csGHkK-m.js} +77 -16
- package/dist/server-csGHkK-m.js.map +1 -0
- package/dist/{start-DDII-0ML.js → start-BVraN8xz.js} +5 -5
- package/dist/{start-DDII-0ML.js.map → start-BVraN8xz.js.map} +1 -1
- package/dist/{token-BQlDdqtI.js → token-Dj8XsAxn.js} +2 -2
- package/dist/{token-BQlDdqtI.js.map → token-Dj8XsAxn.js.map} +1 -1
- package/dist/{utils-C5ej0z8n.js → utils-jHLgqAq2.js} +3 -3
- package/dist/{utils-C5ej0z8n.js.map → utils-jHLgqAq2.js.map} +1 -1
- package/package.json +1 -1
- package/dist/proxy-De0Po8kG.js.map +0 -1
- package/dist/server-DpVPS3zt.js.map +0 -1
package/README.md
CHANGED
|
@@ -176,6 +176,8 @@ https://github.com/caozhiyuan/copilot-api/releases
|
|
|
176
176
|
|
|
177
177
|
Download the installer for your platform, sign in inside the app, choose a port, start the server, then point your client at the local endpoint shown in the app. Packaged desktop builds use the bundled Electron runtime, so normal desktop usage does not require installing Node.js separately. Token usage history is enabled when that bundled runtime supports SQLite.
|
|
178
178
|
|
|
179
|
+
The desktop app's Advanced Config page reads and writes model mappings through `GET/POST /admin/config/model-mappings`. It uses `auth.adminApiKey` instead of the regular `auth.apiKeys`, and the app reads that key directly from `config.json` after the server has generated it on startup.
|
|
180
|
+
|
|
179
181
|
### Desktop App Screenshots
|
|
180
182
|
|
|
181
183
|
Main dashboard, token usage breakdown in the bundled Electron app:
|
|
@@ -302,7 +304,8 @@ The following command line options are available for the `start` command:
|
|
|
302
304
|
```json
|
|
303
305
|
{
|
|
304
306
|
"auth": {
|
|
305
|
-
"apiKeys": []
|
|
307
|
+
"apiKeys": [],
|
|
308
|
+
"adminApiKey": "<auto-generated-on-startup>"
|
|
306
309
|
},
|
|
307
310
|
"providers": {
|
|
308
311
|
"custom": {
|
|
@@ -345,6 +348,7 @@ The following command line options are available for the `start` command:
|
|
|
345
348
|
}
|
|
346
349
|
}
|
|
347
350
|
},
|
|
351
|
+
"modelMappings": {},
|
|
348
352
|
"extraPrompts": {
|
|
349
353
|
"gpt-5-mini": "<built-in exploration prompt>",
|
|
350
354
|
"gpt-5.3-codex": "<built-in commentary prompt>",
|
|
@@ -365,7 +369,9 @@ The following command line options are available for the `start` command:
|
|
|
365
369
|
"useResponsesApiWebSearch": true
|
|
366
370
|
}
|
|
367
371
|
```
|
|
368
|
-
- **auth.apiKeys:** API keys used for request authentication. Supports multiple keys for rotation. Requests can authenticate with either `x-api-key: <key>` or `Authorization: Bearer <key>`. If empty or omitted, authentication is disabled.
|
|
372
|
+
- **auth.apiKeys:** API keys used for request authentication on non-admin routes. Supports multiple keys for rotation. Requests can authenticate with either `x-api-key: <key>` or `Authorization: Bearer <key>`. If empty or omitted, authentication for non-admin routes is disabled.
|
|
373
|
+
- **auth.adminApiKey:** Single admin key used only for `/admin/*` routes. If missing, the server generates a random key at startup and writes it back to `config.json`. Requests use the same `x-api-key` or `Authorization: Bearer` headers, but regular `auth.apiKeys` never grant access to `/admin/*`.
|
|
374
|
+
- **modelMappings:** Exact `sourceModel -> targetModel` rewrites for top-level `POST /v1/messages` and `POST /v1/messages/count_tokens` requests. Omit it or leave it as `{}` to disable rewrites. Both the source and target must be non-empty strings. Targets can be regular model IDs or `provider/model` aliases such as `dashscope/qwen3.6-plus`, and the rewrite happens before provider alias parsing. The admin endpoints `GET/POST /admin/config/model-mappings` read and update only this field.
|
|
369
375
|
- **extraPrompts:** Map of `model -> prompt` appended to the first system prompt when translating Anthropic-style requests to Copilot. Use this to inject guardrails or guidance per model. Missing default entries are auto-added without overwriting your custom prompts. The built-in prompts for `gpt-5.3-codex` and `gpt-5.4` enable phase-aware commentary, which lets the model emit a short user-facing progress update before tools or deeper reasoning.
|
|
370
376
|
- **providers:** Global upstream provider map. Each provider key (for example `custom`) becomes a route prefix (`/custom/v1/messages`). Supports `type: "anthropic"` and `type: "openai-compatible"`. Top-level Anthropic clients can also use `model: "custom/model-id"` with `/v1/messages` and `/v1/messages/count_tokens`; the proxy strips the `custom/` prefix before forwarding upstream. `GET /v1/models` does not aggregate provider models; use `GET /custom/v1/models` for provider model lists.
|
|
371
377
|
- `enabled` defaults to `true` if omitted.
|
|
@@ -395,20 +401,28 @@ Edit this file to customize prompts or swap in your own fast model. Restart the
|
|
|
395
401
|
|
|
396
402
|
## API Authentication
|
|
397
403
|
|
|
398
|
-
- **Protected routes:** All routes except `/`, `/usage-viewer`, and `/usage-viewer/` require authentication when `auth.apiKeys` is configured and non-empty.
|
|
404
|
+
- **Protected non-admin routes:** All routes except `/`, `/usage-viewer`, and `/usage-viewer/` require authentication when `auth.apiKeys` is configured and non-empty.
|
|
405
|
+
- **Admin routes:** All `/admin/*` routes require `auth.adminApiKey`. If it is missing, the server generates one at startup and persists it to `config.json` before serving requests.
|
|
399
406
|
- **Allowed auth headers:**
|
|
400
407
|
- `x-api-key: <your_key>`
|
|
401
408
|
- `Authorization: Bearer <your_key>`
|
|
402
409
|
- **CORS preflight:** `OPTIONS` requests are always allowed.
|
|
403
|
-
- **When no keys are configured:**
|
|
410
|
+
- **When no regular keys are configured:** Non-admin routes continue to allow requests. This does not apply to `/admin/*`, which only accepts `auth.adminApiKey`.
|
|
404
411
|
|
|
405
|
-
Example request:
|
|
412
|
+
Example request for a regular protected route:
|
|
406
413
|
|
|
407
414
|
```sh
|
|
408
415
|
curl http://localhost:4141/v1/models \
|
|
409
416
|
-H "x-api-key: your_api_key"
|
|
410
417
|
```
|
|
411
418
|
|
|
419
|
+
Example request for an admin route:
|
|
420
|
+
|
|
421
|
+
```sh
|
|
422
|
+
curl http://localhost:4141/admin/config/model-mappings \
|
|
423
|
+
-H "x-api-key: your_admin_api_key"
|
|
424
|
+
```
|
|
425
|
+
|
|
412
426
|
## API Endpoints
|
|
413
427
|
|
|
414
428
|
The server exposes several endpoints to interact with the Copilot API. It provides OpenAI-compatible endpoints and now also includes support for Anthropic-compatible endpoints, allowing for greater flexibility with different tools and services.
|
|
@@ -445,6 +459,15 @@ New endpoints for monitoring your Copilot usage and quotas.
|
|
|
445
459
|
| `GET /usage` | `GET` | Get detailed Copilot usage statistics and quota information. |
|
|
446
460
|
| `GET /token` | `GET` | Get the current Copilot token being used by the API. |
|
|
447
461
|
|
|
462
|
+
### Admin / Configuration Endpoints
|
|
463
|
+
|
|
464
|
+
These endpoints are reserved for local administrative actions and only accept `auth.adminApiKey`.
|
|
465
|
+
|
|
466
|
+
| Endpoint | Method | Description |
|
|
467
|
+
| ------------------------------------- | ------ | --------------------------------------------------------------------------- |
|
|
468
|
+
| `GET /admin/config/model-mappings` | `GET` | Returns the current `config.json` path and the active `modelMappings` map. |
|
|
469
|
+
| `POST /admin/config/model-mappings` | `POST` | Updates only the `modelMappings` field in `config.json` and returns it back. |
|
|
470
|
+
|
|
448
471
|
## Example Usage
|
|
449
472
|
|
|
450
473
|
Using with npx:
|
package/README.zh-CN.md
CHANGED
|
@@ -178,6 +178,8 @@ https://github.com/caozhiyuan/copilot-api/releases
|
|
|
178
178
|
|
|
179
179
|
下载对应平台的安装包后,在应用内登录、选择端口并启动服务,再把你的客户端指向应用里显示的本地端点即可。发布版桌面应用使用随包内置的 Electron 运行时,正常使用不需要额外安装 Node.js;token usage 历史记录会在该内置运行时支持 SQLite 时启用。
|
|
180
180
|
|
|
181
|
+
桌面应用里的高级配置页会通过 `GET/POST /admin/config/model-mappings` 读写模型映射。它使用的是 `auth.adminApiKey`,不是普通的 `auth.apiKeys`;应用会在服务启动并自动生成该 key 后,直接从 `config.json` 读取它来发起请求。
|
|
182
|
+
|
|
181
183
|
### 桌面应用截图
|
|
182
184
|
|
|
183
185
|
下面展示了桌面应用中的首页、Token 用量统计页面:
|
|
@@ -306,7 +308,8 @@ Copilot API 现在使用子命令结构,主要命令包括:
|
|
|
306
308
|
```json
|
|
307
309
|
{
|
|
308
310
|
"auth": {
|
|
309
|
-
"apiKeys": []
|
|
311
|
+
"apiKeys": [],
|
|
312
|
+
"adminApiKey": "<startup 自动生成>"
|
|
310
313
|
},
|
|
311
314
|
"providers": {
|
|
312
315
|
"custom": {
|
|
@@ -349,6 +352,7 @@ Copilot API 现在使用子命令结构,主要命令包括:
|
|
|
349
352
|
}
|
|
350
353
|
}
|
|
351
354
|
},
|
|
355
|
+
"modelMappings": {},
|
|
352
356
|
"extraPrompts": {
|
|
353
357
|
"gpt-5-mini": "<built-in exploration prompt>",
|
|
354
358
|
"gpt-5.3-codex": "<built-in commentary prompt>",
|
|
@@ -369,7 +373,9 @@ Copilot API 现在使用子命令结构,主要命令包括:
|
|
|
369
373
|
"useResponsesApiWebSearch": true
|
|
370
374
|
}
|
|
371
375
|
```
|
|
372
|
-
- **auth.apiKeys:**
|
|
376
|
+
- **auth.apiKeys:** 用于普通非 admin 路由的 API key。支持多个 key 轮换使用。请求可通过 `x-api-key: <key>` 或 `Authorization: Bearer <key>` 进行认证。若为空或省略,则普通路由的认证会被禁用。
|
|
377
|
+
- **auth.adminApiKey:** 仅用于 `/admin/*` 路由的单个 admin key。若未配置,服务会在启动时自动生成一个随机 key,并回写到 `config.json`。它同样使用 `x-api-key` 或 `Authorization: Bearer` 这两种头,但普通 `auth.apiKeys` 不能访问 `/admin/*`。
|
|
378
|
+
- **modelMappings:** 用于顶层 `POST /v1/messages` 和 `POST /v1/messages/count_tokens` 请求的精确 `sourceModel -> targetModel` 重写映射。省略该字段或保留为 `{}` 时,不会做模型重写。`source` 和 `target` 都必须是非空字符串。`target` 可以是普通模型 ID,也可以是 `provider/model` 形式的别名,例如 `dashscope/qwen3.6-plus`;重写发生在 provider alias 解析之前。`GET/POST /admin/config/model-mappings` 管理接口读写的也只有这个字段。
|
|
373
379
|
- **extraPrompts:** `model -> prompt` 的映射。把 Anthropic 风格请求翻译给 Copilot 时,会将其附加到第一条 system prompt 后面。你可以借此为不同模型注入护栏或指引。缺失的默认项会自动补齐,但不会覆盖你自定义的 prompt。内置的 `gpt-5.3-codex` 和 `gpt-5.4` prompt 会启用带阶段感知的 commentary,让模型在工具调用或更深层推理前先发出简短的用户可见进度说明。
|
|
374
380
|
- **providers:** 全局上游 provider 映射。每个 provider key(例如 `custom`)都会变成一个路由前缀(`/custom/v1/messages`)。支持 `type: "anthropic"` 和 `type: "openai-compatible"`。顶层 Anthropic 客户端也可以在 `/v1/messages` 和 `/v1/messages/count_tokens` 中使用 `model: "custom/model-id"`;代理会在转发上游前移除 `custom/` 前缀。`GET /v1/models` 不聚合 provider 模型;provider 模型列表请使用 `GET /custom/v1/models`。
|
|
375
381
|
- `enabled`:可选,若省略则默认为 `true`。
|
|
@@ -399,20 +405,28 @@ Copilot API 现在使用子命令结构,主要命令包括:
|
|
|
399
405
|
|
|
400
406
|
## API 认证
|
|
401
407
|
|
|
402
|
-
-
|
|
408
|
+
- **受保护的普通路由:** 当配置了 `auth.apiKeys` 且非空时,除 `/`、`/usage-viewer` 和 `/usage-viewer/` 以外的普通路由都需要认证。
|
|
409
|
+
- **Admin 路由:** 所有 `/admin/*` 路由都要求 `auth.adminApiKey`。如果缺失,服务会在启动时自动生成并在开始提供服务前写回 `config.json`。
|
|
403
410
|
- **允许的认证头:**
|
|
404
411
|
- `x-api-key: <your_key>`
|
|
405
412
|
- `Authorization: Bearer <your_key>`
|
|
406
413
|
- **CORS 预检:** `OPTIONS` 请求始终允许。
|
|
407
|
-
-
|
|
414
|
+
- **未配置普通 key 时:** 普通路由仍可直接访问;但这条规则不适用于 `/admin/*`,后者只接受 `auth.adminApiKey`。
|
|
408
415
|
|
|
409
|
-
|
|
416
|
+
普通受保护路由的示例请求:
|
|
410
417
|
|
|
411
418
|
```sh
|
|
412
419
|
curl http://localhost:4141/v1/models \
|
|
413
420
|
-H "x-api-key: your_api_key"
|
|
414
421
|
```
|
|
415
422
|
|
|
423
|
+
Admin 路由的示例请求:
|
|
424
|
+
|
|
425
|
+
```sh
|
|
426
|
+
curl http://localhost:4141/admin/config/model-mappings \
|
|
427
|
+
-H "x-api-key: your_admin_api_key"
|
|
428
|
+
```
|
|
429
|
+
|
|
416
430
|
## API 端点
|
|
417
431
|
|
|
418
432
|
服务端提供多个端点来与 Copilot API 交互。它支持 OpenAI 兼容端点,也支持 Anthropic 兼容端点,因此可以更灵活地接入不同工具与服务。
|
|
@@ -449,6 +463,15 @@ curl http://localhost:4141/v1/models \
|
|
|
449
463
|
| `GET /usage` | `GET` | 获取详细的 Copilot 使用统计与额度信息。 |
|
|
450
464
|
| `GET /token` | `GET` | 获取当前 API 正在使用的 Copilot token。 |
|
|
451
465
|
|
|
466
|
+
### Admin / 配置端点
|
|
467
|
+
|
|
468
|
+
这些端点用于本地管理操作,只接受 `auth.adminApiKey`。
|
|
469
|
+
|
|
470
|
+
| 端点 | 方法 | 说明 |
|
|
471
|
+
| --- | --- | --- |
|
|
472
|
+
| `GET /admin/config/model-mappings` | `GET` | 返回当前 `config.json` 路径以及生效中的 `modelMappings` 映射。 |
|
|
473
|
+
| `POST /admin/config/model-mappings` | `POST` | 只更新 `config.json` 里的 `modelMappings` 字段,并回传更新后的结果。 |
|
|
474
|
+
|
|
452
475
|
## 使用示例
|
|
453
476
|
|
|
454
477
|
通过 npx 使用:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as ensurePaths, t as PATHS } from "./paths-DC-mqCY3.js";
|
|
2
|
-
import { I as state } from "./utils-
|
|
3
|
-
import { r as setupGitHubToken } from "./token-
|
|
2
|
+
import { I as state } from "./utils-jHLgqAq2.js";
|
|
3
|
+
import { r as setupGitHubToken } from "./token-Dj8XsAxn.js";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
//#region src/auth.ts
|
|
@@ -42,4 +42,4 @@ const auth = defineCommand({
|
|
|
42
42
|
//#endregion
|
|
43
43
|
export { auth };
|
|
44
44
|
|
|
45
|
-
//# sourceMappingURL=auth-
|
|
45
|
+
//# sourceMappingURL=auth-BHa2OHXf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-
|
|
1
|
+
{"version":3,"file":"auth-BHa2OHXf.js","names":[],"sources":["../src/auth.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"./lib/paths\"\nimport { state } from \"./lib/state\"\nimport { setupGitHubToken } from \"./lib/token\"\n\ninterface RunAuthOptions {\n verbose: boolean\n showToken: boolean\n}\n\nexport async function runAuth(options: RunAuthOptions): Promise<void> {\n if (options.verbose) {\n consola.level = 5\n consola.info(\"Verbose logging enabled\")\n }\n\n state.showToken = options.showToken\n\n await ensurePaths()\n await setupGitHubToken({ force: true })\n consola.success(\"GitHub token written to\", PATHS.GITHUB_TOKEN_PATH)\n}\n\nexport const auth = defineCommand({\n meta: {\n name: \"auth\",\n description: \"Run GitHub auth flow without running the server\",\n },\n args: {\n verbose: {\n alias: \"v\",\n type: \"boolean\",\n default: false,\n description: \"Enable verbose logging\",\n },\n \"show-token\": {\n type: \"boolean\",\n default: false,\n description: \"Show GitHub token on auth\",\n },\n },\n run({ args }) {\n return runAuth({\n verbose: args.verbose,\n showToken: args[\"show-token\"],\n })\n },\n})\n"],"mappings":";;;;;;AAcA,eAAsB,QAAQ,SAAwC;CACpE,IAAI,QAAQ,SAAS;EACnB,QAAQ,QAAQ;EAChB,QAAQ,KAAK,0BAA0B;;CAGzC,MAAM,YAAY,QAAQ;CAE1B,MAAM,aAAa;CACnB,MAAM,iBAAiB,EAAE,OAAO,MAAM,CAAC;CACvC,QAAQ,QAAQ,2BAA2B,MAAM,kBAAkB;;AAGrE,MAAa,OAAO,cAAc;CAChC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,SAAS;GACP,OAAO;GACP,MAAM;GACN,SAAS;GACT,aAAa;GACd;EACD,cAAc;GACZ,MAAM;GACN,SAAS;GACT,aAAa;GACd;EACF;CACD,IAAI,EAAE,QAAQ;EACZ,OAAO,QAAQ;GACb,SAAS,KAAK;GACd,WAAW,KAAK;GACjB,CAAC;;CAEL,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as ensurePaths } from "./paths-DC-mqCY3.js";
|
|
2
|
-
import { f as getCopilotUsage } from "./utils-
|
|
3
|
-
import { r as setupGitHubToken } from "./token-
|
|
2
|
+
import { f as getCopilotUsage } from "./utils-jHLgqAq2.js";
|
|
3
|
+
import { r as setupGitHubToken } from "./token-Dj8XsAxn.js";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
//#region src/check-usage.ts
|
|
@@ -40,4 +40,4 @@ const checkUsage = defineCommand({
|
|
|
40
40
|
//#endregion
|
|
41
41
|
export { checkUsage };
|
|
42
42
|
|
|
43
|
-
//# sourceMappingURL=check-usage-
|
|
43
|
+
//# sourceMappingURL=check-usage-BdXGp1Wr.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-usage-
|
|
1
|
+
{"version":3,"file":"check-usage-BdXGp1Wr.js","names":[],"sources":["../src/check-usage.ts"],"sourcesContent":["import { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { ensurePaths } from \"./lib/paths\"\nimport { setupGitHubToken } from \"./lib/token\"\nimport {\n getCopilotUsage,\n type QuotaDetail,\n} from \"./services/github/get-copilot-usage\"\n\nexport const checkUsage = defineCommand({\n meta: {\n name: \"check-usage\",\n description: \"Show current GitHub Copilot usage/quota information\",\n },\n async run() {\n await ensurePaths()\n await setupGitHubToken()\n try {\n const usage = await getCopilotUsage()\n const premium = usage.quota_snapshots.premium_interactions\n const premiumTotal = premium.entitlement\n const premiumUsed = premiumTotal - premium.remaining\n const premiumPercentUsed =\n premiumTotal > 0 ? (premiumUsed / premiumTotal) * 100 : 0\n const premiumPercentRemaining = premium.percent_remaining\n\n // Helper to summarize a quota snapshot\n function summarizeQuota(name: string, snap: QuotaDetail | undefined) {\n if (!snap) return `${name}: N/A`\n const total = snap.entitlement\n const used = total - snap.remaining\n const percentUsed = total > 0 ? (used / total) * 100 : 0\n const percentRemaining = snap.percent_remaining\n return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`\n }\n\n const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`\n const chatLine = summarizeQuota(\"Chat\", usage.quota_snapshots.chat)\n const completionsLine = summarizeQuota(\n \"Completions\",\n usage.quota_snapshots.completions,\n )\n\n consola.box(\n `Copilot Usage (plan: ${usage.copilot_plan})\\n`\n + `Quota resets: ${usage.quota_reset_date}\\n`\n + `\\nQuotas:\\n`\n + ` ${premiumLine}\\n`\n + ` ${chatLine}\\n`\n + ` ${completionsLine}`,\n )\n } catch (err) {\n consola.error(\"Failed to fetch Copilot usage:\", err)\n process.exit(1)\n }\n },\n})\n"],"mappings":";;;;;;AAUA,MAAa,aAAa,cAAc;CACtC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,MAAM;EACV,MAAM,aAAa;EACnB,MAAM,kBAAkB;EACxB,IAAI;GACF,MAAM,QAAQ,MAAM,iBAAiB;GACrC,MAAM,UAAU,MAAM,gBAAgB;GACtC,MAAM,eAAe,QAAQ;GAC7B,MAAM,cAAc,eAAe,QAAQ;GAC3C,MAAM,qBACJ,eAAe,IAAK,cAAc,eAAgB,MAAM;GAC1D,MAAM,0BAA0B,QAAQ;GAGxC,SAAS,eAAe,MAAc,MAA+B;IACnE,IAAI,CAAC,MAAM,OAAO,GAAG,KAAK;IAC1B,MAAM,QAAQ,KAAK;IACnB,MAAM,OAAO,QAAQ,KAAK;IAC1B,MAAM,cAAc,QAAQ,IAAK,OAAO,QAAS,MAAM;IACvD,MAAM,mBAAmB,KAAK;IAC9B,OAAO,GAAG,KAAK,IAAI,KAAK,GAAG,MAAM,SAAS,YAAY,QAAQ,EAAE,CAAC,UAAU,iBAAiB,QAAQ,EAAE,CAAC;;GAGzG,MAAM,cAAc,YAAY,YAAY,GAAG,aAAa,SAAS,mBAAmB,QAAQ,EAAE,CAAC,UAAU,wBAAwB,QAAQ,EAAE,CAAC;GAChJ,MAAM,WAAW,eAAe,QAAQ,MAAM,gBAAgB,KAAK;GACnE,MAAM,kBAAkB,eACtB,eACA,MAAM,gBAAgB,YACvB;GAED,QAAQ,IACN,wBAAwB,MAAM,aAAa,mBACtB,MAAM,iBAAiB,iBAEnC,YAAY,MACZ,SAAS,MACT,kBACV;WACM,KAAK;GACZ,QAAQ,MAAM,kCAAkC,IAAI;GACpD,QAAQ,KAAK,EAAE;;;CAGpB,CAAC"}
|
package/dist/main.js
CHANGED
|
@@ -39,10 +39,10 @@ if (typeof args["api-home"] === "string") process.env.COPILOT_API_HOME = args["a
|
|
|
39
39
|
if (typeof args["oauth-app"] === "string") process.env.COPILOT_API_OAUTH_APP = args["oauth-app"];
|
|
40
40
|
if (typeof args["enterprise-url"] === "string") process.env.COPILOT_API_ENTERPRISE_URL = args["enterprise-url"];
|
|
41
41
|
bindElectronFetch();
|
|
42
|
-
const { auth } = await import("./auth-
|
|
43
|
-
const { checkUsage } = await import("./check-usage-
|
|
42
|
+
const { auth } = await import("./auth-BHa2OHXf.js");
|
|
43
|
+
const { checkUsage } = await import("./check-usage-BdXGp1Wr.js");
|
|
44
44
|
const { debug } = await import("./debug-C_TBkyUw.js");
|
|
45
|
-
const { start } = await import("./start-
|
|
45
|
+
const { start } = await import("./start-BVraN8xz.js");
|
|
46
46
|
await runMain(defineCommand({
|
|
47
47
|
meta: {
|
|
48
48
|
name: "copilot-api",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as PATHS } from "./paths-DC-mqCY3.js";
|
|
2
2
|
import consola from "consola";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
5
6
|
import { getProxyForUrl } from "proxy-from-env";
|
|
@@ -32,6 +33,7 @@ You interact with the user through a terminal. You have 2 ways of communicating
|
|
|
32
33
|
const defaultConfig = {
|
|
33
34
|
auth: { apiKeys: [] },
|
|
34
35
|
providers: {},
|
|
36
|
+
modelMappings: {},
|
|
35
37
|
extraPrompts: {
|
|
36
38
|
"gpt-5-mini": gpt5ExplorationPrompt,
|
|
37
39
|
"gpt-5.3-codex": gpt5CommentaryPrompt,
|
|
@@ -54,6 +56,24 @@ const defaultConfig = {
|
|
|
54
56
|
useResponsesApiWebSearch: true
|
|
55
57
|
};
|
|
56
58
|
let cachedConfig = null;
|
|
59
|
+
function normalizeAdminApiKey(adminApiKey) {
|
|
60
|
+
if (typeof adminApiKey !== "string") {
|
|
61
|
+
if (adminApiKey !== void 0) consola.warn("Invalid auth.adminApiKey config. Expected a non-empty string.");
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const normalizedAdminApiKey = adminApiKey.trim();
|
|
65
|
+
if (!normalizedAdminApiKey) {
|
|
66
|
+
consola.warn("Invalid auth.adminApiKey config. Expected a non-empty string.");
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return normalizedAdminApiKey;
|
|
70
|
+
}
|
|
71
|
+
function generateAdminApiKey() {
|
|
72
|
+
return randomBytes(32).toString("hex");
|
|
73
|
+
}
|
|
74
|
+
function isNodeError(error) {
|
|
75
|
+
return error instanceof Error && "code" in error;
|
|
76
|
+
}
|
|
57
77
|
function ensureConfigFile() {
|
|
58
78
|
try {
|
|
59
79
|
fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK | fs.constants.W_OK);
|
|
@@ -81,6 +101,21 @@ function readConfigFromDisk() {
|
|
|
81
101
|
return defaultConfig;
|
|
82
102
|
}
|
|
83
103
|
}
|
|
104
|
+
function readEditableConfigFromDisk() {
|
|
105
|
+
try {
|
|
106
|
+
const raw = fs.readFileSync(PATHS.CONFIG_PATH, "utf8");
|
|
107
|
+
if (!raw.trim()) return {};
|
|
108
|
+
return JSON.parse(raw);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (isNodeError(error) && error.code === "ENOENT") return {};
|
|
111
|
+
if (error instanceof SyntaxError) throw new Error(`Config file is not valid JSON: ${PATHS.CONFIG_PATH}`);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function writeConfigToDisk(config) {
|
|
116
|
+
fs.mkdirSync(PATHS.APP_DIR, { recursive: true });
|
|
117
|
+
fs.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
118
|
+
}
|
|
84
119
|
function mergeDefaultConfig(config) {
|
|
85
120
|
const extraPrompts = config.extraPrompts ?? {};
|
|
86
121
|
const defaultExtraPrompts = defaultConfig.extraPrompts ?? {};
|
|
@@ -109,23 +144,88 @@ function mergeDefaultConfig(config) {
|
|
|
109
144
|
changed: true
|
|
110
145
|
};
|
|
111
146
|
}
|
|
147
|
+
function ensureAdminApiKey(config) {
|
|
148
|
+
const normalizedAdminApiKey = normalizeAdminApiKey(config.auth?.adminApiKey);
|
|
149
|
+
if (normalizedAdminApiKey) {
|
|
150
|
+
if (config.auth?.adminApiKey === normalizedAdminApiKey) return {
|
|
151
|
+
mergedConfig: config,
|
|
152
|
+
changed: false
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
mergedConfig: {
|
|
156
|
+
...config,
|
|
157
|
+
auth: {
|
|
158
|
+
...config.auth ?? {},
|
|
159
|
+
adminApiKey: normalizedAdminApiKey
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
changed: true
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const editableConfig = readEditableConfigFromDisk();
|
|
166
|
+
const { mergedConfig } = mergeDefaultConfig({
|
|
167
|
+
...editableConfig,
|
|
168
|
+
auth: {
|
|
169
|
+
...editableConfig.auth ?? {},
|
|
170
|
+
adminApiKey: generateAdminApiKey()
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
mergedConfig,
|
|
175
|
+
changed: true
|
|
176
|
+
};
|
|
177
|
+
}
|
|
112
178
|
function mergeConfigWithDefaults() {
|
|
113
179
|
const { mergedConfig, changed } = mergeDefaultConfig(readConfigFromDisk());
|
|
114
|
-
|
|
115
|
-
|
|
180
|
+
const { mergedConfig: mergedConfigWithAdminApiKey, changed: adminApiKeyChanged } = ensureAdminApiKey(mergedConfig);
|
|
181
|
+
if (changed || adminApiKeyChanged) try {
|
|
182
|
+
writeConfigToDisk(mergedConfigWithAdminApiKey);
|
|
116
183
|
} catch (writeError) {
|
|
184
|
+
if (adminApiKeyChanged) throw writeError;
|
|
117
185
|
consola.warn("Failed to write merged extraPrompts to config file", writeError);
|
|
118
186
|
}
|
|
119
|
-
cachedConfig =
|
|
120
|
-
return
|
|
187
|
+
cachedConfig = mergedConfigWithAdminApiKey;
|
|
188
|
+
return mergedConfigWithAdminApiKey;
|
|
121
189
|
}
|
|
122
190
|
function getConfig() {
|
|
123
191
|
cachedConfig ??= mergeDefaultConfig(readConfigFromDisk()).mergedConfig;
|
|
124
192
|
return cachedConfig;
|
|
125
193
|
}
|
|
194
|
+
function reloadConfig() {
|
|
195
|
+
return mergeConfigWithDefaults();
|
|
196
|
+
}
|
|
126
197
|
function getExtraPromptForModel(model) {
|
|
127
198
|
return getConfig().extraPrompts?.[model] ?? "";
|
|
128
199
|
}
|
|
200
|
+
function getModelMappings() {
|
|
201
|
+
const modelMappings = getConfig().modelMappings;
|
|
202
|
+
if (!modelMappings) return { ...defaultConfig.modelMappings ?? {} };
|
|
203
|
+
const validMappings = {};
|
|
204
|
+
for (const [sourceModel, targetModel] of Object.entries(modelMappings)) {
|
|
205
|
+
if (!sourceModel || typeof targetModel !== "string" || targetModel.length === 0) continue;
|
|
206
|
+
validMappings[sourceModel] = targetModel;
|
|
207
|
+
}
|
|
208
|
+
return validMappings;
|
|
209
|
+
}
|
|
210
|
+
function validateModelMappings(modelMappings) {
|
|
211
|
+
const validatedMappings = {};
|
|
212
|
+
for (const [sourceModel, targetModel] of Object.entries(modelMappings)) {
|
|
213
|
+
if (!sourceModel || !targetModel) throw new Error("Each model mapping must use non-empty source and target values.");
|
|
214
|
+
validatedMappings[sourceModel] = targetModel;
|
|
215
|
+
}
|
|
216
|
+
return validatedMappings;
|
|
217
|
+
}
|
|
218
|
+
function setModelMappings(modelMappings) {
|
|
219
|
+
writeConfigToDisk({
|
|
220
|
+
...readEditableConfigFromDisk(),
|
|
221
|
+
modelMappings: validateModelMappings(modelMappings)
|
|
222
|
+
});
|
|
223
|
+
cachedConfig = reloadConfig();
|
|
224
|
+
return getModelMappings();
|
|
225
|
+
}
|
|
226
|
+
function resolveMappedModel(model) {
|
|
227
|
+
return getModelMappings()[model] ?? model;
|
|
228
|
+
}
|
|
129
229
|
function getSmallModel() {
|
|
130
230
|
return getConfig().smallModel ?? "gpt-5-mini";
|
|
131
231
|
}
|
|
@@ -248,6 +348,6 @@ function initProxyFromEnv() {
|
|
|
248
348
|
}
|
|
249
349
|
}
|
|
250
350
|
//#endregion
|
|
251
|
-
export { getConfig as a,
|
|
351
|
+
export { setModelMappings as _, getConfig as a, getProviderConfig as c, isMessagesApiEnabled as d, isResponsesApiContextManagementModel as f, resolveMappedModel as g, mergeConfigWithDefaults as h, getClaudeTokenMultiplier as i, getReasoningEffortForModel as l, isResponsesApiWebSocketEnabled as m, initProxyFromEnv as n, getExtraPromptForModel as o, isResponsesApiWebSearchEnabled as p, getAnthropicApiKey as r, getModelMappings as s, getProxyEnvDispatcher as t, getSmallModel as u };
|
|
252
352
|
|
|
253
|
-
//# sourceMappingURL=proxy-
|
|
353
|
+
//# sourceMappingURL=proxy-DQLzdeq3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-DQLzdeq3.js","names":["get"],"sources":["../src/lib/config.ts","../src/lib/proxy.ts"],"sourcesContent":["import consola from \"consola\"\nimport { randomBytes } from \"node:crypto\"\nimport fs from \"node:fs\"\n\nimport { PATHS } from \"./paths\"\n\nexport interface AppConfig {\n auth?: {\n apiKeys?: Array<string>\n adminApiKey?: string\n }\n providers?: Record<string, ProviderConfig>\n modelMappings?: Record<string, string>\n extraPrompts?: Record<string, string>\n smallModel?: string\n responsesApiContextManagementModels?: Array<string>\n modelReasoningEfforts?: Record<\n string,\n \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"\n >\n useFunctionApplyPatch?: boolean\n useMessagesApi?: boolean\n useResponsesApiWebSocket?: boolean\n anthropicApiKey?: string\n useResponsesApiWebSearch?: boolean\n claudeTokenMultiplier?: number\n}\n\nexport interface ModelConfig {\n temperature?: number\n topP?: number\n topK?: number\n extraBody?: Record<string, unknown>\n contextCache?: boolean\n supportPdf?: boolean\n toolContentSupportType?: Array<ToolContentSupportType>\n}\n\nexport type ProviderAuthType = \"authorization\" | \"x-api-key\"\nexport type ProviderType = \"anthropic\" | \"openai-compatible\"\nexport type ToolContentSupportType = \"array\" | \"image\" | \"pdf\"\n\nexport interface ProviderConfig {\n type?: string\n enabled?: boolean\n baseUrl?: string\n apiKey?: string\n authType?: ProviderAuthType\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nexport interface ResolvedProviderConfig {\n name: string\n type: ProviderType\n baseUrl: string\n apiKey: string\n authType: ProviderAuthType\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nconst gpt5ExplorationPrompt = `## Exploration and reading files\n- **Think first.** Before any tool call, decide ALL files/resources you will need.\n- **Batch everything.** If you need multiple files (even from different places), read them together.\n- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.\n- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**\n- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`\n\nconst gpt5CommentaryPrompt = `# Working with the user\n\nYou interact with the user through a terminal. You have 2 ways of communicating with the users: \n- Share intermediary updates in \\`commentary\\` channel. \n- After you have completed all your work, send a message to the \\`final\\` channel. \n\n## Intermediary updates\n\n- Intermediary updates go to the \\`commentary\\` channel.\n- User updates are short updates while you are working, they are NOT final answers.\n- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.\n- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements (“Done —”, “Got it”, “Great question, ”) or framing phrases.\n- You provide user updates frequently, every 20s.\n- Before exploring or doing substantial work, you start with a user update acknowledging the request and explaining your first step. You should include your understanding of the user request and explain what you will do. Avoid commenting on the request or using starters such as \"Got it -\" or \"Understood -\" etc.\n- When exploring, e.g. searching, reading files, you provide user updates as you go, every 20s, explaining what context you are gathering and what you've learned. Vary your sentence structure when providing these updates to avoid sounding repetitive - in particular, don't start each sentence the same way.\n- After you have sufficient context, and the work is substantial, you provide a longer plan (this is the only user update that may be longer than 2 sentences and can contain formatting).\n- Before performing file edits of any kind, you provide updates explaining what edits you are making.\n- As you are thinking, you very frequently provide updates even if not taking any actions, informing the user of your progress. You interrupt your thinking and send multiple updates in a row if thinking for more than 100 words.\n- Tone of your updates MUST match your personality.`\n\nconst defaultConfig: AppConfig = {\n auth: {\n apiKeys: [],\n },\n providers: {},\n modelMappings: {},\n extraPrompts: {\n \"gpt-5-mini\": gpt5ExplorationPrompt,\n \"gpt-5.3-codex\": gpt5CommentaryPrompt,\n \"gpt-5.4-mini\": gpt5CommentaryPrompt,\n \"gpt-5.4\": gpt5CommentaryPrompt,\n \"gpt-5.5\": gpt5CommentaryPrompt,\n },\n smallModel: \"gpt-5-mini\",\n responsesApiContextManagementModels: [],\n modelReasoningEfforts: {\n \"gpt-5-mini\": \"low\",\n \"gpt-5.3-codex\": \"xhigh\",\n \"gpt-5.4-mini\": \"xhigh\",\n \"gpt-5.4\": \"xhigh\",\n \"gpt-5.5\": \"xhigh\",\n },\n useFunctionApplyPatch: true,\n useMessagesApi: true,\n useResponsesApiWebSocket: true,\n useResponsesApiWebSearch: true,\n}\n\nlet cachedConfig: AppConfig | null = null\n\nfunction normalizeAdminApiKey(adminApiKey: unknown): string | null {\n if (typeof adminApiKey !== \"string\") {\n if (adminApiKey !== undefined) {\n consola.warn(\n \"Invalid auth.adminApiKey config. Expected a non-empty string.\",\n )\n }\n return null\n }\n\n const normalizedAdminApiKey = adminApiKey.trim()\n if (!normalizedAdminApiKey) {\n consola.warn(\n \"Invalid auth.adminApiKey config. Expected a non-empty string.\",\n )\n return null\n }\n\n return normalizedAdminApiKey\n}\n\nfunction generateAdminApiKey(): string {\n return randomBytes(32).toString(\"hex\")\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error\n}\n\nfunction ensureConfigFile(): void {\n try {\n fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK | fs.constants.W_OK)\n } catch {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n try {\n fs.chmodSync(PATHS.CONFIG_PATH, 0o600)\n } catch {\n return\n }\n }\n}\n\nfunction readConfigFromDisk(): AppConfig {\n ensureConfigFile()\n try {\n const raw = fs.readFileSync(PATHS.CONFIG_PATH, \"utf8\")\n if (!raw.trim()) {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n return defaultConfig\n }\n return JSON.parse(raw) as AppConfig\n } catch (error) {\n consola.error(\"Failed to read config file, using default config\", error)\n return defaultConfig\n }\n}\n\nfunction readEditableConfigFromDisk(): AppConfig {\n try {\n const raw = fs.readFileSync(PATHS.CONFIG_PATH, \"utf8\")\n if (!raw.trim()) {\n return {}\n }\n return JSON.parse(raw) as AppConfig\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return {}\n }\n if (error instanceof SyntaxError) {\n throw new Error(`Config file is not valid JSON: ${PATHS.CONFIG_PATH}`)\n }\n throw error\n }\n}\n\nfunction writeConfigToDisk(config: AppConfig): void {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(config, null, 2)}\\n`,\n \"utf8\",\n )\n}\n\nfunction mergeDefaultConfig(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const extraPrompts = config.extraPrompts ?? {}\n const defaultExtraPrompts = defaultConfig.extraPrompts ?? {}\n const modelReasoningEfforts = config.modelReasoningEfforts ?? {}\n const defaultModelReasoningEfforts = defaultConfig.modelReasoningEfforts ?? {}\n\n const missingExtraPromptModels = Object.keys(defaultExtraPrompts).filter(\n (model) => !Object.hasOwn(extraPrompts, model),\n )\n\n const missingReasoningEffortModels = Object.keys(\n defaultModelReasoningEfforts,\n ).filter((model) => !Object.hasOwn(modelReasoningEfforts, model))\n\n const hasExtraPromptChanges = missingExtraPromptModels.length > 0\n const hasReasoningEffortChanges = missingReasoningEffortModels.length > 0\n\n if (!hasExtraPromptChanges && !hasReasoningEffortChanges) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n extraPrompts: {\n ...defaultExtraPrompts,\n ...extraPrompts,\n },\n modelReasoningEfforts: {\n ...defaultModelReasoningEfforts,\n ...modelReasoningEfforts,\n },\n },\n changed: true,\n }\n}\n\nfunction ensureAdminApiKey(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const normalizedAdminApiKey = normalizeAdminApiKey(config.auth?.adminApiKey)\n if (normalizedAdminApiKey) {\n if (config.auth?.adminApiKey === normalizedAdminApiKey) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n auth: {\n ...(config.auth ?? {}),\n adminApiKey: normalizedAdminApiKey,\n },\n },\n changed: true,\n }\n }\n\n const editableConfig = readEditableConfigFromDisk()\n const { mergedConfig } = mergeDefaultConfig({\n ...editableConfig,\n auth: {\n ...(editableConfig.auth ?? {}),\n adminApiKey: generateAdminApiKey(),\n },\n })\n\n return { mergedConfig, changed: true }\n}\n\nexport function mergeConfigWithDefaults(): AppConfig {\n const config = readConfigFromDisk()\n const { mergedConfig, changed } = mergeDefaultConfig(config)\n const {\n mergedConfig: mergedConfigWithAdminApiKey,\n changed: adminApiKeyChanged,\n } = ensureAdminApiKey(mergedConfig)\n const shouldPersistConfig = changed || adminApiKeyChanged\n\n if (shouldPersistConfig) {\n try {\n writeConfigToDisk(mergedConfigWithAdminApiKey)\n } catch (writeError) {\n if (adminApiKeyChanged) {\n throw writeError\n }\n\n consola.warn(\n \"Failed to write merged extraPrompts to config file\",\n writeError,\n )\n }\n }\n\n cachedConfig = mergedConfigWithAdminApiKey\n return mergedConfigWithAdminApiKey\n}\n\nexport function getConfig(): AppConfig {\n cachedConfig ??= mergeDefaultConfig(readConfigFromDisk()).mergedConfig\n return cachedConfig\n}\n\nexport function reloadConfig(): AppConfig {\n return mergeConfigWithDefaults()\n}\n\nexport function getExtraPromptForModel(model: string): string {\n const config = getConfig()\n return config.extraPrompts?.[model] ?? \"\"\n}\n\nexport function getModelMappings(): Record<string, string> {\n const config = getConfig()\n const modelMappings = config.modelMappings\n if (!modelMappings) {\n return { ...(defaultConfig.modelMappings ?? {}) }\n }\n\n const validMappings: Record<string, string> = {}\n for (const [sourceModel, targetModel] of Object.entries(modelMappings)) {\n if (\n !sourceModel\n || typeof targetModel !== \"string\"\n || targetModel.length === 0\n ) {\n continue\n }\n validMappings[sourceModel] = targetModel\n }\n\n return validMappings\n}\n\nfunction validateModelMappings(\n modelMappings: Record<string, string>,\n): Record<string, string> {\n const validatedMappings: Record<string, string> = {}\n for (const [sourceModel, targetModel] of Object.entries(modelMappings)) {\n if (!sourceModel || !targetModel) {\n throw new Error(\n \"Each model mapping must use non-empty source and target values.\",\n )\n }\n validatedMappings[sourceModel] = targetModel\n }\n\n return validatedMappings\n}\n\nexport function setModelMappings(\n modelMappings: Record<string, string>,\n): Record<string, string> {\n const nextConfig = {\n ...readEditableConfigFromDisk(),\n modelMappings: validateModelMappings(modelMappings),\n }\n\n writeConfigToDisk(nextConfig)\n cachedConfig = reloadConfig()\n return getModelMappings()\n}\n\nexport function resolveMappedModel(model: string): string {\n return getModelMappings()[model] ?? model\n}\n\nexport function getSmallModel(): string {\n const config = getConfig()\n return config.smallModel ?? \"gpt-5-mini\"\n}\n\nexport function getResponsesApiContextManagementModels(): Array<string> {\n const config = getConfig()\n return (\n config.responsesApiContextManagementModels\n ?? defaultConfig.responsesApiContextManagementModels\n ?? []\n )\n}\n\nexport function isResponsesApiContextManagementModel(model: string): boolean {\n return getResponsesApiContextManagementModels().includes(model)\n}\n\nexport function getReasoningEffortForModel(\n model: string,\n): \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" {\n const config = getConfig()\n return config.modelReasoningEfforts?.[model] ?? \"high\"\n}\n\nexport function normalizeProviderBaseUrl(url: string): string {\n return url.trim().replace(/\\/+$/u, \"\")\n}\n\nfunction getDefaultProviderAuthType(\n providerType: ProviderType,\n): ProviderAuthType {\n return providerType === \"openai-compatible\" ? \"authorization\" : \"x-api-key\"\n}\n\nexport function resolveProviderAuthType(\n providerName: string,\n authType: string | undefined,\n providerType: ProviderType,\n): ProviderAuthType {\n if (authType === undefined) {\n return getDefaultProviderAuthType(providerType)\n }\n\n if (authType === \"x-api-key\") {\n return \"x-api-key\"\n }\n\n if (authType === \"authorization\") {\n return authType\n }\n\n consola.warn(\n `Provider ${providerName} has invalid authType '${authType}', falling back to ${getDefaultProviderAuthType(providerType)}`,\n )\n return getDefaultProviderAuthType(providerType)\n}\n\nexport function getProviderConfig(name: string): ResolvedProviderConfig | null {\n const providerName = name.trim()\n if (!providerName) {\n return null\n }\n\n const config = getConfig()\n const provider = config.providers?.[providerName]\n if (!provider) {\n return null\n }\n\n if (provider.enabled === false) {\n return null\n }\n\n const type = provider.type ?? \"anthropic\"\n if (type !== \"anthropic\" && type !== \"openai-compatible\") {\n consola.warn(\n `Provider ${providerName} is ignored because type '${type}' is not supported`,\n )\n return null\n }\n\n const baseUrl = normalizeProviderBaseUrl(provider.baseUrl ?? \"\")\n const apiKey = (provider.apiKey ?? \"\").trim()\n const authType = resolveProviderAuthType(\n providerName,\n provider.authType,\n type,\n )\n if (!baseUrl || !apiKey) {\n consola.warn(\n `Provider ${providerName} is enabled but missing baseUrl or apiKey`,\n )\n return null\n }\n\n return {\n name: providerName,\n type,\n baseUrl,\n apiKey,\n authType,\n models: provider.models,\n adjustInputTokens: provider.adjustInputTokens,\n }\n}\n\nexport function listEnabledProviders(): Array<string> {\n const config = getConfig()\n const providerNames = Object.keys(config.providers ?? {})\n return providerNames.filter((name) => getProviderConfig(name) !== null)\n}\n\nexport function isMessagesApiEnabled(): boolean {\n const config = getConfig()\n return config.useMessagesApi ?? true\n}\n\nexport function isResponsesApiWebSocketEnabled(): boolean {\n const config = getConfig()\n return config.useResponsesApiWebSocket ?? true\n}\n\nexport function getAnthropicApiKey(): string | undefined {\n const config = getConfig()\n return config.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? undefined\n}\n\nexport function isResponsesApiWebSearchEnabled(): boolean {\n const config = getConfig()\n return config.useResponsesApiWebSearch ?? true\n}\n\nexport function getClaudeTokenMultiplier(): number {\n const config = getConfig()\n return config.claudeTokenMultiplier ?? 1.15\n}\n","import consola from \"consola\"\nimport { getProxyForUrl } from \"proxy-from-env\"\nimport { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from \"undici\"\n\nlet proxyEnvDispatcher: Dispatcher | undefined\n\nexport function getProxyEnvDispatcher(): Dispatcher | undefined {\n return proxyEnvDispatcher\n}\n\nexport function initProxyFromEnv(): void {\n try {\n const direct = new Agent()\n const proxies = new Map<string, ProxyAgent>()\n\n // We only need a minimal dispatcher that implements `dispatch` at runtime.\n // Typing the object as `Dispatcher` forces TypeScript to require many\n // additional methods. Instead, keep a plain object and cast when passing\n // to `setGlobalDispatcher`.\n const dispatcher = {\n dispatch(\n options: Dispatcher.DispatchOptions,\n handler: Dispatcher.DispatchHandler,\n ) {\n try {\n const origin =\n typeof options.origin === \"string\" ?\n new URL(options.origin)\n : (options.origin as URL)\n const get = getProxyForUrl as unknown as (\n u: string,\n ) => string | undefined\n const raw = get(origin.toString())\n const proxyUrl = raw && raw.length > 0 ? raw : undefined\n if (!proxyUrl) {\n consola.debug(`HTTP proxy bypass: ${origin.hostname}`)\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n let agent = proxies.get(proxyUrl)\n if (!agent) {\n agent = new ProxyAgent(proxyUrl)\n proxies.set(proxyUrl, agent)\n }\n let label = proxyUrl\n try {\n const u = new URL(proxyUrl)\n label = `${u.protocol}//${u.host}`\n } catch {\n /* noop */\n }\n consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`)\n return (agent as unknown as Dispatcher).dispatch(options, handler)\n } catch {\n return (direct as unknown as Dispatcher).dispatch(options, handler)\n }\n },\n close() {\n return direct.close()\n },\n destroy() {\n return direct.destroy()\n },\n }\n\n proxyEnvDispatcher = dispatcher as unknown as Dispatcher\n\n if (typeof Bun !== \"undefined\") {\n consola.debug(\"WebSocket proxy configured from environment (per-URL)\")\n return\n }\n\n setGlobalDispatcher(proxyEnvDispatcher)\n consola.debug(\"HTTP proxy configured from environment (per-URL)\")\n } catch (err) {\n consola.debug(\"Proxy setup skipped:\", err)\n }\n}\n"],"mappings":"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"}
|