@nick3/copilot-api 1.9.15 → 1.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +56 -7
  2. package/README.zh-CN.md +55 -6
  3. package/dist/{account-DjCbqJ2Q.js → account-COtMmvzU.js} +2 -2
  4. package/dist/{account-DjCbqJ2Q.js.map → account-COtMmvzU.js.map} +1 -1
  5. package/dist/admin/assets/{index-BRnD4-DB.js → index-DG4TRVMu.js} +36 -36
  6. package/dist/admin/index.html +1 -1
  7. package/dist/{auth--I1utaB6.js → auth-B0y-2njL.js} +3 -3
  8. package/dist/{auth--I1utaB6.js.map → auth-B0y-2njL.js.map} +1 -1
  9. package/dist/{check-usage-DHvjdha4.js → check-usage-DdevqHE5.js} +3 -3
  10. package/dist/{check-usage-DHvjdha4.js.map → check-usage-DdevqHE5.js.map} +1 -1
  11. package/dist/{get-copilot-token-ZbmbVF0I.js → get-copilot-token-8Rm-rVsp.js} +2 -2
  12. package/dist/{get-copilot-token-ZbmbVF0I.js.map → get-copilot-token-8Rm-rVsp.js.map} +1 -1
  13. package/dist/main.js +6 -4
  14. package/dist/main.js.map +1 -1
  15. package/dist/mcp-9Hgepkc5.js +37 -0
  16. package/dist/mcp-9Hgepkc5.js.map +1 -0
  17. package/dist/{poll-access-token-CIPDXrcm.js → poll-access-token-BAgM2-7k.js} +62 -6
  18. package/dist/poll-access-token-BAgM2-7k.js.map +1 -0
  19. package/dist/{quota-refresh-scheduler-runtime-XD2fDa2K.js → proxy-YVh74m0I.js} +67 -8
  20. package/dist/proxy-YVh74m0I.js.map +1 -0
  21. package/dist/{request-outbound-Cy6huWjK.js → request-outbound-BJjWS_jF.js} +1 -1
  22. package/dist/{request-outbound-CxvpSkOn.js → request-outbound-Pu1kp2x8.js} +3 -1
  23. package/dist/request-outbound-Pu1kp2x8.js.map +1 -0
  24. package/dist/{server-BDCnb3Ao.js → server-DmDAepfa.js} +667 -77
  25. package/dist/server-DmDAepfa.js.map +1 -0
  26. package/dist/{start-DkBnp9d8.js → start-D37Bi12h.js} +5 -52
  27. package/dist/start-D37Bi12h.js.map +1 -0
  28. package/dist/tool-search-BrN7M0Dd.js +110 -0
  29. package/dist/tool-search-BrN7M0Dd.js.map +1 -0
  30. package/package.json +3 -6
  31. package/dist/poll-access-token-CIPDXrcm.js.map +0 -1
  32. package/dist/quota-refresh-scheduler-runtime-XD2fDa2K.js.map +0 -1
  33. package/dist/request-outbound-CxvpSkOn.js.map +0 -1
  34. package/dist/server-BDCnb3Ao.js.map +0 -1
  35. package/dist/start-DkBnp9d8.js.map +0 -1
package/README.md CHANGED
@@ -361,6 +361,7 @@ The `<target>` can be either the account ID (GitHub login) or a 1-based index.
361
361
  "modelRefreshIntervalHours": 24,
362
362
  "sessionAffinityRetentionDays": 7,
363
363
  "useMessagesApi": true,
364
+ "useResponsesApiWebSocket": true,
364
365
  "useResponsesApiWebSearch": true,
365
366
  "logLevel": "info"
366
367
  }
@@ -443,7 +444,7 @@ The `<target>` can be either the account ID (GitHub login) or a 1-based index.
443
444
  - **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.
444
445
  - **accountAffinity:** Enable sticky account routing based on session identity. When enabled, requests from the same session for the same model are routed to the account that last handled them successfully. Applies to both free and premium models. Defaults to `true`. Set to `false` to use sequential routing for all models.
445
446
  - **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`.- **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.
446
- - **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.
447
+ - **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, and targets may be configured `provider/model` aliases for top-level `/v1/messages` and `/v1/messages/count_tokens` routing.
447
448
  - **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.
448
449
  - **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.
449
450
  - **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.
@@ -452,6 +453,7 @@ The `<target>` can be either the account ID (GitHub login) or a 1-based index.
452
453
  - **modelRefreshIntervalHours:** Interval for refreshing account model lists in the background. Set to `0` to disable refresh. Defaults to `24`.
453
454
  - **sessionAffinityRetentionDays:** Number of days to retain session affinity bindings. Defaults to `7`.
454
455
  - **useMessagesApi:** When `true` (default), Claude-family models that support Copilot's native `/v1/messages` endpoint may use the Messages API path. Set to `false` to skip the Messages API candidate and fall back to `/responses` (if supported) or `/chat/completions`.
456
+ - **useResponsesApiWebSocket:** When `true` (default), Responses API requests use Copilot's WebSocket transport for models that advertise `ws:/responses`; models that only advertise `/responses` continue to use HTTP. Set to `false` to disable WebSocket routing.
455
457
  - **useResponsesApiWebSearch:** When `true` (default), `/v1/responses` keeps tools with `type: "web_search"` and forwards them upstream. Set to `false` to strip them before the Copilot request is sent.
456
458
  - **logLevel:** Controls handler file-log verbosity under `logs/*.log`. Allowed values: `error`, `warn`, `info`, `debug`. Defaults to `info`. Set it to `debug` when you need payload- or stream-level diagnostics written into file logs.
457
459
  - **anthropicApiKey:** Optional Anthropic API key used for accurate Claude token counting (see [Accurate Claude Token Counting](#accurate-claude-token-counting) below). Can also be set via the `ANTHROPIC_API_KEY` environment variable. If not set, token counting falls back to GPT tokenizer estimation.
@@ -728,6 +730,47 @@ You can find more options here: [Claude Code settings](https://docs.anthropic.co
728
730
 
729
731
  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)
730
732
 
733
+ ## GPT Tool Search
734
+
735
+ For GPT Responses models such as `gpt-5.4+`, this proxy can expose Responses `tool_search` through a small MCP bridge. The same bridge can be used by Claude Code and opencode, as long as the client loads MCP servers and sends Anthropic Messages traffic through this proxy.
736
+
737
+ Do not set Claude Code's native `ENABLE_TOOL_SEARCH` for GPT models. That flag enables Claude Code's own client-side tool search mode, and it may stop forwarding deferred tool definitions. This proxy needs the full tool definitions so it can keep the small always-loaded tool set eager and translate every other tool into Responses deferred namespaces.
738
+
739
+ If you install `tool-search@copilot-api-marketplace`, Claude Code receives this MCP bridge automatically and you can skip the manual Claude Code MCP setup below.
740
+
741
+ Add the tool search bridge to the MCP config used by Claude Code:
742
+
743
+ ```json
744
+ {
745
+ "mcpServers": {
746
+ "tool_search": {
747
+ "type": "stdio",
748
+ "command": "npx",
749
+ "args": ["-y", "@nick3/copilot-api@latest", "mcp"]
750
+ }
751
+ }
752
+ }
753
+ ```
754
+
755
+ Add the tool search bridge to the MCP config used by opencode:
756
+
757
+ ```json
758
+ {
759
+ "mcp": {
760
+ "tool_search": {
761
+ "type": "local",
762
+ "command": ["npx", "-y", "@nick3/copilot-api@latest", "mcp"]
763
+ }
764
+ }
765
+ }
766
+ ```
767
+
768
+ For local development, use `bun` as the command and `["run", "./src/main.ts", "mcp"]` as the args.
769
+
770
+ Internally, the proxy now configures OpenAI Responses `tool_search` in client-executed mode. Deferred tools are still exposed as searchable namespaces, but the model is explicitly asked to return the exact deferred tool names it wants to load next.
771
+
772
+ The bridge uses direct tool selection, not query search. Its tool input is `names`, a comma-separated list of exact deferred tool names, for example `TaskList,TaskGet,mcp__fetch__fetch`.
773
+
731
774
  ## Using with OpenCode
732
775
 
733
776
  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.
@@ -889,10 +932,13 @@ Plugin integrations are available for Claude Code and opencode.
889
932
 
890
933
  #### Claude Code plugin integration (marketplace-based)
891
934
 
892
- The Claude Code integration is packaged as a plugin named `claude-plugin`.
935
+ The Claude Code integration is packaged as two plugins:
936
+
937
+ - `agent-inject` injects `__SUBAGENT_MARKER__...` on `SubagentStart`, so this proxy can infer `x-initiator: agent`.
938
+ - `tool-search` registers the `tool_search` MCP bridge used for GPT Responses deferred tool loading.
893
939
 
894
940
  - Marketplace catalog in this repository: `.claude-plugin/marketplace.json`
895
- - Plugin source in this repository: `claude-plugin`
941
+ - Plugin sources in this repository: `claude-plugin/agent-inject`, `claude-plugin/tool-search`
896
942
 
897
943
  Add the marketplace remotely:
898
944
 
@@ -900,19 +946,22 @@ Add the marketplace remotely:
900
946
  /plugin marketplace add https://github.com/nick3/copilot-api.git#all
901
947
  ```
902
948
 
903
- Install the plugin from the marketplace:
949
+ Install the plugins from the marketplace:
904
950
 
905
951
  ```sh
906
- /plugin install claude-plugin@copilot-api-marketplace
952
+ /plugin install agent-inject@copilot-api-marketplace
953
+ /plugin install tool-search@copilot-api-marketplace
907
954
  ```
908
955
 
909
- After installation, the plugin injects `__SUBAGENT_MARKER__...` on `SubagentStart`, and this proxy uses it to infer `x-initiator: agent`.
956
+ After installation, `agent-inject` injects `__SUBAGENT_MARKER__...` on `SubagentStart`, and this proxy uses it to infer `x-initiator: agent`.
910
957
 
911
- The plugin also registers a `UserPromptSubmit` hook that returns `{"continue": true}`, and it can inject `SessionStart` reminder rules through environment variables:
958
+ The `agent-inject` plugin also registers a `UserPromptSubmit` hook that returns `{"continue": true}`, and it can inject `SessionStart` reminder rules through environment variables:
912
959
 
913
960
  - `CLAUDE_PLUGIN_ENABLE_QUESTION_RULES=1` enables the two reminders about using the `question` tool automatically for Claude Code. Alternatively, you can add the same reminders manually in `CLAUDE.md`; see [CLAUDE.md or AGENTS.md Recommended Content](#claudemd-or-agentsmd-recommended-content).
914
961
  - `CLAUDE_PLUGIN_ENABLE_NO_BACKGROUND_AGENTS_RULE=1` enables the `run_in_background: true` avoidance reminder for agent hooks.
915
962
 
963
+ The `tool-search` plugin bundles the same MCP bridge described in [GPT Tool Search](#gpt-tool-search), so Claude Code users do not need to add the `tool_search` server manually when they install that plugin.
964
+
916
965
  #### Opencode plugin
917
966
 
918
967
  The subagent marker producer is packaged as an opencode plugin located at `.opencode/plugins/subagent-marker.js`.
package/README.zh-CN.md CHANGED
@@ -370,6 +370,7 @@ Copilot API 现在使用子命令结构,主要命令包括:
370
370
  "modelRefreshIntervalHours": 24,
371
371
  "sessionAffinityRetentionDays": 7,
372
372
  "useMessagesApi": true,
373
+ "useResponsesApiWebSocket": true,
373
374
  "useResponsesApiWebSearch": true,
374
375
  "logLevel": "info"
375
376
  }
@@ -452,7 +453,7 @@ Copilot API 现在使用子命令结构,主要命令包括:
452
453
  - **smallModel:** 用于无工具预热消息、compact/background 请求以及其他短小维护型轮次(例如 Claude Code 或 OpenCode 发出的 housekeeping 请求)的回退模型,用来避免消耗 premium requests;默认是 `gpt-5-mini`。如果原始模型名被屏蔽,而这里指向的是某个别名目标模型,则会解析为首选别名。
453
454
  - **accountAffinity:** 是否根据 session 标识启用粘性账号路由。开启后,同一 session 针对同一模型的请求会优先路由到上次成功处理它的账号。该策略同时适用于免费模型和付费模型。默认值为 `true`。设为 `false` 则所有模型都改为顺序路由。
454
455
  - **apiKey(已弃用):** 兼容迁移的旧单 key 字段。优先使用 `auth.apiKeys`。当 `auth.apiKeys` 为空时,服务端会回退到 `COPILOT_API_KEY`,再回退到 `apiKey`。- **modelReasoningEfforts:** 按模型配置发送到 Copilot Responses API 的 `reasoning.effort`。可选值包括 `none`、`minimal`、`low`、`medium`、`high` 和 `xhigh`。若某模型未配置,则默认使用 `high`。
455
- - **modelAliases:** `alias -> { target, allowOriginal? }` 的映射(也仍然接受旧的字符串写法)。别名 key 会先做标准化(trim + lowercase),且不能为空;别名不能映射回自己(大小写不敏感),冲突的标准化别名会被拒绝。`allowOriginal` 可为单个别名覆盖全局默认值。如果多个别名映射到同一个 target,只要其中任意一个设置了 `allowOriginal: true`,原始模型名就会被允许(allow-wins)。Admin UI/API 会拒绝被屏蔽的键(`__proto__`、`constructor`、`prototype`)。下游请求可以直接使用这些别名。
456
+ - **modelAliases:** `alias -> { target, allowOriginal? }` 的映射(也仍然接受旧的字符串写法)。别名 key 会先做标准化(trim + lowercase),且不能为空;别名不能映射回自己(大小写不敏感),冲突的标准化别名会被拒绝。`allowOriginal` 可为单个别名覆盖全局默认值。如果多个别名映射到同一个 target,只要其中任意一个设置了 `allowOriginal: true`,原始模型名就会被允许(allow-wins)。Admin UI/API 会拒绝被屏蔽的键(`__proto__`、`constructor`、`prototype`)。下游请求可以直接使用这些别名,target 也可以是 `provider/model` 形式,用于顶层 `/v1/messages` 与 `/v1/messages/count_tokens` 路由。
456
457
  - **allowOriginalModelNamesForAliases:** 对未显式设置 `allowOriginal` 的别名所采用的全局默认值。当其为 `false`(默认)时,target 原名默认被屏蔽,除非某个别名显式允许;当其为 `true` 时,target 原名默认可用,除非所有别名都显式阻止。
457
458
  - **useFunctionApplyPatch:** 当为 `true`(默认)时,`POST /v1/responses` 会把 `tools` 中形如 `{ "type": "custom", "name": "apply_patch" }` 的条目转换为 OpenAI 风格的 `function` 工具(带参数 schema),以便与上游更好兼容。设为 `false` 则保留自定义工具原样。
458
459
  - **forceAgent:** 当为 `true` 时,只要 `POST /v1/responses` 的任一 input item 带有 `role: "assistant"`,就会把请求视为由 agent 发起;当为 `false`(默认)时,只检查最后一个 input item。
@@ -461,6 +462,7 @@ Copilot API 现在使用子命令结构,主要命令包括:
461
462
  - **modelRefreshIntervalHours:** 后台刷新账号模型列表的间隔小时数。设为 `0` 可关闭自动刷新。默认值为 `24`。
462
463
  - **sessionAffinityRetentionDays:** session affinity 绑定的保留天数。默认值为 `7`。
463
464
  - **useMessagesApi:** 当为 `true`(默认)时,支持 Copilot 原生 `/v1/messages` 端点的 Claude 系模型会走 Messages API 路径。设为 `false` 时,将跳过 Messages API 候选,回退到 `/responses`(如支持)或 `/chat/completions`。
465
+ - **useResponsesApiWebSocket:** 当为 `true`(默认)时,Responses API 请求会对声明了 `ws:/responses` 的模型使用 Copilot WebSocket transport;仅声明 `/responses` 的模型仍走 HTTP。设为 `false` 可禁用 WebSocket 路由。
464
466
  - **useResponsesApiWebSearch:** 当为 `true`(默认)时,`/v1/responses` 会保留 `type: "web_search"` 的工具并转发到上游。设为 `false` 则会在发送 Copilot 请求之前将其剥离。
465
467
  - **logLevel:** 控制 `logs/*.log` 下 handler 文件日志的详细级别。可选值:`error`、`warn`、`info`、`debug`。默认值为 `info`。如果你需要把 payload 级或 stream 级的调试内容写入文件日志,请显式设置为 `debug`。
466
468
  - **anthropicApiKey:** 可选的 Anthropic API key,用于精确的 Claude token 计数(见下文 [精确的 Claude Token 计数](#accurate-claude-token-counting))。也可通过环境变量 `ANTHROPIC_API_KEY` 设置。未配置时会回退到 GPT tokenizer 估算。
@@ -740,6 +742,47 @@ npx @nick3/copilot-api@latest start --claude-code
740
742
 
741
743
  也可以参考 IDE 集成说明:[Add Claude Code to your IDE](https://docs.anthropic.com/en/docs/claude-code/ide-integrations)
742
744
 
745
+ ## GPT Tool Search
746
+
747
+ 对于 `gpt-5.4+` 这类 GPT Responses 模型,本代理可以通过一个很小的 MCP bridge 暴露 Responses `tool_search`。Claude Code 和 opencode 都可以使用同一个 bridge,前提是客户端会加载 MCP server,并且 Anthropic Messages 流量会经过本代理。
748
+
749
+ GPT 模型不要设置 Claude Code 原生的 `ENABLE_TOOL_SEARCH`。这个开关启用的是 Claude Code 自己的客户端 tool search 模式,可能导致 deferred 工具定义不再转发给代理。本代理需要完整的工具定义,这样才能只保留那一小组常驻加载工具,其余工具统一转换为 Responses deferred namespace。
750
+
751
+ 如果你安装了 `tool-search@copilot-api-marketplace`,Claude Code 会自动带上这个 MCP bridge,可以跳过下面这段 Claude Code MCP 手动配置。
752
+
753
+ 请把 tool search bridge 加到 Claude Code 使用的 MCP 配置中:
754
+
755
+ ```json
756
+ {
757
+ "mcpServers": {
758
+ "tool_search": {
759
+ "type": "stdio",
760
+ "command": "npx",
761
+ "args": ["-y", "@nick3/copilot-api@latest", "mcp"]
762
+ }
763
+ }
764
+ }
765
+ ```
766
+
767
+ 请把 tool search bridge 加到 opencode 使用的 MCP 配置中:
768
+
769
+ ```json
770
+ {
771
+ "mcp": {
772
+ "tool_search": {
773
+ "type": "local",
774
+ "command": ["npx", "-y", "@nick3/copilot-api@latest", "mcp"]
775
+ }
776
+ }
777
+ }
778
+ ```
779
+
780
+ 本地开发时可以将命令换成 `bun`,参数换成 `["run", "./src/main.ts", "mcp"]`。
781
+
782
+ 代理内部现在会把 OpenAI Responses `tool_search` 配置成 client-executed 模式。deferred tools 仍然会作为可搜索 namespace 暴露给模型,但会明确要求模型直接返回下一步要加载的精确工具名列表。
783
+
784
+ 该 bridge 使用直接工具选择,不做 query 搜索。工具入参是 `names`,值为逗号分隔的精确 deferred 工具名,例如 `TaskList,TaskGet,mcp__fetch__fetch`。
785
+
743
786
  ## 与 OpenCode 一起使用
744
787
 
745
788
  OpenCode 已经内置了 GitHub Copilot provider。本节适用于你希望让 OpenCode 通过 `@ai-sdk/anthropic` 指向这个代理,并复用本 README 前面提到的 agent 行为时。
@@ -903,10 +946,13 @@ UI 会把 token 保存在 `sessionStorage` 中,并通过 `x-admin-token` 请
903
946
 
904
947
  #### Claude Code 插件集成(基于 marketplace)
905
948
 
906
- Claude Code 集成被打包为名为 `claude-plugin` 的插件。
949
+ Claude Code 集成现在拆分为两个插件:
950
+
951
+ - `agent-inject` 会在 `SubagentStart` 时注入 `__SUBAGENT_MARKER__...`,以便本代理推导 `x-initiator: agent`。
952
+ - `tool-search` 会注册用于 GPT Responses deferred tool loading 的 `tool_search` MCP bridge。
907
953
 
908
954
  - 本仓库中的 marketplace catalog:`.claude-plugin/marketplace.json`
909
- - 本仓库中的插件源码:`claude-plugin`
955
+ - 本仓库中的插件源码:`claude-plugin/agent-inject`、`claude-plugin/tool-search`
910
956
 
911
957
  远程添加 marketplace:
912
958
 
@@ -917,16 +963,19 @@ Claude Code 集成被打包为名为 `claude-plugin` 的插件。
917
963
  从 marketplace 安装插件:
918
964
 
919
965
  ```sh
920
- /plugin install claude-plugin@copilot-api-marketplace
966
+ /plugin install agent-inject@copilot-api-marketplace
967
+ /plugin install tool-search@copilot-api-marketplace
921
968
  ```
922
969
 
923
- 安装后,插件会在 `SubagentStart` 时注入 `__SUBAGENT_MARKER__...`,该代理会利用它推导 `x-initiator: agent`。
970
+ 安装后,`agent-inject` 会在 `SubagentStart` 时注入 `__SUBAGENT_MARKER__...`,该代理会利用它推导 `x-initiator: agent`。
924
971
 
925
- 插件还会注册一个 `UserPromptSubmit` hook,并返回 `{"continue": true}`;同时它也可以通过环境变量注入 `SessionStart` reminder 规则:
972
+ `agent-inject` 还会注册一个 `UserPromptSubmit` hook,并返回 `{"continue": true}`;同时它也可以通过环境变量注入 `SessionStart` reminder 规则:
926
973
 
927
974
  - `CLAUDE_PLUGIN_ENABLE_QUESTION_RULES=1` 会自动为 Claude Code 启用两条关于使用 `question` 工具的提醒。你也可以把同样的提醒手动写进 `CLAUDE.md`;见 [CLAUDE.md 或 AGENTS.md 推荐内容](#claudemd-or-agentsmd-recommended-content)。
928
975
  - `CLAUDE_PLUGIN_ENABLE_NO_BACKGROUND_AGENTS_RULE=1` 会启用关于避免在 agent hooks 中使用 `run_in_background: true` 的提醒。
929
976
 
977
+ `tool-search` 插件内置了 [GPT Tool Search](#gpt-tool-search) 一节描述的同一个 MCP bridge,因此安装该插件后,Claude Code 用户无需再手动配置 `tool_search` server。
978
+
930
979
  #### Opencode 插件
931
980
 
932
981
  subagent marker 生成器被打包为一个 opencode 插件,位于 `.opencode/plugins/subagent-marker.js`。
@@ -1,4 +1,4 @@
1
- import { w as normalizeDomain } from "./poll-access-token-CIPDXrcm.js";
1
+ import { T as normalizeDomain } from "./poll-access-token-BAgM2-7k.js";
2
2
  import { n as accountTokenPath, t as PATHS } from "./paths-CclKwouX.js";
3
3
  import fs from "node:fs/promises";
4
4
  import { z } from "zod";
@@ -363,4 +363,4 @@ function parseAccountType(value) {
363
363
  //#endregion
364
364
  export { DEFAULT_IDENTITY_ENTERPRISE_DOMAIN as _, getAccountClientIdentityByLoginAndApp as a, getCurrentIdentityEnvironment as b, isAccountEnabled as c, loadRegistry as d, readLegacyToken as f, saveRegistry as g, saveAccountToken as h, ensureAccountClientIdentity as i, listAccountsFromRegistry as l, removeAccountToken as m, parseAccountType as n, hasLegacyToken as o, removeAccountFromRegistry as p, addAccountToRegistry as r, hasRegistry as s, isAccountType as t, loadAccountToken as u, buildIdentityKey as v, createAccountSessionId as y };
365
365
 
366
- //# sourceMappingURL=account-DjCbqJ2Q.js.map
366
+ //# sourceMappingURL=account-COtMmvzU.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"account-DjCbqJ2Q.js","names":[],"sources":["../src/lib/account-client-identity.ts","../src/lib/accounts-registry.ts","../src/lib/types/account.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\"\n\nimport { normalizeDomain } from \"./api-config\"\n\nexport const DEFAULT_IDENTITY_OAUTH_APP = \"default\"\nexport const DEFAULT_IDENTITY_ENTERPRISE_DOMAIN = \"public\"\n\nexport interface AccountIdentityEnvironment {\n oauthApp: string\n enterpriseDomain: string\n}\n\nexport const getCurrentIdentityEnvironment = (): AccountIdentityEnvironment => {\n const rawOauthApp = process.env.COPILOT_API_OAUTH_APP?.trim().toLowerCase()\n const rawEnterpriseDomain = process.env.COPILOT_API_ENTERPRISE_URL?.trim()\n\n return {\n oauthApp: rawOauthApp || DEFAULT_IDENTITY_OAUTH_APP,\n enterpriseDomain:\n rawEnterpriseDomain ?\n normalizeDomain(rawEnterpriseDomain).toLowerCase()\n : DEFAULT_IDENTITY_ENTERPRISE_DOMAIN,\n }\n}\n\nexport const buildIdentityKey = ({\n login,\n oauthApp,\n enterpriseDomain,\n}: {\n login: string\n oauthApp: string\n enterpriseDomain: string\n}): string => `${enterpriseDomain}:${oauthApp}:${login}`\n\nexport const createAccountDeviceId = (): string => randomUUID().toLowerCase()\n\nexport const createAccountMachineId = (): string =>\n createHash(\"sha256\").update(randomUUID(), \"utf8\").digest(\"hex\")\n\nexport const createAccountSessionId = (): string =>\n randomUUID() + Date.now().toString()\n","import fs from \"node:fs/promises\"\nimport { z } from \"zod\"\n\nimport type {\n AccountClientIdentity,\n AccountMeta,\n AccountRegistry,\n} from \"~/lib/types/account\"\n\nimport {\n DEFAULT_IDENTITY_ENTERPRISE_DOMAIN,\n buildIdentityKey,\n createAccountDeviceId,\n createAccountMachineId,\n getCurrentIdentityEnvironment,\n} from \"~/lib/account-client-identity\"\nimport { accountTokenPath, PATHS } from \"~/lib/paths\"\n\n/**\n * Validate account ID (GitHub login).\n * Rules:\n * - 1-39 chars\n * - Alphanumeric segments may be separated by single hyphens or underscores\n * - Cannot begin or end with a separator\n * - No consecutive separators\n */\nexport function validateAccountId(id: string): boolean {\n if (id.length === 0 || id.length > 39) return false\n return /^[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*$/u.test(id)\n}\n\nfunction assertValidAccountId(id: string): void {\n if (!validateAccountId(id)) {\n throw new Error(`Invalid account ID: ${id}`)\n }\n}\n\nconst ACCOUNT_ID_VALIDATION_RULES =\n \"1-39 chars, alphanumeric with optional single hyphen/underscore separators, no leading/trailing separator, no consecutive separators.\"\n\nconst accountMetaSchema = z.object({\n id: z.string().refine(validateAccountId, {\n message: `Invalid account id. Expected a GitHub login (${ACCOUNT_ID_VALIDATION_RULES})`,\n }),\n accountType: z.enum([\"individual\", \"business\", \"enterprise\"]),\n addedAt: z.number(),\n enabled: z.boolean().optional(),\n})\n\n/**\n * Check whether an account is enabled for request routing.\n * Treats `undefined` and `true` as enabled (backward compatible).\n */\nexport function isAccountEnabled(meta: AccountMeta): boolean {\n return meta.enabled !== false\n}\n\nconst accountClientIdentitySchema = z.object({\n login: z.string().refine(validateAccountId, {\n message: `Invalid client identity login. Expected a GitHub login (${ACCOUNT_ID_VALIDATION_RULES})`,\n }),\n oauthApp: z.string().min(1),\n enterpriseDomain: z.string().min(1),\n deviceId: z\n .string()\n .regex(\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u,\n \"Invalid device ID format. Expected a lowercase UUID.\",\n ),\n machineId: z\n .string()\n .regex(\n /^[0-9a-f]{64}$/u,\n \"Invalid machine ID format. Expected 64 lowercase hexadecimal characters.\",\n ),\n createdAt: z.number(),\n})\n\nconst accountRegistryV1Schema = z.object({\n version: z.literal(1),\n accounts: z.array(accountMetaSchema),\n})\n\nconst accountRegistryV2Schema = z.object({\n version: z.literal(2),\n accounts: z.array(accountMetaSchema),\n clientIdentities: z.record(z.string(), accountClientIdentitySchema),\n})\n\nconst identityLocks = new Map<string, Promise<AccountClientIdentity>>()\nlet registryLock: Promise<void> = Promise.resolve()\n\nconst runWithRegistryLock = async <T>(\n operation: () => Promise<T>,\n): Promise<T> => {\n const previousLock = registryLock\n let releaseLock!: () => void\n registryLock = new Promise<void>((resolve) => {\n releaseLock = resolve\n })\n\n await previousLock\n\n try {\n return await operation()\n } finally {\n releaseLock()\n }\n}\n\n/**\n * Create an empty registry with the current schema version.\n */\nfunction createEmptyRegistry(): AccountRegistry {\n return {\n version: 2,\n accounts: [],\n clientIdentities: {},\n }\n}\n\nconst createClientIdentity = ({\n login,\n oauthApp,\n enterpriseDomain,\n}: {\n login: string\n oauthApp: string\n enterpriseDomain: string\n}): AccountClientIdentity => ({\n login,\n oauthApp,\n enterpriseDomain,\n deviceId: createAccountDeviceId(),\n machineId: createAccountMachineId(),\n createdAt: Date.now(),\n})\n\nconst ensureRegistryIdentity = (\n registry: AccountRegistry,\n {\n login,\n oauthApp,\n enterpriseDomain,\n }: {\n login: string\n oauthApp: string\n enterpriseDomain: string\n },\n): AccountClientIdentity => {\n const identityKey = buildIdentityKey({ login, oauthApp, enterpriseDomain })\n const existing = registry.clientIdentities[identityKey]\n if (existing) {\n return existing\n }\n\n const created = createClientIdentity({\n login,\n oauthApp,\n enterpriseDomain,\n })\n registry.clientIdentities[identityKey] = created\n return created\n}\n\nconst ensureClientIdentitiesForAccounts = (\n registry: AccountRegistry,\n): boolean => {\n const { oauthApp, enterpriseDomain } = getCurrentIdentityEnvironment()\n const countBefore = Object.keys(registry.clientIdentities).length\n\n for (const account of registry.accounts) {\n ensureRegistryIdentity(registry, {\n login: account.id,\n oauthApp,\n enterpriseDomain,\n })\n }\n\n return Object.keys(registry.clientIdentities).length !== countBefore\n}\n\nconst assertNoDuplicateAccounts = (registry: {\n accounts: Array<AccountMeta>\n}) => {\n const seen = new Set<string>()\n for (const account of registry.accounts) {\n if (seen.has(account.id)) {\n throw new Error(\n `Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: duplicate account id \"${account.id}\"`,\n )\n }\n seen.add(account.id)\n }\n}\n\nconst loadRegistrySnapshot = async (): Promise<{\n registry: AccountRegistry\n shouldPersist: boolean\n}> => {\n try {\n const content = await fs.readFile(PATHS.ACCOUNTS_REGISTRY_PATH, \"utf8\")\n if (!content.trim()) {\n return {\n registry: createEmptyRegistry(),\n shouldPersist: false,\n }\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(content) as unknown\n } catch (error) {\n throw new Error(\n `Invalid accounts registry JSON at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n }\n\n const isVersion2Record =\n typeof parsed === \"object\"\n && parsed !== null\n && \"version\" in parsed\n && parsed.version === 2\n const result =\n isVersion2Record ?\n accountRegistryV2Schema.safeParse(parsed)\n : accountRegistryV1Schema.safeParse(parsed)\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => `${issue.path.join(\".\")}: ${issue.message}`)\n .join(\"; \")\n\n throw new Error(\n `Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${issues}`,\n )\n }\n\n const parsedRegistry = result.data\n const registry: AccountRegistry =\n parsedRegistry.version === 2 ?\n parsedRegistry\n : {\n version: 2,\n accounts: parsedRegistry.accounts,\n clientIdentities: {},\n }\n\n assertNoDuplicateAccounts(registry)\n\n const identitiesBackfilled = ensureClientIdentitiesForAccounts(registry)\n\n return {\n registry,\n shouldPersist: parsedRegistry.version !== 2 || identitiesBackfilled,\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return {\n registry: createEmptyRegistry(),\n shouldPersist: false,\n }\n }\n throw error\n }\n}\n\nconst saveRegistryUnlocked = async (\n registry: AccountRegistry,\n): Promise<void> => {\n const content = JSON.stringify(registry, null, 2)\n await fs.writeFile(PATHS.ACCOUNTS_REGISTRY_PATH, content, { mode: 0o600 })\n}\n\n/**\n * Load the accounts registry from disk.\n * Returns an empty registry if the file doesn't exist.\n */\nexport async function loadRegistry(): Promise<AccountRegistry> {\n return runWithRegistryLock(async () => {\n const { registry, shouldPersist } = await loadRegistrySnapshot()\n if (shouldPersist) {\n await saveRegistryUnlocked(registry)\n }\n return registry\n })\n}\n\n/**\n * Save the accounts registry to disk with secure permissions.\n */\nexport async function saveRegistry(registry: AccountRegistry): Promise<void> {\n await runWithRegistryLock(async () => {\n await saveRegistryUnlocked(registry)\n })\n}\n\nexport async function getAccountClientIdentity(\n identityKey: string,\n): Promise<AccountClientIdentity | null> {\n const registry = await loadRegistry()\n return registry.clientIdentities[identityKey] ?? null\n}\n\nexport async function getAccountClientIdentityByLoginAndApp(\n login: string,\n oauthApp: string,\n): Promise<AccountClientIdentity | null> {\n const registry = await loadRegistry()\n\n const candidates = Object.values(registry.clientIdentities).filter(\n (identity): identity is AccountClientIdentity =>\n identity !== undefined\n && identity.login === login\n && identity.oauthApp === oauthApp,\n )\n\n const preferredCandidates = candidates.filter(\n (identity) =>\n identity.enterpriseDomain !== DEFAULT_IDENTITY_ENTERPRISE_DOMAIN,\n )\n const selectionPool =\n preferredCandidates.length > 0 ? preferredCandidates : candidates\n\n return selectionPool.reduce<AccountClientIdentity | null>(\n (latest, current) => {\n if (!latest || current.createdAt > latest.createdAt) return current\n return latest\n },\n null,\n )\n}\n\nexport async function ensureAccountClientIdentity({\n login,\n oauthApp,\n enterpriseDomain,\n}: {\n login: string\n oauthApp: string\n enterpriseDomain: string\n}): Promise<AccountClientIdentity> {\n assertValidAccountId(login)\n\n const normalizedOauthApp = oauthApp.trim()\n if (!normalizedOauthApp) {\n throw new Error(\"OAuth app namespace must not be empty\")\n }\n\n const normalizedEnterpriseDomain = enterpriseDomain.trim()\n if (!normalizedEnterpriseDomain) {\n throw new Error(\"Enterprise domain namespace must not be empty\")\n }\n\n const identityKey = buildIdentityKey({\n login,\n oauthApp: normalizedOauthApp,\n enterpriseDomain: normalizedEnterpriseDomain,\n })\n const existingLock = identityLocks.get(identityKey)\n if (existingLock) {\n return existingLock\n }\n\n const identityPromise = runWithRegistryLock(\n async (): Promise<AccountClientIdentity> => {\n const { registry, shouldPersist } = await loadRegistrySnapshot()\n const existing = registry.clientIdentities[identityKey]\n if (existing) {\n if (shouldPersist) {\n await saveRegistryUnlocked(registry)\n }\n return existing\n }\n\n const created = createClientIdentity({\n login,\n oauthApp: normalizedOauthApp,\n enterpriseDomain: normalizedEnterpriseDomain,\n })\n registry.clientIdentities[identityKey] = created\n await saveRegistryUnlocked(registry)\n return created\n },\n )\n\n identityLocks.set(identityKey, identityPromise)\n\n try {\n return await identityPromise\n } finally {\n if (identityLocks.get(identityKey) === identityPromise) {\n identityLocks.delete(identityKey)\n }\n }\n}\n\n/**\n * Add an account to the registry.\n * The account is appended to the end of the list (lowest priority).\n */\nexport async function addAccountToRegistry(meta: AccountMeta): Promise<void> {\n assertValidAccountId(meta.id)\n\n await runWithRegistryLock(async () => {\n const { registry } = await loadRegistrySnapshot()\n\n // Check for duplicate\n if (registry.accounts.some((a) => a.id === meta.id)) {\n throw new Error(`Account already exists: ${meta.id}`)\n }\n\n registry.accounts.push(meta)\n const { oauthApp, enterpriseDomain } = getCurrentIdentityEnvironment()\n ensureRegistryIdentity(registry, {\n login: meta.id,\n oauthApp,\n enterpriseDomain,\n })\n await saveRegistryUnlocked(registry)\n })\n}\n\n/**\n * Remove an account from the registry by ID or index (1-based).\n * Returns the removed account metadata.\n */\nexport async function removeAccountFromRegistry(\n idOrIndex: string | number,\n): Promise<AccountMeta> {\n return runWithRegistryLock(async () => {\n const { registry } = await loadRegistrySnapshot()\n let index: number\n\n if (typeof idOrIndex === \"number\") {\n // 1-based index\n index = idOrIndex - 1\n if (index < 0 || index >= registry.accounts.length) {\n throw new Error(`Invalid account index: ${idOrIndex}`)\n }\n } else {\n index = registry.accounts.findIndex((a) => a.id === idOrIndex)\n if (index === -1) {\n throw new Error(`Account not found: ${idOrIndex}`)\n }\n }\n\n const [removed] = registry.accounts.splice(index, 1)\n await saveRegistryUnlocked(registry)\n return removed\n })\n}\n\n/**\n * List all accounts from the registry.\n */\nexport async function listAccountsFromRegistry(): Promise<Array<AccountMeta>> {\n const registry = await loadRegistry()\n return registry.accounts\n}\n\n/**\n * Load the GitHub token for a specific account.\n * Returns null if the token file doesn't exist.\n */\nexport async function loadAccountToken(id: string): Promise<string | null> {\n assertValidAccountId(id)\n\n try {\n const tokenPath = accountTokenPath(id)\n const token = await fs.readFile(tokenPath, \"utf8\")\n return token.trim() || null\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null\n }\n throw error\n }\n}\n\n/**\n * Save the GitHub token for a specific account with secure permissions.\n */\nexport async function saveAccountToken(\n id: string,\n token: string,\n): Promise<void> {\n assertValidAccountId(id)\n\n const tokenPath = accountTokenPath(id)\n await fs.writeFile(tokenPath, token, { mode: 0o600 })\n}\n\n/**\n * Remove the GitHub token file for a specific account.\n */\nexport async function removeAccountToken(id: string): Promise<void> {\n assertValidAccountId(id)\n\n const tokenPath = accountTokenPath(id)\n try {\n await fs.unlink(tokenPath)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n // File doesn't exist, nothing to remove\n }\n}\n\n/**\n * Check if the legacy github_token file exists.\n */\nexport async function hasLegacyToken(): Promise<boolean> {\n try {\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\n/**\n * Read the legacy github_token file.\n * Returns null if the file doesn't exist or is empty.\n */\nexport async function readLegacyToken(): Promise<string | null> {\n try {\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim() || null\n } catch {\n return null\n }\n}\n\n/**\n * Check if the registry file exists and has accounts.\n */\nexport async function hasRegistry(): Promise<boolean> {\n const registry = await loadRegistry()\n return registry.accounts.length > 0\n}\n","import type { ModelsResponse } from \"~/services/copilot/get-models\"\n\n/**\n * Account type for GitHub Copilot subscription.\n */\nexport type AccountType = \"individual\" | \"business\" | \"enterprise\"\n\nexport const ACCOUNT_TYPE_VALUES: ReadonlyArray<AccountType> = [\n \"individual\",\n \"business\",\n \"enterprise\",\n]\n\nexport function isAccountType(value: unknown): value is AccountType {\n return (\n typeof value === \"string\"\n && (ACCOUNT_TYPE_VALUES as ReadonlyArray<string>).includes(value)\n )\n}\n\nexport function parseAccountType(value: unknown): AccountType {\n if (!isAccountType(value)) {\n throw new Error(\n `Invalid account type: ${String(value)}. Valid values: ${ACCOUNT_TYPE_VALUES.join(\n \", \",\n )}`,\n )\n }\n return value\n}\n\n/**\n * Metadata for a registered account, stored in the registry file.\n */\nexport interface AccountMeta {\n /** GitHub login */\n id: string\n /** Account subscription type */\n accountType: AccountType\n /** Timestamp when the account was added */\n addedAt: number\n /** Whether the account is enabled for request routing (undefined = true) */\n enabled?: boolean\n}\n\nexport interface AccountClientIdentity {\n /** Real GitHub login */\n login: string\n /** OAuth app namespace */\n oauthApp: string\n /** Enterprise domain namespace (\"public\" for github.com) */\n enterpriseDomain: string\n /** Account-scoped upstream device identifier */\n deviceId: string\n /** Account-scoped upstream machine identifier */\n machineId: string\n /** Creation timestamp for debugging/auditing */\n createdAt: number\n}\n\n/**\n * Registry file structure for storing account metadata.\n */\nexport interface AccountRegistry {\n /** Schema version for future migrations */\n version: 2\n /** Ordered list of accounts (order = priority) */\n accounts: Array<AccountMeta>\n /** Persistent client identities keyed by logical environment + login */\n clientIdentities: Partial<Record<string, AccountClientIdentity>>\n}\n\n/**\n * Runtime state for an account, including tokens and quota information.\n */\nexport interface AccountRuntime extends AccountMeta {\n /** Real GitHub login, used to resolve account-scoped identity */\n accountLogin?: string\n /** Persistent identity key used to load/store account-scoped identifiers */\n identityKey?: string\n /** GitHub personal access token */\n githubToken: string\n /** Copilot API token (obtained from GitHub) */\n copilotToken?: string\n /** Account-specific Copilot API base URL returned by GitHub */\n copilotApiUrl?: string\n /** VS Code version for API headers */\n vsCodeVersion?: string\n /** Account-scoped device identifier sent upstream */\n clientDeviceId?: string\n /** Account-scoped machine identifier sent upstream */\n clientMachineId?: string\n /** Account-scoped session identifier sent upstream */\n clientSessionId?: string\n /** Session refresh timer reference */\n sessionRefreshTimer?: ReturnType<typeof setTimeout>\n /** Cached available models for this account */\n models?: ModelsResponse\n /** Timestamp of last models fetch */\n lastModelsFetch?: number\n /** Whether models refresh is in progress */\n isRefreshingModels?: boolean\n /** Promise for an in-flight models refresh */\n modelsRefreshPromise?: Promise<void>\n /** Total premium interactions quota entitlement */\n premiumEntitlement?: number\n /** Remaining premium interactions quota */\n premiumRemaining?: number\n /** Reserved premium interaction units for in-flight requests */\n premiumReserved?: number\n /** Internal reservation map for idempotent release */\n premiumReservations?: Map<symbol, number>\n /** Whether this account has unlimited quota */\n unlimited?: boolean\n /** Whether this account allows overage billing (enterprise feature) */\n overagePermitted?: boolean\n /** Timestamp of last quota fetch */\n lastQuotaFetch?: number\n /** Token refresh timer reference */\n refreshTimer?: ReturnType<typeof setInterval>\n /** Whether this account has failed (e.g., 401 error) */\n failed?: boolean\n /** Failure reason if failed */\n failureReason?: string\n /** Whether quota refresh is in progress (prevents concurrent refreshes) */\n isRefreshingQuota?: boolean\n /** Promise for an in-flight quota refresh (allows concurrent callers to await the same refresh) */\n quotaRefreshPromise?: Promise<void>\n}\n\n/**\n * Context required for making API calls on behalf of an account.\n * This is a subset of AccountRuntime used by service functions.\n */\nexport interface AccountContext {\n /** Real GitHub login */\n accountLogin?: string\n /** GitHub personal access token */\n githubToken: string\n /** Copilot API token */\n copilotToken?: string\n /** Account-specific Copilot API base URL */\n copilotApiUrl?: string\n /** Account subscription type */\n accountType: AccountType\n /** VS Code version for API headers */\n vsCodeVersion?: string\n /** Account-scoped device identifier */\n clientDeviceId?: string\n /** Account-scoped machine identifier */\n clientMachineId?: string\n /** Account-scoped session identifier */\n clientSessionId?: string\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"account-COtMmvzU.js","names":[],"sources":["../src/lib/account-client-identity.ts","../src/lib/accounts-registry.ts","../src/lib/types/account.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\"\n\nimport { normalizeDomain } from \"./api-config\"\n\nexport const DEFAULT_IDENTITY_OAUTH_APP = \"default\"\nexport const DEFAULT_IDENTITY_ENTERPRISE_DOMAIN = \"public\"\n\nexport interface AccountIdentityEnvironment {\n oauthApp: string\n enterpriseDomain: string\n}\n\nexport const getCurrentIdentityEnvironment = (): AccountIdentityEnvironment => {\n const rawOauthApp = process.env.COPILOT_API_OAUTH_APP?.trim().toLowerCase()\n const rawEnterpriseDomain = process.env.COPILOT_API_ENTERPRISE_URL?.trim()\n\n return {\n oauthApp: rawOauthApp || DEFAULT_IDENTITY_OAUTH_APP,\n enterpriseDomain:\n rawEnterpriseDomain ?\n normalizeDomain(rawEnterpriseDomain).toLowerCase()\n : DEFAULT_IDENTITY_ENTERPRISE_DOMAIN,\n }\n}\n\nexport const buildIdentityKey = ({\n login,\n oauthApp,\n enterpriseDomain,\n}: {\n login: string\n oauthApp: string\n enterpriseDomain: string\n}): string => `${enterpriseDomain}:${oauthApp}:${login}`\n\nexport const createAccountDeviceId = (): string => randomUUID().toLowerCase()\n\nexport const createAccountMachineId = (): string =>\n createHash(\"sha256\").update(randomUUID(), \"utf8\").digest(\"hex\")\n\nexport const createAccountSessionId = (): string =>\n randomUUID() + Date.now().toString()\n","import fs from \"node:fs/promises\"\nimport { z } from \"zod\"\n\nimport type {\n AccountClientIdentity,\n AccountMeta,\n AccountRegistry,\n} from \"~/lib/types/account\"\n\nimport {\n DEFAULT_IDENTITY_ENTERPRISE_DOMAIN,\n buildIdentityKey,\n createAccountDeviceId,\n createAccountMachineId,\n getCurrentIdentityEnvironment,\n} from \"~/lib/account-client-identity\"\nimport { accountTokenPath, PATHS } from \"~/lib/paths\"\n\n/**\n * Validate account ID (GitHub login).\n * Rules:\n * - 1-39 chars\n * - Alphanumeric segments may be separated by single hyphens or underscores\n * - Cannot begin or end with a separator\n * - No consecutive separators\n */\nexport function validateAccountId(id: string): boolean {\n if (id.length === 0 || id.length > 39) return false\n return /^[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*$/u.test(id)\n}\n\nfunction assertValidAccountId(id: string): void {\n if (!validateAccountId(id)) {\n throw new Error(`Invalid account ID: ${id}`)\n }\n}\n\nconst ACCOUNT_ID_VALIDATION_RULES =\n \"1-39 chars, alphanumeric with optional single hyphen/underscore separators, no leading/trailing separator, no consecutive separators.\"\n\nconst accountMetaSchema = z.object({\n id: z.string().refine(validateAccountId, {\n message: `Invalid account id. Expected a GitHub login (${ACCOUNT_ID_VALIDATION_RULES})`,\n }),\n accountType: z.enum([\"individual\", \"business\", \"enterprise\"]),\n addedAt: z.number(),\n enabled: z.boolean().optional(),\n})\n\n/**\n * Check whether an account is enabled for request routing.\n * Treats `undefined` and `true` as enabled (backward compatible).\n */\nexport function isAccountEnabled(meta: AccountMeta): boolean {\n return meta.enabled !== false\n}\n\nconst accountClientIdentitySchema = z.object({\n login: z.string().refine(validateAccountId, {\n message: `Invalid client identity login. Expected a GitHub login (${ACCOUNT_ID_VALIDATION_RULES})`,\n }),\n oauthApp: z.string().min(1),\n enterpriseDomain: z.string().min(1),\n deviceId: z\n .string()\n .regex(\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u,\n \"Invalid device ID format. Expected a lowercase UUID.\",\n ),\n machineId: z\n .string()\n .regex(\n /^[0-9a-f]{64}$/u,\n \"Invalid machine ID format. Expected 64 lowercase hexadecimal characters.\",\n ),\n createdAt: z.number(),\n})\n\nconst accountRegistryV1Schema = z.object({\n version: z.literal(1),\n accounts: z.array(accountMetaSchema),\n})\n\nconst accountRegistryV2Schema = z.object({\n version: z.literal(2),\n accounts: z.array(accountMetaSchema),\n clientIdentities: z.record(z.string(), accountClientIdentitySchema),\n})\n\nconst identityLocks = new Map<string, Promise<AccountClientIdentity>>()\nlet registryLock: Promise<void> = Promise.resolve()\n\nconst runWithRegistryLock = async <T>(\n operation: () => Promise<T>,\n): Promise<T> => {\n const previousLock = registryLock\n let releaseLock!: () => void\n registryLock = new Promise<void>((resolve) => {\n releaseLock = resolve\n })\n\n await previousLock\n\n try {\n return await operation()\n } finally {\n releaseLock()\n }\n}\n\n/**\n * Create an empty registry with the current schema version.\n */\nfunction createEmptyRegistry(): AccountRegistry {\n return {\n version: 2,\n accounts: [],\n clientIdentities: {},\n }\n}\n\nconst createClientIdentity = ({\n login,\n oauthApp,\n enterpriseDomain,\n}: {\n login: string\n oauthApp: string\n enterpriseDomain: string\n}): AccountClientIdentity => ({\n login,\n oauthApp,\n enterpriseDomain,\n deviceId: createAccountDeviceId(),\n machineId: createAccountMachineId(),\n createdAt: Date.now(),\n})\n\nconst ensureRegistryIdentity = (\n registry: AccountRegistry,\n {\n login,\n oauthApp,\n enterpriseDomain,\n }: {\n login: string\n oauthApp: string\n enterpriseDomain: string\n },\n): AccountClientIdentity => {\n const identityKey = buildIdentityKey({ login, oauthApp, enterpriseDomain })\n const existing = registry.clientIdentities[identityKey]\n if (existing) {\n return existing\n }\n\n const created = createClientIdentity({\n login,\n oauthApp,\n enterpriseDomain,\n })\n registry.clientIdentities[identityKey] = created\n return created\n}\n\nconst ensureClientIdentitiesForAccounts = (\n registry: AccountRegistry,\n): boolean => {\n const { oauthApp, enterpriseDomain } = getCurrentIdentityEnvironment()\n const countBefore = Object.keys(registry.clientIdentities).length\n\n for (const account of registry.accounts) {\n ensureRegistryIdentity(registry, {\n login: account.id,\n oauthApp,\n enterpriseDomain,\n })\n }\n\n return Object.keys(registry.clientIdentities).length !== countBefore\n}\n\nconst assertNoDuplicateAccounts = (registry: {\n accounts: Array<AccountMeta>\n}) => {\n const seen = new Set<string>()\n for (const account of registry.accounts) {\n if (seen.has(account.id)) {\n throw new Error(\n `Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: duplicate account id \"${account.id}\"`,\n )\n }\n seen.add(account.id)\n }\n}\n\nconst loadRegistrySnapshot = async (): Promise<{\n registry: AccountRegistry\n shouldPersist: boolean\n}> => {\n try {\n const content = await fs.readFile(PATHS.ACCOUNTS_REGISTRY_PATH, \"utf8\")\n if (!content.trim()) {\n return {\n registry: createEmptyRegistry(),\n shouldPersist: false,\n }\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(content) as unknown\n } catch (error) {\n throw new Error(\n `Invalid accounts registry JSON at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n }\n\n const isVersion2Record =\n typeof parsed === \"object\"\n && parsed !== null\n && \"version\" in parsed\n && parsed.version === 2\n const result =\n isVersion2Record ?\n accountRegistryV2Schema.safeParse(parsed)\n : accountRegistryV1Schema.safeParse(parsed)\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => `${issue.path.join(\".\")}: ${issue.message}`)\n .join(\"; \")\n\n throw new Error(\n `Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${issues}`,\n )\n }\n\n const parsedRegistry = result.data\n const registry: AccountRegistry =\n parsedRegistry.version === 2 ?\n parsedRegistry\n : {\n version: 2,\n accounts: parsedRegistry.accounts,\n clientIdentities: {},\n }\n\n assertNoDuplicateAccounts(registry)\n\n const identitiesBackfilled = ensureClientIdentitiesForAccounts(registry)\n\n return {\n registry,\n shouldPersist: parsedRegistry.version !== 2 || identitiesBackfilled,\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return {\n registry: createEmptyRegistry(),\n shouldPersist: false,\n }\n }\n throw error\n }\n}\n\nconst saveRegistryUnlocked = async (\n registry: AccountRegistry,\n): Promise<void> => {\n const content = JSON.stringify(registry, null, 2)\n await fs.writeFile(PATHS.ACCOUNTS_REGISTRY_PATH, content, { mode: 0o600 })\n}\n\n/**\n * Load the accounts registry from disk.\n * Returns an empty registry if the file doesn't exist.\n */\nexport async function loadRegistry(): Promise<AccountRegistry> {\n return runWithRegistryLock(async () => {\n const { registry, shouldPersist } = await loadRegistrySnapshot()\n if (shouldPersist) {\n await saveRegistryUnlocked(registry)\n }\n return registry\n })\n}\n\n/**\n * Save the accounts registry to disk with secure permissions.\n */\nexport async function saveRegistry(registry: AccountRegistry): Promise<void> {\n await runWithRegistryLock(async () => {\n await saveRegistryUnlocked(registry)\n })\n}\n\nexport async function getAccountClientIdentity(\n identityKey: string,\n): Promise<AccountClientIdentity | null> {\n const registry = await loadRegistry()\n return registry.clientIdentities[identityKey] ?? null\n}\n\nexport async function getAccountClientIdentityByLoginAndApp(\n login: string,\n oauthApp: string,\n): Promise<AccountClientIdentity | null> {\n const registry = await loadRegistry()\n\n const candidates = Object.values(registry.clientIdentities).filter(\n (identity): identity is AccountClientIdentity =>\n identity !== undefined\n && identity.login === login\n && identity.oauthApp === oauthApp,\n )\n\n const preferredCandidates = candidates.filter(\n (identity) =>\n identity.enterpriseDomain !== DEFAULT_IDENTITY_ENTERPRISE_DOMAIN,\n )\n const selectionPool =\n preferredCandidates.length > 0 ? preferredCandidates : candidates\n\n return selectionPool.reduce<AccountClientIdentity | null>(\n (latest, current) => {\n if (!latest || current.createdAt > latest.createdAt) return current\n return latest\n },\n null,\n )\n}\n\nexport async function ensureAccountClientIdentity({\n login,\n oauthApp,\n enterpriseDomain,\n}: {\n login: string\n oauthApp: string\n enterpriseDomain: string\n}): Promise<AccountClientIdentity> {\n assertValidAccountId(login)\n\n const normalizedOauthApp = oauthApp.trim()\n if (!normalizedOauthApp) {\n throw new Error(\"OAuth app namespace must not be empty\")\n }\n\n const normalizedEnterpriseDomain = enterpriseDomain.trim()\n if (!normalizedEnterpriseDomain) {\n throw new Error(\"Enterprise domain namespace must not be empty\")\n }\n\n const identityKey = buildIdentityKey({\n login,\n oauthApp: normalizedOauthApp,\n enterpriseDomain: normalizedEnterpriseDomain,\n })\n const existingLock = identityLocks.get(identityKey)\n if (existingLock) {\n return existingLock\n }\n\n const identityPromise = runWithRegistryLock(\n async (): Promise<AccountClientIdentity> => {\n const { registry, shouldPersist } = await loadRegistrySnapshot()\n const existing = registry.clientIdentities[identityKey]\n if (existing) {\n if (shouldPersist) {\n await saveRegistryUnlocked(registry)\n }\n return existing\n }\n\n const created = createClientIdentity({\n login,\n oauthApp: normalizedOauthApp,\n enterpriseDomain: normalizedEnterpriseDomain,\n })\n registry.clientIdentities[identityKey] = created\n await saveRegistryUnlocked(registry)\n return created\n },\n )\n\n identityLocks.set(identityKey, identityPromise)\n\n try {\n return await identityPromise\n } finally {\n if (identityLocks.get(identityKey) === identityPromise) {\n identityLocks.delete(identityKey)\n }\n }\n}\n\n/**\n * Add an account to the registry.\n * The account is appended to the end of the list (lowest priority).\n */\nexport async function addAccountToRegistry(meta: AccountMeta): Promise<void> {\n assertValidAccountId(meta.id)\n\n await runWithRegistryLock(async () => {\n const { registry } = await loadRegistrySnapshot()\n\n // Check for duplicate\n if (registry.accounts.some((a) => a.id === meta.id)) {\n throw new Error(`Account already exists: ${meta.id}`)\n }\n\n registry.accounts.push(meta)\n const { oauthApp, enterpriseDomain } = getCurrentIdentityEnvironment()\n ensureRegistryIdentity(registry, {\n login: meta.id,\n oauthApp,\n enterpriseDomain,\n })\n await saveRegistryUnlocked(registry)\n })\n}\n\n/**\n * Remove an account from the registry by ID or index (1-based).\n * Returns the removed account metadata.\n */\nexport async function removeAccountFromRegistry(\n idOrIndex: string | number,\n): Promise<AccountMeta> {\n return runWithRegistryLock(async () => {\n const { registry } = await loadRegistrySnapshot()\n let index: number\n\n if (typeof idOrIndex === \"number\") {\n // 1-based index\n index = idOrIndex - 1\n if (index < 0 || index >= registry.accounts.length) {\n throw new Error(`Invalid account index: ${idOrIndex}`)\n }\n } else {\n index = registry.accounts.findIndex((a) => a.id === idOrIndex)\n if (index === -1) {\n throw new Error(`Account not found: ${idOrIndex}`)\n }\n }\n\n const [removed] = registry.accounts.splice(index, 1)\n await saveRegistryUnlocked(registry)\n return removed\n })\n}\n\n/**\n * List all accounts from the registry.\n */\nexport async function listAccountsFromRegistry(): Promise<Array<AccountMeta>> {\n const registry = await loadRegistry()\n return registry.accounts\n}\n\n/**\n * Load the GitHub token for a specific account.\n * Returns null if the token file doesn't exist.\n */\nexport async function loadAccountToken(id: string): Promise<string | null> {\n assertValidAccountId(id)\n\n try {\n const tokenPath = accountTokenPath(id)\n const token = await fs.readFile(tokenPath, \"utf8\")\n return token.trim() || null\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null\n }\n throw error\n }\n}\n\n/**\n * Save the GitHub token for a specific account with secure permissions.\n */\nexport async function saveAccountToken(\n id: string,\n token: string,\n): Promise<void> {\n assertValidAccountId(id)\n\n const tokenPath = accountTokenPath(id)\n await fs.writeFile(tokenPath, token, { mode: 0o600 })\n}\n\n/**\n * Remove the GitHub token file for a specific account.\n */\nexport async function removeAccountToken(id: string): Promise<void> {\n assertValidAccountId(id)\n\n const tokenPath = accountTokenPath(id)\n try {\n await fs.unlink(tokenPath)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n // File doesn't exist, nothing to remove\n }\n}\n\n/**\n * Check if the legacy github_token file exists.\n */\nexport async function hasLegacyToken(): Promise<boolean> {\n try {\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\n/**\n * Read the legacy github_token file.\n * Returns null if the file doesn't exist or is empty.\n */\nexport async function readLegacyToken(): Promise<string | null> {\n try {\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim() || null\n } catch {\n return null\n }\n}\n\n/**\n * Check if the registry file exists and has accounts.\n */\nexport async function hasRegistry(): Promise<boolean> {\n const registry = await loadRegistry()\n return registry.accounts.length > 0\n}\n","import type { ModelsResponse } from \"~/services/copilot/get-models\"\n\n/**\n * Account type for GitHub Copilot subscription.\n */\nexport type AccountType = \"individual\" | \"business\" | \"enterprise\"\n\nexport const ACCOUNT_TYPE_VALUES: ReadonlyArray<AccountType> = [\n \"individual\",\n \"business\",\n \"enterprise\",\n]\n\nexport function isAccountType(value: unknown): value is AccountType {\n return (\n typeof value === \"string\"\n && (ACCOUNT_TYPE_VALUES as ReadonlyArray<string>).includes(value)\n )\n}\n\nexport function parseAccountType(value: unknown): AccountType {\n if (!isAccountType(value)) {\n throw new Error(\n `Invalid account type: ${String(value)}. Valid values: ${ACCOUNT_TYPE_VALUES.join(\n \", \",\n )}`,\n )\n }\n return value\n}\n\n/**\n * Metadata for a registered account, stored in the registry file.\n */\nexport interface AccountMeta {\n /** GitHub login */\n id: string\n /** Account subscription type */\n accountType: AccountType\n /** Timestamp when the account was added */\n addedAt: number\n /** Whether the account is enabled for request routing (undefined = true) */\n enabled?: boolean\n}\n\nexport interface AccountClientIdentity {\n /** Real GitHub login */\n login: string\n /** OAuth app namespace */\n oauthApp: string\n /** Enterprise domain namespace (\"public\" for github.com) */\n enterpriseDomain: string\n /** Account-scoped upstream device identifier */\n deviceId: string\n /** Account-scoped upstream machine identifier */\n machineId: string\n /** Creation timestamp for debugging/auditing */\n createdAt: number\n}\n\n/**\n * Registry file structure for storing account metadata.\n */\nexport interface AccountRegistry {\n /** Schema version for future migrations */\n version: 2\n /** Ordered list of accounts (order = priority) */\n accounts: Array<AccountMeta>\n /** Persistent client identities keyed by logical environment + login */\n clientIdentities: Partial<Record<string, AccountClientIdentity>>\n}\n\n/**\n * Runtime state for an account, including tokens and quota information.\n */\nexport interface AccountRuntime extends AccountMeta {\n /** Real GitHub login, used to resolve account-scoped identity */\n accountLogin?: string\n /** Persistent identity key used to load/store account-scoped identifiers */\n identityKey?: string\n /** GitHub personal access token */\n githubToken: string\n /** Copilot API token (obtained from GitHub) */\n copilotToken?: string\n /** Account-specific Copilot API base URL returned by GitHub */\n copilotApiUrl?: string\n /** VS Code version for API headers */\n vsCodeVersion?: string\n /** Account-scoped device identifier sent upstream */\n clientDeviceId?: string\n /** Account-scoped machine identifier sent upstream */\n clientMachineId?: string\n /** Account-scoped session identifier sent upstream */\n clientSessionId?: string\n /** Session refresh timer reference */\n sessionRefreshTimer?: ReturnType<typeof setTimeout>\n /** Cached available models for this account */\n models?: ModelsResponse\n /** Timestamp of last models fetch */\n lastModelsFetch?: number\n /** Whether models refresh is in progress */\n isRefreshingModels?: boolean\n /** Promise for an in-flight models refresh */\n modelsRefreshPromise?: Promise<void>\n /** Total premium interactions quota entitlement */\n premiumEntitlement?: number\n /** Remaining premium interactions quota */\n premiumRemaining?: number\n /** Reserved premium interaction units for in-flight requests */\n premiumReserved?: number\n /** Internal reservation map for idempotent release */\n premiumReservations?: Map<symbol, number>\n /** Whether this account has unlimited quota */\n unlimited?: boolean\n /** Whether this account allows overage billing (enterprise feature) */\n overagePermitted?: boolean\n /** Timestamp of last quota fetch */\n lastQuotaFetch?: number\n /** Token refresh timer reference */\n refreshTimer?: ReturnType<typeof setInterval>\n /** Whether this account has failed (e.g., 401 error) */\n failed?: boolean\n /** Failure reason if failed */\n failureReason?: string\n /** Whether quota refresh is in progress (prevents concurrent refreshes) */\n isRefreshingQuota?: boolean\n /** Promise for an in-flight quota refresh (allows concurrent callers to await the same refresh) */\n quotaRefreshPromise?: Promise<void>\n}\n\n/**\n * Context required for making API calls on behalf of an account.\n * This is a subset of AccountRuntime used by service functions.\n */\nexport interface AccountContext {\n /** Real GitHub login */\n accountLogin?: string\n /** GitHub personal access token */\n githubToken: string\n /** Copilot API token */\n copilotToken?: string\n /** Account-specific Copilot API base URL */\n copilotApiUrl?: string\n /** Account subscription type */\n accountType: AccountType\n /** VS Code version for API headers */\n vsCodeVersion?: string\n /** Account-scoped device identifier */\n clientDeviceId?: string\n /** Account-scoped machine identifier */\n clientMachineId?: string\n /** Account-scoped session identifier */\n clientSessionId?: string\n}\n"],"mappings":";;;;;AAKA,MAAa,qCAAqC;AAOlD,MAAa,sCAAkE;CAC7E,MAAM,cAAc,QAAQ,IAAI,uBAAuB,MAAM,CAAC,aAAa;CAC3E,MAAM,sBAAsB,QAAQ,IAAI,4BAA4B,MAAM;CAE1E,OAAO;EACL,UAAU,eAAA;EACV,kBACE,sBACE,gBAAgB,oBAAoB,CAAC,aAAa,GAClD;EACL;;AAGH,MAAa,oBAAoB,EAC/B,OACA,UACA,uBAKY,GAAG,iBAAiB,GAAG,SAAS,GAAG;AAEjD,MAAa,8BAAsC,YAAY,CAAC,aAAa;AAE7E,MAAa,+BACX,WAAW,SAAS,CAAC,OAAO,YAAY,EAAE,OAAO,CAAC,OAAO,MAAM;AAEjE,MAAa,+BACX,YAAY,GAAG,KAAK,KAAK,CAAC,UAAU;;;;;;;;;;;ACftC,SAAgB,kBAAkB,IAAqB;CACrD,IAAI,GAAG,WAAW,KAAK,GAAG,SAAS,IAAI,OAAO;CAC9C,OAAO,uCAAuC,KAAK,GAAG;;AAGxD,SAAS,qBAAqB,IAAkB;CAC9C,IAAI,CAAC,kBAAkB,GAAG,EACxB,MAAM,IAAI,MAAM,uBAAuB,KAAK;;AAIhD,MAAM,8BACJ;AAEF,MAAM,oBAAoB,EAAE,OAAO;CACjC,IAAI,EAAE,QAAQ,CAAC,OAAO,mBAAmB,EACvC,SAAS,gDAAgD,4BAA4B,IACtF,CAAC;CACF,aAAa,EAAE,KAAK;EAAC;EAAc;EAAY;EAAa,CAAC;CAC7D,SAAS,EAAE,QAAQ;CACnB,SAAS,EAAE,SAAS,CAAC,UAAU;CAChC,CAAC;;;;;AAMF,SAAgB,iBAAiB,MAA4B;CAC3D,OAAO,KAAK,YAAY;;AAG1B,MAAM,8BAA8B,EAAE,OAAO;CAC3C,OAAO,EAAE,QAAQ,CAAC,OAAO,mBAAmB,EAC1C,SAAS,2DAA2D,4BAA4B,IACjG,CAAC;CACF,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC3B,kBAAkB,EAAE,QAAQ,CAAC,IAAI,EAAE;CACnC,UAAU,EACP,QAAQ,CACR,MACC,mEACA,uDACD;CACH,WAAW,EACR,QAAQ,CACR,MACC,mBACA,2EACD;CACH,WAAW,EAAE,QAAQ;CACtB,CAAC;AAEF,MAAM,0BAA0B,EAAE,OAAO;CACvC,SAAS,EAAE,QAAQ,EAAE;CACrB,UAAU,EAAE,MAAM,kBAAkB;CACrC,CAAC;AAEF,MAAM,0BAA0B,EAAE,OAAO;CACvC,SAAS,EAAE,QAAQ,EAAE;CACrB,UAAU,EAAE,MAAM,kBAAkB;CACpC,kBAAkB,EAAE,OAAO,EAAE,QAAQ,EAAE,4BAA4B;CACpE,CAAC;AAEF,MAAM,gCAAgB,IAAI,KAA6C;AACvE,IAAI,eAA8B,QAAQ,SAAS;AAEnD,MAAM,sBAAsB,OAC1B,cACe;CACf,MAAM,eAAe;CACrB,IAAI;CACJ,eAAe,IAAI,SAAe,YAAY;EAC5C,cAAc;GACd;CAEF,MAAM;CAEN,IAAI;EACF,OAAO,MAAM,WAAW;WAChB;EACR,aAAa;;;;;;AAOjB,SAAS,sBAAuC;CAC9C,OAAO;EACL,SAAS;EACT,UAAU,EAAE;EACZ,kBAAkB,EAAE;EACrB;;AAGH,MAAM,wBAAwB,EAC5B,OACA,UACA,wBAK4B;CAC5B;CACA;CACA;CACA,UAAU,uBAAuB;CACjC,WAAW,wBAAwB;CACnC,WAAW,KAAK,KAAK;CACtB;AAED,MAAM,0BACJ,UACA,EACE,OACA,UACA,uBAMwB;CAC1B,MAAM,cAAc,iBAAiB;EAAE;EAAO;EAAU;EAAkB,CAAC;CAC3E,MAAM,WAAW,SAAS,iBAAiB;CAC3C,IAAI,UACF,OAAO;CAGT,MAAM,UAAU,qBAAqB;EACnC;EACA;EACA;EACD,CAAC;CACF,SAAS,iBAAiB,eAAe;CACzC,OAAO;;AAGT,MAAM,qCACJ,aACY;CACZ,MAAM,EAAE,UAAU,qBAAqB,+BAA+B;CACtE,MAAM,cAAc,OAAO,KAAK,SAAS,iBAAiB,CAAC;CAE3D,KAAK,MAAM,WAAW,SAAS,UAC7B,uBAAuB,UAAU;EAC/B,OAAO,QAAQ;EACf;EACA;EACD,CAAC;CAGJ,OAAO,OAAO,KAAK,SAAS,iBAAiB,CAAC,WAAW;;AAG3D,MAAM,6BAA6B,aAE7B;CACJ,MAAM,uBAAO,IAAI,KAAa;CAC9B,KAAK,MAAM,WAAW,SAAS,UAAU;EACvC,IAAI,KAAK,IAAI,QAAQ,GAAG,EACtB,MAAM,IAAI,MACR,gCAAgC,MAAM,uBAAuB,0BAA0B,QAAQ,GAAG,GACnG;EAEH,KAAK,IAAI,QAAQ,GAAG;;;AAIxB,MAAM,uBAAuB,YAGvB;CACJ,IAAI;EACF,MAAM,UAAU,MAAM,GAAG,SAAS,MAAM,wBAAwB,OAAO;EACvE,IAAI,CAAC,QAAQ,MAAM,EACjB,OAAO;GACL,UAAU,qBAAqB;GAC/B,eAAe;GAChB;EAGH,IAAI;EACJ,IAAI;GACF,SAAS,KAAK,MAAM,QAAQ;WACrB,OAAO;GACd,MAAM,IAAI,MACR,qCAAqC,MAAM,uBAAuB,IAChE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAEzD;;EAQH,MAAM,SAJJ,OAAO,WAAW,YACf,WAAW,QACX,aAAa,UACb,OAAO,YAAY,IAGpB,wBAAwB,UAAU,OAAO,GACzC,wBAAwB,UAAU,OAAO;EAC7C,IAAI,CAAC,OAAO,SAAS;GACnB,MAAM,SAAS,OAAO,MAAM,OACzB,KAAK,UAAU,GAAG,MAAM,KAAK,KAAK,IAAI,CAAC,IAAI,MAAM,UAAU,CAC3D,KAAK,KAAK;GAEb,MAAM,IAAI,MACR,gCAAgC,MAAM,uBAAuB,IAAI,SAClE;;EAGH,MAAM,iBAAiB,OAAO;EAC9B,MAAM,WACJ,eAAe,YAAY,IACzB,iBACA;GACE,SAAS;GACT,UAAU,eAAe;GACzB,kBAAkB,EAAE;GACrB;EAEL,0BAA0B,SAAS;EAEnC,MAAM,uBAAuB,kCAAkC,SAAS;EAExE,OAAO;GACL;GACA,eAAe,eAAe,YAAY,KAAK;GAChD;UACM,OAAO;EACd,IAAK,MAAgC,SAAS,UAC5C,OAAO;GACL,UAAU,qBAAqB;GAC/B,eAAe;GAChB;EAEH,MAAM;;;AAIV,MAAM,uBAAuB,OAC3B,aACkB;CAClB,MAAM,UAAU,KAAK,UAAU,UAAU,MAAM,EAAE;CACjD,MAAM,GAAG,UAAU,MAAM,wBAAwB,SAAS,EAAE,MAAM,KAAO,CAAC;;;;;;AAO5E,eAAsB,eAAyC;CAC7D,OAAO,oBAAoB,YAAY;EACrC,MAAM,EAAE,UAAU,kBAAkB,MAAM,sBAAsB;EAChE,IAAI,eACF,MAAM,qBAAqB,SAAS;EAEtC,OAAO;GACP;;;;;AAMJ,eAAsB,aAAa,UAA0C;CAC3E,MAAM,oBAAoB,YAAY;EACpC,MAAM,qBAAqB,SAAS;GACpC;;AAUJ,eAAsB,sCACpB,OACA,UACuC;CACvC,MAAM,WAAW,MAAM,cAAc;CAErC,MAAM,aAAa,OAAO,OAAO,SAAS,iBAAiB,CAAC,QACzD,aACC,aAAa,KAAA,KACV,SAAS,UAAU,SACnB,SAAS,aAAa,SAC5B;CAED,MAAM,sBAAsB,WAAW,QACpC,aACC,SAAS,qBAAqB,mCACjC;CAID,QAFE,oBAAoB,SAAS,IAAI,sBAAsB,YAEpC,QAClB,QAAQ,YAAY;EACnB,IAAI,CAAC,UAAU,QAAQ,YAAY,OAAO,WAAW,OAAO;EAC5D,OAAO;IAET,KACD;;AAGH,eAAsB,4BAA4B,EAChD,OACA,UACA,oBAKiC;CACjC,qBAAqB,MAAM;CAE3B,MAAM,qBAAqB,SAAS,MAAM;CAC1C,IAAI,CAAC,oBACH,MAAM,IAAI,MAAM,wCAAwC;CAG1D,MAAM,6BAA6B,iBAAiB,MAAM;CAC1D,IAAI,CAAC,4BACH,MAAM,IAAI,MAAM,gDAAgD;CAGlE,MAAM,cAAc,iBAAiB;EACnC;EACA,UAAU;EACV,kBAAkB;EACnB,CAAC;CACF,MAAM,eAAe,cAAc,IAAI,YAAY;CACnD,IAAI,cACF,OAAO;CAGT,MAAM,kBAAkB,oBACtB,YAA4C;EAC1C,MAAM,EAAE,UAAU,kBAAkB,MAAM,sBAAsB;EAChE,MAAM,WAAW,SAAS,iBAAiB;EAC3C,IAAI,UAAU;GACZ,IAAI,eACF,MAAM,qBAAqB,SAAS;GAEtC,OAAO;;EAGT,MAAM,UAAU,qBAAqB;GACnC;GACA,UAAU;GACV,kBAAkB;GACnB,CAAC;EACF,SAAS,iBAAiB,eAAe;EACzC,MAAM,qBAAqB,SAAS;EACpC,OAAO;GAEV;CAED,cAAc,IAAI,aAAa,gBAAgB;CAE/C,IAAI;EACF,OAAO,MAAM;WACL;EACR,IAAI,cAAc,IAAI,YAAY,KAAK,iBACrC,cAAc,OAAO,YAAY;;;;;;;AASvC,eAAsB,qBAAqB,MAAkC;CAC3E,qBAAqB,KAAK,GAAG;CAE7B,MAAM,oBAAoB,YAAY;EACpC,MAAM,EAAE,aAAa,MAAM,sBAAsB;EAGjD,IAAI,SAAS,SAAS,MAAM,MAAM,EAAE,OAAO,KAAK,GAAG,EACjD,MAAM,IAAI,MAAM,2BAA2B,KAAK,KAAK;EAGvD,SAAS,SAAS,KAAK,KAAK;EAC5B,MAAM,EAAE,UAAU,qBAAqB,+BAA+B;EACtE,uBAAuB,UAAU;GAC/B,OAAO,KAAK;GACZ;GACA;GACD,CAAC;EACF,MAAM,qBAAqB,SAAS;GACpC;;;;;;AAOJ,eAAsB,0BACpB,WACsB;CACtB,OAAO,oBAAoB,YAAY;EACrC,MAAM,EAAE,aAAa,MAAM,sBAAsB;EACjD,IAAI;EAEJ,IAAI,OAAO,cAAc,UAAU;GAEjC,QAAQ,YAAY;GACpB,IAAI,QAAQ,KAAK,SAAS,SAAS,SAAS,QAC1C,MAAM,IAAI,MAAM,0BAA0B,YAAY;SAEnD;GACL,QAAQ,SAAS,SAAS,WAAW,MAAM,EAAE,OAAO,UAAU;GAC9D,IAAI,UAAU,IACZ,MAAM,IAAI,MAAM,sBAAsB,YAAY;;EAItD,MAAM,CAAC,WAAW,SAAS,SAAS,OAAO,OAAO,EAAE;EACpD,MAAM,qBAAqB,SAAS;EACpC,OAAO;GACP;;;;;AAMJ,eAAsB,2BAAwD;CAE5E,QAAO,MADgB,cAAc,EACrB;;;;;;AAOlB,eAAsB,iBAAiB,IAAoC;CACzE,qBAAqB,GAAG;CAExB,IAAI;EACF,MAAM,YAAY,iBAAiB,GAAG;EAEtC,QAAO,MADa,GAAG,SAAS,WAAW,OAAO,EACrC,MAAM,IAAI;UAChB,OAAO;EACd,IAAK,MAAgC,SAAS,UAC5C,OAAO;EAET,MAAM;;;;;;AAOV,eAAsB,iBACpB,IACA,OACe;CACf,qBAAqB,GAAG;CAExB,MAAM,YAAY,iBAAiB,GAAG;CACtC,MAAM,GAAG,UAAU,WAAW,OAAO,EAAE,MAAM,KAAO,CAAC;;;;;AAMvD,eAAsB,mBAAmB,IAA2B;CAClE,qBAAqB,GAAG;CAExB,MAAM,YAAY,iBAAiB,GAAG;CACtC,IAAI;EACF,MAAM,GAAG,OAAO,UAAU;UACnB,OAAO;EACd,IAAK,MAAgC,SAAS,UAC5C,MAAM;;;;;;AASZ,eAAsB,iBAAmC;CACvD,IAAI;EAEF,QAAO,MADe,GAAG,SAAS,MAAM,mBAAmB,OAAO,EACnD,MAAM,CAAC,SAAS;SACzB;EACN,OAAO;;;;;;;AAQX,eAAsB,kBAA0C;CAC9D,IAAI;EAEF,QAAO,MADe,GAAG,SAAS,MAAM,mBAAmB,OAAO,EACnD,MAAM,IAAI;SACnB;EACN,OAAO;;;;;;AAOX,eAAsB,cAAgC;CAEpD,QAAO,MADgB,cAAc,EACrB,SAAS,SAAS;;;;ACthBpC,MAAa,sBAAkD;CAC7D;CACA;CACA;CACD;AAED,SAAgB,cAAc,OAAsC;CAClE,OACE,OAAO,UAAU,YACb,oBAA8C,SAAS,MAAM;;AAIrE,SAAgB,iBAAiB,OAA6B;CAC5D,IAAI,CAAC,cAAc,MAAM,EACvB,MAAM,IAAI,MACR,yBAAyB,OAAO,MAAM,CAAC,kBAAkB,oBAAoB,KAC3E,KACD,GACF;CAEH,OAAO"}