@nick3/copilot-api 1.2.4 → 1.3.1

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
@@ -23,7 +23,8 @@ English | [中文](./README_CN.md)
23
23
 
24
24
  ---
25
25
 
26
- **Note:** If you are using [opencode](https://github.com/sst/opencode), you do not need this project. Opencode supports GitHub Copilot provider out of the box.
26
+ > [!NOTE]
27
+ > [opencode](https://github.com/sst/opencode) already ships with a built-in GitHub Copilot provider, so you may not need this project for basic usage. This proxy is still useful if you want OpenCode to talk to Copilot through `@ai-sdk/anthropic`, preserve Anthropic Messages semantics for tool use, prefer the native Messages API over plain Chat Completions for Claude-family models, use `gpt-5.4` phase-aware commentary, or fine-tune premium-request usage with small-model fallbacks.
27
28
 
28
29
  ---
29
30
 
@@ -31,9 +32,17 @@ English | [中文](./README_CN.md)
31
32
 
32
33
  A reverse-engineered proxy for the GitHub Copilot API that exposes it as an OpenAI and Anthropic compatible service. This allows you to use GitHub Copilot with any tool that supports the OpenAI Chat Completions API or the Anthropic Messages API, including to power [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview).
33
34
 
35
+ Compared with routing everything through plain Chat Completions compatibility, this proxy can prefer Copilot's native Anthropic-style Messages API for Claude-family models, preserve more native thinking/tool semantics, reduce unnecessary Premium request consumption on warmup or resumed tool turns, and expose phase-aware `gpt-5.4` / `gpt-5.3-codex` responses that are easier for users to follow.
36
+
34
37
  ## Features
35
38
 
36
39
  - **OpenAI & Anthropic Compatibility**: Exposes GitHub Copilot as an OpenAI-compatible (`/v1/responses`, `/v1/chat/completions`, `/v1/models`, `/v1/embeddings`) and Anthropic-compatible (`/v1/messages`) API.
40
+ - **Anthropic-First Routing for Claude Models**: When a model supports Copilot's native `/v1/messages` endpoint, the proxy prefers it over `/responses` or `/chat/completions`, preserving Anthropic-style `tool_use` / `tool_result` flows and more Claude-native behavior.
41
+ - **Fewer Unnecessary Premium Requests**: Reduces wasted premium usage by routing warmup and compact/background requests to `smallModel`, merging `tool_result` follow-ups back into the tool flow, and treating resumed tool turns as continuation traffic instead of fresh premium interactions.
42
+ - **Phase-Aware `gpt-5.4` and `gpt-5.3-codex`**: These models can emit user-friendly commentary before deeper reasoning or tool use, so long-running coding actions are easier to understand instead of appearing as a sudden tool burst.
43
+ - **Claude Native Beta Support**: On the Messages API path, supports Anthropic-native capabilities such as `interleaved-thinking`, `advanced-tool-use`, and `context-management`, which are difficult or unavailable through plain Chat Completions compatibility.
44
+ - **Subagent Marker Integration**: Optional Claude Code and opencode plugins can inject `__SUBAGENT_MARKER__...` and propagate `x-session-id` so subagent traffic keeps the correct root session and agent/user semantics.
45
+ - **OpenCode via `@ai-sdk/anthropic`**: Point OpenCode at this proxy as an Anthropic provider so Anthropic Messages semantics, premium-request optimizations, and Claude-native behavior are preserved end to end.
37
46
  - **Claude Code Integration**: Easily configure and launch [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) to use Copilot as its backend with a simple command-line flag (`--claude-code`).
38
47
  - **Usage Dashboard**: A web-based dashboard to monitor your Copilot API usage, view quotas, and see detailed statistics.
39
48
  - **Admin UI**: Modern admin console (`/admin`) to inspect account runtime status and request history, with rich filtering and a request-detail JSON viewer (search/copy/download). Includes theme (system/light/dark) and motion (magic/subtle/off) toggles.
@@ -43,6 +52,49 @@ A reverse-engineered proxy for the GitHub Copilot API that exposes it as an Open
43
52
  - **Flexible Authentication**: Authenticate interactively or provide a GitHub token directly, suitable for CI/CD environments.
44
53
  - **Support for Different Account Types**: Works with individual, business, and enterprise GitHub Copilot plans.
45
54
  - **Multi-Account Support**: Use multiple GitHub Copilot accounts with automatic routing: premium models use accounts in order and fall back on quota exhaustion; free models are distributed round-robin across accounts by default (configurable in config.json).
55
+ - **Opencode OAuth Support**: Use opencode GitHub Copilot authentication by setting `COPILOT_API_OAUTH_APP=opencode` environment variable.
56
+ - **GitHub Enterprise Support**: Connect to GHE.com by setting `COPILOT_API_ENTERPRISE_URL` environment variable (e.g., `company.ghe.com`).
57
+ - **Custom Data Directory**: Change the default data directory (where tokens and config are stored) by setting `COPILOT_API_HOME` environment variable.
58
+ - **Multi-Provider Anthropic Proxy Routes**: Add global provider configs and call external Anthropic-compatible APIs via `/:provider/v1/messages` and `/:provider/v1/models`.
59
+
60
+ ## Better Agent Semantics
61
+
62
+ ### Native Anthropic Messages API when available
63
+
64
+ For models that advertise Copilot support for `/v1/messages`, this project sends the request to the native Messages API first and only falls back to `/responses` or `/chat/completions` when needed.
65
+
66
+ Compared with using Claude-family models only through Chat Completions compatibility, the Messages API path keeps more Anthropic-native behavior, including support for:
67
+
68
+ - `interleaved-thinking-2025-05-14`
69
+ - `advanced-tool-use-2025-11-20`
70
+ - `context-management-2025-06-27`
71
+
72
+ Supported `anthropic-beta` values are filtered and forwarded on the native Messages path, and `interleaved-thinking` is added automatically when a thinking budget is requested for non-adaptive extended thinking.
73
+
74
+ ### Fewer unnecessary Premium requests
75
+
76
+ The proxy includes request-accounting safeguards designed for tool-heavy coding workflows:
77
+
78
+ - tool-less warmup or probe requests can be forced onto `smallModel` so background checks do not spend premium usage;
79
+ - compact/background requests can be downgraded to `smallModel` automatically;
80
+ - mixed `tool_result` + reminder text blocks are merged back into the `tool_result` flow instead of being counted like fresh user turns;
81
+ - `x-initiator` is derived from the latest message or item, not stale assistant history.
82
+
83
+ This helps resumed tool turns continue the existing workflow instead of consuming an extra Premium request as a brand-new interaction.
84
+
85
+ ### Phase-aware `gpt-5.4` and `gpt-5.3-codex`
86
+
87
+ By default, the built-in `extraPrompts` for `gpt-5.4` and `gpt-5.3-codex` enable intermediary-update behavior, and the proxy translates assistant turns into `phase: "commentary"` before tool calls and `phase: "final_answer"` for the final response.
88
+
89
+ That gives clients a short, user-friendly explanation of what the model is about to do before deeper reasoning or tool execution begins.
90
+
91
+ ### Subagent marker integration
92
+
93
+ For subagent-based clients, this project can preserve root session context and correctly classify subagent-originated traffic.
94
+
95
+ The marker flow uses `__SUBAGENT_MARKER__...` inside a `<system-reminder>` block together with root `x-session-id` propagation. When a marker is detected, the proxy can keep the parent session identity, infer `x-initiator: agent`, and tag the interaction as subagent traffic instead of a fresh top-level request.
96
+
97
+ Optional marker producers are included for both Claude Code and opencode; see [Subagent Marker Integration](#subagent-marker-integration-optional) below for setup details.
46
98
 
47
99
  ## Demo
48
100
 
@@ -245,14 +297,32 @@ The `<target>` can be either the account ID (GitHub username) or a 1-based index
245
297
  "auth": {
246
298
  "apiKeys": []
247
299
  },
300
+ "providers": {
301
+ "custom": {
302
+ "type": "anthropic",
303
+ "enabled": true,
304
+ "baseUrl": "your-base-url",
305
+ "apiKey": "sk-your-provider-key",
306
+ "models": {
307
+ "kimi-k2.5": {
308
+ "temperature": 1,
309
+ "topP": 0.95
310
+ }
311
+ }
312
+ }
313
+ },
248
314
  "extraPrompts": {
249
315
  "gpt-5-mini": "<built-in exploration prompt>",
250
- "gpt-5.1-codex-max": "<built-in exploration prompt>"
251
- },
252
- "smallModel": "gpt-5-mini",
253
- "freeModelLoadBalancing": true,
254
- "modelReasoningEfforts": {
255
- "gpt-5-mini": "low"
316
+ "gpt-5.3-codex": "<built-in commentary prompt>",
317
+ "gpt-5.4": "<built-in commentary prompt>"
318
+ },
319
+ "smallModel": "gpt-5-mini",
320
+ "freeModelLoadBalancing": true,
321
+ "responsesApiContextManagementModels": [],
322
+ "modelReasoningEfforts": {
323
+ "gpt-5-mini": "low",
324
+ "gpt-5.3-codex": "xhigh",
325
+ "gpt-5.4": "xhigh"
256
326
  },
257
327
  "modelAliases": {
258
328
  "fast": { "target": "gpt-5-mini", "allowOriginal": false },
@@ -265,13 +335,22 @@ The `<target>` can be either the account ID (GitHub username) or a 1-based index
265
335
  }
266
336
  ```
267
337
  - **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.
268
- - **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.
269
- - **smallModel:** Fallback model used for tool-less warmup messages (e.g., Claude Code probe requests) to avoid spending premium requests; defaults to `gpt-5-mini`. If original names are blocked and this points to an aliased target, it resolves to the first alias.
270
- - **freeModelLoadBalancing:** Enable round-robin routing for free-model requests across multiple accounts. Defaults to `true`. Set to `false` to route free-model requests sequentially (same ordering strategy as premium models).
271
- - **apiKey (deprecated):** Legacy single-key field kept for migration compatibility. Prefer `auth.apiKeys`. When `auth.apiKeys` is empty, the server falls back to `COPILOT_API_KEY` and then `apiKey`.
272
- - **modelReasoningEfforts:** Per-model `reasoning.effort` sent to the Copilot Responses API. Allowed values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. If a model isn’t listed, `high` is used by default.
273
- - **modelAliases:** Map of `alias -> { target, allowOriginal? }` (legacy string values are still accepted). Alias keys are normalized (trim + lowercase) and must be non-empty; aliases cannot map to themselves (case-insensitive), and conflicting normalized aliases are rejected. `allowOriginal` overrides the global default per alias. If multiple aliases map to the same target, original names are allowed when any alias sets `allowOriginal: true` (allow-wins). Admin UI/API rejects blocked keys (`__proto__`, `constructor`, `prototype`). Aliases can be used in downstream requests.
274
- - **allowOriginalModelNamesForAliases:** Global default for aliases that omit `allowOriginal`. When `false` (default), targets are blocked unless an alias explicitly allows them; when `true`, targets are allowed unless all aliases explicitly block them.
338
+ - **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.
339
+ - **providers:** Global upstream provider map. Each provider key (for example `custom`) becomes a route prefix (`/custom/v1/messages`). Currently only `type: "anthropic"` is supported.
340
+ - `enabled` defaults to `true` if omitted.
341
+ - `baseUrl` should be provider API base URL without trailing `/v1/messages`.
342
+ - `apiKey` is used as upstream `x-api-key`.
343
+ - `models` (optional): Per-model configuration map. Each key is a model ID (matching the model name in requests), and the value is:
344
+ - `temperature` (optional): Default temperature value used when the request does not specify one.
345
+ - `topP` (optional): Default top_p value used when the request does not specify one.
346
+ - `topK` (optional): Default top_k value used when the request does not specify one.
347
+ - **responsesApiContextManagementModels:** List of model IDs that should receive Responses API `context_management` compaction instructions. Use this when a model supports server-side context management and you want the proxy to keep only the latest compaction carrier on follow-up turns.
348
+ - **smallModel:** Fallback model used for tool-less warmup messages, compact/background requests, and other short housekeeping turns (for example from Claude Code or OpenCode) to avoid spending premium requests; defaults to `gpt-5-mini`. If original names are blocked and this points to an aliased target, it resolves to the preferred alias.
349
+ - **freeModelLoadBalancing:** Enable round-robin routing for free-model requests across multiple accounts. Defaults to `true`. Set to `false` to route free-model requests sequentially (same ordering strategy as premium models).
350
+ - **apiKey (deprecated):** Legacy single-key field kept for migration compatibility. Prefer `auth.apiKeys`. When `auth.apiKeys` is empty, the server falls back to `COPILOT_API_KEY` and then `apiKey`.
351
+ - **modelReasoningEfforts:** Per-model `reasoning.effort` sent to the Copilot Responses API. Allowed values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. If a model isn’t listed, `high` is used by default.
352
+ - **modelAliases:** Map of `alias -> { target, allowOriginal? }` (legacy string values are still accepted). Alias keys are normalized (trim + lowercase) and must be non-empty; aliases cannot map to themselves (case-insensitive), and conflicting normalized aliases are rejected. `allowOriginal` overrides the global default per alias. If multiple aliases map to the same target, original names are allowed when any alias sets `allowOriginal: true` (allow-wins). Admin UI/API rejects blocked keys (`__proto__`, `constructor`, `prototype`). Aliases can be used in downstream requests.
353
+ - **allowOriginalModelNamesForAliases:** Global default for aliases that omit `allowOriginal`. When `false` (default), targets are blocked unless an alias explicitly allows them; when `true`, targets are allowed unless all aliases explicitly block them.
275
354
  - **useFunctionApplyPatch:** When `true` (default), `POST /v1/responses` converts a `tools` entry with `{ "type": "custom", "name": "apply_patch" }` into an OpenAI-style `function` tool (with a parameter schema) for upstream compatibility. Set to `false` to leave custom tools untouched.
276
355
  - **forceAgent:** When `true`, `POST /v1/responses` treats a request as agent-initiated if **any** input item has `role: "assistant"`. When `false` (default), only the **last** input item is checked.
277
356
  - **compactUseSmallModel:** When `true`, detected "compact" requests (e.g., from Claude Code or opencode compact mode) will automatically use the configured `smallModel` to avoid consuming premium usage for short/background tasks. Defaults to `true`.
@@ -319,6 +398,9 @@ These endpoints are designed to be compatible with the Anthropic Messages API.
319
398
  | -------------------------------- | ------ | ------------------------------------------------------------ |
320
399
  | `POST /v1/messages` | `POST` | Creates a model response for a given conversation. |
321
400
  | `POST /v1/messages/count_tokens` | `POST` | Calculates the number of tokens for a given set of messages. |
401
+ | `POST /:provider/v1/messages` | `POST` | Proxies Anthropic Messages API to the configured provider. |
402
+ | `GET /:provider/v1/models` | `GET` | Proxies Anthropic Models API to the configured provider. |
403
+ | `POST /:provider/v1/messages/count_tokens` | `POST` | Calculates tokens locally for provider route requests. |
322
404
 
323
405
  ### Usage Monitoring Endpoints
324
406
 
@@ -445,8 +527,122 @@ npx @nick3/copilot-api@latest debug --json
445
527
 
446
528
  # Initialize proxy from environment variables (HTTP_PROXY, HTTPS_PROXY, etc.)
447
529
  npx @nick3/copilot-api@latest start --proxy-env
530
+
531
+ # Use opencode GitHub Copilot authentication
532
+ COPILOT_API_OAUTH_APP=opencode npx @nick3/copilot-api@latest start
533
+ ```
534
+
535
+ ### Opencode OAuth Authentication
536
+
537
+ You can use opencode GitHub Copilot authentication instead of the default one:
538
+
539
+ ```sh
540
+ # Set environment variable before running any command
541
+ export COPILOT_API_OAUTH_APP=opencode
542
+
543
+ # Then run start or auth commands
544
+ npx @nick3/copilot-api@latest start
545
+ npx @nick3/copilot-api@latest auth
546
+ ```
547
+
548
+ Or use inline environment variable:
549
+
550
+ ```sh
551
+ COPILOT_API_OAUTH_APP=opencode npx @nick3/copilot-api@latest start
552
+ ```
553
+
554
+ ## Using with OpenCode
555
+
556
+ OpenCode already has a direct GitHub Copilot provider. Use this section when you want OpenCode to point at this proxy through `@ai-sdk/anthropic` and reuse the agent behaviors described earlier in this README.
557
+
558
+ ### Minimal setup
559
+
560
+ Start the proxy with the OpenCode OAuth app:
561
+
562
+ ```sh
563
+ COPILOT_API_OAUTH_APP=opencode npx @nick3/copilot-api@latest start
448
564
  ```
449
565
 
566
+ Then point OpenCode at the proxy with `@ai-sdk/anthropic`.
567
+
568
+ Example `~/.config/opencode/opencode.json`:
569
+
570
+ ```json
571
+ {
572
+ "$schema": "https://opencode.ai/config.json",
573
+ "model": "local/gpt-5.4",
574
+ "small_model": "local/gpt-5-mini",
575
+ "agent": {
576
+ "build": {
577
+ "model": "local/gpt-5.4"
578
+ },
579
+ "plan": {
580
+ "model": "local/gpt-5.4"
581
+ },
582
+ "explore": {
583
+ "model": "local/gpt-5-mini"
584
+ }
585
+ },
586
+ "provider": {
587
+ "local": {
588
+ "npm": "@ai-sdk/anthropic",
589
+ "name": "Copilot API Proxy",
590
+ "options": {
591
+ "baseURL": "http://localhost:4141/v1",
592
+ "apiKey": "dummy"
593
+ },
594
+ "models": {
595
+ "gpt-5.4": {
596
+ "name": "gpt-5.4",
597
+ "modalities": {
598
+ "input": ["text", "image"],
599
+ "output": ["text"]
600
+ },
601
+ "limit": {
602
+ "context": 272000,
603
+ "output": 128000
604
+ }
605
+ },
606
+ "gpt-5-mini": {
607
+ "name": "gpt-5-mini",
608
+ "limit": {
609
+ "context": 128000,
610
+ "output": 64000
611
+ }
612
+ },
613
+ "claude-sonnet-4.6": {
614
+ "id": "claude-sonnet-4.6",
615
+ "name": "claude-sonnet-4.6",
616
+ "modalities": {
617
+ "input": ["text", "image"],
618
+ "output": ["text"]
619
+ },
620
+ "limit": {
621
+ "context": 128000,
622
+ "output": 32000
623
+ },
624
+ "options": {
625
+ "thinking": {
626
+ "type": "enabled",
627
+ "budgetTokens": 31999
628
+ }
629
+ }
630
+ }
631
+ }
632
+ }
633
+ }
634
+ }
635
+ ```
636
+
637
+ Why these fields matter:
638
+
639
+ - `npm: "@ai-sdk/anthropic"` is the important part. OpenCode will speak Anthropic Messages semantics to this proxy instead of flattening everything into OpenAI Chat Completions.
640
+ - `options.baseURL` should be `http://localhost:4141/v1`; the Anthropic SDK will append `/messages`, `/models`, and `/messages/count_tokens` automatically.
641
+ - `model`, `small_model`, and `agent.*.model` let you keep `gpt-5.4` for build/plan work while routing exploration and background work to `gpt-5-mini`.
642
+ - If you enable `auth.apiKeys` in this proxy, replace `dummy` with a real key. Otherwise any placeholder value is fine.
643
+
644
+ ## Using the Usage Viewer
645
+
450
646
  ### Admin API examples
451
647
 
452
648
  ```sh
@@ -544,8 +740,8 @@ Here is an example `.claude/settings.json` file:
544
740
  "env": {
545
741
  "ANTHROPIC_BASE_URL": "http://localhost:4141",
546
742
  "ANTHROPIC_AUTH_TOKEN": "dummy",
547
- "ANTHROPIC_MODEL": "gpt-5.2",
548
- "ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-5.2",
743
+ "ANTHROPIC_MODEL": "gpt-5.4",
744
+ "ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-5.4",
549
745
  "ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-5-mini",
550
746
  "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
551
747
  "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
@@ -561,20 +757,20 @@ Here is an example `.claude/settings.json` file:
561
757
  }
562
758
  ```
563
759
 
564
- ### CLAUDE.md Recommended Content
760
+ ### CLAUDE.md / AGENTS.md (Optional)
565
761
 
566
- Please include the following in `CLAUDE.md` (for Claude usage):
762
+ If you're using Claude Code (or another agent runner) and want stricter “ask before acting” behavior, you may choose to add rules like:
567
763
 
568
- - Prohibited from directly asking questions to users, MUST use AskUserQuestion tool.
569
- - Once you can confirm that the task is complete, MUST use AskUserQuestion tool to make user confirm. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
764
+ - Prefer using the runner's dedicated question tool for user-facing questions.
765
+ - Ask the user to confirm completion before ending the task.
570
766
 
571
767
  You can find more options here: [Claude Code settings](https://docs.anthropic.com/en/docs/claude-code/settings#environment-variables)
572
768
 
573
769
  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)
574
770
 
575
- ### Subagent Marker Integration (Optional)
771
+ ## Subagent Marker Integration (Optional)
576
772
 
577
- This project supports `x-initiator: agent` for subagent-originated requests.
773
+ This project supports `x-initiator: agent` for subagent-originated requests and can preserve the root session identity with `x-session-id` when a subagent marker is present.
578
774
 
579
775
  #### Claude Code plugin producer (marketplace-based)
580
776
 
@@ -7,9 +7,11 @@ import { createHash, randomUUID } from "node:crypto";
7
7
  import fs$1 from "node:fs";
8
8
 
9
9
  //#region src/lib/paths.ts
10
+ const AUTH_APP = process.env.COPILOT_API_OAUTH_APP?.trim() || "";
11
+ const ENTERPRISE_PREFIX = process.env.COPILOT_API_ENTERPRISE_URL ? "ent_" : "";
10
12
  const DEFAULT_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
11
13
  const APP_DIR = process.env.COPILOT_API_HOME || DEFAULT_DIR;
12
- const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
14
+ const GITHUB_TOKEN_PATH = path.join(APP_DIR, AUTH_APP, ENTERPRISE_PREFIX + "github_token");
13
15
  const CONFIG_PATH = path.join(APP_DIR, "config.json");
14
16
  const MODELS_PATH = path.join(APP_DIR, "models.json");
15
17
  const TOKENS_DIR = path.join(APP_DIR, "tokens");
@@ -32,6 +34,7 @@ function accountTokenPath(id) {
32
34
  }
33
35
  async function ensurePaths() {
34
36
  await fs.mkdir(PATHS.APP_DIR, { recursive: true });
37
+ await fs.mkdir(path.join(PATHS.APP_DIR, AUTH_APP), { recursive: true });
35
38
  await fs.mkdir(PATHS.TOKENS_DIR, { recursive: true });
36
39
  await ensureFile(PATHS.GITHUB_TOKEN_PATH);
37
40
  await ensureFile(PATHS.CONFIG_PATH);
@@ -245,6 +248,59 @@ function accountFromState() {
245
248
 
246
249
  //#endregion
247
250
  //#region src/lib/api-config.ts
251
+ const isOpencodeOauthApp = () => {
252
+ return process.env.COPILOT_API_OAUTH_APP?.trim() === "opencode";
253
+ };
254
+ const normalizeDomain = (input) => {
255
+ return input.trim().replace(/^https?:\/\//u, "").replace(/\/+$/u, "");
256
+ };
257
+ const getEnterpriseDomain = () => {
258
+ const raw = (process.env.COPILOT_API_ENTERPRISE_URL ?? "").trim();
259
+ if (!raw) return null;
260
+ return normalizeDomain(raw) || null;
261
+ };
262
+ const getGitHubBaseUrl = () => {
263
+ const resolvedDomain = getEnterpriseDomain();
264
+ return resolvedDomain ? `https://${resolvedDomain}` : GITHUB_BASE_URL;
265
+ };
266
+ const getGitHubApiBaseUrl = () => {
267
+ const resolvedDomain = getEnterpriseDomain();
268
+ return resolvedDomain ? `https://${resolvedDomain}/api/v3` : GITHUB_API_BASE_URL;
269
+ };
270
+ const getOpencodeOauthHeaders = () => {
271
+ return {
272
+ Accept: "application/json",
273
+ "Content-Type": "application/json",
274
+ "User-Agent": "opencode/1.2.16 ai-sdk/provider-utils/3.0.21 runtime/bun/1.3.10, opencode/1.2.16"
275
+ };
276
+ };
277
+ const getOauthUrls = () => {
278
+ const githubBaseUrl = getGitHubBaseUrl();
279
+ return {
280
+ deviceCodeUrl: `${githubBaseUrl}/login/device/code`,
281
+ accessTokenUrl: `${githubBaseUrl}/login/oauth/access_token`
282
+ };
283
+ };
284
+ const getOauthAppConfig = () => {
285
+ if (isOpencodeOauthApp()) return {
286
+ clientId: OPENCODE_GITHUB_CLIENT_ID,
287
+ headers: getOpencodeOauthHeaders(),
288
+ scope: GITHUB_APP_SCOPES
289
+ };
290
+ return {
291
+ clientId: GITHUB_CLIENT_ID,
292
+ headers: standardHeaders(),
293
+ scope: GITHUB_APP_SCOPES
294
+ };
295
+ };
296
+ const prepareInteractionHeaders = (sessionId, isSubagent, headers) => {
297
+ const sendInteractionHeaders = !isOpencodeOauthApp();
298
+ if (isSubagent) {
299
+ headers["x-initiator"] = "agent";
300
+ if (sendInteractionHeaders) headers["x-interaction-type"] = "conversation-subagent";
301
+ }
302
+ if (sessionId && sendInteractionHeaders) headers["x-interaction-id"] = sessionId;
303
+ };
248
304
  const standardHeaders = () => ({
249
305
  "content-type": "application/json",
250
306
  accept: "application/json"
@@ -253,8 +309,21 @@ const COPILOT_VERSION = "0.38.2";
253
309
  const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
254
310
  const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
255
311
  const API_VERSION = "2025-10-01";
256
- const copilotBaseUrl = (account) => account.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${account.accountType}.githubcopilot.com`;
312
+ const copilotBaseUrl = (account) => {
313
+ const enterpriseDomain = getEnterpriseDomain();
314
+ if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;
315
+ return account.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${account.accountType}.githubcopilot.com`;
316
+ };
257
317
  const copilotHeaders = (account, vision = false, requestId) => {
318
+ if (isOpencodeOauthApp()) {
319
+ const headers$1 = {
320
+ Authorization: `Bearer ${account.copilotToken ?? account.githubToken}`,
321
+ ...getOpencodeOauthHeaders(),
322
+ "Openai-Intent": "conversation-edits"
323
+ };
324
+ if (vision) headers$1["Copilot-Vision-Request"] = "true";
325
+ return headers$1;
326
+ }
258
327
  const resolvedRequestId = requestId ?? randomUUID();
259
328
  const headers = {
260
329
  Authorization: `Bearer ${account.copilotToken}`,
@@ -288,6 +357,7 @@ const githubHeaders = (account) => ({
288
357
  const GITHUB_BASE_URL = "https://github.com";
289
358
  const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
290
359
  const GITHUB_APP_SCOPES = ["read:user"].join(" ");
360
+ const OPENCODE_GITHUB_CLIENT_ID = "Ov23li8tweQw6odWQebz";
291
361
 
292
362
  //#endregion
293
363
  //#region src/lib/error.ts
@@ -324,7 +394,7 @@ async function forwardError(c, error) {
324
394
  //#region src/services/github/get-copilot-usage.ts
325
395
  const getCopilotUsage = async (account) => {
326
396
  const ctx = account ?? accountFromState();
327
- const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { headers: githubHeaders(ctx) });
397
+ const response = await fetch(`${getGitHubApiBaseUrl()}/copilot_internal/user`, { headers: githubHeaders(ctx) });
328
398
  if (!response.ok) throw new HTTPError("Failed to get Copilot usage", response);
329
399
  return await response.json();
330
400
  };
@@ -333,7 +403,8 @@ const getCopilotUsage = async (account) => {
333
403
  //#region src/services/github/get-user.ts
334
404
  async function getGitHubUser(account) {
335
405
  const token = account?.githubToken ?? state.githubToken;
336
- const response = await fetch(`${GITHUB_API_BASE_URL}/user`, { headers: {
406
+ if (!token) throw new Error("GitHub token not set");
407
+ const response = await fetch(`${getGitHubApiBaseUrl()}/user`, { headers: {
337
408
  authorization: `token ${token}`,
338
409
  ...standardHeaders()
339
410
  } });
@@ -476,7 +547,7 @@ const getUUID = (content) => {
476
547
  //#region src/services/github/get-copilot-token.ts
477
548
  const getCopilotToken = async (account) => {
478
549
  const ctx = account ?? accountFromState();
479
- const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, { headers: githubHeaders(ctx) });
550
+ const response = await fetch(`${getGitHubApiBaseUrl()}/copilot_internal/v2/token`, { headers: githubHeaders(ctx) });
480
551
  if (!response.ok) throw new HTTPError("Failed to get Copilot token", response);
481
552
  return await response.json();
482
553
  };
@@ -510,6 +581,7 @@ You interact with the user through a terminal. You have 2 ways of communicating
510
581
  - Tone of your updates MUST match your personality.`;
511
582
  const defaultConfig = {
512
583
  auth: { apiKeys: [] },
584
+ providers: {},
513
585
  extraPrompts: {
514
586
  "gpt-5-mini": gpt5ExplorationPrompt,
515
587
  "gpt-5.3-codex": gpt5CommentaryPrompt,
@@ -520,7 +592,8 @@ const defaultConfig = {
520
592
  responsesApiContextManagementModels: [],
521
593
  modelReasoningEfforts: {
522
594
  "gpt-5-mini": "low",
523
- "gpt-5.3-codex": "xhigh"
595
+ "gpt-5.3-codex": "xhigh",
596
+ "gpt-5.4": "xhigh"
524
597
  },
525
598
  allowOriginalModelNamesForAliases: false,
526
599
  useFunctionApplyPatch: true,
@@ -809,6 +882,34 @@ function isForceAgentEnabled() {
809
882
  function shouldCompactUseSmallModel() {
810
883
  return getConfig().compactUseSmallModel ?? true;
811
884
  }
885
+ function normalizeProviderBaseUrl(url) {
886
+ return url.trim().replace(/\/+$/u, "");
887
+ }
888
+ function getProviderConfig(name) {
889
+ const providerName = name.trim();
890
+ if (!providerName) return null;
891
+ const provider = getConfig().providers?.[providerName];
892
+ if (!provider) return null;
893
+ if (provider.enabled === false) return null;
894
+ const type = provider.type ?? "anthropic";
895
+ if (type !== "anthropic") {
896
+ consola.warn(`Provider ${providerName} is ignored because only anthropic type is supported`);
897
+ return null;
898
+ }
899
+ const baseUrl = normalizeProviderBaseUrl(provider.baseUrl ?? "");
900
+ const apiKey = (provider.apiKey ?? "").trim();
901
+ if (!baseUrl || !apiKey) {
902
+ consola.warn(`Provider ${providerName} is enabled but missing baseUrl or apiKey`);
903
+ return null;
904
+ }
905
+ return {
906
+ name: providerName,
907
+ type,
908
+ baseUrl,
909
+ apiKey,
910
+ models: provider.models
911
+ };
912
+ }
812
913
 
813
914
  //#endregion
814
915
  //#region src/lib/accounts-manager-auth.ts
@@ -1615,5 +1716,5 @@ var AccountsManager = class {
1615
1716
  const accountsManager = new AccountsManager();
1616
1717
 
1617
1718
  //#endregion
1618
- export { GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, accountFromState, accountsManager, addAccountToRegistry, cacheMacMachineId, cacheVSCodeVersion, cacheVsCodeSessionId, copilotBaseUrl, copilotHeaders, ensurePaths, forwardError, generateRequestIdFromPayload, getAliasTargetSet, getConfig, getCopilotUsage, getExtraPromptForModel, getGitHubUser, getModelAliases, getModelAliasesInfo, getModelRefreshIntervalMs, getReasoningEffortForModel, getRootSessionId, getSmallModel, getUUID, isForceAgentEnabled, isFreeModelLoadBalancingEnabled, isMessageStartInputTokensFallbackEnabled, isNullish, isResponsesApiContextManagementModel, listAccountsFromRegistry, loadAccountToken, mergeConfigWithDefaults, removeAccountFromRegistry, removeAccountToken, saveAccountToken, saveRegistry, shouldCompactUseSmallModel, sleep, standardHeaders, state };
1619
- //# sourceMappingURL=accounts-manager-eec8Wj3_.js.map
1719
+ export { HTTPError, PATHS, accountFromState, accountsManager, addAccountToRegistry, cacheMacMachineId, cacheVSCodeVersion, cacheVsCodeSessionId, copilotBaseUrl, copilotHeaders, ensurePaths, forwardError, generateRequestIdFromPayload, getAliasTargetSet, getConfig, getCopilotUsage, getExtraPromptForModel, getGitHubUser, getModelAliases, getModelAliasesInfo, getModelRefreshIntervalMs, getOauthAppConfig, getOauthUrls, getProviderConfig, getReasoningEffortForModel, getRootSessionId, getSmallModel, getUUID, isForceAgentEnabled, isFreeModelLoadBalancingEnabled, isMessageStartInputTokensFallbackEnabled, isNullish, isResponsesApiContextManagementModel, listAccountsFromRegistry, loadAccountToken, mergeConfigWithDefaults, prepareInteractionHeaders, removeAccountFromRegistry, removeAccountToken, saveAccountToken, saveRegistry, shouldCompactUseSmallModel, sleep, state };
1720
+ //# sourceMappingURL=accounts-manager-BeKvbv0T.js.map