@dotobokuri/fleet-console 1.45.0 → 1.46.0

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 (68) hide show
  1. package/dist/cli.d.ts +4 -0
  2. package/dist/cli.mjs +416 -899
  3. package/dist/client/assets/{_baseUniq-DtJH2bh8.js → _baseUniq-CaqaUUIT.js} +1 -1
  4. package/dist/client/assets/{arc-DHCKMqqF.js → arc-B4MjkR5w.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-Q4EWVU46-BOuyINL8.js → architectureDiagram-Q4EWVU46-wP-4WWY-.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-DXYQGD6D-cIre3_5n.js → blockDiagram-DXYQGD6D-D5Y2AdkX.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-AHTNJAMY-DTX-qjSW.js → c4Diagram-AHTNJAMY-hKNJrUod.js} +1 -1
  8. package/dist/client/assets/channel-Cy7Wdt-y.js +1 -0
  9. package/dist/client/assets/{chunk-4BX2VUAB-DiBwpozK.js → chunk-4BX2VUAB-RdaAserw.js} +1 -1
  10. package/dist/client/assets/{chunk-4TB4RGXK-uCZ-B3-y.js → chunk-4TB4RGXK-BVvGheRq.js} +1 -1
  11. package/dist/client/assets/{chunk-55IACEB6-CmBu2sf0.js → chunk-55IACEB6-BKBu793s.js} +1 -1
  12. package/dist/client/assets/{chunk-EDXVE4YY-BXpw2VyB.js → chunk-EDXVE4YY-CF6P1HJI.js} +1 -1
  13. package/dist/client/assets/{chunk-FMBD7UC4-Cn9sKTn5.js → chunk-FMBD7UC4-DYrffXxC.js} +1 -1
  14. package/dist/client/assets/{chunk-OYMX7WX6-BDgH1zlW.js → chunk-OYMX7WX6-ggkv39bf.js} +1 -1
  15. package/dist/client/assets/{chunk-QZHKN3VN-BOdvRibu.js → chunk-QZHKN3VN-DfsoMKS1.js} +1 -1
  16. package/dist/client/assets/{chunk-YZCP3GAM-BDIEapcU.js → chunk-YZCP3GAM-nK3fd1vK.js} +1 -1
  17. package/dist/client/assets/classDiagram-6PBFFD2Q-BXKZy3mo.js +1 -0
  18. package/dist/client/assets/classDiagram-v2-HSJHXN6E-BXKZy3mo.js +1 -0
  19. package/dist/client/assets/clone-BaDkZwuY.js +1 -0
  20. package/dist/client/assets/{cose-bilkent-S5V4N54A-BVlc3Dad.js → cose-bilkent-S5V4N54A-BXTuHPIj.js} +1 -1
  21. package/dist/client/assets/{dagre-KV5264BT-raV4BK6w.js → dagre-KV5264BT-Bz4nIBUS.js} +1 -1
  22. package/dist/client/assets/{diagram-5BDNPKRD-BMAh1LJt.js → diagram-5BDNPKRD-ByqgG2sy.js} +1 -1
  23. package/dist/client/assets/{diagram-G4DWMVQ6-AM5t2f3j.js → diagram-G4DWMVQ6-D6Bh5CCC.js} +1 -1
  24. package/dist/client/assets/{diagram-MMDJMWI5-DMw_UwWR.js → diagram-MMDJMWI5-CfDQLuVW.js} +1 -1
  25. package/dist/client/assets/{diagram-TYMM5635-CiDAD3e9.js → diagram-TYMM5635-KI98IUyc.js} +1 -1
  26. package/dist/client/assets/{erDiagram-SMLLAGMA-D1IXSwFZ.js → erDiagram-SMLLAGMA-CqprUEiC.js} +1 -1
  27. package/dist/client/assets/{flowDiagram-DWJPFMVM-pEq1L6HP.js → flowDiagram-DWJPFMVM-ABvSPDqG.js} +1 -1
  28. package/dist/client/assets/{ganttDiagram-T4ZO3ILL-BAXTU16v.js → ganttDiagram-T4ZO3ILL-Dsx-U_Nn.js} +1 -1
  29. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-PkEvpSio.js → gitGraphDiagram-UUTBAWPF-BSUFEUnR.js} +1 -1
  30. package/dist/client/assets/{graph-OOLam9NE.js → graph-CzNfjcWq.js} +1 -1
  31. package/dist/client/assets/index-DJz5h6Wn.js +482 -0
  32. package/dist/client/assets/index-DuMyAui7.css +1 -0
  33. package/dist/client/assets/{infoDiagram-42DDH7IO-CcLveXnw.js → infoDiagram-42DDH7IO-Ds0ENS7R.js} +1 -1
  34. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-D-p_eX0W.js → ishikawaDiagram-UXIWVN3A-B18YRUdD.js} +1 -1
  35. package/dist/client/assets/{journeyDiagram-VCZTEJTY-D81ht6ZY.js → journeyDiagram-VCZTEJTY-BupFc-Zd.js} +1 -1
  36. package/dist/client/assets/{kanban-definition-6JOO6SKY-D8SIIBWy.js → kanban-definition-6JOO6SKY-BSLofAt_.js} +1 -1
  37. package/dist/client/assets/{layout-CeMy87tl.js → layout-Dv50_a5m.js} +1 -1
  38. package/dist/client/assets/{linear-C0Txkuhf.js → linear-BrKghOcX.js} +1 -1
  39. package/dist/client/assets/{mermaid.core-zdTcCzmD.js → mermaid.core-CxEBNiNY.js} +4 -4
  40. package/dist/client/assets/{min-BPp_9Vnv.js → min-CCGzVC9y.js} +1 -1
  41. package/dist/client/assets/{mindmap-definition-QFDTVHPH-DRJbMf8W.js → mindmap-definition-QFDTVHPH-BrgndQgy.js} +1 -1
  42. package/dist/client/assets/{pieDiagram-DEJITSTG-CqPISwE-.js → pieDiagram-DEJITSTG-DaQb845q.js} +1 -1
  43. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-BGLaujDD.js → quadrantDiagram-34T5L4WZ-BKqKJrUA.js} +1 -1
  44. package/dist/client/assets/{requirementDiagram-MS252O5E-DWF6sFQ_.js → requirementDiagram-MS252O5E-DDEiXfFB.js} +1 -1
  45. package/dist/client/assets/{sankeyDiagram-XADWPNL6-Dv2O23aj.js → sankeyDiagram-XADWPNL6-Bz8ZuIVc.js} +1 -1
  46. package/dist/client/assets/{sequenceDiagram-FGHM5R23-BXgYtweg.js → sequenceDiagram-FGHM5R23-DIKllaFC.js} +1 -1
  47. package/dist/client/assets/{stateDiagram-FHFEXIEX-COtDK2gC.js → stateDiagram-FHFEXIEX-DVGXi05S.js} +1 -1
  48. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BpjJbYdv.js +1 -0
  49. package/dist/client/assets/{timeline-definition-GMOUNBTQ-B_-QYLHK.js → timeline-definition-GMOUNBTQ-BysxZqEH.js} +1 -1
  50. package/dist/client/assets/{vennDiagram-DHZGUBPP-BNVDRgAa.js → vennDiagram-DHZGUBPP-zKrKFH0U.js} +1 -1
  51. package/dist/client/assets/{wardley-RL74JXVD-B3XwoW1A.js → wardley-RL74JXVD-Bfs1dQE_.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-B6-OKLa-.js → wardleyDiagram-NUSXRM2D-9Nv_Fl4e.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-5P7HB3ND-DepfDqrZ.js → xychartDiagram-5P7HB3ND-BxnDNWHU.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/fleet-plugins/ledger/routes.mjs +45 -105
  56. package/dist/fleet-plugins/quota/routes.mjs +455 -64
  57. package/dist/fleet-plugins/repository/routes.mjs +26 -9
  58. package/dist/fleet-plugins/scuttlebutt/routes.mjs +150 -703
  59. package/dist/fleet-plugins/skills/routes.mjs +45 -105
  60. package/dist/fleet-plugins/terminal/routes.mjs +1297 -975
  61. package/package.json +1 -1
  62. package/dist/client/assets/channel-BhciS8jR.js +0 -1
  63. package/dist/client/assets/classDiagram-6PBFFD2Q-BjRUnHce.js +0 -1
  64. package/dist/client/assets/classDiagram-v2-HSJHXN6E-BjRUnHce.js +0 -1
  65. package/dist/client/assets/clone-DygBt67n.js +0 -1
  66. package/dist/client/assets/index-BgHFwZy9.css +0 -1
  67. package/dist/client/assets/index-CvXOp1di.js +0 -452
  68. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BzCFtEfV.js +0 -1
@@ -20150,225 +20150,6 @@ function inferBinName(packageName) {
20150
20150
  return lastSegment.replace(/@[^@/]+$/, "");
20151
20151
  }
20152
20152
 
20153
- // ../../packages/core-unified-agent/models.json
20154
- var models_default = {
20155
- version: 1,
20156
- updatedAt: "2026-07-25T00:00:00Z",
20157
- providers: {
20158
- claude: {
20159
- name: "Claude Code",
20160
- defaultModel: "opus[1m]",
20161
- models: [
20162
- { modelId: "haiku", name: "Claude Haiku", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "low" } },
20163
- { modelId: "sonnet", name: "Claude Sonnet", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20164
- { modelId: "opus", name: "Claude Opus", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20165
- { modelId: "opus[1m]", name: "Claude Opus [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20166
- { modelId: "claude-opus-4-6[1m]", name: "Claude Opus 4.6 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20167
- { modelId: "claude-opus-4-7[1m]", name: "Claude Opus 4.7 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20168
- { modelId: "claude-opus-4-8[1m]", name: "Claude Opus 4.8 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } }
20169
- ]
20170
- },
20171
- codex: {
20172
- name: "Codex",
20173
- defaultModel: "gpt-5.6-sol",
20174
- models: [
20175
- { modelId: "gpt-5.6-sol", name: "GPT-5.6-Sol", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
20176
- { modelId: "gpt-5.6-sol-fast", name: "GPT-5.6-Sol Fast", providerModelId: "gpt-5.6-sol", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
20177
- { modelId: "gpt-5.6-terra", name: "GPT-5.6-Terra", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
20178
- { modelId: "gpt-5.6-terra-fast", name: "GPT-5.6-Terra Fast", providerModelId: "gpt-5.6-terra", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
20179
- { modelId: "gpt-5.6-luna", name: "GPT-5.6-Luna", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20180
- { modelId: "gpt-5.6-luna-fast", name: "GPT-5.6-Luna Fast", providerModelId: "gpt-5.6-luna", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20181
- { modelId: "gpt-5.5", name: "GPT-5.5", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } },
20182
- { modelId: "gpt-5.5-fast", name: "GPT-5.5 Fast", providerModelId: "gpt-5.5", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } }
20183
- ]
20184
- },
20185
- cursor: {
20186
- name: "Cursor Agent",
20187
- defaultModel: "auto",
20188
- models: [
20189
- { modelId: "auto", name: "Auto", effort: { supported: false } },
20190
- { modelId: "composer-2.5", name: "Composer 2.5", effort: { supported: false } },
20191
- { modelId: "composer-2.5-fast", name: "Composer 2.5 Fast", effort: { supported: false } },
20192
- { modelId: "gpt-5.6-sol", name: "GPT-5.6 Sol", spawnModelTemplate: "gpt-5.6-sol-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20193
- { modelId: "gpt-5.6-sol-fast", name: "GPT-5.6 Sol Fast", spawnModelTemplate: "gpt-5.6-sol-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20194
- { modelId: "gpt-5.6-sol-none", name: "GPT-5.6 Sol None", effort: { supported: false } },
20195
- { modelId: "gpt-5.6-sol-none-fast", name: "GPT-5.6 Sol None Fast", effort: { supported: false } },
20196
- { modelId: "gpt-5.6-luna", name: "GPT-5.6 Luna", spawnModelTemplate: "gpt-5.6-luna-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20197
- { modelId: "gpt-5.6-luna-fast", name: "GPT-5.6 Luna Fast", spawnModelTemplate: "gpt-5.6-luna-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20198
- { modelId: "gpt-5.6-luna-none", name: "GPT-5.6 Luna None", effort: { supported: false } },
20199
- { modelId: "gpt-5.6-luna-none-fast", name: "GPT-5.6 Luna None Fast", effort: { supported: false } },
20200
- { modelId: "gpt-5.6-terra", name: "GPT-5.6 Terra", spawnModelTemplate: "gpt-5.6-terra-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20201
- { modelId: "gpt-5.6-terra-fast", name: "GPT-5.6 Terra Fast", spawnModelTemplate: "gpt-5.6-terra-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20202
- { modelId: "gpt-5.6-terra-none", name: "GPT-5.6 Terra None", effort: { supported: false } },
20203
- { modelId: "gpt-5.6-terra-none-fast", name: "GPT-5.6 Terra None Fast", effort: { supported: false } },
20204
- { modelId: "cursor-grok-4.5", name: "Cursor Grok 4.5", spawnModelTemplate: "cursor-grok-4.5-{effort}", effort: { supported: true, levels: ["low", "medium", "high"], default: "high" } },
20205
- { modelId: "cursor-grok-4.5-fast", name: "Cursor Grok 4.5 Fast", spawnModelTemplate: "cursor-grok-4.5-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high"], default: "high" } },
20206
- { modelId: "claude-opus-4-8", name: "Claude Opus 4.8", spawnModelTemplate: "claude-opus-4-8-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20207
- { modelId: "claude-opus-4-8-fast", name: "Claude Opus 4.8 Fast", spawnModelTemplate: "claude-opus-4-8-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20208
- { modelId: "claude-opus-4-8-thinking", name: "Claude Opus 4.8 Thinking", spawnModelTemplate: "claude-opus-4-8-thinking-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20209
- { modelId: "claude-opus-4-8-thinking-fast", name: "Claude Opus 4.8 Thinking Fast", spawnModelTemplate: "claude-opus-4-8-thinking-{effort}-fast", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20210
- { modelId: "claude-sonnet-5", name: "Claude Sonnet 5", spawnModelTemplate: "claude-sonnet-5-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20211
- { modelId: "claude-sonnet-5-thinking", name: "Claude Sonnet 5 Thinking", spawnModelTemplate: "claude-sonnet-5-thinking-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20212
- { modelId: "claude-fable-5", name: "Claude Fable 5", description: "NO ZDR per cursor-agent model list", spawnModelTemplate: "claude-fable-5-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20213
- { modelId: "claude-fable-5-thinking", name: "Claude Fable 5 Thinking", description: "NO ZDR per cursor-agent model list", spawnModelTemplate: "claude-fable-5-thinking-{effort}", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20214
- { modelId: "gemini-3.1-pro", name: "Gemini 3.1 Pro", effort: { supported: false } },
20215
- { modelId: "gemini-3.5-flash", name: "Gemini 3.5 Flash", effort: { supported: false } },
20216
- { modelId: "kimi-k2.7-code", name: "Kimi K2.7 Code", effort: { supported: false } },
20217
- { modelId: "glm-5.2", name: "GLM 5.2", spawnModelTemplate: "glm-5.2-{effort}", effort: { supported: true, levels: ["high", "max"], default: "max" } }
20218
- ]
20219
- }
20220
- }
20221
- };
20222
-
20223
- // ../../packages/core-unified-agent/src/models/registry.ts
20224
- var EffortLevelSchema = external_exports.enum([
20225
- "low",
20226
- "medium",
20227
- "high",
20228
- "xhigh",
20229
- "max",
20230
- "ultra"
20231
- ]);
20232
- var EffortSchema = external_exports.union([
20233
- external_exports.object({
20234
- supported: external_exports.literal(true),
20235
- levels: external_exports.array(EffortLevelSchema).min(1),
20236
- default: EffortLevelSchema
20237
- }).strict(),
20238
- external_exports.object({
20239
- supported: external_exports.literal(false)
20240
- }).strict()
20241
- ]);
20242
- var ModelEntrySchema = external_exports.object({
20243
- /** 모델 고유 식별자 (session/set_model에 전달되는 값) */
20244
- modelId: external_exports.string(),
20245
- /** 사람이 읽을 수 있는 모델 이름 */
20246
- name: external_exports.string(),
20247
- /** 모델 설명 (선택) */
20248
- description: external_exports.string().optional(),
20249
- /** spawn 시 실제 CLI 모델 ID를 조립해야 하는 경우의 템플릿 */
20250
- spawnModelTemplate: external_exports.string().optional(),
20251
- /** 카탈로그 ID와 실제 provider 모델 ID가 다른 경우의 원본 모델 ID */
20252
- providerModelId: external_exports.string().optional(),
20253
- /** provider가 모델과 별도로 받는 서비스 티어 */
20254
- serviceTier: external_exports.string().optional(),
20255
- /** 모델의 컨텍스트 윈도우 크기 (토큰) */
20256
- contextWindow: external_exports.number().int().positive().optional(),
20257
- /** 모델별 effort 설정 */
20258
- effort: EffortSchema
20259
- }).check((ctx) => {
20260
- const effort = ctx.value.effort;
20261
- if (effort.supported && !effort.levels.includes(effort.default)) {
20262
- ctx.issues.push({
20263
- code: "custom",
20264
- input: effort.default,
20265
- message: `effort.default "${effort.default}"\uC740(\uB294) levels \uBAA9\uB85D\uC5D0 \uC874\uC7AC\uD574\uC57C \uD569\uB2C8\uB2E4`,
20266
- path: ["effort", "default"]
20267
- });
20268
- }
20269
- if (effort.supported && ctx.value.spawnModelTemplate && !ctx.value.spawnModelTemplate.includes("{effort}")) {
20270
- ctx.issues.push({
20271
- code: "custom",
20272
- input: ctx.value.spawnModelTemplate,
20273
- message: 'effort \uC9C0\uC6D0 \uBAA8\uB378\uC758 spawnModelTemplate\uC740 "{effort}" \uD50C\uB808\uC774\uC2A4\uD640\uB354\uB97C \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4',
20274
- path: ["spawnModelTemplate"]
20275
- });
20276
- }
20277
- if (ctx.value.serviceTier && !ctx.value.providerModelId) {
20278
- ctx.issues.push({
20279
- code: "custom",
20280
- input: ctx.value.serviceTier,
20281
- message: "serviceTier\uB97C \uC9C0\uC815\uD55C \uBAA8\uB378\uC740 providerModelId\uB3C4 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4",
20282
- path: ["serviceTier"]
20283
- });
20284
- }
20285
- });
20286
- var ProviderSchema = external_exports.object({
20287
- /** 프로바이더 표시 이름 */
20288
- name: external_exports.string(),
20289
- /** 기본 모델 ID */
20290
- defaultModel: external_exports.string(),
20291
- /** 사용 가능한 모델 목록 */
20292
- models: external_exports.array(ModelEntrySchema).min(1)
20293
- }).check((ctx) => {
20294
- const ids = new Set(ctx.value.models.map((m) => m.modelId));
20295
- if (!ids.has(ctx.value.defaultModel)) {
20296
- ctx.issues.push({
20297
- code: "custom",
20298
- input: ctx.value.defaultModel,
20299
- message: `defaultModel "${ctx.value.defaultModel}"\uC740(\uB294) models \uBAA9\uB85D\uC5D0 \uC874\uC7AC\uD574\uC57C \uD569\uB2C8\uB2E4`,
20300
- path: ["defaultModel"]
20301
- });
20302
- }
20303
- });
20304
- var ModelMappingSchema = external_exports.record(external_exports.string(), external_exports.string());
20305
- var ModelsMapperEntrySchema = external_exports.object({
20306
- /** modelId와 연관된 매핑 모델 */
20307
- modelMapping: ModelMappingSchema,
20308
- /** 추가 환경변수 (선택) */
20309
- env: external_exports.record(external_exports.string(), external_exports.string()).optional()
20310
- });
20311
- external_exports.record(external_exports.string(), ModelsMapperEntrySchema);
20312
- var ModelsRegistrySchema = external_exports.object({
20313
- /** 스키마 버전 */
20314
- version: external_exports.number().int().positive(),
20315
- /** 최종 업데이트 시각 */
20316
- updatedAt: external_exports.string(),
20317
- /** 프로바이더별 모델 정보 */
20318
- providers: external_exports.record(external_exports.string(), ProviderSchema)
20319
- });
20320
- var registry2 = Object.freeze(ModelsRegistrySchema.parse(models_default));
20321
- function getProviderModels(cli) {
20322
- const provider = registry2.providers[cli];
20323
- if (!provider) {
20324
- throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uBC14\uC774\uB354: "${cli}"`);
20325
- }
20326
- return structuredClone(provider);
20327
- }
20328
- function getEffort(cli, modelId) {
20329
- const model = findProviderModel(cli, modelId);
20330
- return structuredClone(model.effort);
20331
- }
20332
- function getCursorSpawnEffortInfo(modelId) {
20333
- const model = findProviderModel("cursor", modelId);
20334
- if (!model.spawnModelTemplate || !model.effort.supported) {
20335
- return { supported: false, levels: [], default: null };
20336
- }
20337
- return {
20338
- supported: true,
20339
- levels: [...model.effort.levels],
20340
- default: model.effort.default
20341
- };
20342
- }
20343
- function resolveCursorSpawnModel(modelId, effort) {
20344
- const model = findProviderModel("cursor", modelId);
20345
- if (!model.spawnModelTemplate) {
20346
- return model.modelId;
20347
- }
20348
- if (!model.effort.supported) {
20349
- return model.spawnModelTemplate;
20350
- }
20351
- const level = effort ?? model.effort.default;
20352
- const resolvedLevel = model.effort.levels.includes(level) ? level : model.effort.default;
20353
- if (!model.effort.levels.includes(resolvedLevel)) {
20354
- throw new Error(
20355
- `cursor/${modelId} \uBAA8\uB378\uC740 effort "${level}"\uC744(\uB97C) \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9 \uAC00\uB2A5: ${model.effort.levels.join(", ")}`
20356
- );
20357
- }
20358
- return model.spawnModelTemplate.replace("{effort}", resolvedLevel);
20359
- }
20360
- function findProviderModel(cli, modelId) {
20361
- const provider = registry2.providers[cli];
20362
- if (!provider) {
20363
- throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uBC14\uC774\uB354: "${cli}"`);
20364
- }
20365
- const model = provider.models.find((m) => m.modelId === modelId);
20366
- if (!model) {
20367
- throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uBAA8\uB378: "${cli}/${modelId}"`);
20368
- }
20369
- return model;
20370
- }
20371
-
20372
20153
  // ../../packages/core-unified-agent/src/config/CliConfigs.ts
20373
20154
  var CLI_BACKENDS = {
20374
20155
  claude: {
@@ -20404,21 +20185,6 @@ var CLI_BACKENDS = {
20404
20185
  requiresModelAtSpawn: false,
20405
20186
  usesNpxBridge: false,
20406
20187
  defaultMaxTokens: 1e5
20407
- },
20408
- cursor: {
20409
- id: "cursor",
20410
- cliCommand: "cursor-agent",
20411
- protocol: "acp",
20412
- authRequired: true,
20413
- acpArgs: ["acp"],
20414
- modes: [
20415
- { id: "agent", label: "Agent" }
20416
- ],
20417
- supportsSessionClose: false,
20418
- supportsSessionLoad: true,
20419
- requiresModelAtSpawn: true,
20420
- usesNpxBridge: false,
20421
- defaultMaxTokens: 2e5
20422
20188
  }
20423
20189
  };
20424
20190
  function createSpawnConfig(cli, options) {
@@ -20449,9 +20215,6 @@ function createSpawnConfig(cli, options) {
20449
20215
  }
20450
20216
  const command = options.cliPath ?? backend.cliCommand;
20451
20217
  const args = backend.acpArgs ? [...backend.acpArgs] : [];
20452
- if (cli === "cursor" && options.model) {
20453
- args.unshift("--model", resolveCursorSpawnModel(options.model, options.effort));
20454
- }
20455
20218
  return {
20456
20219
  command,
20457
20220
  args,
@@ -20465,8 +20228,6 @@ function getYoloModeId(cli) {
20465
20228
  switch (cli) {
20466
20229
  case "claude":
20467
20230
  return "bypassPermissions";
20468
- case "cursor":
20469
- return "agent";
20470
20231
  case "codex":
20471
20232
  return "yolo";
20472
20233
  }
@@ -20665,6 +20426,152 @@ var CliDetector = class {
20665
20426
  }
20666
20427
  };
20667
20428
 
20429
+ // ../../packages/core-unified-agent/models.json
20430
+ var models_default = {
20431
+ version: 1,
20432
+ updatedAt: "2026-07-25T00:00:00Z",
20433
+ providers: {
20434
+ claude: {
20435
+ name: "Claude Code",
20436
+ defaultModel: "opus[1m]",
20437
+ models: [
20438
+ { modelId: "haiku", name: "Claude Haiku", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "low" } },
20439
+ { modelId: "sonnet", name: "Claude Sonnet", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20440
+ { modelId: "opus", name: "Claude Opus", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20441
+ { modelId: "opus[1m]", name: "Claude Opus [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
20442
+ { modelId: "claude-opus-4-6[1m]", name: "Claude Opus 4.6 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20443
+ { modelId: "claude-opus-4-7[1m]", name: "Claude Opus 4.7 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
20444
+ { modelId: "claude-opus-4-8[1m]", name: "Claude Opus 4.8 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } }
20445
+ ]
20446
+ },
20447
+ codex: {
20448
+ name: "Codex",
20449
+ defaultModel: "gpt-5.6-sol",
20450
+ models: [
20451
+ { modelId: "gpt-5.6-sol", name: "GPT-5.6-Sol", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
20452
+ { modelId: "gpt-5.6-sol-fast", name: "GPT-5.6-Sol Fast", providerModelId: "gpt-5.6-sol", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
20453
+ { modelId: "gpt-5.6-terra", name: "GPT-5.6-Terra", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
20454
+ { modelId: "gpt-5.6-terra-fast", name: "GPT-5.6-Terra Fast", providerModelId: "gpt-5.6-terra", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
20455
+ { modelId: "gpt-5.6-luna", name: "GPT-5.6-Luna", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20456
+ { modelId: "gpt-5.6-luna-fast", name: "GPT-5.6-Luna Fast", providerModelId: "gpt-5.6-luna", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
20457
+ { modelId: "gpt-5.5", name: "GPT-5.5", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } },
20458
+ { modelId: "gpt-5.5-fast", name: "GPT-5.5 Fast", providerModelId: "gpt-5.5", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } }
20459
+ ]
20460
+ }
20461
+ }
20462
+ };
20463
+
20464
+ // ../../packages/core-unified-agent/src/models/registry.ts
20465
+ var EffortLevelSchema = external_exports.enum([
20466
+ "low",
20467
+ "medium",
20468
+ "high",
20469
+ "xhigh",
20470
+ "max",
20471
+ "ultra"
20472
+ ]);
20473
+ var EffortSchema = external_exports.union([
20474
+ external_exports.object({
20475
+ supported: external_exports.literal(true),
20476
+ levels: external_exports.array(EffortLevelSchema).min(1),
20477
+ default: EffortLevelSchema
20478
+ }).strict(),
20479
+ external_exports.object({
20480
+ supported: external_exports.literal(false)
20481
+ }).strict()
20482
+ ]);
20483
+ var ModelEntrySchema = external_exports.object({
20484
+ /** 모델 고유 식별자 (session/set_model에 전달되는 값) */
20485
+ modelId: external_exports.string(),
20486
+ /** 사람이 읽을 수 있는 모델 이름 */
20487
+ name: external_exports.string(),
20488
+ /** 모델 설명 (선택) */
20489
+ description: external_exports.string().optional(),
20490
+ /** 카탈로그 ID와 실제 provider 모델 ID가 다른 경우의 원본 모델 ID */
20491
+ providerModelId: external_exports.string().optional(),
20492
+ /** provider가 모델과 별도로 받는 서비스 티어 */
20493
+ serviceTier: external_exports.string().optional(),
20494
+ /** 모델의 컨텍스트 윈도우 크기 (토큰) */
20495
+ contextWindow: external_exports.number().int().positive().optional(),
20496
+ /** 모델별 effort 설정 */
20497
+ effort: EffortSchema
20498
+ }).check((ctx) => {
20499
+ const effort = ctx.value.effort;
20500
+ if (effort.supported && !effort.levels.includes(effort.default)) {
20501
+ ctx.issues.push({
20502
+ code: "custom",
20503
+ input: effort.default,
20504
+ message: `effort.default "${effort.default}"\uC740(\uB294) levels \uBAA9\uB85D\uC5D0 \uC874\uC7AC\uD574\uC57C \uD569\uB2C8\uB2E4`,
20505
+ path: ["effort", "default"]
20506
+ });
20507
+ }
20508
+ if (ctx.value.serviceTier && !ctx.value.providerModelId) {
20509
+ ctx.issues.push({
20510
+ code: "custom",
20511
+ input: ctx.value.serviceTier,
20512
+ message: "serviceTier\uB97C \uC9C0\uC815\uD55C \uBAA8\uB378\uC740 providerModelId\uB3C4 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4",
20513
+ path: ["serviceTier"]
20514
+ });
20515
+ }
20516
+ });
20517
+ var ProviderSchema = external_exports.object({
20518
+ /** 프로바이더 표시 이름 */
20519
+ name: external_exports.string(),
20520
+ /** 기본 모델 ID */
20521
+ defaultModel: external_exports.string(),
20522
+ /** 사용 가능한 모델 목록 */
20523
+ models: external_exports.array(ModelEntrySchema).min(1)
20524
+ }).check((ctx) => {
20525
+ const ids = new Set(ctx.value.models.map((m) => m.modelId));
20526
+ if (!ids.has(ctx.value.defaultModel)) {
20527
+ ctx.issues.push({
20528
+ code: "custom",
20529
+ input: ctx.value.defaultModel,
20530
+ message: `defaultModel "${ctx.value.defaultModel}"\uC740(\uB294) models \uBAA9\uB85D\uC5D0 \uC874\uC7AC\uD574\uC57C \uD569\uB2C8\uB2E4`,
20531
+ path: ["defaultModel"]
20532
+ });
20533
+ }
20534
+ });
20535
+ var ModelMappingSchema = external_exports.record(external_exports.string(), external_exports.string());
20536
+ var ModelsMapperEntrySchema = external_exports.object({
20537
+ /** modelId와 연관된 매핑 모델 */
20538
+ modelMapping: ModelMappingSchema,
20539
+ /** 추가 환경변수 (선택) */
20540
+ env: external_exports.record(external_exports.string(), external_exports.string()).optional()
20541
+ });
20542
+ external_exports.record(external_exports.string(), ModelsMapperEntrySchema);
20543
+ var ModelsRegistrySchema = external_exports.object({
20544
+ /** 스키마 버전 */
20545
+ version: external_exports.number().int().positive(),
20546
+ /** 최종 업데이트 시각 */
20547
+ updatedAt: external_exports.string(),
20548
+ /** 프로바이더별 모델 정보 */
20549
+ providers: external_exports.record(external_exports.string(), ProviderSchema)
20550
+ });
20551
+ var registry2 = Object.freeze(ModelsRegistrySchema.parse(models_default));
20552
+ function getProviderModels(cli) {
20553
+ const provider = registry2.providers[cli];
20554
+ if (!provider) {
20555
+ throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uBC14\uC774\uB354: "${cli}"`);
20556
+ }
20557
+ return structuredClone(provider);
20558
+ }
20559
+ function getEffort(cli, modelId) {
20560
+ const model = findProviderModel(cli, modelId);
20561
+ return structuredClone(model.effort);
20562
+ }
20563
+ function findProviderModel(cli, modelId) {
20564
+ const provider = registry2.providers[cli];
20565
+ if (!provider) {
20566
+ throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uD504\uB85C\uBC14\uC774\uB354: "${cli}"`);
20567
+ }
20568
+ const model = provider.models.find((m) => m.modelId === modelId);
20569
+ if (!model) {
20570
+ throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uBAA8\uB378: "${cli}/${modelId}"`);
20571
+ }
20572
+ return model;
20573
+ }
20574
+
20668
20575
  // ../../packages/core-unified-agent/src/client/UnifiedClaudeAgentClient.ts
20669
20576
  var UnifiedClaudeAgentClient = class extends EventEmitter {
20670
20577
  connection = null;
@@ -22396,453 +22303,6 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
22396
22303
  };
22397
22304
  }
22398
22305
  };
22399
- var UnifiedCursorAgentClient = class extends EventEmitter {
22400
- connection = null;
22401
- sessionId = null;
22402
- sessionCwd = null;
22403
- currentSystemPrompt = null;
22404
- firstPromptPending = null;
22405
- currentConnectOptions = null;
22406
- detector = new CliDetector();
22407
- on(event, listener) {
22408
- return super.on(event, listener);
22409
- }
22410
- once(event, listener) {
22411
- return super.once(event, listener);
22412
- }
22413
- off(event, listener) {
22414
- return super.off(event, listener);
22415
- }
22416
- emitTyped(event, ...args) {
22417
- return super.emit(event, ...args);
22418
- }
22419
- async connect(options) {
22420
- await this.disconnect();
22421
- if (options.cli && options.cli !== "cursor") {
22422
- throw new Error("UnifiedCursorAgentClient\uB294 cursor CLI\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.");
22423
- }
22424
- const acpMcpServers = this.resolveMcpServers(options.mcpServers);
22425
- const spawnConfig = createSpawnConfig("cursor", options);
22426
- const cleanEnv = cleanEnvironment(process.env, options.env);
22427
- const env = { ...cleanEnv };
22428
- const connection = new AcpConnection({
22429
- command: spawnConfig.command,
22430
- args: spawnConfig.args,
22431
- cliType: "cursor",
22432
- cwd: options.cwd,
22433
- env,
22434
- requestTimeout: options.timeout,
22435
- initTimeout: options.timeout,
22436
- promptIdleTimeout: options.promptIdleTimeout,
22437
- clientInfo: options.clientInfo,
22438
- autoApprove: options.autoApprove,
22439
- fsAccess: options.fsAccess
22440
- });
22441
- this.connection = connection;
22442
- this.setupEventForwarding();
22443
- const recentLogs = [];
22444
- const collectLog = (message) => {
22445
- recentLogs.push(message);
22446
- if (recentLogs.length > 30) {
22447
- recentLogs.shift();
22448
- }
22449
- };
22450
- connection.on("log", collectLog);
22451
- let session;
22452
- try {
22453
- session = await connection.connect(
22454
- options.cwd,
22455
- options.sessionId,
22456
- acpMcpServers,
22457
- options.systemPrompt
22458
- );
22459
- } catch (error51) {
22460
- const connectionError = this.buildConnectionError(error51, recentLogs);
22461
- await this.cleanupFailedConnection();
22462
- throw connectionError;
22463
- } finally {
22464
- connection.off("log", collectLog);
22465
- }
22466
- return this.finalizeConnect(options, session);
22467
- }
22468
- async disconnect() {
22469
- if (!this.connection) {
22470
- this.clearSessionState();
22471
- return;
22472
- }
22473
- const conn = this.connection;
22474
- if (this.sessionId && conn.canResetSession) {
22475
- try {
22476
- await conn.endSession(this.sessionId);
22477
- } catch {
22478
- }
22479
- }
22480
- await conn.disconnect();
22481
- conn.removeAllListeners();
22482
- this.connection = null;
22483
- this.clearSessionState();
22484
- }
22485
- async endSession() {
22486
- if (!this.connection || !this.sessionId) {
22487
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22488
- }
22489
- await this.connection.endSession(this.sessionId);
22490
- this.sessionId = null;
22491
- }
22492
- getConnectionInfo() {
22493
- return {
22494
- cli: this.connection ? "cursor" : null,
22495
- protocol: this.connection ? "acp" : null,
22496
- sessionId: this.sessionId,
22497
- state: this.connection ? this.connection.connectionState : "disconnected"
22498
- };
22499
- }
22500
- async detectClis() {
22501
- return this.detector.detectAll(true);
22502
- }
22503
- async sendMessage(content) {
22504
- if (!this.connection || !this.sessionId) {
22505
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22506
- }
22507
- const systemPrompt = this.firstPromptPending;
22508
- if (!systemPrompt) {
22509
- return this.connection.sendPrompt(this.sessionId, content);
22510
- }
22511
- const userBlocks = typeof content === "string" ? [{ type: "text", text: content }] : content;
22512
- const response = await this.connection.sendPrompt(this.sessionId, [
22513
- { type: "text", text: systemPrompt },
22514
- ...userBlocks
22515
- ]);
22516
- this.firstPromptPending = null;
22517
- return response;
22518
- }
22519
- async cancelPrompt() {
22520
- if (!this.connection || !this.sessionId) {
22521
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22522
- }
22523
- await this.connection.cancelSession(this.sessionId);
22524
- }
22525
- async setModel(model) {
22526
- if (!this.connection || !this.sessionId) {
22527
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22528
- }
22529
- const reconnectOptions = this.buildReconnectOptions(model);
22530
- await this.reconnectWithRestore(reconnectOptions, "\uBAA8\uB378 \uBCC0\uACBD");
22531
- }
22532
- async setConfigOption(configId, value) {
22533
- if (!this.connection || !this.sessionId) {
22534
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22535
- }
22536
- if (configId === "effort" || configId === "reasoning_effort") {
22537
- const reconnectOptions = this.buildEffortReconnectOptions(value);
22538
- if (!reconnectOptions) {
22539
- return;
22540
- }
22541
- await this.reconnectWithRestore(reconnectOptions, "effort \uBCC0\uACBD");
22542
- return;
22543
- }
22544
- if (configId === "model") {
22545
- return this.setModel(value);
22546
- }
22547
- await this.connection.setConfigOption(this.sessionId, configId, value);
22548
- }
22549
- async setMode(mode) {
22550
- if (!this.connection || !this.sessionId) {
22551
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22552
- }
22553
- await this.connection.setMode(this.sessionId, mode);
22554
- }
22555
- async setYoloMode(enabled) {
22556
- return this.setMode(enabled ? getYoloModeId("cursor") : "default");
22557
- }
22558
- getAvailableModes() {
22559
- return getBackendConfig("cursor").modes ?? [];
22560
- }
22561
- getAvailableModels() {
22562
- return getProviderModels("cursor");
22563
- }
22564
- getCurrentSystemPrompt() {
22565
- return this.currentSystemPrompt;
22566
- }
22567
- async loadSession(sessionId, mcpServers) {
22568
- if (!this.connection) {
22569
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22570
- }
22571
- await this.connection.loadSession({
22572
- sessionId,
22573
- cwd: this.sessionCwd ?? process.cwd(),
22574
- mcpServers: this.resolveMcpServers(mcpServers)
22575
- });
22576
- this.sessionId = sessionId;
22577
- this.currentSystemPrompt = null;
22578
- this.firstPromptPending = null;
22579
- }
22580
- async resetSession(cwd) {
22581
- if (!this.connection || !this.sessionId) {
22582
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
22583
- }
22584
- const targetCwd = cwd ?? this.sessionCwd ?? process.cwd();
22585
- if (!this.connection.canResetSession) {
22586
- throw new Error("[cursor] \uC138\uC158 \uB9AC\uC14B\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. disconnect() \uD6C4 \uC7AC\uC5F0\uACB0\uD558\uC138\uC694.");
22587
- }
22588
- await this.connection.endSession(this.sessionId);
22589
- this.sessionId = null;
22590
- const session = this.currentSystemPrompt ? await this.connection.reconnectSession(
22591
- targetCwd,
22592
- void 0,
22593
- void 0,
22594
- this.currentSystemPrompt
22595
- ) : await this.connection.reconnectSession(targetCwd);
22596
- this.sessionId = session.sessionId;
22597
- this.sessionCwd = targetCwd;
22598
- this.firstPromptPending = this.currentSystemPrompt;
22599
- return {
22600
- cli: "cursor",
22601
- protocol: "acp",
22602
- session
22603
- };
22604
- }
22605
- resolveMcpServers(servers) {
22606
- return servers?.length ? mcpServerConfigsToAcp(servers) : [];
22607
- }
22608
- async finalizeConnect(options, session) {
22609
- if (options.yoloMode && session.sessionId) {
22610
- try {
22611
- await this.connection.setMode(session.sessionId, getYoloModeId("cursor"));
22612
- } catch {
22613
- }
22614
- }
22615
- this.sessionId = session.sessionId;
22616
- this.sessionCwd = options.cwd;
22617
- this.currentSystemPrompt = options.systemPrompt ?? null;
22618
- this.firstPromptPending = options.sessionId ? null : this.currentSystemPrompt;
22619
- this.currentConnectOptions = this.cloneConnectOptions(options);
22620
- return {
22621
- cli: "cursor",
22622
- protocol: "acp",
22623
- session
22624
- };
22625
- }
22626
- clearSessionState() {
22627
- this.sessionId = null;
22628
- this.sessionCwd = null;
22629
- this.currentSystemPrompt = null;
22630
- this.firstPromptPending = null;
22631
- this.currentConnectOptions = null;
22632
- }
22633
- buildReconnectOptions(model) {
22634
- if (!this.currentConnectOptions) {
22635
- throw new Error("[cursor] \uBAA8\uB378 \uC804\uD658\uC744 \uC704\uD55C \uAE30\uC874 \uC5F0\uACB0 \uC635\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. connect()\uB85C \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.");
22636
- }
22637
- const options = this.cloneConnectOptions(this.currentConnectOptions);
22638
- const effort = this.resolveEffortForModel(model, options.effort);
22639
- delete options.sessionId;
22640
- if (effort) {
22641
- options.effort = effort;
22642
- } else {
22643
- delete options.effort;
22644
- }
22645
- return {
22646
- ...options,
22647
- cli: "cursor",
22648
- model
22649
- };
22650
- }
22651
- buildEffortReconnectOptions(effort) {
22652
- if (!this.currentConnectOptions) {
22653
- throw new Error("[cursor] effort \uC804\uD658\uC744 \uC704\uD55C \uAE30\uC874 \uC5F0\uACB0 \uC635\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. connect()\uB85C \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.");
22654
- }
22655
- if (!this.currentConnectOptions.model) {
22656
- throw new Error("[cursor] effort \uC804\uD658\uC744 \uC704\uD55C \uD604\uC7AC \uBAA8\uB378 \uC815\uBCF4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBAA8\uB378\uC744 \uC9C0\uC815\uD574 \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.");
22657
- }
22658
- if (this.currentConnectOptions.effort === effort) {
22659
- return null;
22660
- }
22661
- const effortInfo = getCursorSpawnEffortInfo(this.currentConnectOptions.model);
22662
- if (!effortInfo.supported) {
22663
- return null;
22664
- }
22665
- if (!effortInfo.levels.includes(effort)) {
22666
- throw new Error(
22667
- `[cursor] ${this.currentConnectOptions.model} \uBAA8\uB378\uC740 effort "${effort}"\uC744(\uB97C) \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9 \uAC00\uB2A5: ${effortInfo.levels.join(", ")}`
22668
- );
22669
- }
22670
- const options = this.cloneConnectOptions(this.currentConnectOptions);
22671
- delete options.sessionId;
22672
- return {
22673
- ...options,
22674
- cli: "cursor",
22675
- effort
22676
- };
22677
- }
22678
- resolveEffortForModel(model, effort) {
22679
- const effortInfo = getCursorSpawnEffortInfo(model);
22680
- if (!effortInfo.supported) {
22681
- return void 0;
22682
- }
22683
- if (effort && effortInfo.levels.includes(effort)) {
22684
- return effort;
22685
- }
22686
- return effortInfo.default ?? void 0;
22687
- }
22688
- async reconnectWithRestore(nextOptions, label) {
22689
- if (!this.currentConnectOptions) {
22690
- throw new Error(`[cursor] ${label}\uC744 \uC704\uD55C \uAE30\uC874 \uC5F0\uACB0 \uC635\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. connect()\uB85C \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`);
22691
- }
22692
- const previousOptions = this.cloneConnectOptions(this.currentConnectOptions);
22693
- try {
22694
- await this.connect(nextOptions);
22695
- } catch (error51) {
22696
- try {
22697
- await this.connect(previousOptions);
22698
- } catch (restoreError) {
22699
- throw new Error(
22700
- `[cursor] ${label} \uC2E4\uD328 \uD6C4 \uC774\uC804 \uC5F0\uACB0 \uBCF5\uAD6C\uB3C4 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. ${label} \uC624\uB958: ${this.formatErrorMessage(error51)} / \uBCF5\uAD6C \uC624\uB958: ${this.formatErrorMessage(restoreError)}`
22701
- );
22702
- }
22703
- throw new Error(
22704
- `[cursor] ${label} \uC2E4\uD328\uB85C \uC774\uC804 \uC5F0\uACB0\uC744 \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4. ${label} \uC624\uB958: ${this.formatErrorMessage(error51)}`
22705
- );
22706
- }
22707
- }
22708
- formatErrorMessage(error51) {
22709
- if (error51 instanceof Error) {
22710
- return error51.message;
22711
- }
22712
- return String(error51);
22713
- }
22714
- cloneConnectOptions(options) {
22715
- return {
22716
- ...options,
22717
- cli: "cursor",
22718
- env: options.env ? { ...options.env } : void 0,
22719
- clientInfo: options.clientInfo ? { ...options.clientInfo } : void 0,
22720
- mcpServers: options.mcpServers?.map((server) => ({
22721
- ...server,
22722
- headers: server.headers?.map((header) => ({ ...header }))
22723
- }))
22724
- };
22725
- }
22726
- setupEventForwarding() {
22727
- if (!this.connection) return;
22728
- this.connection.on("stateChange", (state) => {
22729
- this.emitTyped("stateChange", state);
22730
- });
22731
- this.connection.on("userMessageChunk", (text2, sessionId) => {
22732
- this.emitTyped("userMessageChunk", text2, sessionId);
22733
- });
22734
- this.connection.on("messageChunk", (text2, sessionId) => {
22735
- this.emitTyped("messageChunk", text2, sessionId);
22736
- });
22737
- this.connection.on("thoughtChunk", (text2, sessionId) => {
22738
- this.emitTyped("thoughtChunk", text2, sessionId);
22739
- });
22740
- this.connection.on("toolCall", (title, status, sessionId, data) => {
22741
- this.emitTyped("toolCall", title, status, sessionId, data);
22742
- });
22743
- this.connection.on("toolCallUpdate", (title, status, sessionId, data) => {
22744
- this.emitTyped("toolCallUpdate", title, status, sessionId, data);
22745
- });
22746
- this.connection.on("plan", (plan, sessionId) => {
22747
- this.emitTyped("plan", plan, sessionId);
22748
- });
22749
- this.connection.on("availableCommandsUpdate", (commands, sessionId) => {
22750
- this.emitTyped("availableCommandsUpdate", commands, sessionId);
22751
- });
22752
- this.connection.on("sessionUpdate", (update) => {
22753
- this.emitTyped("sessionUpdate", update);
22754
- });
22755
- this.connection.on("permissionRequest", (params, resolve3) => {
22756
- this.emitTyped("permissionRequest", params, resolve3);
22757
- });
22758
- this.connection.on("fileRead", (params, resolve3) => {
22759
- this.emitTyped("fileRead", params, resolve3);
22760
- });
22761
- this.connection.on("fileWrite", (params, resolve3) => {
22762
- this.emitTyped("fileWrite", params, resolve3);
22763
- });
22764
- this.connection.on("promptComplete", (sessionId) => {
22765
- this.emitTyped("promptComplete", sessionId);
22766
- });
22767
- this.connection.on("error", (err) => {
22768
- this.emitTyped("error", err);
22769
- });
22770
- this.connection.on("exit", (code, signal) => {
22771
- this.emitTyped("exit", code, signal);
22772
- });
22773
- this.connection.on("log", (msg) => {
22774
- this.emitTyped("log", msg);
22775
- });
22776
- this.connection.on("logEntry", (entry) => {
22777
- this.emitTyped("logEntry", entry);
22778
- });
22779
- }
22780
- async cleanupFailedConnection() {
22781
- if (!this.connection) {
22782
- return;
22783
- }
22784
- try {
22785
- await this.connection.disconnect();
22786
- } catch {
22787
- }
22788
- this.connection.removeAllListeners();
22789
- this.connection = null;
22790
- this.clearSessionState();
22791
- }
22792
- buildConnectionError(error51, recentLogs) {
22793
- if (getBackendConfig("cursor").authRequired && this.isAuthenticationError(error51, recentLogs)) {
22794
- return new Error(
22795
- "[cursor] \uC778\uC99D\uC774 \uD544\uC694\uD558\uAC70\uB098 \uC778\uC99D\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD574\uB2F9 CLI\uC5D0\uC11C \uB85C\uADF8\uC778/\uC778\uC99D\uC744 \uC644\uB8CC\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."
22796
- );
22797
- }
22798
- if (error51 instanceof Error) {
22799
- return error51;
22800
- }
22801
- if (typeof error51 === "object" && error51 !== null) {
22802
- const obj = error51;
22803
- if (typeof obj.message === "string") {
22804
- const code = typeof obj.code === "number" ? ` (code: ${obj.code})` : "";
22805
- const data = obj.data ? ` \u2014 ${JSON.stringify(obj.data)}` : "";
22806
- return new Error(`${obj.message}${code}${data}`);
22807
- }
22808
- return new Error(JSON.stringify(error51));
22809
- }
22810
- return new Error(String(error51));
22811
- }
22812
- isAuthenticationError(error51, recentLogs) {
22813
- const authPatterns = [
22814
- /auth_required/i,
22815
- /authentication required/i,
22816
- /not authenticated/i,
22817
- /please login/i,
22818
- /please log in/i,
22819
- /sign in/i,
22820
- /reauth/i,
22821
- /unauthorized/i,
22822
- /invalid api key/i
22823
- ];
22824
- if (this.matchAnyPattern(this.extractErrorText(error51), authPatterns)) {
22825
- return true;
22826
- }
22827
- return recentLogs.some((log) => this.matchAnyPattern(log, authPatterns));
22828
- }
22829
- extractErrorText(error51) {
22830
- if (error51 instanceof Error) {
22831
- const code = error51.code;
22832
- if (code === -32e3) {
22833
- return `auth_required ${error51.message}`;
22834
- }
22835
- return error51.message;
22836
- }
22837
- if (typeof error51 === "string") {
22838
- return error51;
22839
- }
22840
- return String(error51);
22841
- }
22842
- matchAnyPattern(text2, patterns) {
22843
- return patterns.some((pattern) => pattern.test(text2));
22844
- }
22845
- };
22846
22306
 
22847
22307
  // ../../packages/core-unified-agent/src/client/UnifiedAgent.ts
22848
22308
  var UnifiedAgent = {
@@ -22852,8 +22312,6 @@ var UnifiedAgent = {
22852
22312
  return new UnifiedClaudeAgentClient();
22853
22313
  case "codex":
22854
22314
  return new UnifiedCodexAgentClient();
22855
- case "cursor":
22856
- return new UnifiedCursorAgentClient();
22857
22315
  }
22858
22316
  },
22859
22317
  async build(options = {}) {
@@ -22866,7 +22324,7 @@ var UnifiedAgent = {
22866
22324
  const preferred = await new CliDetector().getPreferred();
22867
22325
  if (!preferred) {
22868
22326
  throw new Error(
22869
- "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C CLI\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. claude, codex, cursor \uC911 \uD558\uB098\uB97C \uC124\uCE58\uD574\uC8FC\uC138\uC694."
22327
+ "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C CLI\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. claude, codex \uC911 \uD558\uB098\uB97C \uC124\uCE58\uD574\uC8FC\uC138\uC694."
22870
22328
  );
22871
22329
  }
22872
22330
  return this.createClient(preferred.cli);
@@ -32753,7 +32211,7 @@ Both surfaces require the user's request. When the one the work needs is gated,
32753
32211
  ### Model Loadout
32754
32212
  Which model and effort a run uses is routing, so it stays with the host agent. Call the ${"`"}gateway_models${"`"} MCP tool before every run on either surface, then pick the identity this work needs — a measured role fit first, and the model's own allowance wherever measurement is silent. Never let the session's own model be the default answer; it is the most expensive way to obtain what any identity produces equally well, and an unpinned run spends that allowance too.
32755
32213
 
32756
- A staged workflow spreads its stages across identities and balances them against provider allowances instead of inheriting one model for every stage. The ${"`"}workflow${"`"} skill owns that procedure; what each roster field means stays in the tool's own metadata.
32214
+ A staged workflow spreads its stages across identities and balances them against provider allowances instead of inheriting one model for every stage — reading each allowance's own reported verdict rather than comparing raw percentages across windows that reset on different clocks. The ${"`"}workflow${"`"} skill owns that procedure; what each roster field means stays in the tool's own metadata.
32757
32215
 
32758
32216
  ### Skill Routing
32759
32217
  Load the ${"`"}workflow${"`"} skill before executing a stage skeleton or assigning models across runs. The skeleton itself belongs to the skill matching the work: ${"`"}architecture-review${"`"} to decide, ${"`"}codebase-research${"`"} to establish facts, ${"`"}implementation-run${"`"} to change files, ${"`"}quality-review${"`"} to judge what exists.`
@@ -37740,7 +37198,7 @@ function isRecord4(value) {
37740
37198
  }
37741
37199
  var models_default2 = {
37742
37200
  version: 1,
37743
- updatedAt: "2026-08-01T00:00:00Z",
37201
+ updatedAt: "2026-08-03T00:00:00Z",
37744
37202
  providers: {
37745
37203
  codex: {
37746
37204
  name: "Codex",
@@ -38025,13 +37483,167 @@ var models_default2 = {
38025
37483
  ]
38026
37484
  }
38027
37485
  ]
37486
+ },
37487
+ opencode: {
37488
+ name: "OpenCode",
37489
+ defaultModel: "minimax-m3",
37490
+ source: "live GET https://opencode.ai/zen/go/v1/models + per-model wire probes on /messages, /responses, /chat/completions (2026-08-03); contextWindow cross-checked against models.dev api.json opencode-go entries and oh-my-pi packages/catalog/src/models.json (2026-08-03, both agree). Excluded: mimo-v2-pro/mimo-v2-omni (upstream server_error), hy3-preview (listed but Router.ModelNotFound)",
37491
+ models: [
37492
+ {
37493
+ modelId: "minimax-m3",
37494
+ name: "MiniMax-M3",
37495
+ contextWindow: 1e6
37496
+ },
37497
+ {
37498
+ modelId: "minimax-m2.7",
37499
+ name: "MiniMax-M2.7",
37500
+ contextWindow: 204800
37501
+ },
37502
+ {
37503
+ modelId: "minimax-m2.5",
37504
+ name: "MiniMax-M2.5",
37505
+ contextWindow: 204800
37506
+ },
37507
+ {
37508
+ modelId: "qwen3.8-max",
37509
+ name: "Qwen3.8-Max",
37510
+ contextWindow: 1e6
37511
+ },
37512
+ {
37513
+ modelId: "qwen3.7-max",
37514
+ name: "Qwen3.7-Max",
37515
+ contextWindow: 1e6
37516
+ },
37517
+ {
37518
+ modelId: "qwen3.7-plus",
37519
+ name: "Qwen3.7-Plus",
37520
+ contextWindow: 1e6
37521
+ },
37522
+ {
37523
+ modelId: "qwen3.6-plus",
37524
+ name: "Qwen3.6-Plus",
37525
+ contextWindow: 1e6
37526
+ },
37527
+ {
37528
+ modelId: "qwen3.5-plus",
37529
+ name: "Qwen3.5-Plus",
37530
+ contextWindow: 262144
37531
+ },
37532
+ {
37533
+ modelId: "gpt-5.6-luna",
37534
+ name: "GPT-5.6-Luna",
37535
+ wire: "responses",
37536
+ contextWindow: 105e4,
37537
+ effort: {
37538
+ supported: true,
37539
+ levels: [
37540
+ "low",
37541
+ "medium",
37542
+ "high",
37543
+ "xhigh",
37544
+ "max"
37545
+ ]
37546
+ }
37547
+ },
37548
+ {
37549
+ modelId: "grok-4.5",
37550
+ name: "Grok-4.5",
37551
+ wire: "responses",
37552
+ contextWindow: 5e5,
37553
+ effort: {
37554
+ supported: true,
37555
+ levels: [
37556
+ "low",
37557
+ "medium",
37558
+ "high"
37559
+ ]
37560
+ }
37561
+ },
37562
+ {
37563
+ modelId: "deepseek-v4-flash",
37564
+ name: "DeepSeek-V4-Flash",
37565
+ wire: "chat-completions",
37566
+ contextWindow: 1e6
37567
+ },
37568
+ {
37569
+ modelId: "deepseek-v4-pro",
37570
+ name: "DeepSeek-V4-Pro",
37571
+ wire: "chat-completions",
37572
+ contextWindow: 1e6
37573
+ },
37574
+ {
37575
+ modelId: "glm-5.2",
37576
+ name: "GLM-5.2",
37577
+ wire: "chat-completions",
37578
+ contextWindow: 1e6
37579
+ },
37580
+ {
37581
+ modelId: "glm-5.1",
37582
+ name: "GLM-5.1",
37583
+ wire: "chat-completions",
37584
+ contextWindow: 202752
37585
+ },
37586
+ {
37587
+ modelId: "glm-5",
37588
+ name: "GLM-5",
37589
+ wire: "chat-completions",
37590
+ contextWindow: 202752
37591
+ },
37592
+ {
37593
+ modelId: "kimi-k3",
37594
+ name: "Kimi-K3",
37595
+ wire: "chat-completions",
37596
+ contextWindow: 1048576
37597
+ },
37598
+ {
37599
+ modelId: "kimi-k2.7-code",
37600
+ name: "Kimi-K2.7-Code",
37601
+ wire: "chat-completions",
37602
+ contextWindow: 262144
37603
+ },
37604
+ {
37605
+ modelId: "kimi-k2.6",
37606
+ name: "Kimi-K2.6",
37607
+ wire: "chat-completions",
37608
+ contextWindow: 262144
37609
+ },
37610
+ {
37611
+ modelId: "kimi-k2.5",
37612
+ name: "Kimi-K2.5",
37613
+ wire: "chat-completions",
37614
+ contextWindow: 262144
37615
+ },
37616
+ {
37617
+ modelId: "mimo-v2.5-pro",
37618
+ name: "MiMo-V2.5-Pro",
37619
+ wire: "chat-completions",
37620
+ contextWindow: 1048576
37621
+ },
37622
+ {
37623
+ modelId: "mimo-v2.5",
37624
+ name: "MiMo-V2.5",
37625
+ wire: "chat-completions",
37626
+ contextWindow: 1e6
37627
+ },
37628
+ {
37629
+ modelId: "hy3",
37630
+ name: "HY3",
37631
+ wire: "chat-completions",
37632
+ contextWindow: 256e3
37633
+ }
37634
+ ]
38028
37635
  }
38029
37636
  }
38030
37637
  };
38031
37638
  var KIMI_AUTH_PROVIDER_ID = "Claude Code with Moonshot Kimi";
38032
37639
  var KIMI_CODE_API_BASE_URL = "https://api.kimi.com/coding";
38033
37640
  var KIMI_CODE_MODEL = "k3";
38034
- var GATEWAY_PROVIDERS = ["codex", "cursor", "kimi"];
37641
+ var GATEWAY_PROVIDERS = ["codex", "cursor", "kimi", "opencode"];
37642
+ var GATEWAY_MODEL_WIRES = ["anthropic", "responses", "chat-completions"];
37643
+ function isAnthropicPassthroughModel(model) {
37644
+ if (model.provider === "kimi") return true;
37645
+ return model.provider === "opencode" && (model.wire ?? "anthropic") === "anthropic";
37646
+ }
38035
37647
  var GATEWAY_REASONING_EFFORTS = [
38036
37648
  "low",
38037
37649
  "medium",
@@ -38064,6 +37676,7 @@ var GatewayModelEntrySchema = external_exports.object({
38064
37676
  serviceTier: external_exports.literal("priority").optional(),
38065
37677
  cursorMaxMode: external_exports.literal(true).optional(),
38066
37678
  quotaScope: external_exports.enum(GATEWAY_QUOTA_SCOPES).optional(),
37679
+ wire: external_exports.enum(GATEWAY_MODEL_WIRES).optional(),
38067
37680
  aliases: external_exports.array(external_exports.string().min(1)).optional(),
38068
37681
  contextWindow: external_exports.number().int().positive().optional(),
38069
37682
  effort: GatewayModelEffortSchema.optional()
@@ -38080,7 +37693,8 @@ var GatewayModelsRegistrySchema = external_exports.object({
38080
37693
  providers: external_exports.object({
38081
37694
  codex: GatewayProviderSchema,
38082
37695
  cursor: GatewayProviderSchema,
38083
- kimi: GatewayProviderSchema
37696
+ kimi: GatewayProviderSchema,
37697
+ opencode: GatewayProviderSchema
38084
37698
  }).strict()
38085
37699
  }).strict();
38086
37700
  var UNSUPPORTED_GATEWAY_MODEL_EFFORT = Object.freeze({ supported: false });
@@ -38105,6 +37719,7 @@ var GATEWAY_MODELS = Object.freeze(
38105
37719
  providerModels("codex");
38106
37720
  var CURSOR_SUBSCRIPTION_MODELS = providerModels("cursor");
38107
37721
  providerModels("kimi");
37722
+ providerModels("opencode");
38108
37723
  var GATEWAY_MODEL_ALIAS_PREFIX = "claude-gateway--";
38109
37724
  var CLAUDE_ONE_MILLION_MARKER = "[1m]";
38110
37725
  var CLAUDE_ONE_MILLION_DISPLAY_SUFFIX = " (1M Context)";
@@ -38113,7 +37728,7 @@ function toGatewayModelAlias(modelId) {
38113
37728
  }
38114
37729
  function toClaudeGatewayModelId(model) {
38115
37730
  const alias = toGatewayModelAlias(model.id);
38116
- if (canProjectClaudeContextWindow(model.contextWindow) && (model.provider !== "kimi" || isClaudeOneMillionContextWindow(model.contextWindow))) {
37731
+ if (canProjectClaudeContextWindow(model.contextWindow) && (!isAnthropicPassthroughModel(model) || isClaudeOneMillionContextWindow(model.contextWindow))) {
38117
37732
  return `${alias}${CLAUDE_ONE_MILLION_MARKER}`;
38118
37733
  }
38119
37734
  return alias;
@@ -38213,6 +37828,7 @@ function toGatewayModel(provider, providerName, entry) {
38213
37828
  ...entry.serviceTier ? { serviceTier: entry.serviceTier } : {},
38214
37829
  ...entry.cursorMaxMode ? { cursorMaxMode: entry.cursorMaxMode } : {},
38215
37830
  ...entry.quotaScope ? { quotaScope: entry.quotaScope } : {},
37831
+ ...entry.wire ? { wire: entry.wire } : {},
38216
37832
  ...entry.description ? { description: entry.description } : {},
38217
37833
  ...entry.contextWindow ? { contextWindow: entry.contextWindow } : {},
38218
37834
  effort: freezeGatewayModelEffort(entry.effort),
@@ -38247,6 +37863,9 @@ function validateRegistry(value) {
38247
37863
  if (model.quotaScope && provider !== "cursor") {
38248
37864
  throw new Error(`Gateway quota scope is only supported by Cursor: ${provider}/${model.modelId}`);
38249
37865
  }
37866
+ if (model.wire && provider !== "opencode") {
37867
+ throw new Error(`Gateway model wire is only supported by OpenCode: ${provider}/${model.modelId}`);
37868
+ }
38250
37869
  if (model.effort?.supported) {
38251
37870
  if (new Set(model.effort.levels).size !== model.effort.levels.length) {
38252
37871
  throw new Error(`Gateway effort levels contain duplicates: ${provider}/${model.modelId}`);
@@ -40216,21 +39835,9 @@ var AgentServerMessageSchema = messageDesc(cursorAgentFile, 119);
40216
39835
  var ConversationStepSchema = messageDesc(cursorAgentFile, 53);
40217
39836
  var UserMessageSchema = messageDesc(cursorAgentFile, 63);
40218
39837
  var ConversationTurnStructureSchema = messageDesc(cursorAgentFile, 70);
40219
- var OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses";
40220
- var CHATGPT_CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
40221
- var DEFAULT_MAX_UPSTREAM_BODY_BYTES = 64 * 1024 * 1024;
40222
- var DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS = 3e4;
40223
- var CHATGPT_UNSUPPORTED_FIELDS = [
40224
- "max_output_tokens",
40225
- "temperature",
40226
- "top_p",
40227
- "stop",
40228
- "user",
40229
- "metadata"
40230
- ];
40231
39838
  var UpstreamBodyLimitError = class extends Error {
40232
39839
  constructor(maxBodyBytes) {
40233
- super(`OpenAI response exceeded ${maxBodyBytes} bytes`);
39840
+ super(`Upstream response exceeded ${maxBodyBytes} bytes`);
40234
39841
  this.maxBodyBytes = maxBodyBytes;
40235
39842
  this.name = "UpstreamBodyLimitError";
40236
39843
  }
@@ -40238,7 +39845,7 @@ var UpstreamBodyLimitError = class extends Error {
40238
39845
  };
40239
39846
  var UpstreamIdleTimeoutError = class extends Error {
40240
39847
  constructor(idleTimeoutMs) {
40241
- super(`OpenAI response was idle for ${idleTimeoutMs}ms`);
39848
+ super(`Upstream response was idle for ${idleTimeoutMs}ms`);
40242
39849
  this.idleTimeoutMs = idleTimeoutMs;
40243
39850
  this.name = "UpstreamIdleTimeoutError";
40244
39851
  }
@@ -40250,6 +39857,132 @@ var UpstreamProtocolError = class extends Error {
40250
39857
  this.name = "UpstreamProtocolError";
40251
39858
  }
40252
39859
  };
39860
+ async function readBoundedBody(body, options) {
39861
+ if (body === null) {
39862
+ return new Uint8Array();
39863
+ }
39864
+ const reader = body.getReader();
39865
+ const chunks = [];
39866
+ let byteLength3 = 0;
39867
+ try {
39868
+ while (true) {
39869
+ const result = await readWithIdleTimeout(reader, options);
39870
+ if (result.done) {
39871
+ break;
39872
+ }
39873
+ byteLength3 += result.value.byteLength;
39874
+ if (byteLength3 > options.maxBodyBytes) {
39875
+ const error51 = new UpstreamBodyLimitError(options.maxBodyBytes);
39876
+ options.controller.abort(error51);
39877
+ throw error51;
39878
+ }
39879
+ chunks.push(result.value);
39880
+ }
39881
+ } finally {
39882
+ await reader.cancel().catch(() => void 0);
39883
+ }
39884
+ const bodyBytes = new Uint8Array(byteLength3);
39885
+ let offset = 0;
39886
+ for (const chunk of chunks) {
39887
+ bodyBytes.set(chunk, offset);
39888
+ offset += chunk.byteLength;
39889
+ }
39890
+ return bodyBytes;
39891
+ }
39892
+ async function readWithIdleTimeout(reader, options) {
39893
+ return await new Promise((resolve3, reject) => {
39894
+ let settled = false;
39895
+ const finish = (callback, value) => {
39896
+ if (settled) {
39897
+ return;
39898
+ }
39899
+ settled = true;
39900
+ clearTimeout(timeout);
39901
+ options.controller.signal.removeEventListener("abort", abort);
39902
+ callback(value);
39903
+ };
39904
+ const abort = () => {
39905
+ const reason = options.controller.signal.reason instanceof Error ? options.controller.signal.reason : new DOMException("The operation was aborted", "AbortError");
39906
+ void reader.cancel(reason).catch(() => void 0);
39907
+ finish(reject, reason);
39908
+ };
39909
+ const timeout = setTimeout(() => {
39910
+ const error51 = new UpstreamIdleTimeoutError(options.idleTimeoutMs);
39911
+ options.controller.abort(error51);
39912
+ }, options.idleTimeoutMs);
39913
+ options.controller.signal.addEventListener("abort", abort, { once: true });
39914
+ if (options.controller.signal.aborted) {
39915
+ abort();
39916
+ return;
39917
+ }
39918
+ reader.read().then(
39919
+ (result) => {
39920
+ finish(resolve3, result);
39921
+ },
39922
+ (error51) => {
39923
+ finish(reject, error51);
39924
+ }
39925
+ );
39926
+ });
39927
+ }
39928
+ function nextEventBoundary(buffer) {
39929
+ const match = /\r\n\r\n|\n\n|\r\r/.exec(buffer);
39930
+ return match === null ? void 0 : { index: match.index, length: match[0].length };
39931
+ }
39932
+ function parseSseFrameFields(frame) {
39933
+ let eventName;
39934
+ const data = [];
39935
+ for (const line of frame.split(/\r\n|\n|\r/)) {
39936
+ if (line.startsWith(":")) {
39937
+ continue;
39938
+ }
39939
+ const separator = line.indexOf(":");
39940
+ const field = separator === -1 ? line : line.slice(0, separator);
39941
+ let value = separator === -1 ? "" : line.slice(separator + 1);
39942
+ if (value.startsWith(" ")) {
39943
+ value = value.slice(1);
39944
+ }
39945
+ if (field === "event") {
39946
+ eventName = value;
39947
+ } else if (field === "data") {
39948
+ data.push(value);
39949
+ }
39950
+ }
39951
+ return {
39952
+ ...eventName === void 0 ? {} : { event: eventName },
39953
+ data: data.join("\n")
39954
+ };
39955
+ }
39956
+ function linkAbortSignal(signal, controller) {
39957
+ if (signal === void 0) {
39958
+ return () => void 0;
39959
+ }
39960
+ const abort = () => controller.abort(signal.reason);
39961
+ if (signal.aborted) {
39962
+ abort();
39963
+ return () => void 0;
39964
+ }
39965
+ signal.addEventListener("abort", abort, { once: true });
39966
+ return () => signal.removeEventListener("abort", abort);
39967
+ }
39968
+ function positiveInteger(value, name) {
39969
+ if (!Number.isInteger(value) || value <= 0) {
39970
+ throw new TypeError(`${name} must be a positive integer`);
39971
+ }
39972
+ return value;
39973
+ }
39974
+ var OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses";
39975
+ var CHATGPT_CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
39976
+ var DEFAULT_MAX_UPSTREAM_BODY_BYTES = 64 * 1024 * 1024;
39977
+ var DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS = 3e4;
39978
+ var CHATGPT_UNSUPPORTED_FIELDS = [
39979
+ "max_output_tokens",
39980
+ "temperature",
39981
+ "top_p",
39982
+ "stop",
39983
+ "user",
39984
+ "metadata"
39985
+ ];
40253
39986
  var OpenAIResponsesAdapter = class {
40254
39987
  capabilities = { nativeTools: ["web_search"] };
40255
39988
  fetchImpl;
@@ -40396,38 +40129,6 @@ function forChatGptBackend(request) {
40396
40129
  copy.store = false;
40397
40130
  return copy;
40398
40131
  }
40399
- async function readBoundedBody(body, options) {
40400
- if (body === null) {
40401
- return new Uint8Array();
40402
- }
40403
- const reader = body.getReader();
40404
- const chunks = [];
40405
- let byteLength3 = 0;
40406
- try {
40407
- while (true) {
40408
- const result = await readWithIdleTimeout(reader, options);
40409
- if (result.done) {
40410
- break;
40411
- }
40412
- byteLength3 += result.value.byteLength;
40413
- if (byteLength3 > options.maxBodyBytes) {
40414
- const error51 = new UpstreamBodyLimitError(options.maxBodyBytes);
40415
- options.controller.abort(error51);
40416
- throw error51;
40417
- }
40418
- chunks.push(result.value);
40419
- }
40420
- } finally {
40421
- await reader.cancel().catch(() => void 0);
40422
- }
40423
- const bodyBytes = new Uint8Array(byteLength3);
40424
- let offset = 0;
40425
- for (const chunk of chunks) {
40426
- bodyBytes.set(chunk, offset);
40427
- offset += chunk.byteLength;
40428
- }
40429
- return bodyBytes;
40430
- }
40431
40132
  async function* parseOpenAIEventStream(body, options) {
40432
40133
  if (body === null) {
40433
40134
  options.onClose();
@@ -40473,71 +40174,14 @@ async function* parseOpenAIEventStream(body, options) {
40473
40174
  options.onClose();
40474
40175
  }
40475
40176
  }
40476
- async function readWithIdleTimeout(reader, options) {
40477
- return await new Promise((resolve3, reject) => {
40478
- let settled = false;
40479
- const finish = (callback, value) => {
40480
- if (settled) {
40481
- return;
40482
- }
40483
- settled = true;
40484
- clearTimeout(timeout);
40485
- options.controller.signal.removeEventListener("abort", abort);
40486
- callback(value);
40487
- };
40488
- const abort = () => {
40489
- const reason = options.controller.signal.reason instanceof Error ? options.controller.signal.reason : new DOMException("The operation was aborted", "AbortError");
40490
- void reader.cancel(reason).catch(() => void 0);
40491
- finish(reject, reason);
40492
- };
40493
- const timeout = setTimeout(() => {
40494
- const error51 = new UpstreamIdleTimeoutError(options.idleTimeoutMs);
40495
- options.controller.abort(error51);
40496
- }, options.idleTimeoutMs);
40497
- options.controller.signal.addEventListener("abort", abort, { once: true });
40498
- if (options.controller.signal.aborted) {
40499
- abort();
40500
- return;
40501
- }
40502
- reader.read().then(
40503
- (result) => {
40504
- finish(resolve3, result);
40505
- },
40506
- (error51) => {
40507
- finish(reject, error51);
40508
- }
40509
- );
40510
- });
40511
- }
40512
- function nextEventBoundary(buffer) {
40513
- const match = /\r\n\r\n|\n\n|\r\r/.exec(buffer);
40514
- return match === null ? void 0 : { index: match.index, length: match[0].length };
40515
- }
40516
40177
  function parseEventFrame(frame) {
40517
- let eventName;
40518
- const data = [];
40519
- for (const line of frame.split(/\r\n|\n|\r/)) {
40520
- if (line.startsWith(":")) {
40521
- continue;
40522
- }
40523
- const separator = line.indexOf(":");
40524
- const field = separator === -1 ? line : line.slice(0, separator);
40525
- let value = separator === -1 ? "" : line.slice(separator + 1);
40526
- if (value.startsWith(" ")) {
40527
- value = value.slice(1);
40528
- }
40529
- if (field === "event") {
40530
- eventName = value;
40531
- } else if (field === "data") {
40532
- data.push(value);
40533
- }
40534
- }
40535
- if (data.length === 0 || data.join("\n") === "[DONE]") {
40178
+ const { event: eventName, data } = parseSseFrameFields(frame);
40179
+ if (data.length === 0 || data === "[DONE]") {
40536
40180
  return void 0;
40537
40181
  }
40538
40182
  let parsed;
40539
40183
  try {
40540
- parsed = JSON.parse(data.join("\n"));
40184
+ parsed = JSON.parse(data);
40541
40185
  } catch (error51) {
40542
40186
  throw new UpstreamProtocolError(
40543
40187
  `OpenAI SSE contained invalid JSON: ${error51 instanceof Error ? error51.message : String(error51)}`
@@ -40669,7 +40313,6 @@ var STRICT_ALLOWED_KEYWORDS = /* @__PURE__ */ new Set([
40669
40313
  "$ref",
40670
40314
  "description",
40671
40315
  "title",
40672
- "format",
40673
40316
  "pattern",
40674
40317
  "minimum",
40675
40318
  "maximum",
@@ -40682,7 +40325,8 @@ var STRICT_ALLOWED_KEYWORDS = /* @__PURE__ */ new Set([
40682
40325
  "maxItems",
40683
40326
  // Dropped by `strictSchema` before the schema reaches the wire.
40684
40327
  "$schema",
40685
- "default"
40328
+ "default",
40329
+ "format"
40686
40330
  ]);
40687
40331
  function strictCompatible(schema) {
40688
40332
  if (!isRecord42(schema)) return true;
@@ -40721,11 +40365,19 @@ function strictSchema(schema) {
40721
40365
  const next = { ...schema };
40722
40366
  delete next.$schema;
40723
40367
  delete next.default;
40368
+ delete next.format;
40724
40369
  for (const key of ["anyOf", "oneOf", "allOf"]) {
40725
40370
  const branches = next[key];
40726
40371
  if (Array.isArray(branches)) next[key] = branches.map(strictSchema);
40727
40372
  }
40728
40373
  if (isRecord42(next.items)) next.items = strictSchema(next.items);
40374
+ if (isRecord42(next.$defs)) {
40375
+ const rewrittenDefs = {};
40376
+ for (const [name, value] of Object.entries(next.$defs)) {
40377
+ rewrittenDefs[name] = strictSchema(value);
40378
+ }
40379
+ next.$defs = rewrittenDefs;
40380
+ }
40729
40381
  const properties = next.properties;
40730
40382
  if (isRecord42(properties)) {
40731
40383
  const required2 = new Set(
@@ -40775,10 +40427,15 @@ function withoutNulls(value) {
40775
40427
  const out = {};
40776
40428
  for (const [key, entry] of Object.entries(value)) {
40777
40429
  if (entry === null) continue;
40778
- out[key] = isRecord42(entry) ? withoutNulls(entry) : entry;
40430
+ out[key] = withoutNullMembers(entry);
40779
40431
  }
40780
40432
  return out;
40781
40433
  }
40434
+ function withoutNullMembers(entry) {
40435
+ if (isRecord42(entry)) return withoutNulls(entry);
40436
+ if (Array.isArray(entry)) return entry.map(withoutNullMembers);
40437
+ return entry;
40438
+ }
40782
40439
  function outputItem(value) {
40783
40440
  const item = record2(value, "item");
40784
40441
  if (item.type === "message") {
@@ -40866,24 +40523,6 @@ function canonicalError(value) {
40866
40523
  const type = typeof error51.type === "string" && error51.type !== "error" ? error51.type : typeof error51.code === "string" ? error51.code : "api_error";
40867
40524
  return { type, message };
40868
40525
  }
40869
- function linkAbortSignal(signal, controller) {
40870
- if (signal === void 0) {
40871
- return () => void 0;
40872
- }
40873
- const abort = () => controller.abort(signal.reason);
40874
- if (signal.aborted) {
40875
- abort();
40876
- return () => void 0;
40877
- }
40878
- signal.addEventListener("abort", abort, { once: true });
40879
- return () => signal.removeEventListener("abort", abort);
40880
- }
40881
- function positiveInteger(value, name) {
40882
- if (!Number.isInteger(value) || value <= 0) {
40883
- throw new TypeError(`${name} must be a positive integer`);
40884
- }
40885
- return value;
40886
- }
40887
40526
  function isRecord42(value) {
40888
40527
  return typeof value === "object" && value !== null && !Array.isArray(value);
40889
40528
  }
@@ -43297,6 +42936,419 @@ async function resolveCodexCredentials(deps) {
43297
42936
  return null;
43298
42937
  }
43299
42938
  }
42939
+ var DEFAULT_CHAT_MAX_UPSTREAM_BODY_BYTES = 64 * 1024 * 1024;
42940
+ var DEFAULT_CHAT_UPSTREAM_IDLE_TIMEOUT_MS = 3e4;
42941
+ var OpenAIChatCompletionsAdapter = class {
42942
+ fetchImpl;
42943
+ maxBodyBytes;
42944
+ idleTimeoutMs;
42945
+ url;
42946
+ extraHeaders;
42947
+ constructor(options) {
42948
+ this.url = options.url;
42949
+ this.extraHeaders = options.headers ?? {};
42950
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
42951
+ this.maxBodyBytes = positiveInteger(
42952
+ options.maxBodyBytes ?? DEFAULT_CHAT_MAX_UPSTREAM_BODY_BYTES,
42953
+ "maxBodyBytes"
42954
+ );
42955
+ this.idleTimeoutMs = positiveInteger(
42956
+ options.idleTimeoutMs ?? DEFAULT_CHAT_UPSTREAM_IDLE_TIMEOUT_MS,
42957
+ "idleTimeoutMs"
42958
+ );
42959
+ }
42960
+ async stream(request, options) {
42961
+ if (options.apiKey.length === 0) {
42962
+ throw new TypeError("apiKey must not be empty");
42963
+ }
42964
+ const controller = new AbortController();
42965
+ const unlinkAbort = linkAbortSignal(options.signal, controller);
42966
+ const payload = forChatCompletionsBackend(request);
42967
+ wireLog("openai-chat.wire.request", { url: this.url, payload });
42968
+ let response;
42969
+ try {
42970
+ response = await this.fetchImpl(this.url, {
42971
+ method: "POST",
42972
+ headers: {
42973
+ accept: "text/event-stream",
42974
+ authorization: `Bearer ${options.apiKey}`,
42975
+ "content-type": "application/json",
42976
+ ...this.extraHeaders
42977
+ },
42978
+ body: JSON.stringify(payload),
42979
+ signal: controller.signal
42980
+ });
42981
+ } catch (error51) {
42982
+ unlinkAbort();
42983
+ throw error51;
42984
+ }
42985
+ const readOptions = {
42986
+ controller,
42987
+ idleTimeoutMs: this.idleTimeoutMs,
42988
+ maxBodyBytes: this.maxBodyBytes
42989
+ };
42990
+ if (!response.ok) {
42991
+ try {
42992
+ const body = await readBoundedBody(response.body, readOptions);
42993
+ return { ok: false, status: response.status, headers: response.headers, body };
42994
+ } finally {
42995
+ unlinkAbort();
42996
+ }
42997
+ }
42998
+ return {
42999
+ ok: true,
43000
+ status: response.status,
43001
+ headers: response.headers,
43002
+ events: translateChatCompletionsStream(response.body, {
43003
+ ...readOptions,
43004
+ onClose: unlinkAbort
43005
+ })
43006
+ };
43007
+ }
43008
+ };
43009
+ function forChatCompletionsBackend(request) {
43010
+ const messages = [];
43011
+ if (request.instructions !== void 0 && request.instructions.length > 0) {
43012
+ messages.push({ role: "system", content: request.instructions });
43013
+ }
43014
+ let pendingToolCalls = [];
43015
+ let pendingAssistantText;
43016
+ let deferredMessages = [];
43017
+ const flushToolCalls = () => {
43018
+ if (pendingToolCalls.length === 0) return;
43019
+ messages.push({
43020
+ role: "assistant",
43021
+ content: pendingAssistantText ?? null,
43022
+ tool_calls: pendingToolCalls
43023
+ });
43024
+ pendingToolCalls = [];
43025
+ pendingAssistantText = void 0;
43026
+ };
43027
+ const flushDeferredMessages = () => {
43028
+ if (deferredMessages.length === 0) return;
43029
+ messages.push(...deferredMessages);
43030
+ deferredMessages = [];
43031
+ };
43032
+ for (const item of request.input) {
43033
+ if (item.type === "function_call") {
43034
+ flushDeferredMessages();
43035
+ pendingToolCalls.push({
43036
+ id: item.call_id,
43037
+ type: "function",
43038
+ function: { name: item.name, arguments: item.arguments }
43039
+ });
43040
+ continue;
43041
+ }
43042
+ if (item.type === "function_call_output") {
43043
+ flushToolCalls();
43044
+ messages.push({ role: "tool", tool_call_id: item.call_id, content: item.output });
43045
+ continue;
43046
+ }
43047
+ if (pendingToolCalls.length > 0) {
43048
+ if (item.role === "assistant") {
43049
+ const text2 = canonicalMessageText(item.content);
43050
+ if (text2.length > 0) {
43051
+ pendingAssistantText = pendingAssistantText === void 0 ? text2 : `${pendingAssistantText}
43052
+
43053
+ ${text2}`;
43054
+ }
43055
+ } else {
43056
+ deferredMessages.push(chatWireMessage(item));
43057
+ }
43058
+ continue;
43059
+ }
43060
+ flushDeferredMessages();
43061
+ messages.push(chatWireMessage(item));
43062
+ }
43063
+ flushToolCalls();
43064
+ flushDeferredMessages();
43065
+ const payload = {
43066
+ model: request.model,
43067
+ messages,
43068
+ stream: true,
43069
+ stream_options: { include_usage: true }
43070
+ };
43071
+ const tools = (request.tools ?? []).map((tool) => ({
43072
+ type: "function",
43073
+ function: {
43074
+ name: tool.name,
43075
+ ...tool.description === void 0 ? {} : { description: tool.description },
43076
+ parameters: tool.parameters
43077
+ }
43078
+ }));
43079
+ if (tools.length > 0) {
43080
+ payload.tools = tools;
43081
+ }
43082
+ const toolChoice = request.tool_choice;
43083
+ if (toolChoice !== void 0) {
43084
+ payload.tool_choice = typeof toolChoice === "string" ? toolChoice : { type: "function", function: { name: toolChoice.name } };
43085
+ }
43086
+ if (request.parallel_tool_calls !== void 0 && tools.length > 0) {
43087
+ payload.parallel_tool_calls = request.parallel_tool_calls;
43088
+ }
43089
+ if (request.max_output_tokens !== void 0) {
43090
+ payload.max_tokens = request.max_output_tokens;
43091
+ }
43092
+ return payload;
43093
+ }
43094
+ function chatWireMessage(item) {
43095
+ const role = item.role === "developer" ? "system" : item.role;
43096
+ if (typeof item.content === "string") {
43097
+ return { role, content: item.content };
43098
+ }
43099
+ if (role !== "user") {
43100
+ return { role, content: canonicalMessageText(item.content) };
43101
+ }
43102
+ const parts = item.content.map((part) => {
43103
+ if (part.type === "input_text") {
43104
+ return { type: "text", text: part.text };
43105
+ }
43106
+ return {
43107
+ type: "image_url",
43108
+ image_url: {
43109
+ url: part.image_url,
43110
+ // Chat 와이어의 detail 집합에는 original이 없다. 축소 없이 보내는 데 가장
43111
+ // 가까운 값은 high다.
43112
+ ...part.detail === void 0 ? {} : { detail: part.detail === "original" ? "high" : part.detail }
43113
+ }
43114
+ };
43115
+ });
43116
+ return { role, content: parts };
43117
+ }
43118
+ async function* translateChatCompletionsStream(body, options) {
43119
+ if (body === null) {
43120
+ options.onClose();
43121
+ throw new UpstreamProtocolError("Chat Completions streaming response had no body");
43122
+ }
43123
+ const reader = body.getReader();
43124
+ const decoder = new TextDecoder();
43125
+ let buffer = "";
43126
+ let byteLength3 = 0;
43127
+ let responseId;
43128
+ let responseModel;
43129
+ let createdEmitted = false;
43130
+ let textSeen = false;
43131
+ let accumulatedText = "";
43132
+ let usage2 = null;
43133
+ let failed = false;
43134
+ const toolCalls = /* @__PURE__ */ new Map();
43135
+ const MESSAGE_ITEM_ID = "chat_message_0";
43136
+ function* consumeChunk(value) {
43137
+ if (!isRecord7(value)) {
43138
+ throw new UpstreamProtocolError("Chat Completions SSE event was not an object");
43139
+ }
43140
+ if (isRecord7(value.error)) {
43141
+ failed = true;
43142
+ yield { type: "error", error: chatCanonicalError(value.error) };
43143
+ return;
43144
+ }
43145
+ if (typeof value.id === "string" && responseId === void 0) {
43146
+ responseId = value.id;
43147
+ }
43148
+ if (typeof value.model === "string" && responseModel === void 0) {
43149
+ responseModel = value.model;
43150
+ }
43151
+ if (!createdEmitted) {
43152
+ createdEmitted = true;
43153
+ yield {
43154
+ type: "response.created",
43155
+ response: {
43156
+ id: responseId ?? "chat_response",
43157
+ model: responseModel ?? "",
43158
+ usage: null
43159
+ }
43160
+ };
43161
+ }
43162
+ if (isRecord7(value.usage)) {
43163
+ usage2 = chatUsage(value.usage);
43164
+ }
43165
+ const choices = Array.isArray(value.choices) ? value.choices : [];
43166
+ for (const choice of choices) {
43167
+ if (!isRecord7(choice)) continue;
43168
+ const delta = isRecord7(choice.delta) ? choice.delta : {};
43169
+ if (typeof delta.content === "string" && delta.content.length > 0) {
43170
+ textSeen = true;
43171
+ accumulatedText += delta.content;
43172
+ yield {
43173
+ type: "response.output_text.delta",
43174
+ item_id: MESSAGE_ITEM_ID,
43175
+ output_index: 0,
43176
+ content_index: 0,
43177
+ delta: delta.content
43178
+ };
43179
+ }
43180
+ const wireToolCalls = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
43181
+ for (const call of wireToolCalls) {
43182
+ if (!isRecord7(call)) continue;
43183
+ const index = typeof call.index === "number" ? call.index : 0;
43184
+ const pending = toolCalls.get(index) ?? { arguments: "" };
43185
+ if (typeof call.id === "string" && call.id.length > 0) {
43186
+ pending.id ??= call.id;
43187
+ }
43188
+ const fn = isRecord7(call.function) ? call.function : {};
43189
+ if (typeof fn.name === "string" && fn.name.length > 0) {
43190
+ pending.name ??= fn.name;
43191
+ }
43192
+ if (typeof fn.arguments === "string") {
43193
+ pending.arguments += fn.arguments;
43194
+ }
43195
+ toolCalls.set(index, pending);
43196
+ }
43197
+ }
43198
+ }
43199
+ function* finish() {
43200
+ if (failed) return;
43201
+ if (!createdEmitted) {
43202
+ createdEmitted = true;
43203
+ yield {
43204
+ type: "response.created",
43205
+ response: { id: responseId ?? "chat_response", model: responseModel ?? "", usage: null }
43206
+ };
43207
+ }
43208
+ if (textSeen) {
43209
+ yield {
43210
+ type: "response.output_text.done",
43211
+ item_id: MESSAGE_ITEM_ID,
43212
+ output_index: 0,
43213
+ content_index: 0,
43214
+ text: accumulatedText
43215
+ };
43216
+ }
43217
+ let outputIndex = 1;
43218
+ for (const [index, pending] of [...toolCalls.entries()].sort(([a], [b]) => a - b)) {
43219
+ if (pending.name === void 0) {
43220
+ throw new UpstreamProtocolError(`Chat Completions tool call ${index} ended without a name`);
43221
+ }
43222
+ const id = pending.id ?? `chat_call_${index}`;
43223
+ const item = {
43224
+ id,
43225
+ type: "function_call",
43226
+ call_id: id,
43227
+ name: pending.name,
43228
+ arguments: pending.arguments
43229
+ };
43230
+ yield { type: "response.output_item.added", output_index: outputIndex, item: { ...item, arguments: "" } };
43231
+ yield {
43232
+ type: "response.function_call_arguments.done",
43233
+ item_id: id,
43234
+ output_index: outputIndex,
43235
+ arguments: pending.arguments
43236
+ };
43237
+ yield { type: "response.output_item.done", output_index: outputIndex, item };
43238
+ outputIndex += 1;
43239
+ }
43240
+ yield {
43241
+ type: "response.completed",
43242
+ response: {
43243
+ id: responseId ?? "chat_response",
43244
+ model: responseModel ?? "",
43245
+ // 하류 message_delta는 usage가 필수다. include_usage에도 usage 청크를 주지
43246
+ // 않는 백엔드에서는 0-usage로 완결하고, 실제 회계는 provider 콘솔이 맡는다.
43247
+ usage: usage2 ?? { input_tokens: 0, output_tokens: 0 }
43248
+ }
43249
+ };
43250
+ }
43251
+ try {
43252
+ while (true) {
43253
+ const result = await readWithIdleTimeout(reader, options);
43254
+ if (result.done) {
43255
+ buffer += decoder.decode();
43256
+ break;
43257
+ }
43258
+ byteLength3 += result.value.byteLength;
43259
+ if (byteLength3 > options.maxBodyBytes) {
43260
+ const error51 = new UpstreamBodyLimitError(options.maxBodyBytes);
43261
+ options.controller.abort(error51);
43262
+ throw error51;
43263
+ }
43264
+ buffer += decoder.decode(result.value, { stream: true });
43265
+ let boundary = nextEventBoundary(buffer);
43266
+ while (boundary !== void 0) {
43267
+ const frame = buffer.slice(0, boundary.index);
43268
+ buffer = buffer.slice(boundary.index + boundary.length);
43269
+ const data = chatFrameData(frame);
43270
+ if (data !== void 0) {
43271
+ yield* consumeChunk(data);
43272
+ }
43273
+ boundary = nextEventBoundary(buffer);
43274
+ }
43275
+ }
43276
+ if (buffer.trim().length > 0) {
43277
+ const data = chatFrameData(buffer);
43278
+ if (data !== void 0) {
43279
+ yield* consumeChunk(data);
43280
+ }
43281
+ }
43282
+ yield* finish();
43283
+ } finally {
43284
+ await reader.cancel().catch(() => void 0);
43285
+ options.onClose();
43286
+ }
43287
+ }
43288
+ function chatFrameData(frame) {
43289
+ const { data } = parseSseFrameFields(frame);
43290
+ if (data.length === 0 || data === "[DONE]") {
43291
+ return void 0;
43292
+ }
43293
+ try {
43294
+ return JSON.parse(data);
43295
+ } catch (error51) {
43296
+ throw new UpstreamProtocolError(
43297
+ `Chat Completions SSE contained invalid JSON: ${error51 instanceof Error ? error51.message : String(error51)}`
43298
+ );
43299
+ }
43300
+ }
43301
+ function chatUsage(value) {
43302
+ const inputTokens = nonNegativeOrZero(value.prompt_tokens);
43303
+ const outputTokens = nonNegativeOrZero(value.completion_tokens);
43304
+ const promptDetails = isRecord7(value.prompt_tokens_details) ? value.prompt_tokens_details : void 0;
43305
+ const completionDetails = isRecord7(value.completion_tokens_details) ? value.completion_tokens_details : void 0;
43306
+ const cachedInputTokens = promptDetails === void 0 ? void 0 : optionalNonNegative(promptDetails.cached_tokens);
43307
+ const reasoningOutputTokens = completionDetails === void 0 ? void 0 : optionalNonNegative(completionDetails.reasoning_tokens);
43308
+ const totalTokens = optionalNonNegative(value.total_tokens);
43309
+ return {
43310
+ input_tokens: inputTokens,
43311
+ output_tokens: outputTokens,
43312
+ ...cachedInputTokens === void 0 ? {} : { cached_input_tokens: cachedInputTokens },
43313
+ ...reasoningOutputTokens === void 0 ? {} : { reasoning_output_tokens: reasoningOutputTokens },
43314
+ ...totalTokens === void 0 ? {} : { total_tokens: totalTokens }
43315
+ };
43316
+ }
43317
+ function chatCanonicalError(error51) {
43318
+ const message = typeof error51.message === "string" ? error51.message : JSON.stringify(error51);
43319
+ const type = typeof error51.type === "string" && error51.type !== "error" ? error51.type : typeof error51.code === "string" ? error51.code : "api_error";
43320
+ return { type, message };
43321
+ }
43322
+ function nonNegativeOrZero(value) {
43323
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
43324
+ }
43325
+ function optionalNonNegative(value) {
43326
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
43327
+ }
43328
+ function isRecord7(value) {
43329
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43330
+ }
43331
+ var OPENCODE_AUTH_PROVIDER_ID = "Claude Code with OpenCode Go";
43332
+ var OPENCODE_GO_API_BASE_URL = "https://opencode.ai/zen/go";
43333
+ var OPENCODE_GO_MODEL = "minimax-m2.5";
43334
+ var OPENCODE_GO_MESSAGES_URL = `${OPENCODE_GO_API_BASE_URL}/v1/messages`;
43335
+ var OPENCODE_GO_RESPONSES_URL = `${OPENCODE_GO_API_BASE_URL}/v1/responses`;
43336
+ var OPENCODE_GO_CHAT_COMPLETIONS_URL = `${OPENCODE_GO_API_BASE_URL}/v1/chat/completions`;
43337
+ function opencodeGoWire(model) {
43338
+ return model.wire ?? "anthropic";
43339
+ }
43340
+ function createOpencodeGoAdapter(wire, options = {}) {
43341
+ if (wire === "responses") {
43342
+ return new OpenAIResponsesAdapter({
43343
+ url: OPENCODE_GO_RESPONSES_URL,
43344
+ ...options.fetch ? { fetch: options.fetch } : {}
43345
+ });
43346
+ }
43347
+ return new OpenAIChatCompletionsAdapter({
43348
+ url: OPENCODE_GO_CHAT_COMPLETIONS_URL,
43349
+ ...options.fetch ? { fetch: options.fetch } : {}
43350
+ });
43351
+ }
43300
43352
 
43301
43353
  // ../../packages/fleet-admiral/src/ai-gateway/role-fit.ts
43302
43354
  var ROLE_FIT = Object.freeze({
@@ -43368,7 +43420,7 @@ function buildGatewayLoadout(input) {
43368
43420
  revision: loadoutRevision(models),
43369
43421
  catalogUpdatedAt: GATEWAY_MODELS_UPDATED_AT,
43370
43422
  models,
43371
- providers: buildProviders(input.exposed, input.quota)
43423
+ providers: buildProviders(input.exposed, input.quota, input.now ?? Date.now)
43372
43424
  };
43373
43425
  }
43374
43426
  function toLoadoutModel(model, defaultModel) {
@@ -43380,7 +43432,7 @@ function toLoadoutModel(model, defaultModel) {
43380
43432
  isSessionDefault: defaultModel !== void 0 && defaultModel.id === model.id
43381
43433
  };
43382
43434
  }
43383
- function buildProviders(exposed, quota) {
43435
+ function buildProviders(exposed, quota, now) {
43384
43436
  const ids = [PARENT_PROVIDER_ID];
43385
43437
  for (const model of exposed) {
43386
43438
  if (!ids.includes(model.provider)) ids.push(model.provider);
@@ -43388,7 +43440,68 @@ function buildProviders(exposed, quota) {
43388
43440
  for (const id of Object.keys(quota ?? {})) {
43389
43441
  if (!ids.includes(id)) ids.push(id);
43390
43442
  }
43391
- return ids.map((id) => ({ id, quota: quota?.[id] ?? UNSUPPORTED_QUOTA }));
43443
+ return ids.map((id) => ({ id, quota: enrichProviderQuota(quota?.[id], now) ?? UNSUPPORTED_QUOTA }));
43444
+ }
43445
+ var MS_PER_HOUR = 36e5;
43446
+ var CADENCE_SESSION_MAX_MS = 20 * MS_PER_HOUR;
43447
+ var CADENCE_DAILY_MAX_MS = 3 * 24 * MS_PER_HOUR;
43448
+ var CADENCE_WEEKLY_MAX_MS = 20 * 24 * MS_PER_HOUR;
43449
+ var MIN_ELAPSED_FRACTION = 0.05;
43450
+ var PACE_CRITICAL = 1.5;
43451
+ var PACE_ELEVATED = 1.1;
43452
+ var USED_CRITICAL_PERCENT = 95;
43453
+ var USED_ELEVATED_PERCENT = 80;
43454
+ function enrichProviderQuota(quota, now) {
43455
+ if (!quota) return void 0;
43456
+ const { windows, ...rest } = quota;
43457
+ if (!windows || windows.length === 0) return rest;
43458
+ const at = typeof quota.fetchedAt === "number" && Number.isFinite(quota.fetchedAt) ? quota.fetchedAt : now();
43459
+ return { ...rest, windows: windows.map((window) => enrichQuotaWindow(window, at)) };
43460
+ }
43461
+ function windowCadence(durationMs) {
43462
+ if (durationMs <= CADENCE_SESSION_MAX_MS) return "session";
43463
+ if (durationMs <= CADENCE_DAILY_MAX_MS) return "daily";
43464
+ if (durationMs <= CADENCE_WEEKLY_MAX_MS) return "weekly";
43465
+ return "monthly";
43466
+ }
43467
+ function windowPressure(usedPercent, paceRatio) {
43468
+ if (usedPercent >= USED_CRITICAL_PERCENT || paceRatio !== void 0 && paceRatio >= PACE_CRITICAL) {
43469
+ return "critical";
43470
+ }
43471
+ if (usedPercent >= USED_ELEVATED_PERCENT || paceRatio !== void 0 && paceRatio >= PACE_ELEVATED) {
43472
+ return "elevated";
43473
+ }
43474
+ return "ok";
43475
+ }
43476
+ function enrichQuotaWindow(window, at) {
43477
+ const durationMs = window.period?.durationMs;
43478
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs <= 0) {
43479
+ return { ...window, pressure: windowPressure(window.usedPercent, void 0) };
43480
+ }
43481
+ const startsAt = window.period?.startsAt ?? (window.resetsAt !== void 0 && window.resetsAt > durationMs ? window.resetsAt - durationMs : void 0);
43482
+ const resetBoundary = window.resetsAt ?? (startsAt !== void 0 ? startsAt + durationMs : void 0);
43483
+ const stale = resetBoundary !== void 0 && at > resetBoundary;
43484
+ let paceRatio;
43485
+ let projectedExhaustionAt;
43486
+ if (startsAt !== void 0 && resetBoundary !== void 0 && !stale && at > startsAt) {
43487
+ const elapsed = Math.min(1, (at - startsAt) / durationMs);
43488
+ if (elapsed >= MIN_ELAPSED_FRACTION) {
43489
+ const used = Math.min(1, Math.max(0, window.usedPercent / 100));
43490
+ paceRatio = Math.round(used / elapsed * 100) / 100;
43491
+ if (used > 0) {
43492
+ const exhaustionAt = startsAt + Math.round((at - startsAt) / used);
43493
+ if (exhaustionAt < resetBoundary) projectedExhaustionAt = exhaustionAt;
43494
+ }
43495
+ }
43496
+ }
43497
+ return {
43498
+ ...window,
43499
+ cadence: windowCadence(durationMs),
43500
+ ...paceRatio !== void 0 ? { paceRatio } : {},
43501
+ ...projectedExhaustionAt !== void 0 ? { projectedExhaustionAt } : {},
43502
+ recoveryHalfLifeMs: Math.round(durationMs / 2),
43503
+ pressure: windowPressure(window.usedPercent, paceRatio)
43504
+ };
43392
43505
  }
43393
43506
  function loadoutRevision(models) {
43394
43507
  const material = [
@@ -43418,7 +43531,10 @@ var GATEWAY_MODELS_DOCTRINE = {
43418
43531
  `models[] contains only the exposed models. A model absent here is one the user turned off \u2014 the gateway still executes it, so pinning it would quietly override that choice with no error.`,
43419
43532
  `constraints.effortLadder lists the only reasoning levels that survive; a level outside it is clamped upstream without notice. Ladders differ per model, and some models have no effort control at all.`,
43420
43533
  `roleFit is null when the axis was never measured. Unmeasured is not unsuitable \u2014 it means quality gives no reason to prefer one identity, so the choice falls to allowance rather than back to the session's own model.`,
43421
- `constraints.quotaScope names the sub-allowance a model is billed against. Read the provider window whose scope matches it; the scope-less window is the sum of pools and can look healthy while that model's own pool is spent.`,
43534
+ `constraints.quotaScope names the sub-allowance a model is billed against. Read the provider window whose scope matches it; a window marked isAggregate sums the pools and can look healthy while that model's own pool is spent \u2014 exclude it from headroom math.`,
43535
+ `Each window's pressure is the server's verdict on that window's state \u2014 "ok", "elevated", or "critical" \u2014 combining remaining headroom with burn pace against the window's own reset clock. Read it before doing any arithmetic of your own; it describes the window, never which model to choose.`,
43536
+ `cadence names a window's normalized reset length (session/daily/weekly/monthly). Window ids do not: two providers can both say "cycle" and mean different lengths. Compare usedPercent only between windows that share a cadence.`,
43537
+ `paceRatio above 1.0 means the window is being spent faster than its clock refills it, and projectedExhaustionAt appears only when that pace would empty the window before it resets. recoveryHalfLifeMs is the average lockout bought by draining the pool now \u2014 emptying a monthly window costs weeks where a session window costs hours. A derived field that is absent means the reading could not support it, never that the window is safe.`,
43422
43538
  `A provider quota of status "unsupported" means the allowance cannot be read, never that it is plentiful.`,
43423
43539
  `An unpinned stage spends whatever this session is currently running on, so its provider is the baseline an offload is measured against. The roster cannot identify that provider \u2014 it is registered once per runtime and cannot see which model a given session launched with \u2014 so match it yourself against the model you are running, and read that provider's window.`,
43424
43540
  `isSessionDefault reflects what Settings currently designates, not necessarily what an already-running session launched with; the two diverge when the setting is changed mid-session.`,
@@ -43496,17 +43612,31 @@ function getExecutorMcpTools(registry4, carrierRuntime, carrierId) {
43496
43612
 
43497
43613
  // ../../packages/fleet-admiral/src/ai-gateway/auth.ts
43498
43614
  async function validateKimiAuthKey(apiKey) {
43499
- const validation = await validateAnthropicCompatibleApiKey({
43615
+ return validateAnthropicCompatibleAuthKey(apiKey, {
43500
43616
  providerId: KIMI_AUTH_PROVIDER_ID,
43501
- apiKey,
43502
43617
  baseUrl: KIMI_CODE_API_BASE_URL,
43503
43618
  model: KIMI_CODE_MODEL
43504
43619
  });
43620
+ }
43621
+ async function validateOpencodeGoAuthKey(apiKey) {
43622
+ return validateAnthropicCompatibleAuthKey(apiKey, {
43623
+ providerId: OPENCODE_AUTH_PROVIDER_ID,
43624
+ baseUrl: OPENCODE_GO_API_BASE_URL,
43625
+ model: OPENCODE_GO_MODEL
43626
+ });
43627
+ }
43628
+ async function validateAnthropicCompatibleAuthKey(apiKey, coordinates) {
43629
+ const validation = await validateAnthropicCompatibleApiKey({
43630
+ providerId: coordinates.providerId,
43631
+ apiKey,
43632
+ baseUrl: coordinates.baseUrl,
43633
+ model: coordinates.model
43634
+ });
43505
43635
  if (isAuthValidationSuccess(validation)) {
43506
- return { providerId: KIMI_AUTH_PROVIDER_ID, status: "success" };
43636
+ return { providerId: coordinates.providerId, status: "success" };
43507
43637
  }
43508
43638
  return {
43509
- providerId: KIMI_AUTH_PROVIDER_ID,
43639
+ providerId: coordinates.providerId,
43510
43640
  status: validation.status,
43511
43641
  detail: validation.detail
43512
43642
  };
@@ -43809,7 +43939,7 @@ If the intended Carrier is unavailable or carrier_dispatch rejects the requested
43809
43939
  { relativePath: "gateway/codebase-research/SKILL.md", content: "---\nname: codebase-research\ndescription: Answer a question about a codebase or an external subject by fanning out independent searches, reading sources directly, and separating what was verified from what was only claimed. Load before orchestrating reconnaissance across many files, subsystems, or external sources. Skip for a single lookup you can perform directly.\n---\n\n# Codebase Research\n\nReconnaissance whose product is **evidence, not a summary**. The run's value comes from covering angles a single reader would miss and from being explicit about what it failed to establish.\n\nExecuting this skeleton \u2014 the surface it runs on, the wiring between stages, and model and effort assignment \u2014 belongs to `workflow`; this skill owns the shape of the run.\n\n## When Not To Use\n\n- A fact one grep or one file read settles. Fanning out costs more than the answer is worth.\n- Work that will change files. Use `implementation-run`.\n- Judging code that already exists against a standard. Use `quality-review`.\n\n## Stage Skeleton\n\n| Stage | Role | Fan | Returns |\n|---|---|---|---|\n| Scope | decompose | 1 | 3-6 angles, each a distinct search strategy \u2014 not paraphrases of one query |\n| Sweep | scan | one per angle | Located candidates with a path or URL and why each is relevant |\n| Read | extract | one per surviving candidate | Claims, each with a verbatim quote and its exact source |\n| Reconcile | synthesize | 1 | Merged findings, ranked, with contradictions kept visible |\n\nRun Sweep and Read as a pipeline. A barrier between them buys nothing: each candidate can be read the moment its angle finds it. Insert a barrier only before Reconcile, which genuinely needs the whole set.\n\n## Rules\n\n- **Angles must differ in method, not wording.** By-name, by-caller, by-test, by-history, by-config are different angles. Three rephrasings of one query is one angle run three times.\n- **A claim without a quote is a lead, not a finding.** Require the source and the literal text; report the count of leads that never became findings.\n- **Deduplicate before reading, not after.** Deduplicate on a normalized identity (path, or host plus path for a URL) so the same source is not read once per angle.\n- **Contradictions survive to the report.** When two sources disagree, say so and name both. Collapsing them into whichever sounds more confident destroys the run's most valuable output.\n- **Name what you failed to reach.** Blocked networks, unreadable files, and truncated searches are results. A report that omits them reads as exhaustive when it is not.\n\n## Stopping\n\nStop when a full sweep round adds no source you had not already read. Do not keep spawning searchers because the subject is large \u2014 spawn them because the last round found something new.\n\n## Gotchas\n\n- **Symptom:** The report is confident and short, and every finding traces to one or two sources.\n **Action:** Check whether the angles actually differed. Re-run with methods, not phrasings.\n **Why:** Similar queries return the same top results, so the fan-out produced redundancy that reads as corroboration.\n\n- **Symptom:** A cited file path or symbol does not exist.\n **Action:** Treat the whole finding as unverified and re-read the source before keeping it.\n **Why:** A stage that could not reach a source may still produce a plausible path; requiring a verbatim quote is what makes this detectable.\n" },
43810
43940
  { relativePath: "gateway/implementation-run/SKILL.md", content: '---\nname: implementation-run\ndescription: Apply one decided change across many files, packages, or call sites by discovering the sites, transforming each in isolation, and inspecting the artifacts rather than the reports. Load before a migration, a sweeping refactor, or a multi-package edit. Skip when the change fits in a few files you will edit directly, or when the approach is not yet decided.\n---\n\n# Implementation Run\n\nThe only stage shape here that **writes**. Its risk is not failure \u2014 a failed edit is visible \u2014 but convergence: many branches each producing something reasonable that together do not match the codebase.\n\nExecuting this skeleton \u2014 the surface it runs on, the wiring between stages, and model and effort assignment \u2014 belongs to `workflow`; this skill owns the shape of the run.\n\n## When Not To Use\n\n- The approach is undecided. Decide first with `architecture-review`; a stage handed an open decision will close it for you, differently in each branch.\n- A handful of files you can edit directly. The per-stage overhead exceeds the work.\n- Judging existing code. Use `quality-review`.\n\n## Stage Skeleton\n\n| Stage | Role | Fan | Returns |\n|---|---|---|---|\n| Discover | map | 1-3 | Every site that must change, each with a path and why it qualifies |\n| **Decide** | \u2014 | **host only** | The literal values every site will use. Decided here, never in a stage. |\n| Apply | implement | one per site or coherent group, `isolation: \'worktree\'` | Files changed, and which existing conventions were matched |\n| Inspect | verify | host reads the diff | Accept or reject per site |\n\nDiscover and Apply pipeline naturally, but **Decide is a barrier by necessity** \u2014 the literals must exist before any site is touched, or each branch invents its own.\n\n## Decisions Travel as Literals\n\nBefore starting any branch, close every judgment gap. Ask both:\n\n1. Must the stage choose a concrete value?\n2. Does it lack the doctrine or convention context to justify that choice?\n\nIf both are yes, **the host chooses the value and passes it verbatim**. This covers design tokens, API paths, setting keys, protocol tokens, names, error message text, thresholds, and constants \u2014 not an exhaustive list.\n\nNever leave a choice to a stage behind phrases like "match the existing style", "pick a consistent name", "follow the convention", or "\uC801\uC808\uD788". A stage on another model has no feel for this repository and will produce something defensible but foreign.\n\n## Rules\n\n- **Isolate every writing branch.** Parallel edits to a shared tree corrupt each other. Worktree isolation costs setup time and disk; pay it whenever more than one branch writes.\n- **Inspect artifacts, never narratives.** Read the actual diff for each site. A stage\'s summary of what it did is evidence of what it believed, not of what it wrote.\n- **Verbatim match or defect.** A literal you sent must appear exactly. An equivalent-looking substitution \u2014 a synonym token, a reformatted path, a renamed key \u2014 is a defect, not a variation.\n- **A site that needs a new decision stops.** When Apply discovers a case Decide did not cover, it returns that fact instead of choosing. Resolve it on the host and start that branch again with the value; do not let one branch set precedent for the rest.\n- **Reject rather than patch.** A branch whose output drifted is re-run with a sharper prompt. Fixing its output by hand hides that the prompt was insufficient, and the next site will drift the same way.\n\n## Scope Warning\n\nMeasurement covered only **local, well-precedented edits** \u2014 a couple of files with an obvious existing pattern to follow. Every model tested handled those correctly. Nothing establishes that this holds for sweeping or cross-package work, where convention drift compounds and each branch sees only its own slice. Treat wide runs as unproven: keep groups small, inspect every diff, and keep a structural change on the host rather than spreading it across branches that each see one slice.\n\n## Stopping\n\nStop when every discovered site is either accepted or explicitly deferred with a reason. Do not accept a run with unexamined sites because the count is large \u2014 an unexamined site is an unknown edit.\n\n## Gotchas\n\n- **Symptom:** Tests pass and the build is green, but the change reads as foreign to the surrounding code.\n **Action:** Diff the produced values against the literals you sent. Re-run the drifted sites with the literal spelled out.\n **Why:** Green checks confirm the code runs, not that it belongs; convention is invisible to a compiler.\n\n- **Symptom:** Different sites solved the same sub-problem differently.\n **Action:** That sub-problem belonged in Decide. Choose once on the host and re-run the affected sites with the value.\n **Why:** Each branch resolved an open decision independently, which is exactly what the Decide barrier exists to prevent.\n\n- **Symptom:** A branch reports success but changed nothing.\n **Action:** Check the returned file list against the actual diff before accepting.\n **Why:** A branch that could not find its target may report the intent as done; only the artifact settles it.\n' },
43811
43941
  { relativePath: "gateway/quality-review/SKILL.md", content: "---\nname: quality-review\ndescription: Review existing code or a change set by splitting the work into independent dimensions, hunting within each, then adversarially verifying every finding before it is reported. Load before a correctness, security, or quality pass over a diff or subsystem. Skip when you already know the defect and only need it fixed.\n---\n\n# Quality Review\n\nThe output is a **judged finding list, not a fix list**. A reviewer that also repairs what it finds loses the independence that made the finding worth having, and repairs things that were never broken.\n\nExecuting this skeleton \u2014 the surface it runs on, the wiring between stages, and model and effort assignment \u2014 belongs to `workflow`; this skill owns the shape of the run.\n\n## When Not To Use\n\n- The defect is known and only the repair remains. Use `implementation-run`.\n- Deciding between designs. Use `architecture-review`.\n- Establishing facts with no standard to judge against. Use `codebase-research`.\n\n## Stage Skeleton\n\n| Stage | Role | Fan | Returns |\n|---|---|---|---|\n| Split | decompose | 1 | The dimensions this review will cover, each with its own standard |\n| Hunt | scan | one per dimension | Candidate findings, each with a file, a line, and a concrete failing scenario |\n| Verify | verify | 2-3 per finding, mixed lineage, prompted to refute | Refuted or survived, with the specific evidence |\n| Adjudicate | \u2014 | **host only** | Confirmed / declined / deferred, with the reason |\n\nPipeline Hunt into Verify \u2014 a dimension's findings can be verified while another dimension is still hunting. Nothing here needs a global barrier.\n\n## Dimensions Stay Separate\n\nNever combine security auditing with functional or end-to-end review in one hunt. Measured outcome: the combined run drops the functional pass \u2014 security findings are more legible, so the agent spends its budget there and reports the run as complete. Give each dimension its own hunter with its own standard.\n\nTypical dimensions, chosen per target rather than run wholesale: correctness, security and input trust, boundary and ownership rules, error and failure handling, test coverage, and convention conformance.\n\n## Verify Is Adversarial\n\nVerifiers are prompted to **refute**, not to confirm. A finding survives only when the refutation attempt fails.\n\n- Default to refuted when uncertain. An unreproduced finding is a hypothesis.\n- Require a concrete failing scenario: inputs or state, and the wrong result. \"This could break\" is not a finding.\n- Distinguish three outcomes. Survived, refuted on merit, and **unverifiable because the verifier errored** are different; collapsing the third into \"refuted\" silently discards real findings when infrastructure fails.\n- Mix lineage across a finding's verifiers. Identical models produce correlated verdicts, which reads as agreement.\n\n## Adjudication Stays on the Host\n\nA surviving finding is evidence, not an instruction. For each one the host decides:\n\n- **Confirm** when it occurs on a path a real workflow reaches, is in scope, and the repair costs less than the defect.\n- **Decline** when it is hypothetical, overfit to the reviewer's reading, outside scope, or contradicts an intended trade-off. Record the reason; a silent skip is indistinguishable from an oversight.\n- **Defer** when it is real but belongs to different work. Say why it is real and why not here.\n\nSeverity never decides disposition. A reviewer's P1 on a path nothing reaches is still a decline.\n\n## Stopping\n\nStop when a hunting round produces no finding that survives verification. Two consecutive dry rounds end the run. A reviewer can always generate another suggestion, so waiting for it to fall silent is an unbounded loop.\n\n## Gotchas\n\n- **Symptom:** The run reports many findings and all of them survived.\n **Action:** Check that verifiers were prompted to refute rather than to assess. A confirming verifier confirms.\n **Why:** Adversarial framing is the entire mechanism; without it the verify stage is a second opinion that agrees by default.\n\n- **Symptom:** Fixing one finding produced the next round's findings.\n **Action:** Roll back the fix rather than widening it. That is evidence the repair was over-scoped.\n **Why:** A repair that breeds findings changed more than the defect required.\n\n- **Symptom:** The security dimension is thorough and the functional one is a sentence.\n **Action:** Re-run the functional dimension on its own hunter.\n **Why:** Combined dimensions do not split budget evenly; the more legible one absorbs it.\n" },
43812
- { relativePath: "gateway/workflow/SKILL.md", content: "---\nname: workflow\ndescription: Run a staged multi-agent operation \u2014 map a stage skeleton onto the workflow execution surface, choose pipeline or barrier between stages, keep failures visible, and assign each stage its model and reasoning effort. Load before executing any skeleton from architecture-review, codebase-research, implementation-run, or quality-review, and before pinning a model or effort anywhere. Skip when the work is one stage you will perform directly.\n---\n\n# Workflow\n\nThe other gateway skills each own the *shape* of a run \u2014 which stages exist, what each returns, where the judgment stays on the host. This skill owns **turning that shape into an actual run**: the surface it executes on, how stages are wired to each other, and what each stage runs on.\n\nA skeleton that is never executed as stages is not a cheaper version of the run. It is a single reader doing every job in one context, which is the failure mode the skeleton exists to prevent.\n\n## Execution Surface\n\nStaged execution requires the workflow execution surface \u2014 the one that runs a script of stages, wires them together, and lets each stage carry its own model and effort. Inspect the live tool surface before concluding anything about it; tools may be lazy-loaded.\n\nThis skill covers that surface only, and that surface is not the default. An Agent \u2014 one run, or a named teammate you can continue \u2014 carries work that needs no wiring between its parts, and the Orchestration Policy Standing Order makes it the default for exactly that reason. A staged run is what the user asks for on top of it, and what it buys is the wiring: data flowing between stages, barriers, fan-out, and a fleet of different models working the same problem at once. Model and effort assignment below applies to both surfaces.\n\n**A surface gated behind user opt-in is unavailable until that opt-in exists.** Some workflow surfaces refuse to run unless the user explicitly asked for a multi-agent run. That refusal is not a defect and it is not a reason to quietly do the whole thing yourself in one context. Report the gate, say what the staged run would cost and what it would buy, and wait \u2014 the same way you would report any unavailable surface.\n\nTwo things stay out of this skill on purpose:\n\n- **Call mechanics.** Argument names, script syntax, return shapes, and which values a field accepts live in the live tool description. Read them there every time. Anything restated here would be a copy that goes stale silently.\n- **Whether to run at all.** That is the Orchestration Policy Standing Order's call, not this skill's.\n\n## Reading a Stage Skeleton\n\nEvery gateway skeleton is a table of `Stage | Role | Fan | Returns`. Read it as an execution plan:\n\n- **Role** is the one-word job \u2014 map, propose, implement, verify, synthesize, transform. It is also the input to model assignment below.\n- **Fan** is how many parallel branches that stage runs. `1` is one branch. `one per <item>` is a fan-out sized by the previous stage's output, not by a number you pick. **`host only` is not a stage you hand off** \u2014 it is a barrier where you do the work yourself, and handing it off defeats the skeleton.\n- **Returns** is the contract. When a stage returns structured data, declare the schema rather than parsing prose; a stage that must fill a shape will retry against it, while a stage asked to write prose will improvise.\n\n## Pipeline by Default\n\nBetween two stages, the choice is pipeline or barrier, and **pipeline is the default**.\n\nA barrier \u2014 waiting for every branch of stage N before starting stage N+1 \u2014 is correct only when stage N+1 genuinely needs the whole set at once: deduplicating across all results before expensive downstream work, deciding literals every later branch must share, early-exit when the total is zero, or a prompt that compares one result against the others.\n\nA barrier is **not** justified by needing to flatten, map, or filter between stages \u2014 do that inside a stage \u2014 nor by the stages feeling conceptually separate, nor by the script reading more cleanly. Each unjustified barrier costs the difference between the slowest branch and the fastest, on every item, for nothing.\n\nEach skill's skeleton already names its own barriers, and they are the load-bearing part of that shape. `implementation-run`'s Decide barrier and `quality-review`'s Adjudicate barrier are the two places the run stops being parallel because a single decision must exist before anything downstream. Do not optimize them away.\n\n## Failures Must Be Loud\n\nFan-out helpers routinely turn a failed branch into an empty result rather than an error. A run that lost three of eight branches then looks like a run that found less, which is indistinguishable from a thorough run over a quiet subject.\n\n- Have each stage **return its failure as a value** \u2014 a result that says it failed and why \u2014 instead of throwing into the helper.\n- Before synthesizing, check the branch count against what you started. A missing branch is a finding.\n- Never report coverage you did not verify. If the run capped, sampled, or dropped anything, say so in the report; silent truncation reads as completeness.\n\n## Model and Effort Assignment\n\nDistribution is the default. Concentrating a run on the model this session happens to run on is the exception, and the exception carries the burden of proof \u2014 the binding rule is in the Orchestration Policy Standing Order. This is the procedure that discharges it.\n\n**Call `gateway_models` first, every time.** Not once per session: allowances move while work is in flight, and a roster entry can be enabled or disabled between two runs.\n\nWork through these in order.\n\n1. **Name the role.** Take it from the skeleton's Role column. If you cannot name it in one word, the stage boundary is wrong; fix the split before choosing a model.\n2. **Name the dominant risk.** What would ruin *this* stage: too little context, unreliable tool use, correlated judgment, drift from repository convention, or incomplete coverage. One risk, not a list.\n3. **Look for a measured fit.** Read `roleFit` for that risk. A declared `fit` is a reason to prefer an identity and a declared `unfit` a reason to avoid it. `null` means unmeasured: it says nothing about quality, and it is never a reason to fall back to the session model.\n4. **Spread the rest by allowance.** For every stage with no measured fit, choose by cost. Read the window that belongs to the model \u2014 the one whose `scope` matches `constraints.quotaScope` when the model declares one, and the provider's scope-less window when it does not \u2014 then send stages toward the lower `usedPercent`. A scope is declared only where one subscription splits into pools; there, and only there, the scope-less figure is a sum that can read healthy while the model's own pool is spent. Move off a provider as it approaches exhaustion instead of discovering it mid-run.\n5. **Re-pick effort for the model you chose.** Ladders differ between identities. A level a model does not advertise is clamped down to the next rung below it with no signal to you, and rejected outright when nothing is below. Take a rung the target's `effortLadder` actually lists, and check the stage's input against the target's `contextWindow`.\n6. **Diversify where disagreement is the product.** A majority-vote or judging stage wants different lineages \u2014 a verifier sharing its subject's lineage inherits the same blind spots. `homolineage: true` marks an identity sharing the parent Claude session's lineage: useful for moving spend, useless for independence.\n7. **Confirm the name exists on both sides.** Roster membership resolves live, but Agent names were fixed when the session started. Pick only a name present in both; a model enabled mid-session is unreachable until restart. `400 unknown model` means re-read the roster.\n8. **Do not choose the load-bearing stage by allowance alone.** When everything downstream rests on one stage \u2014 the contract survey, the final synthesis, the integrating judgment \u2014 let measured fit and lineage independence decide it, and let cost break ties only after those.\n9. **Record the split.** One line per run: which identities carried which stages, and what decided it. A distribution nobody can audit is indistinguishable from a random one.\n\n### What Measurement Actually Showed\n\nTwo measurements, both on 2026-08-02.\n\nThree models against seven stage roles were **indistinguishable on five of them**: structured output, repository search, adversarial judgment, mechanical transformation, and a small implementation task.\n\nTwelve identities were then given one identical mapping task \u2014 twelve files, exact line counts, exact export symbols. **All twelve answered it perfectly**: full coverage, no fabricated file, and every one caught the trap entry whose correct answer was an empty list. What separated them was spend. The cheapest finished on 176k total tokens over 5 tool calls; the most expensive spent 5.20M over 29 for the same answer. Output tokens alone ran 1.7k to 20.3k, so this is not a cache-read artifact.\n\nRead the two together. Quality parity is the prior \u2014 and parity is exactly what makes cost the deciding axis. **Indistinguishable never meant \"inherit\"; it means the expensive choice buys nothing.** The roster declares fit only where a measurement separated the models, and reports `null` everywhere else \u2014 `null` means unmeasured, never unsuitable.\n\n### Rules That Measurement Refuted\n\n- **A larger context window does not mean better reading.** Asked to map a 22-file subsystem, the 1M-window model opened 16 files and a 372k-window model opened all 22. What separated them was thoroughness in tool use, which no catalog field predicts. Use the window as a floor \u2014 can this model hold the input at all \u2014 not as a ranking.\n- **Raising effort does not reliably improve judgment.** The same verification task at the lowest and highest rungs produced the same verdict with equal reasoning quality. Effort pays only once a task is hard enough to need it; raising it by habit buys nothing and costs throughput.\n- **A local, well-precedented edit does not need the session model.** Every model tested landed the change in the right files, found the package's existing export pattern instead of inventing one, and matched the surrounding comment language. This does **not** generalize to sweeping or multi-package work, where convention drift compounds and goes unseen.\n\n### Handing Work to a Different Model\n\nA stage running on another model has no feel for this repository's conventions, so decisions must travel as literal values, not as descriptions. Name the exact token, path, setting key, or constant; never write \"match the existing style\" or \"pick something consistent\". On return, check the artifacts against the literals you sent \u2014 an equivalent-looking substitution is a defect, not a variation.\n\n## Gotchas\n\n- **Symptom:** A run that pinned several models produced uniform-looking results, or one stage's output is missing with no error.\n **Action:** Check whether that branch failed rather than ran. Confirm each pinned id is still in the roster and return branch failures as values instead of letting the helper collapse them.\n **Why:** A de-selected or mistyped id fails at the gateway, but the fan-out helper turns a failed branch into an empty slot, so a heterogeneous run silently becomes a partial one.\n\n- **Symptom:** A stage ran at a different reasoning level than the one requested.\n **Action:** Read that model's ladder from the roster and request a level it actually advertises.\n **Why:** Ladders are not uniform \u2014 some models have no `medium`, others no effort control at all \u2014 and an off-ladder level is clamped upstream without any signal.\n\n- **Symptom:** A provider looked like it had room, but its requests began failing.\n **Action:** Read the window whose `scope` matches the model's `quotaScope`, not the provider's combined figure.\n **Why:** One subscription can bill through separate pools; the sum can read comfortable while the pool a given model draws from is nearly spent.\n\n- **Symptom:** A stage returned nothing at all \u2014 no result, no error you can quote \u2014 while other stages on the same provider succeeded.\n **Action:** Treat a high `usedPercent` on that model's own window as the explanation and move those stages to another provider. Do not wait for a message that says exhausted.\n **Why:** There is no exhaustion status. `status` distinguishes *reading* failures \u2014 not connected, signed out, expired, no subscription, stale, error \u2014 and a spent pool is visible only as `usedPercent` near 100. A stage dying after retries with an empty return is what exhaustion actually looks like from here.\n\n- **Symptom:** A model you just enabled is in `gateway_models` but every attempt to run a stage on it fails as an unknown Agent.\n **Action:** Use only names present in both the live roster and the Agent names this session started with. Reaching a newly enabled model requires a new session.\n **Why:** The roster re-reads the user's selection on every call, but Agent names were serialized once at session start. The two drift apart the moment settings change mid-session.\n\n- **Symptom:** The run took as long as doing it yourself, with the same total cost.\n **Action:** Count the barriers. Each one that no stage actually needed becomes wall-clock spent waiting for the slowest branch.\n **Why:** Staging buys overlap; a skeleton executed as a sequence of barriers pays the coordination cost and collects none of it back.\n\n- **Symptom:** A stage came back asking what to do, or made a choice the skeleton reserved for the host.\n **Action:** Move that decision to the preceding host-only barrier and run the stage again with the value spelled out.\n **Why:** A stage handed an open decision always closes it, differently in each branch \u2014 which is the failure the barrier was placed there to prevent.\n" },
43942
+ { relativePath: "gateway/workflow/SKILL.md", content: "---\nname: workflow\ndescription: Run a staged multi-agent operation \u2014 map a stage skeleton onto the workflow execution surface, choose pipeline or barrier between stages, keep failures visible, and assign each stage its model and reasoning effort. Load before executing any skeleton from architecture-review, codebase-research, implementation-run, or quality-review, and before pinning a model or effort anywhere. Skip when the work is one stage you will perform directly.\n---\n\n# Workflow\n\nThe other gateway skills each own the *shape* of a run \u2014 which stages exist, what each returns, where the judgment stays on the host. This skill owns **turning that shape into an actual run**: the surface it executes on, how stages are wired to each other, and what each stage runs on.\n\nA skeleton that is never executed as stages is not a cheaper version of the run. It is a single reader doing every job in one context, which is the failure mode the skeleton exists to prevent.\n\n## Execution Surface\n\nStaged execution requires the workflow execution surface \u2014 the one that runs a script of stages, wires them together, and lets each stage carry its own model and effort. Inspect the live tool surface before concluding anything about it; tools may be lazy-loaded.\n\nThis skill covers that surface only, and that surface is not the default. An Agent \u2014 one run, or a named teammate you can continue \u2014 carries work that needs no wiring between its parts, and the Orchestration Policy Standing Order makes it the default for exactly that reason. A staged run is what the user asks for on top of it, and what it buys is the wiring: data flowing between stages, barriers, fan-out, and a fleet of different models working the same problem at once. Model and effort assignment below applies to both surfaces.\n\n**A surface gated behind user opt-in is unavailable until that opt-in exists.** Some workflow surfaces refuse to run unless the user explicitly asked for a multi-agent run. That refusal is not a defect and it is not a reason to quietly do the whole thing yourself in one context. Report the gate, say what the staged run would cost and what it would buy, and wait \u2014 the same way you would report any unavailable surface.\n\nTwo things stay out of this skill on purpose:\n\n- **Call mechanics.** Argument names, script syntax, return shapes, and which values a field accepts live in the live tool description. Read them there every time. Anything restated here would be a copy that goes stale silently.\n- **Whether to run at all.** That is the Orchestration Policy Standing Order's call, not this skill's.\n\n## Reading a Stage Skeleton\n\nEvery gateway skeleton is a table of `Stage | Role | Fan | Returns`. Read it as an execution plan:\n\n- **Role** is the one-word job \u2014 map, propose, implement, verify, synthesize, transform. It is also the input to model assignment below.\n- **Fan** is how many parallel branches that stage runs. `1` is one branch. `one per <item>` is a fan-out sized by the previous stage's output, not by a number you pick. **`host only` is not a stage you hand off** \u2014 it is a barrier where you do the work yourself, and handing it off defeats the skeleton.\n- **Returns** is the contract. When a stage returns structured data, declare the schema rather than parsing prose; a stage that must fill a shape will retry against it, while a stage asked to write prose will improvise.\n\n## Pipeline by Default\n\nBetween two stages, the choice is pipeline or barrier, and **pipeline is the default**.\n\nA barrier \u2014 waiting for every branch of stage N before starting stage N+1 \u2014 is correct only when stage N+1 genuinely needs the whole set at once: deduplicating across all results before expensive downstream work, deciding literals every later branch must share, early-exit when the total is zero, or a prompt that compares one result against the others.\n\nA barrier is **not** justified by needing to flatten, map, or filter between stages \u2014 do that inside a stage \u2014 nor by the stages feeling conceptually separate, nor by the script reading more cleanly. Each unjustified barrier costs the difference between the slowest branch and the fastest, on every item, for nothing.\n\nEach skill's skeleton already names its own barriers, and they are the load-bearing part of that shape. `implementation-run`'s Decide barrier and `quality-review`'s Adjudicate barrier are the two places the run stops being parallel because a single decision must exist before anything downstream. Do not optimize them away.\n\n## Failures Must Be Loud\n\nFan-out helpers routinely turn a failed branch into an empty result rather than an error. A run that lost three of eight branches then looks like a run that found less, which is indistinguishable from a thorough run over a quiet subject.\n\n- Have each stage **return its failure as a value** \u2014 a result that says it failed and why \u2014 instead of throwing into the helper.\n- Before synthesizing, check the branch count against what you started. A missing branch is a finding.\n- Never report coverage you did not verify. If the run capped, sampled, or dropped anything, say so in the report; silent truncation reads as completeness.\n\n## Model and Effort Assignment\n\nDistribution is the default. Concentrating a run on the model this session happens to run on is the exception, and the exception carries the burden of proof \u2014 the binding rule is in the Orchestration Policy Standing Order. This is the procedure that discharges it.\n\n**Call `gateway_models` first, every time.** Not once per session: allowances move while work is in flight, and a roster entry can be enabled or disabled between two runs.\n\nWork through these in order.\n\n1. **Name the role.** Take it from the skeleton's Role column. If you cannot name it in one word, the stage boundary is wrong; fix the split before choosing a model.\n2. **Name the dominant risk.** What would ruin *this* stage: too little context, unreliable tool use, correlated judgment, drift from repository convention, or incomplete coverage. One risk, not a list.\n3. **Look for a measured fit.** Read `roleFit` for that risk. A declared `fit` is a reason to prefer an identity and a declared `unfit` a reason to avoid it. `null` means unmeasured: it says nothing about quality, and it is never a reason to fall back to the session model.\n4. **Spread the rest by allowance.** For every stage with no measured fit, choose by cost. Read the window that belongs to the model \u2014 the one whose `scope` matches `constraints.quotaScope` when the model declares one, and the provider's scope-less window when it does not \u2014 and let the roster's own verdict lead: prefer windows at `pressure: \"ok\"`, treat `\"elevated\"` as a reason to route elsewhere, and send nothing to `\"critical\"` unless every alternative is worse. Break a tie between windows that share a `cadence` by the lower `usedPercent`, and never compare percentages across cadences \u2014 a weekly window at 49% early in its week burns hotter than a monthly one at 78% near its reset, and `paceRatio` above 1.0 says so directly. On an older reading that carries none of the derived fields, treat percentages as comparable only within a single provider's windows \u2014 a shared id like `cycle` does not mean a shared length \u2014 and across providers trust only the extreme: a window near 100 is spent whatever its clock. A scope is declared only where one subscription splits into pools; there the scope-less figure is marked `isAggregate` \u2014 a sum that can read healthy while the model's own pool is spent, and one that stays out of headroom math. Move off a provider as its windows go elevated instead of discovering exhaustion mid-run.\n5. **Re-pick effort for the model you chose.** Ladders differ between identities. A level a model does not advertise is clamped down to the next rung below it with no signal to you, and rejected outright when nothing is below. Take a rung the target's `effortLadder` actually lists, and check the stage's input against the target's `contextWindow`.\n6. **Diversify where disagreement is the product.** A majority-vote or judging stage wants different lineages \u2014 a verifier sharing its subject's lineage inherits the same blind spots. `homolineage: true` marks an identity sharing the parent Claude session's lineage: useful for moving spend, useless for independence.\n7. **Confirm the name exists on both sides.** Roster membership resolves live, but Agent names were fixed when the session started. Pick only a name present in both; a model enabled mid-session is unreachable until restart. `400 unknown model` means re-read the roster.\n8. **Do not choose the load-bearing stage by allowance alone.** When everything downstream rests on one stage \u2014 the contract survey, the final synthesis, the integrating judgment \u2014 let measured fit and lineage independence decide it, and let cost break ties only after those.\n9. **Record the split.** One line per run: which identities carried which stages, and what decided it. A distribution nobody can audit is indistinguishable from a random one.\n\n### What Measurement Actually Showed\n\nTwo measurements, both on 2026-08-02.\n\nThree models against seven stage roles were **indistinguishable on five of them**: structured output, repository search, adversarial judgment, mechanical transformation, and a small implementation task.\n\nTwelve identities were then given one identical mapping task \u2014 twelve files, exact line counts, exact export symbols. **All twelve answered it perfectly**: full coverage, no fabricated file, and every one caught the trap entry whose correct answer was an empty list. What separated them was spend. The cheapest finished on 176k total tokens over 5 tool calls; the most expensive spent 5.20M over 29 for the same answer. Output tokens alone ran 1.7k to 20.3k, so this is not a cache-read artifact.\n\nRead the two together. Quality parity is the prior \u2014 and parity is exactly what makes cost the deciding axis. **Indistinguishable never meant \"inherit\"; it means the expensive choice buys nothing.** The roster declares fit only where a measurement separated the models, and reports `null` everywhere else \u2014 `null` means unmeasured, never unsuitable.\n\n### Rules That Measurement Refuted\n\n- **A larger context window does not mean better reading.** Asked to map a 22-file subsystem, the 1M-window model opened 16 files and a 372k-window model opened all 22. What separated them was thoroughness in tool use, which no catalog field predicts. Use the window as a floor \u2014 can this model hold the input at all \u2014 not as a ranking.\n- **Raising effort does not reliably improve judgment.** The same verification task at the lowest and highest rungs produced the same verdict with equal reasoning quality. Effort pays only once a task is hard enough to need it; raising it by habit buys nothing and costs throughput.\n- **A local, well-precedented edit does not need the session model.** Every model tested landed the change in the right files, found the package's existing export pattern instead of inventing one, and matched the surrounding comment language. This does **not** generalize to sweeping or multi-package work, where convention drift compounds and goes unseen.\n\n### Handing Work to a Different Model\n\nA stage running on another model has no feel for this repository's conventions, so decisions must travel as literal values, not as descriptions. Name the exact token, path, setting key, or constant; never write \"match the existing style\" or \"pick something consistent\". On return, check the artifacts against the literals you sent \u2014 an equivalent-looking substitution is a defect, not a variation.\n\n## Gotchas\n\n- **Symptom:** A run that pinned several models produced uniform-looking results, or one stage's output is missing with no error.\n **Action:** Check whether that branch failed rather than ran. Confirm each pinned id is still in the roster and return branch failures as values instead of letting the helper collapse them.\n **Why:** A de-selected or mistyped id fails at the gateway, but the fan-out helper turns a failed branch into an empty slot, so a heterogeneous run silently becomes a partial one.\n\n- **Symptom:** A stage ran at a different reasoning level than the one requested.\n **Action:** Read that model's ladder from the roster and request a level it actually advertises.\n **Why:** Ladders are not uniform \u2014 some models have no `medium`, others no effort control at all \u2014 and an off-ladder level is clamped upstream without any signal.\n\n- **Symptom:** A provider looked like it had room, but its requests began failing.\n **Action:** Read the window whose `scope` matches the model's `quotaScope`, not the provider's combined figure.\n **Why:** One subscription can bill through separate pools; the sum can read comfortable while the pool a given model draws from is nearly spent.\n\n- **Symptom:** A stage returned nothing at all \u2014 no result, no error you can quote \u2014 while other stages on the same provider succeeded.\n **Action:** Treat a `\"critical\"` pressure \u2014 or a `usedPercent` near 100 \u2014 on that model's own window as the explanation and move those stages to another provider. Do not wait for a message that says exhausted.\n **Why:** There is no exhaustion status. `status` distinguishes *reading* failures \u2014 not connected, signed out, expired, no subscription, stale, error \u2014 and a spent pool is visible only in its own window's figures. A stage dying after retries with an empty return is what exhaustion actually looks like from here.\n\n- **Symptom:** A model you just enabled is in `gateway_models` but every attempt to run a stage on it fails as an unknown Agent.\n **Action:** Use only names present in both the live roster and the Agent names this session started with. Reaching a newly enabled model requires a new session.\n **Why:** The roster re-reads the user's selection on every call, but Agent names were serialized once at session start. The two drift apart the moment settings change mid-session.\n\n- **Symptom:** The run took as long as doing it yourself, with the same total cost.\n **Action:** Count the barriers. Each one that no stage actually needed becomes wall-clock spent waiting for the slowest branch.\n **Why:** Staging buys overlap; a skeleton executed as a sequence of barriers pays the coordination cost and collects none of it back.\n\n- **Symptom:** A stage came back asking what to do, or made a choice the skeleton reserved for the host.\n **Action:** Move that decision to the preceding host-only barrier and run the stage again with the value spelled out.\n **Why:** A stage handed an open decision always closes it, differently in each branch \u2014 which is the failure the barrier was placed there to prevent.\n" },
43813
43943
  { relativePath: "protocol-baseline/SKILL.md", content: "---\nname: protocol-baseline\ndescription: Use the compact Fleet protocol mode for simple, reversible, single-surface work.\n---\n\n# Fleet Protocol: Baseline\n\nUse this mode only for simple, reversible, single-surface operational work.\n\nAt any point during the work, if a Downward Guard trigger appears, stop and re-classify.\n\n## Checkpoints\n\nNone. Selecting baseline implies Mission Anchor Compact Mode.\n\n## Reporting Cadence\n\nAs you move through this protocol, report progress to the user in order.\n\n1. Brief in one line how the Procedure will proceed. \u2192 report `brief: <\u2026>`\n2. State that execution is beginning and run the Procedure. \u2192 report `status: executing`\n\n## General Quarters\n\nConfirm each readiness check below before the Procedure. Work through them in order and report each as you confirm it, then proceed to the Objective anchor. These checks prepare the work; they do not gate entry.\n\n- [ ] **Common** \u2014 objective stated (Mission Anchor), mode-fit holds (Mode Gate), Standing Orders binding. \u2192 report `common: ready`\n- [ ] **Single surface** \u2014 the exact file, command, or fact is identified. \u2192 report `surface: <x>`\n- [ ] **Reversibility** \u2014 the change is trivially reversible. \u2192 report `reversible: yes`\n\n## Procedure\n\n1. Objective statement: state the Mission Anchor objective in one line.\n2. Exact fact/file verification: verify the exact file, command, or fact needed for the request.\n3. Execution: make the smallest reversible change or run the exact requested command.\n4. Result verification: check the touched surface or command result.\n5. One-line report: report what changed, verification, and any skipped escalation trigger.\n" },
43814
43944
  { relativePath: "protocol-frontline/SKILL.md", content: "---\nname: protocol-frontline\ndescription: Use the coordinated Fleet protocol mode for multi-carrier or parallel ownership work.\n---\n\n# Fleet Protocol: Frontline\n\nUse this mode when operational work requires multiple Carriers, independent parallel workstreams, cross-carrier review loops, or file ownership coordination. If the work is high risk but single-owner, use `protocol-redline` instead.\n\n## Checkpoints\n\nDecomposition, Dispatch, Integration, Verification.\n\n## Reporting Cadence\n\nAs you move through this protocol, report progress to the user in order \u2014 each step on its own line with its report token.\n\n1. Brief how the Procedure will proceed \u2014 name (a) the Procedure steps that will run, (b) each carrier's file or responsibility ownership, and (c) the dispatch wave sequencing. \u2192 report `brief: <\u2026>`\n2. State that execution is beginning and run the Procedure. \u2192 report `status: executing`\n\n## General Quarters\n\nConfirm each readiness check below before the Procedure. Work through them in order and report each as you confirm it, then proceed to reconnaissance and decomposition. These checks prepare the work; they do not gate entry.\n\n- [ ] **Common** \u2014 objective stated (Mission Anchor), mode-fit holds (Mode Gate), Standing Orders binding. \u2192 report `common: ready`\n- [ ] **Impact radius** \u2014 flag public-surface or API impact, irreversibility, and any security-sensitive surface. \u2192 report `impact: <\u2026>`\n- [ ] **Rollback** \u2014 identify a rollback-safe checkpoint and any user approval point before execution begins. \u2192 report `rollback: <\u2026>`\n- [ ] **Carrier availability** \u2014 confirm the intended carriers are actually exposed and available this session. \u2192 report `carriers: <\u2026>`\n- [ ] **Ownership** \u2014 pre-sketch each carrier's file or responsibility boundary. \u2192 report `ownership: <\u2026>`\n- [ ] **Shared resources** \u2014 flag shared mutable resources (same files, lock files, or a singleton test environment). \u2192 report `shared: <\u2026|none>`\n- [ ] **Dependencies** \u2014 pre-classify parallel versus sequential work before decomposition and dispatch. \u2192 report `dependencies: <parallel|sequenced: \u2026>`\n\n## Procedure\n\n1. Reconnaissance and decomposition: audit known facts, identify gaps, map affected surfaces, and split work into independently verifiable missions.\n2. Ownership graph: assign each Carrier a clear file or responsibility boundary, note dependencies, and identify shared mutable resources.\n3. Host-authored structured planning boundary: `Apply the Context Confidence Standing Order \u2014 entry requires complete`. Resolve all blocking and confirmatory gaps before the host authors the dispatch plan.\n4. Parallel dispatch: use the `carrier-operations` skill's sequencing rules to launch independent Carrier work in parallel; sequence only for explicit dependencies or shared resources.\n5. Integration: re-read files before editing or accepting Carrier output, reconcile overlaps, and preserve unrelated user or Carrier changes.\n6. Cross-carrier review loop: route implementation outputs to review Carriers, send actionable findings back to owners, and re-review changed surfaces.\n7. Verification: run integrated tests and apply Deep Dive to speculative or conflicting Carrier claims.\n8. Documentation and completion report: update directly affected docs and report executed waves, Carrier ownership, QA, unresolved risks, and final Result Integrity checks.\n\n## Cross-Carrier Feedback Patterns\n\nWhen composing waves and review loops, select the structured feedback pattern that fits the task:\n\n| Pattern | Flow | When |\n|---------|------|------|\n| **Build \u2192 Review** | implementation carrier \u2192 review carrier \u2192 findings back to implementation carrier \u2192 re-review | Standard implementation cycle |\n| **Analyze \u2192 Execute** | implementation or refactoring carrier \u2192 review carrier verifies | Refactoring workflow |\n| **Decide \u2192 Host Planning \u2192 Execute** | optional judgment carrier \u2192 host-authored plan \u2192 execution carrier | Complex features |\n| **Research \u2192 Act** | reconnaissance carrier \u2192 appropriate follow-up carrier from the active roster | Unknown scope tasks |\n" },
43815
43945
  { relativePath: "protocol-midline/SKILL.md", content: "---\nname: protocol-midline\ndescription: Use the normal Fleet protocol mode for bounded operational work without downward-guard triggers.\n---\n\n# Fleet Protocol: Midline\n\nUse this mode for ordinary bounded operational work.\n\nAt any point during the work, if a Downward Guard trigger appears, stop and re-classify.\n\n## Checkpoints\n\nReconnaissance, Plan, Execution, Verification.\n\n## Reporting Cadence\n\nAs you move through this protocol, report progress to the user in order \u2014 each step on its own line with its report token.\n\n1. Brief how the Procedure will proceed \u2014 name (a) the Procedure steps that will run, (b) the target surfaces, and (c) the verification command. \u2192 report `brief: <\u2026>`\n2. State that execution is beginning and run the Procedure. \u2192 report `status: executing`\n\n## General Quarters\n\nConfirm each readiness check below before the Procedure. Work through them in order and report each as you confirm it, then proceed to focused reconnaissance. These checks prepare the work; they do not gate entry.\n\n- [ ] **Common** \u2014 objective stated (Mission Anchor), mode-fit holds (Mode Gate), Standing Orders binding. \u2192 report `common: ready`\n- [ ] **Target surfaces** \u2014 provisionally name the minimal modules or files reconnaissance will touch; confirm or revise in the brief after reconnaissance. \u2192 report `surfaces: <\u2026>`\n- [ ] **Verification** \u2014 provisionally pre-load the test, build, or check command that will prove the work done; confirm or revise in the brief after reconnaissance. \u2192 report `verify: <cmd>`\n- [ ] **Carrier** \u2014 declare whether a carrier dispatch is needed. \u2192 report `carrier: <none|\u2026>`\n\n## Procedure\n\n1. Focused reconnaissance: audit known facts, identify blocking and confirmatory gaps, and inspect the minimal relevant surfaces.\n2. Host-authored planning boundary: `Apply the Context Confidence Standing Order \u2014 entry requires sufficient`. Resolve all blocking gaps before the host plans.\n3. Host-authored inline plan: state objective, targets, execution steps, and done criteria.\n4. Execution: implement the plan in narrow batches, using Carrier Operations Policy when delegation is appropriate.\n5. Verification and review: run targeted checks, apply Deep Dive to speculative results, and fix actionable issues.\n6. Documentation and final report: update directly affected docs only when behavior or operator guidance changed, then summarize changes and QA.\n" },
@@ -43999,6 +44129,16 @@ function claudeHooks(options) {
43999
44129
  const userPromptSubmitExecs = [options.captureSessionHookExec, options.turnStartHookExec, options.autoNameHookExec].filter((exec2) => exec2 !== void 0);
44000
44130
  const stopExecs = [options.turnEndHookExec].filter((exec2) => exec2 !== void 0);
44001
44131
  const inputWaitingExec = options.inputWaitingHookExec;
44132
+ const preToolUse = [
44133
+ ...inputWaitingExec ? [{
44134
+ matcher: "AskUserQuestion",
44135
+ hooks: [claudeCommandHook(inputWaitingExec)]
44136
+ }] : [],
44137
+ ...options.backgroundSpawnHookExec ? [{
44138
+ matcher: "Task|Agent|Workflow",
44139
+ hooks: [claudeCommandHook(options.backgroundSpawnHookExec)]
44140
+ }] : []
44141
+ ];
44002
44142
  return {
44003
44143
  hooks: {
44004
44144
  ...userPromptSubmitExecs.length > 0 ? {
@@ -44011,15 +44151,17 @@ function claudeHooks(options) {
44011
44151
  hooks: stopExecs.map(claudeCommandHook)
44012
44152
  }]
44013
44153
  } : {},
44154
+ ...preToolUse.length > 0 ? { PreToolUse: preToolUse } : {},
44014
44155
  ...inputWaitingExec ? {
44015
- PreToolUse: [{
44016
- matcher: "AskUserQuestion",
44017
- hooks: [claudeCommandHook(inputWaitingExec)]
44018
- }],
44019
44156
  Notification: [{
44020
44157
  matcher: "permission_prompt|elicitation_dialog",
44021
44158
  hooks: [claudeCommandHook(inputWaitingExec)]
44022
44159
  }]
44160
+ } : {},
44161
+ ...options.backgroundStopHookExec ? {
44162
+ SubagentStop: [{
44163
+ hooks: [claudeCommandHook(options.backgroundStopHookExec)]
44164
+ }]
44023
44165
  } : {}
44024
44166
  }
44025
44167
  };
@@ -44267,6 +44409,8 @@ async function injectAgentCliProfile(profile, options) {
44267
44409
  turnStartHookExec: options.turnStartHookExec,
44268
44410
  turnEndHookExec: options.turnEndHookExec,
44269
44411
  inputWaitingHookExec: options.inputWaitingHookExec,
44412
+ backgroundSpawnHookExec: options.backgroundSpawnHookExec,
44413
+ backgroundStopHookExec: options.backgroundStopHookExec,
44270
44414
  autoNameHookExec: options.autoNameHookExec,
44271
44415
  withMarketplaceLock: options.withMarketplaceLock
44272
44416
  });
@@ -51351,6 +51495,9 @@ function createTerminalSessionManager(deps) {
51351
51495
  touchActivity(session);
51352
51496
  session.pty.resize(session.cols, session.rows);
51353
51497
  replayScrollback(session, socket);
51498
+ if (socket.readyState === WS_OPEN_STATE) {
51499
+ socket.send(Buffer.from(JSON.stringify({ type: "replay_end" }), "utf8"), { binary: false });
51500
+ }
51354
51501
  socket.on("message", (data, isBinary) => handleSocketMessage(session, data, isBinary));
51355
51502
  socket.once("close", () => detachSocket(session, socket));
51356
51503
  }
@@ -51485,13 +51632,15 @@ function createTerminalSessionManager(deps) {
51485
51632
  function handlePtyData(session, data) {
51486
51633
  touchActivity(session);
51487
51634
  const buffer = Buffer.from(data, "utf8");
51488
- respondToTerminalQueries(session, buffer);
51635
+ const liveSocket = session.activeSocket?.readyState === WS_OPEN_STATE ? session.activeSocket : null;
51636
+ const queryResponses = scanTerminalQueries(session, buffer);
51637
+ if (!liveSocket) {
51638
+ for (const response of queryResponses) writeTerminalQueryResponse(session, response);
51639
+ }
51489
51640
  observeOscTitles(session, buffer);
51490
51641
  session.scrollback.push(buffer);
51491
51642
  while (session.scrollback.length > scrollbackLimit) session.scrollback.shift();
51492
- if (session.activeSocket && session.activeSocket.readyState === WS_OPEN_STATE) {
51493
- session.activeSocket.send(buffer, { binary: true });
51494
- }
51643
+ liveSocket?.send(buffer, { binary: true });
51495
51644
  }
51496
51645
  function observeOscTitles(session, buffer) {
51497
51646
  if (!session.titleParser || !session.titleListener) return;
@@ -51637,8 +51786,9 @@ function clearGraceTimer(session) {
51637
51786
  clearTimeout(session.graceTimer);
51638
51787
  session.graceTimer = null;
51639
51788
  }
51640
- function respondToTerminalQueries(session, buffer) {
51789
+ function scanTerminalQueries(session, buffer) {
51641
51790
  const text2 = `${session.terminalQueryResidual}${buffer.toString("utf8")}`;
51791
+ const responses = [];
51642
51792
  session.terminalQueryResidual = "";
51643
51793
  let cursor = 0;
51644
51794
  while (cursor < text2.length) {
@@ -51652,9 +51802,11 @@ function respondToTerminalQueries(session, buffer) {
51652
51802
  session.terminalQueryResidual = trimTerminalQueryResidual(text2.slice(start));
51653
51803
  break;
51654
51804
  }
51655
- writeTerminalQueryResponse(session, resolveTerminalQueryResponse(session, text2.slice(start, end + 1)));
51805
+ const response = resolveTerminalQueryResponse(session, text2.slice(start, end + 1));
51806
+ if (response) responses.push(response);
51656
51807
  cursor = end + 1;
51657
51808
  }
51809
+ return responses;
51658
51810
  }
51659
51811
  function readTrailingEscape(text2) {
51660
51812
  return text2.endsWith(ANSI_ESCAPE) ? ANSI_ESCAPE : "";
@@ -51926,12 +52078,11 @@ function hashablePath(entry) {
51926
52078
  return filePath;
51927
52079
  }
51928
52080
  var AGENT_CLI_PATHS_STORAGE_KEY = "agent-cli-paths";
51929
- var AGENT_CLI_COMMANDS = ["claude", "cursor-agent"];
51930
- var STORED_AGENT_CLI_COMMANDS = ["claude", "codex", "cursor-agent"];
52081
+ var AGENT_CLI_COMMANDS = ["claude"];
52082
+ var STORED_AGENT_CLI_COMMANDS = ["claude", "codex"];
51931
52083
  var OVERRIDE_ENV_BY_COMMAND = {
51932
52084
  claude: "CLAUDE_BIN",
51933
- codex: "CODEX_BIN",
51934
- "cursor-agent": "CURSOR_AGENT_BIN"
52085
+ codex: "CODEX_BIN"
51935
52086
  };
51936
52087
  var agentCliPathsWriteTail = Promise.resolve();
51937
52088
  function createAgentCliPathStore(storage, pluginId) {
@@ -51960,7 +52111,7 @@ function serializeAgentCliPathsWrite(write) {
51960
52111
  return result;
51961
52112
  }
51962
52113
  function normalizeAgentCliPaths(value) {
51963
- if (!isRecord7(value) || value.version !== 1 || !isRecord7(value.paths)) {
52114
+ if (!isRecord8(value) || value.version !== 1 || !isRecord8(value.paths)) {
51964
52115
  return { version: 1, paths: {} };
51965
52116
  }
51966
52117
  const paths = {};
@@ -52006,7 +52157,6 @@ function validateUserAgentCliPath(executablePath, env, platform = process.platfo
52006
52157
  }
52007
52158
  function agentCliCommandForId(cliId) {
52008
52159
  if (cliId === "claude" || cliId === "claude-native" || cliId === "claude-gateway") return "claude";
52009
- if (cliId === "cursor") return "cursor-agent";
52010
52160
  return null;
52011
52161
  }
52012
52162
  function applyAgentCliPathEnvOverlay(env, cliId, userPaths) {
@@ -52084,7 +52234,7 @@ function readPathEntries(env, platform) {
52084
52234
  const separator = platform === "win32" ? ";" : path23__default.delimiter;
52085
52235
  return value.split(separator).filter((entry) => entry.length > 0);
52086
52236
  }
52087
- function isRecord7(value) {
52237
+ function isRecord8(value) {
52088
52238
  return typeof value === "object" && value !== null && !Array.isArray(value);
52089
52239
  }
52090
52240
  function isNodeError(error51) {
@@ -52093,8 +52243,7 @@ function isNodeError(error51) {
52093
52243
 
52094
52244
  // ../fleet-plugins/terminal/server/agent-api/agent-cli-detect.ts
52095
52245
  var BINARY_DISPLAY_NAMES = {
52096
- claude: "Claude Code",
52097
- "cursor-agent": "Cursor Agent"
52246
+ claude: "Claude Code"
52098
52247
  };
52099
52248
  var VERSION_PROBE_TIMEOUT_MS = 5e3;
52100
52249
  var SEMVER_PATTERN = /(\d+\.\d+\.\d+)/;
@@ -52241,6 +52390,36 @@ async function readConsoleQuotaSnapshot(origin, fetchImpl = fetch) {
52241
52390
  function record3(value) {
52242
52391
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
52243
52392
  }
52393
+ var AMOUNT_PATTERN = /^\d{1,15}$/;
52394
+ var MAX_WINDOW_DURATION_MS = 400 * 24 * 36e5;
52395
+ var DURATION_BASES = /* @__PURE__ */ new Set(["upstream", "catalog"]);
52396
+ var START_BASES = /* @__PURE__ */ new Set(["upstream", "derived"]);
52397
+ function toWindowPeriod(value) {
52398
+ const period = record3(value);
52399
+ if (!period) return void 0;
52400
+ const durationMs = period.durationMs;
52401
+ const durationBasis = period.durationBasis;
52402
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs <= 0) return void 0;
52403
+ if (durationMs > MAX_WINDOW_DURATION_MS) return void 0;
52404
+ if (typeof durationBasis !== "string" || !DURATION_BASES.has(durationBasis)) return void 0;
52405
+ const startsAt = typeof period.startsAt === "number" && Number.isFinite(period.startsAt) ? period.startsAt : void 0;
52406
+ const startsAtBasis = typeof period.startsAtBasis === "string" && START_BASES.has(period.startsAtBasis) ? period.startsAtBasis : void 0;
52407
+ return {
52408
+ durationMs,
52409
+ durationBasis,
52410
+ ...startsAt !== void 0 ? { startsAt } : {},
52411
+ ...startsAt !== void 0 && startsAtBasis !== void 0 ? { startsAtBasis } : {}
52412
+ };
52413
+ }
52414
+ function toWindowAmounts(value) {
52415
+ const amounts = record3(value);
52416
+ if (!amounts) return void 0;
52417
+ const used = amounts.used;
52418
+ const limit = amounts.limit;
52419
+ if (typeof used !== "string" || typeof limit !== "string") return void 0;
52420
+ if (!AMOUNT_PATTERN.test(used) || !AMOUNT_PATTERN.test(limit)) return void 0;
52421
+ return { used, limit };
52422
+ }
52244
52423
  function toQuotaSnapshot(payload) {
52245
52424
  const providers = record3(record3(payload)?.providers);
52246
52425
  if (!providers) return void 0;
@@ -52251,11 +52430,17 @@ function toQuotaSnapshot(payload) {
52251
52430
  const windows = Array.isArray(provider.windows) ? provider.windows.flatMap((entry) => {
52252
52431
  const window = record3(entry);
52253
52432
  if (!window || typeof window.id !== "string" || typeof window.usedPercent !== "number") return [];
52433
+ const period = toWindowPeriod(window.period);
52434
+ const amounts = toWindowAmounts(window.amounts);
52254
52435
  return [{
52255
52436
  id: window.id,
52256
52437
  ...typeof window.scope === "string" ? { scope: window.scope } : {},
52438
+ ...typeof window.label === "string" ? { label: window.label } : {},
52257
52439
  usedPercent: window.usedPercent,
52258
- ...typeof window.resetsAt === "number" ? { resetsAt: window.resetsAt } : {}
52440
+ ...typeof window.resetsAt === "number" ? { resetsAt: window.resetsAt } : {},
52441
+ ...period ? { period } : {},
52442
+ ...window.isAggregate === true ? { isAggregate: true } : {},
52443
+ ...amounts ? { amounts } : {}
52259
52444
  }];
52260
52445
  }) : void 0;
52261
52446
  snapshot[id] = {
@@ -52270,8 +52455,8 @@ function toQuotaSnapshot(payload) {
52270
52455
  // ../fleet-plugins/terminal/server/ai-gateway-settings.ts
52271
52456
  var AI_GATEWAY_SETTINGS_STORAGE_KEY = "ai-gateway";
52272
52457
  function normalizeAiGatewaySettings(value) {
52273
- if (!isRecord8(value) || value.version !== 1) return { version: 1 };
52274
- const models = Array.isArray(value.models) ? value.models.filter((entry) => isRecord8(entry) && typeof entry.id === "string" && entry.id.length > 0).map((entry) => ({ id: entry.id })) : [];
52458
+ if (!isRecord9(value) || value.version !== 1) return { version: 1 };
52459
+ const models = Array.isArray(value.models) ? value.models.filter((entry) => isRecord9(entry) && typeof entry.id === "string" && entry.id.length > 0).map((entry) => ({ id: entry.id })) : [];
52275
52460
  const defaultModel = typeof value.defaultModel === "string" && value.defaultModel.length > 0 ? value.defaultModel : void 0;
52276
52461
  return {
52277
52462
  version: 1,
@@ -52316,7 +52501,7 @@ function serializeAiGatewaySettingsWrite(write) {
52316
52501
  );
52317
52502
  return result;
52318
52503
  }
52319
- function isRecord8(value) {
52504
+ function isRecord9(value) {
52320
52505
  return typeof value === "object" && value !== null && !Array.isArray(value);
52321
52506
  }
52322
52507
  function resolveAiGatewaySelection(settings2) {
@@ -52481,6 +52666,9 @@ var MARKETPLACE_LOCK_DIR_SUFFIX = ".lock";
52481
52666
  function buildConsoleTurnHookCommand(entry, phase) {
52482
52667
  return buildConsoleCliHookExec(entry, ["hook", phase === "start" ? "turn-start" : "turn-end"]);
52483
52668
  }
52669
+ function buildConsoleBackgroundHookCommand(entry, kind) {
52670
+ return buildConsoleCliHookExec(entry, ["hook", kind === "spawn" ? "background-spawn" : "background-stop"]);
52671
+ }
52484
52672
  function buildConsoleAttentionHookCommand(entry) {
52485
52673
  return buildConsoleCliHookExec(entry, ["hook", "attention"]);
52486
52674
  }
@@ -52638,6 +52826,8 @@ async function createAgentCliLaunchSpec(options) {
52638
52826
  ),
52639
52827
  turnStartHookExec: buildConsoleTurnHookCommand(options.hookEntry, "start"),
52640
52828
  turnEndHookExec: buildConsoleTurnHookCommand(options.hookEntry, "end"),
52829
+ backgroundSpawnHookExec: buildConsoleBackgroundHookCommand(options.hookEntry, "spawn"),
52830
+ backgroundStopHookExec: buildConsoleBackgroundHookCommand(options.hookEntry, "stop"),
52641
52831
  inputWaitingHookExec: buildConsoleAttentionHookCommand(options.hookEntry),
52642
52832
  autoNameHookExec: buildConsoleAutoNameHookCommand(options.hookEntry),
52643
52833
  onCleanup: (cleanup) => cleanupStack.push(cleanup),
@@ -52790,6 +52980,7 @@ var TENANT_EVENT_LIMIT = 1e3;
52790
52980
  var TENANT_FINALIZED_JOB_LIMIT = 100;
52791
52981
  var TENANT_JOB_LIMIT = 200;
52792
52982
  var EVENT_TEXT_RETENTION_LIMIT = 8192;
52983
+ var BACKGROUND_PENDING_TTL_MS = 30 * 6e4;
52793
52984
  var REDACTED_REQUEST_PATH = "[redacted path]";
52794
52985
  function createConsoleObservabilityStore(deps = {}) {
52795
52986
  const now = deps.now ?? Date.now;
@@ -52899,6 +53090,8 @@ function createConsoleObservabilityStore(deps = {}) {
52899
53090
  const createdAt = input.createdAt ?? now();
52900
53091
  const canonicalCwd = canonicalizeTheaterPath(input.cwd);
52901
53092
  const theaterId = workspaceHash(canonicalCwd);
53093
+ const previous = terminalSessionsById.get(input.sessionId);
53094
+ if (previous) clearTerminalSessionBackgroundPending(previous);
52902
53095
  const state = {
52903
53096
  sessionId: input.sessionId,
52904
53097
  cwd: input.cwd,
@@ -52914,6 +53107,8 @@ function createConsoleObservabilityStore(deps = {}) {
52914
53107
  return toTerminalSessionInfo(state);
52915
53108
  }
52916
53109
  function injectDormantOperation(operation) {
53110
+ const previous = terminalSessionsById.get(operation.sessionId);
53111
+ if (previous) clearTerminalSessionBackgroundPending(previous);
52917
53112
  const state = {
52918
53113
  sessionId: operation.sessionId,
52919
53114
  cwd: operation.cwd,
@@ -52973,6 +53168,9 @@ function createConsoleObservabilityStore(deps = {}) {
52973
53168
  delete session.modelActivity;
52974
53169
  delete session.attentionPending;
52975
53170
  }
53171
+ if (status === "dormant" || status === "closed" || status === "error") {
53172
+ clearTerminalSessionBackgroundPending(session);
53173
+ }
52976
53174
  return toTerminalSessionInfo(session);
52977
53175
  }
52978
53176
  function setTerminalSessionTurnState(sessionId, turnState) {
@@ -52983,6 +53181,25 @@ function createConsoleObservabilityStore(deps = {}) {
52983
53181
  delete session.attentionPending;
52984
53182
  return toTerminalSessionInfo(session);
52985
53183
  }
53184
+ function setTerminalSessionBackgroundEvent(sessionId, event) {
53185
+ const session = terminalSessionsById.get(sessionId);
53186
+ if (!session) return null;
53187
+ if (session.status === "dormant" || session.status === "closed" || session.status === "error") return null;
53188
+ const count = session.backgroundPendingCount ?? 0;
53189
+ session.backgroundPendingCount = event === "spawn" ? count + 1 : Math.max(0, count - 1);
53190
+ if (session.backgroundPendingCount === 0) {
53191
+ clearTerminalSessionBackgroundPending(session);
53192
+ return toTerminalSessionInfo(session);
53193
+ }
53194
+ if (session.backgroundPendingExpiry) clearTimeout(session.backgroundPendingExpiry);
53195
+ session.backgroundPendingExpiry = setTimeout(() => {
53196
+ session.backgroundPendingCount = 0;
53197
+ delete session.backgroundPendingExpiry;
53198
+ notifySessionUpdated(toTerminalSessionInfo(session));
53199
+ }, BACKGROUND_PENDING_TTL_MS);
53200
+ if (typeof session.backgroundPendingExpiry.unref === "function") session.backgroundPendingExpiry.unref();
53201
+ return toTerminalSessionInfo(session);
53202
+ }
52986
53203
  function setTerminalSessionModelActivity(sessionId, modelActivity) {
52987
53204
  const session = terminalSessionsById.get(sessionId);
52988
53205
  if (!session) return null;
@@ -53003,6 +53220,7 @@ function createConsoleObservabilityStore(deps = {}) {
53003
53220
  session.status = "dormant";
53004
53221
  session.providerSession = providerSession;
53005
53222
  delete session.modelActivity;
53223
+ clearTerminalSessionBackgroundPending(session);
53006
53224
  return toTerminalSessionInfo(session);
53007
53225
  }
53008
53226
  function renameTerminalSession(sessionId, rawLabel) {
@@ -53070,7 +53288,14 @@ function createConsoleObservabilityStore(deps = {}) {
53070
53288
  };
53071
53289
  for (const listener of allListeners) listener(event);
53072
53290
  }
53291
+ function clearTerminalSessionBackgroundPending(session) {
53292
+ if (session.backgroundPendingExpiry) clearTimeout(session.backgroundPendingExpiry);
53293
+ delete session.backgroundPendingExpiry;
53294
+ session.backgroundPendingCount = 0;
53295
+ }
53073
53296
  function removeTerminalSession(sessionId) {
53297
+ const session = terminalSessionsById.get(sessionId);
53298
+ if (session) clearTerminalSessionBackgroundPending(session);
53074
53299
  const workspace = workspacesByCliRunId.get(sessionId);
53075
53300
  if (workspace?.terminalSessionId === sessionId) {
53076
53301
  removeWorkspaceIndexes(workspace);
@@ -53079,6 +53304,7 @@ function createConsoleObservabilityStore(deps = {}) {
53079
53304
  return terminalSessionsById.delete(sessionId);
53080
53305
  }
53081
53306
  function clear() {
53307
+ for (const session of terminalSessionsById.values()) clearTerminalSessionBackgroundPending(session);
53082
53308
  workspacesByCliRunId.clear();
53083
53309
  workspacesByRegistrationId.clear();
53084
53310
  eventsByTenant.clear();
@@ -53115,6 +53341,7 @@ function createConsoleObservabilityStore(deps = {}) {
53115
53341
  clearTerminalSessionProviderSession,
53116
53342
  updateTerminalSessionStatus,
53117
53343
  setTerminalSessionTurnState,
53344
+ setTerminalSessionBackgroundEvent,
53118
53345
  setTerminalSessionModelActivity,
53119
53346
  transitionTerminalSessionToDormant,
53120
53347
  removeTerminalSession,
@@ -53189,6 +53416,7 @@ function toTerminalSessionInfo(state) {
53189
53416
  turnState: state.turnState ?? "none",
53190
53417
  ...state.modelActivity ? { modelActivity: state.modelActivity } : {},
53191
53418
  ...state.attentionPending === true ? { attentionPending: true } : {},
53419
+ ...state.backgroundPendingCount && state.backgroundPendingCount > 0 ? { backgroundPending: true } : {},
53192
53420
  createdAt: state.createdAt,
53193
53421
  theaterId: state.theaterId,
53194
53422
  registrationId: state.registrationId,
@@ -53693,6 +53921,7 @@ function sweepIdleAgentSessions(deps) {
53693
53921
  if (session.status !== "registered" && session.status !== "terminal-only") continue;
53694
53922
  if (session.modelActivity === "working") continue;
53695
53923
  if (session.modelActivity === void 0 && session.turnState === "running") continue;
53924
+ if (session.backgroundPending === true) continue;
53696
53925
  if (deps.hasActiveCarrierJob(session.sessionId)) continue;
53697
53926
  if (!deps.hasProviderSessionCapture(session.sessionId)) continue;
53698
53927
  const lastActivityAt = deps.getSessionLastActivityAt(session.sessionId);
@@ -54004,6 +54233,7 @@ function createAgentApi(ctx, terminalRuntime, deps) {
54004
54233
  }
54005
54234
  async function handleSessionItem(req, res, sessionId, action) {
54006
54235
  if (action === "turn") return handleTurn(req, res, sessionId);
54236
+ if (action === "background") return handleBackground(req, res, sessionId);
54007
54237
  if (action === "attention") return handleAttention(req, res, sessionId);
54008
54238
  if (action === "auto-name") return handleAutoName(req, res, sessionId);
54009
54239
  if (action === "capture") return handleCapture(req, res, sessionId);
@@ -54142,6 +54372,24 @@ function createAgentApi(ctx, terminalRuntime, deps) {
54142
54372
  if (turnState === "ended") scheduleIdentityRefresh(sessionId);
54143
54373
  return true;
54144
54374
  }
54375
+ async function handleBackground(req, res, sessionId) {
54376
+ if (req.method !== "POST") return methodNotAllowed2(res);
54377
+ if (!ctx.host.security.isLockAuthorized(req)) return unauthorized(res);
54378
+ const body = await ctx.host.http.readJsonBody(req);
54379
+ const event = body?.event === "spawn" || body?.event === "stop" ? body.event : null;
54380
+ if (event === null) {
54381
+ ctx.host.http.writeJson(res, 400, { error: "invalid_event" });
54382
+ return true;
54383
+ }
54384
+ const updated = observability.setTerminalSessionBackgroundEvent(sessionId, event);
54385
+ if (!updated) {
54386
+ ctx.host.http.writeJson(res, 404, { error: "terminal_session_not_found" });
54387
+ return true;
54388
+ }
54389
+ observability.notifySessionUpdated(updated);
54390
+ ctx.host.http.writeJson(res, 200, { ok: true });
54391
+ return true;
54392
+ }
54145
54393
  async function handleAttention(req, res, sessionId) {
54146
54394
  if (req.method !== "POST") return methodNotAllowed2(res);
54147
54395
  if (!ctx.host.security.isLockAuthorized(req)) return unauthorized(res);
@@ -55259,8 +55507,7 @@ var ANALYSIS_ERROR_CODES = {
55259
55507
  sessionBusy: "analysis_session_busy"
55260
55508
  };
55261
55509
  var ANALYST_CLI_ENTRIES = [
55262
- { binaryId: "claude", cliId: "claude" },
55263
- { binaryId: "cursor-agent", cliId: "cursor" }
55510
+ { binaryId: "claude", cliId: "claude" }
55264
55511
  ];
55265
55512
  function buildAnalysisCatalog(statuses, modelsFor) {
55266
55513
  const statusById = new Map(statuses.map((status) => [status.id, status]));
@@ -55285,7 +55532,7 @@ function analysisError(code, message) {
55285
55532
  return { error: { code, message } };
55286
55533
  }
55287
55534
  function isAnalysisSelection(catalog, value) {
55288
- if (!isRecord9(value) || !hasExactKeys(value, ["cliId", "model", "effort", "language"]) || typeof value.cliId !== "string" || typeof value.model !== "string" || value.effort !== void 0 && typeof value.effort !== "string" || value.language !== void 0 && value.language !== "en" && value.language !== "ko") return false;
55535
+ if (!isRecord10(value) || !hasExactKeys(value, ["cliId", "model", "effort", "language"]) || typeof value.cliId !== "string" || typeof value.model !== "string" || value.effort !== void 0 && typeof value.effort !== "string" || value.language !== void 0 && value.language !== "en" && value.language !== "ko") return false;
55289
55536
  const cli = catalog.clis.find((candidate) => candidate.cliId === value.cliId);
55290
55537
  if (!cli?.available) return false;
55291
55538
  const model = cli.models.find((candidate) => candidate.id === value.model);
@@ -55294,9 +55541,9 @@ function isAnalysisSelection(catalog, value) {
55294
55541
  return typeof value.effort === "string" && value.effort.length > 0 && model.effortLevels.includes(value.effort);
55295
55542
  }
55296
55543
  function isMessageBody(value) {
55297
- return isRecord9(value) && hasExactKeys(value, ["text"]) && typeof value.text === "string" && value.text.trim().length > 0;
55544
+ return isRecord10(value) && hasExactKeys(value, ["text"]) && typeof value.text === "string" && value.text.trim().length > 0;
55298
55545
  }
55299
- function isRecord9(value) {
55546
+ function isRecord10(value) {
55300
55547
  return typeof value === "object" && value !== null && !Array.isArray(value);
55301
55548
  }
55302
55549
  function hasExactKeys(value, keys) {
@@ -55981,6 +56228,143 @@ async function ignoreMissing(operation) {
55981
56228
  }
55982
56229
  }
55983
56230
 
56231
+ // ../fleet-plugins/terminal/server/ai-gateway-proxy.ts
56232
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
56233
+ "connection",
56234
+ "content-encoding",
56235
+ "content-length",
56236
+ "keep-alive",
56237
+ "proxy-authenticate",
56238
+ "proxy-authorization",
56239
+ "te",
56240
+ "trailer",
56241
+ "transfer-encoding",
56242
+ "upgrade"
56243
+ ]);
56244
+ async function proxyAnthropicMessages(res, body, options) {
56245
+ const upstream = await options.fetchImpl(options.url, {
56246
+ method: "POST",
56247
+ headers: options.headers,
56248
+ body: JSON.stringify(body),
56249
+ signal: options.signal
56250
+ });
56251
+ const responseHeaders = {};
56252
+ upstream.headers.forEach((value, key) => {
56253
+ if (!HOP_BY_HOP_HEADERS.has(key)) responseHeaders[key] = value;
56254
+ });
56255
+ res.writeHead(upstream.status, responseHeaders);
56256
+ if (!upstream.body) {
56257
+ res.end();
56258
+ return;
56259
+ }
56260
+ const rawBody = readResponseBody(upstream.body);
56261
+ const responseBody = options.contextWindow === void 0 ? rawBody : projectAnthropicResponseUsage(rawBody, {
56262
+ contentType: upstream.headers.get("content-type"),
56263
+ contextWindow: options.contextWindow
56264
+ });
56265
+ for await (const chunk of responseBody) {
56266
+ if (!res.write(chunk)) await drain(res);
56267
+ }
56268
+ res.end();
56269
+ }
56270
+ async function* readResponseBody(body) {
56271
+ const reader = body.getReader();
56272
+ try {
56273
+ for (; ; ) {
56274
+ const { done, value } = await reader.read();
56275
+ if (done) return;
56276
+ yield value;
56277
+ }
56278
+ } finally {
56279
+ reader.releaseLock();
56280
+ }
56281
+ }
56282
+ function eagerAnthropicRequestBody(body, model) {
56283
+ return {
56284
+ ...body,
56285
+ model,
56286
+ messages: body.messages.map(eagerAnthropicMessage),
56287
+ ...body.tools === void 0 ? {} : { tools: body.tools.map(eagerAnthropicTool) }
56288
+ };
56289
+ }
56290
+ function eagerAnthropicTool(tool) {
56291
+ if (!("input_schema" in tool)) return tool;
56292
+ const { defer_loading: _deferLoading, ...eagerTool } = tool;
56293
+ return eagerTool;
56294
+ }
56295
+ function eagerAnthropicMessage(message) {
56296
+ if (typeof message.content === "string") return message;
56297
+ return {
56298
+ ...message,
56299
+ content: message.content.map((block) => {
56300
+ if (block.type !== "tool_result" || !Array.isArray(block.content)) return block;
56301
+ return {
56302
+ ...block,
56303
+ content: block.content.map((result) => {
56304
+ if (result.type !== "tool_reference") return result;
56305
+ const toolName = typeof result.tool_name === "string" && result.tool_name.length > 0 ? result.tool_name : "(invalid reference)";
56306
+ return { type: "text", text: `Tool available: ${toolName}` };
56307
+ })
56308
+ };
56309
+ })
56310
+ };
56311
+ }
56312
+ async function drain(res) {
56313
+ await new Promise((resolve3) => res.once("drain", resolve3));
56314
+ }
56315
+ function writeAnthropicError(res, status, type, message) {
56316
+ res.writeHead(status, { "content-type": "application/json" });
56317
+ res.end(JSON.stringify({ type: "error", error: { type, message } }));
56318
+ }
56319
+ function writeSseErrorFrame(res, type, message) {
56320
+ try {
56321
+ const data = JSON.stringify({ type: "error", error: { type, message } });
56322
+ res.write(`
56323
+
56324
+ event: error
56325
+ data: ${data}
56326
+
56327
+ `);
56328
+ } catch {
56329
+ }
56330
+ }
56331
+ function errorMessage(error51) {
56332
+ return error51 instanceof Error ? error51.message : String(error51);
56333
+ }
56334
+
56335
+ // ../fleet-plugins/terminal/server/ai-gateway-opencode.ts
56336
+ function isOpencodeAnthropicPassthrough(model) {
56337
+ return opencodeGoWire(model) === "anthropic";
56338
+ }
56339
+ function createOpencodeGateway(wire) {
56340
+ return new AnthropicMessagesGateway(createOpencodeGoAdapter(wire));
56341
+ }
56342
+ async function proxyToOpencode(requestHeaders, res, body, model, contextWindow, apiKey, fetchImpl, signal) {
56343
+ const headers = {
56344
+ "content-type": "application/json",
56345
+ "anthropic-version": typeof requestHeaders["anthropic-version"] === "string" ? requestHeaders["anthropic-version"] : "2023-06-01",
56346
+ "x-api-key": apiKey
56347
+ };
56348
+ for (const name of ["anthropic-beta", "user-agent"]) {
56349
+ const value = requestHeaders[name];
56350
+ if (typeof value === "string") headers[name] = value;
56351
+ }
56352
+ await proxyAnthropicMessages(res, opencodeRequestBody(body, model), {
56353
+ contextWindow,
56354
+ fetchImpl,
56355
+ headers,
56356
+ signal,
56357
+ url: OPENCODE_GO_MESSAGES_URL
56358
+ });
56359
+ }
56360
+ function opencodeRequestBody(body, model) {
56361
+ const eagerBody = eagerAnthropicRequestBody(body, model);
56362
+ if (body.output_config === void 0) return eagerBody;
56363
+ const { effort: _effort, ...outputConfig } = body.output_config;
56364
+ const { output_config: _outputConfig, ...withoutOutputConfig } = eagerBody;
56365
+ return Object.keys(outputConfig).length > 0 ? { ...withoutOutputConfig, output_config: outputConfig } : withoutOutputConfig;
56366
+ }
56367
+
55984
56368
  // ../fleet-plugins/terminal/server/ai-gateway-routes.ts
55985
56369
  var AI_GATEWAY_ROUTE_SEGMENT = "ai-gateway";
55986
56370
  var AI_GATEWAY_MODEL_ENV = "FLEET_AI_GATEWAY_MODEL";
@@ -56083,6 +56467,13 @@ function createAiGatewayRouter(deps = {}) {
56083
56467
  return true;
56084
56468
  }
56085
56469
  credential = kimiApiKey;
56470
+ } else if (target2?.provider === "opencode") {
56471
+ const opencodeApiKey = await deps.readOpencodeApiKey?.();
56472
+ if (!opencodeApiKey) {
56473
+ writeAnthropicError(res, 401, "authentication_error", "No OpenCode Go API key was found. Sign in to OpenCode Go first.");
56474
+ return true;
56475
+ }
56476
+ credential = opencodeApiKey;
56086
56477
  }
56087
56478
  const controller = new AbortController();
56088
56479
  const abort = () => controller.abort(new Error("client disconnected"));
@@ -56106,7 +56497,20 @@ function createAiGatewayRouter(deps = {}) {
56106
56497
  );
56107
56498
  return true;
56108
56499
  }
56109
- const gateway = deps.gateway ?? (target2.provider === "cursor" ? ownedCursorGateway : createGatewayFor(target2, chatgptAccountId));
56500
+ if (target2.provider === "opencode" && isOpencodeAnthropicPassthrough(target2)) {
56501
+ await proxyToOpencode(
56502
+ req.headers,
56503
+ res,
56504
+ body,
56505
+ upstreamModelId(target2),
56506
+ claudeContextWindow,
56507
+ credential,
56508
+ fetchImpl,
56509
+ controller.signal
56510
+ );
56511
+ return true;
56512
+ }
56513
+ const gateway = deps.gateway ?? (target2.provider === "cursor" ? ownedCursorGateway : target2.provider === "opencode" ? createOpencodeGateway(opencodeGoWire(target2)) : createGatewayFor(target2, chatgptAccountId));
56110
56514
  const diagnosticsEnabled = target2.provider === "cursor" ? await cursorDiagnosticsEnabled() : void 0;
56111
56515
  const modelContextWindow = typeof target2.contextWindow === "number" && Number.isFinite(target2.contextWindow) && target2.contextWindow > 0 ? target2.contextWindow : void 0;
56112
56516
  const upstream = await gateway.stream(body, {
@@ -56195,12 +56599,7 @@ async function proxyToKimi(requestHeaders, res, body, model, contextWindow, apiK
56195
56599
  }
56196
56600
  var KIMI_REASONING_EFFORTS = ["low", "high", "max"];
56197
56601
  function kimiRequestBody(body, model) {
56198
- const eagerBody = {
56199
- ...body,
56200
- model,
56201
- messages: body.messages.map(kimiEagerMessage),
56202
- ...body.tools === void 0 ? {} : { tools: body.tools.map(kimiEagerTool) }
56203
- };
56602
+ const eagerBody = eagerAnthropicRequestBody(body, model);
56204
56603
  const effort = reasoningEffortFromOutputConfig(body.output_config);
56205
56604
  if (effort === void 0) {
56206
56605
  return eagerBody;
@@ -56213,78 +56612,6 @@ function kimiRequestBody(body, model) {
56213
56612
  }
56214
56613
  };
56215
56614
  }
56216
- function kimiEagerTool(tool) {
56217
- if (!("input_schema" in tool)) return tool;
56218
- const { defer_loading: _deferLoading, ...eagerTool } = tool;
56219
- return eagerTool;
56220
- }
56221
- function kimiEagerMessage(message) {
56222
- if (typeof message.content === "string") return message;
56223
- return {
56224
- ...message,
56225
- content: message.content.map((block) => {
56226
- if (block.type !== "tool_result" || !Array.isArray(block.content)) return block;
56227
- return {
56228
- ...block,
56229
- content: block.content.map((result) => {
56230
- if (result.type !== "tool_reference") return result;
56231
- const toolName = typeof result.tool_name === "string" && result.tool_name.length > 0 ? result.tool_name : "(invalid reference)";
56232
- return { type: "text", text: `Tool available: ${toolName}` };
56233
- })
56234
- };
56235
- })
56236
- };
56237
- }
56238
- var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
56239
- "connection",
56240
- "content-encoding",
56241
- "content-length",
56242
- "keep-alive",
56243
- "proxy-authenticate",
56244
- "proxy-authorization",
56245
- "te",
56246
- "trailer",
56247
- "transfer-encoding",
56248
- "upgrade"
56249
- ]);
56250
- async function proxyAnthropicMessages(res, body, options) {
56251
- const upstream = await options.fetchImpl(options.url, {
56252
- method: "POST",
56253
- headers: options.headers,
56254
- body: JSON.stringify(body),
56255
- signal: options.signal
56256
- });
56257
- const responseHeaders = {};
56258
- upstream.headers.forEach((value, key) => {
56259
- if (!HOP_BY_HOP_HEADERS.has(key)) responseHeaders[key] = value;
56260
- });
56261
- res.writeHead(upstream.status, responseHeaders);
56262
- if (!upstream.body) {
56263
- res.end();
56264
- return;
56265
- }
56266
- const rawBody = readResponseBody(upstream.body);
56267
- const responseBody = options.contextWindow === void 0 ? rawBody : projectAnthropicResponseUsage(rawBody, {
56268
- contentType: upstream.headers.get("content-type"),
56269
- contextWindow: options.contextWindow
56270
- });
56271
- for await (const chunk of responseBody) {
56272
- if (!res.write(chunk)) await drain(res);
56273
- }
56274
- res.end();
56275
- }
56276
- async function* readResponseBody(body) {
56277
- const reader = body.getReader();
56278
- try {
56279
- for (; ; ) {
56280
- const { done, value } = await reader.read();
56281
- if (done) return;
56282
- yield value;
56283
- }
56284
- } finally {
56285
- reader.releaseLock();
56286
- }
56287
- }
56288
56615
  function createGatewayFor(model, chatgptAccountId) {
56289
56616
  if (model.provider !== "codex") {
56290
56617
  throw new TypeError(`Unsupported translated gateway provider: ${model.provider}`);
@@ -56321,28 +56648,6 @@ function headerEntries(headers) {
56321
56648
  });
56322
56649
  return entries;
56323
56650
  }
56324
- async function drain(res) {
56325
- await new Promise((resolve3) => res.once("drain", resolve3));
56326
- }
56327
- function writeAnthropicError(res, status, type, message) {
56328
- res.writeHead(status, { "content-type": "application/json" });
56329
- res.end(JSON.stringify({ type: "error", error: { type, message } }));
56330
- }
56331
- function writeSseErrorFrame(res, type, message) {
56332
- try {
56333
- const data = JSON.stringify({ type: "error", error: { type, message } });
56334
- res.write(`
56335
-
56336
- event: error
56337
- data: ${data}
56338
-
56339
- `);
56340
- } catch {
56341
- }
56342
- }
56343
- function errorMessage(error51) {
56344
- return error51 instanceof Error ? error51.message : String(error51);
56345
- }
56346
56651
 
56347
56652
  // ../fleet-plugins/terminal/server/carrier-settings-routes.ts
56348
56653
  var CARRIER_SETTINGS_PRESENTATION_LOCALES = CARRIER_PRESENTATION_LOCALES;
@@ -56776,23 +57081,38 @@ function registerGlobalShellRoutes(ctx, runtime) {
56776
57081
  }
56777
57082
 
56778
57083
  // ../fleet-plugins/terminal/server/model-auth-state.ts
57084
+ var MODEL_AUTH_STORE_IDS = Object.freeze({
57085
+ kimi: KIMI_AUTH_PROVIDER_ID,
57086
+ opencode: OPENCODE_AUTH_PROVIDER_ID
57087
+ });
57088
+ var MODEL_AUTH_DISPLAY_NAMES = Object.freeze({
57089
+ kimi: "Kimi for AI Gateway",
57090
+ opencode: "OpenCode Go for AI Gateway"
57091
+ });
57092
+ function isTerminalModelAuthProviderId(value) {
57093
+ return value in MODEL_AUTH_STORE_IDS;
57094
+ }
56779
57095
  async function buildModelAuthState(authService) {
56780
57096
  const signedInIds = new Set(await authService.listProviderIds());
56781
57097
  return {
56782
- providers: [{
56783
- provider: "kimi",
56784
- displayName: "Kimi for AI Gateway",
56785
- signedIn: signedInIds.has(KIMI_AUTH_PROVIDER_ID)
56786
- }]
57098
+ providers: Object.keys(MODEL_AUTH_STORE_IDS).map((provider) => ({
57099
+ provider,
57100
+ displayName: MODEL_AUTH_DISPLAY_NAMES[provider],
57101
+ signedIn: signedInIds.has(MODEL_AUTH_STORE_IDS[provider])
57102
+ }))
56787
57103
  };
56788
57104
  }
56789
57105
 
56790
57106
  // ../fleet-plugins/terminal/server/model-auth-routes.ts
56791
57107
  var UPSTREAM_FAILURE_STATUSES = /* @__PURE__ */ new Set(["timeout", "network", "server"]);
57108
+ var MODEL_AUTH_VALIDATORS = {
57109
+ kimi: validateKimiAuthKey,
57110
+ opencode: validateOpencodeGoAuthKey
57111
+ };
56792
57112
  function registerTerminalModelAuthRoutes(ctx, deps) {
56793
57113
  registerRouter(ctx, "model-auth", createTerminalModelAuthRouter(ctx, {
56794
57114
  ...deps,
56795
- validateApiKey: validateKimiAuthKey
57115
+ validateApiKey: (provider, apiKey) => MODEL_AUTH_VALIDATORS[provider](apiKey)
56796
57116
  }));
56797
57117
  }
56798
57118
  function createTerminalModelAuthRouter(ctx, deps) {
@@ -56808,11 +57128,11 @@ function createTerminalModelAuthRouter(ctx, deps) {
56808
57128
  }
56809
57129
  const providerId = parseProviderPath(path44);
56810
57130
  if (!providerId) return false;
56811
- if (providerId !== "kimi") {
57131
+ if (!isTerminalModelAuthProviderId(providerId)) {
56812
57132
  ctx.host.http.writeJson(res, 404, { error: "provider_not_found" });
56813
57133
  return true;
56814
57134
  }
56815
- const provider = await findProvider(deps);
57135
+ const provider = await findProvider(deps, providerId);
56816
57136
  if (!provider) {
56817
57137
  ctx.host.http.writeJson(res, 404, { error: "provider_not_found" });
56818
57138
  return true;
@@ -56822,7 +57142,7 @@ function createTerminalModelAuthRouter(ctx, deps) {
56822
57142
  return true;
56823
57143
  }
56824
57144
  if (req.method === "DELETE") {
56825
- await signOutProvider(ctx, req, res, deps);
57145
+ await signOutProvider(ctx, req, res, deps, provider.provider);
56826
57146
  return true;
56827
57147
  }
56828
57148
  ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
@@ -56848,7 +57168,7 @@ async function signInProvider(ctx, req, res, deps, provider) {
56848
57168
  return;
56849
57169
  }
56850
57170
  const apiKey = body.apiKey.trim();
56851
- const validation = await deps.validateApiKey(apiKey);
57171
+ const validation = await deps.validateApiKey(provider.provider, apiKey);
56852
57172
  if (validation.status !== "success") {
56853
57173
  ctx.host.http.writeJson(res, UPSTREAM_FAILURE_STATUSES.has(validation.status) ? 502 : 400, {
56854
57174
  error: formatSignInFailureMessage(provider.displayName, validation.status),
@@ -56856,22 +57176,23 @@ async function signInProvider(ctx, req, res, deps, provider) {
56856
57176
  });
56857
57177
  return;
56858
57178
  }
56859
- await deps.authService.setApiKey(KIMI_AUTH_PROVIDER_ID, apiKey);
57179
+ await deps.authService.setApiKey(MODEL_AUTH_STORE_IDS[provider.provider], apiKey);
56860
57180
  await writeMutationState2(ctx, res, deps);
56861
57181
  }
56862
- async function signOutProvider(ctx, req, res, deps) {
57182
+ async function signOutProvider(ctx, req, res, deps, provider) {
56863
57183
  if (!ctx.host.security.isTerminalAuthorized(req)) {
56864
57184
  ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
56865
57185
  return;
56866
57186
  }
56867
- await deps.authService.deleteApiKey(KIMI_AUTH_PROVIDER_ID);
57187
+ await deps.authService.deleteApiKey(MODEL_AUTH_STORE_IDS[provider]);
56868
57188
  await writeMutationState2(ctx, res, deps);
56869
57189
  }
56870
57190
  async function writeMutationState2(ctx, res, deps) {
56871
57191
  ctx.host.http.writeJson(res, 200, { state: await buildModelAuthState(deps.authService) });
56872
57192
  }
56873
- async function findProvider(deps) {
56874
- return (await buildModelAuthState(deps.authService)).providers[0] ?? null;
57193
+ async function findProvider(deps, providerId) {
57194
+ const state = await buildModelAuthState(deps.authService);
57195
+ return state.providers.find((provider) => provider.provider === providerId) ?? null;
56875
57196
  }
56876
57197
  function parseProviderPath(path44) {
56877
57198
  const parts = path44.split("/").filter(Boolean);
@@ -57031,7 +57352,8 @@ var routes_default = definePlugin({
57031
57352
  registerTerminalModelAuthRoutes(ctx, { authService: infraServices.authService });
57032
57353
  registerAiGatewayRoutes(ctx, {
57033
57354
  readAiGatewaySettings: aiGatewayStore.read,
57034
- readKimiApiKey: () => infraServices.authService.getApiKey(KIMI_AUTH_PROVIDER_ID)
57355
+ readKimiApiKey: () => infraServices.authService.getApiKey(KIMI_AUTH_PROVIDER_ID),
57356
+ readOpencodeApiKey: () => infraServices.authService.getApiKey(OPENCODE_AUTH_PROVIDER_ID)
57035
57357
  });
57036
57358
  registerCarrierSettingsRoutes(ctx, { registry: carrierRegistry });
57037
57359
  const agentCliPathStore = createAgentCliPathStore(ctx.host.storage, ctx.pluginId);