@desplega.ai/agent-swarm 1.172.0 → 2.0.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.
- package/README.md +1 -1
- package/dist/{acp-adapter-fpecx1g1.js → acp-adapter-sj2g0m84.js} +18 -17
- package/dist/{actions-pw87a1a8.js → actions-fegk44gr.js} +65 -35
- package/dist/{amp-adapter-8jem2bc0.js → amp-adapter-ad8ft89x.js} +8 -7
- package/dist/{anthropic-messages-8ee2f22q.js → anthropic-messages-r6czkjv1.js} +7 -7
- package/dist/{app-wmf0m843.js → app-kfvgbvzy.js} +19 -13
- package/dist/{assistant-emp1e108.js → assistant-z7zdy9ja.js} +65 -38
- package/dist/{boot-reembed-1de4f4aj.js → boot-reembed-5c2z0mbe.js} +11 -9
- package/dist/{boot-reembed-4ymn9atn.js → boot-reembed-j8xa181e.js} +11 -9
- package/dist/{boot-scrub-sweep-adhk1s8c.js → boot-scrub-sweep-hnkn3rgt.js} +12 -10
- package/dist/{boot-seeds-8q637aew.js → boot-seeds-kwjq4d2z.js} +15 -13
- package/dist/{claude-adapter-t26cv8a4.js → claude-adapter-9vmvpvgd.js} +20 -19
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
- package/dist/{claude-sdk-session-gsfsh662.js → claude-sdk-session-9jye6cxr.js} +12698 -11995
- package/dist/{cli-k46r9hmk.js → cli-03gedhng.js} +16 -14
- package/dist/{cli-kb4ehzx5.js → cli-1e4b6bzh.js} +7 -7
- package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-8vs9trd4.js → cli-3dj8k4vy.js} +1 -1
- package/dist/{cli-zxm9p3x4.js → cli-3fbmmawm.js} +5 -5
- package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
- package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
- package/dist/{cli-6jb2rf77.js → cli-5t4f29ez.js} +3 -3
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-pj6x9qce.js → cli-6jyj59nb.js} +2 -2
- package/dist/{cli-388egyf6.js → cli-6razj6t4.js} +2 -2
- package/dist/{cli-qsztpfry.js → cli-74ahz61x.js} +2 -2
- package/dist/{cli-4nz8p1p5.js → cli-7x0g4sn1.js} +15474 -16082
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-bf7485pm.js → cli-apx87jne.js} +2 -2
- package/dist/{cli-ztrv9y21.js → cli-bzszjf2p.js} +3689 -1081
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
- package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
- package/dist/{cli-1hgat6pa.js → cli-d5f9ed5c.js} +5 -5
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
- package/dist/{cli-j6kpj9az.js → cli-ee9gtz70.js} +1553 -68
- package/dist/{cli-e0a9sk00.js → cli-eejsgvm4.js} +4 -4
- package/dist/{cli-tgpfnm6r.js → cli-ef1hg1wv.js} +518 -59
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
- package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
- package/dist/{cli-4fnb6884.js → cli-hvqbtna4.js} +7 -1259
- package/dist/{cli-pstq9888.js → cli-j0pfptdp.js} +220 -103
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-esnpehf5.js → cli-k382tshb.js} +12 -13
- package/dist/cli-k3dp5tpf.js +263 -0
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-prrt9fa0.js → cli-ke896fcf.js} +10 -10
- package/dist/{cli-nv4zd0b9.js → cli-kt6zqg0e.js} +6 -6
- package/dist/{cli-g6t9texm.js → cli-m50t4309.js} +5 -18
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-c2pq2rjb.js → cli-n3hzg89b.js} +46 -15
- package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
- package/dist/{cli-5rv1j9ga.js → cli-ne236afy.js} +53 -7
- package/dist/cli-p0sg24zp.js +16 -0
- package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
- package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
- package/dist/{cli-g4f5pj26.js → cli-q0ke1yww.js} +111 -25
- package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-gj81e47t.js → cli-r2pnvj8g.js} +2 -2
- package/dist/{cli-r8smj8b8.js → cli-r62a35ea.js} +19 -3
- package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-nyhb1r6x.js → cli-s2sp957d.js} +52 -29
- package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
- package/dist/{cli-xczpdp9e.js → cli-se8w6wny.js} +6 -6
- package/dist/cli-skxvvpap.js +10140 -0
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
- package/dist/{cli-cf69s3ew.js → cli-tr4sxp68.js} +3 -3
- package/dist/{cli-8hzfb6xz.js → cli-tsde1hdr.js} +15 -15
- package/dist/{cli-cceskcvt.js → cli-txqb9s38.js} +8 -8
- package/dist/{cli-aspcaef3.js → cli-v1yb0rqb.js} +6 -6
- package/dist/{cli-j1pmc07z.js → cli-vatyhshy.js} +268 -40
- package/dist/{cli-04emfe2q.js → cli-vdach5a9.js} +5 -5
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/{cli-hwwvy36w.js → cli-wayw93nt.js} +3 -3
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-gp1rtyvk.js → cli-z5jjpaxw.js} +29 -6
- package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
- package/dist/cli.js +32 -48
- package/dist/{codex-adapter-m4tgdysy.js → codex-adapter-y81sssk0.js} +16 -15
- package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
- package/dist/{codex-session-runner-ymwzm6ea.js → codex-session-runner-8vx4j6a9.js} +16 -15
- package/dist/{commands-n0hshcvs.js → commands-kcpvpcrr.js} +21 -10
- package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
- package/dist/{controllers-connect-y07cqcvy.js → controllers-connect-kwpvj3rf.js} +45 -38
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
- package/dist/{db-hkqvrdcb.js → db-k8ankj20.js} +13 -11
- package/dist/{db-4w7tmt1g.js → db-ww5d7jq3.js} +29 -9
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
- package/dist/{dispatcher-5sd69y14.js → dispatcher-e8gq5m68.js} +41 -34
- package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
- package/dist/{dsh-adapter-2h8m4cab.js → dsh-adapter-3epjm3af.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
- package/dist/external-channel-h3359cwn.js +52 -0
- package/dist/{google-generative-ai-7pavrbv7.js → google-generative-ai-g8db7x0f.js} +5 -3
- package/dist/{google-vertex-3f1jk41f.js → google-vertex-r2692q0r.js} +5 -3
- package/dist/{grok-adapter-8m9yqzbq.js → grok-adapter-0d4x7gkx.js} +19 -18
- package/dist/{handlers-s20xr10r.js → handlers-an1gzv21.js} +44 -37
- package/dist/{heartbeat-3k31q4g2.js → heartbeat-6dakeb61.js} +46 -39
- package/dist/{hook-gdn6a2fn.js → hook-6v6cv6d5.js} +12 -24
- package/dist/{hook-ygwerrdh.js → hook-sa5xye4y.js} +13 -12
- package/dist/{http-jdteeg81.js → http-r7f3eky0.js} +1598 -279
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-e0snym4t.js → index-7ep4gzdk.js} +41 -34
- package/dist/index-9gw58n4k.js +193 -0
- package/dist/index-9j87mcm8.js +56305 -0
- package/dist/index-bsmcresk.js +79424 -0
- package/dist/index-mh85qhec.js +475 -0
- package/dist/{index-vm3bm9t7.js → index-rmpw9a8q.js} +41 -34
- package/dist/{index-6ksewd5y.js → index-s2wxgzr0.js} +1015 -732
- package/dist/{index-tsszt7g9.js → index-v1v6n7hg.js} +46 -39
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{index-ffqzqgwb.js → index-zk96aq1k.js} +11 -9
- package/dist/{keepalive-jekc896t.js → keepalive-be9vwy5h.js} +13 -11
- package/dist/{lead-4k9s9szx.js → lead-0861cwrf.js} +64 -57
- package/dist/{maintenance-shazch4n.js → maintenance-mzzgc8bn.js} +11 -9
- package/dist/node-c9we95xf.js +1 -1
- package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
- package/dist/{oauth-refresh-sweep-5gb36kn8.js → oauth-refresh-sweep-cpdnfwdj.js} +12 -10
- package/dist/{onboard-ydrbnvww.js → onboard-fh8qs4r3.js} +11 -3
- package/dist/{opencode-adapter-5zh0he20.js → opencode-adapter-nm42wwj8.js} +19 -14
- package/dist/{otel-impl-6pf5cv86.js → otel-impl-x6c1c9g4.js} +248 -869
- package/dist/{pi-mono-adapter-ef9wwx9m.js → pi-mono-adapter-9n2ya240.js} +245 -107
- package/dist/{pricing-refresh-s52nsa5q.js → pricing-refresh-qb0rv0ap.js} +13 -11
- package/dist/{rbac-roles-aq3msh4e.js → rbac-roles-1gzdwbh1.js} +12 -10
- package/dist/{rbac-roles-q5gb5e8v.js → rbac-roles-3mraey52.js} +13 -11
- package/dist/{render-v2-pd1rg54q.js → render-v2-agpa45zz.js} +19 -13
- package/dist/{resume-gw4a2c6v.js → resume-x0ydy9s9.js} +27 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-jda1ksdh.js → setup-as9ga0d6.js} +2 -2
- package/dist/{virtual-modules-b3ga2m65.js → virtual-modules-9gkxqrap.js} +7 -6
- package/dist/{work-objects-tb7ycye8.js → work-objects-y5g1y7ev.js} +49 -37
- package/dist/{worker-njjccz5d.js → worker-860c3e38.js} +64 -57
- package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
- package/openapi.json +951 -356
- package/package.json +12 -13
- package/src/apps/catalog.generated.json +1309 -53
- package/src/apps/page-validator.ts +93 -31
- package/src/azure-devops/api.ts +94 -18
- package/src/azure-devops/auth.ts +32 -0
- package/src/azure-devops/boards-sync.ts +708 -0
- package/src/azure-devops/handlers.ts +52 -8
- package/src/azure-devops/html.ts +26 -0
- package/src/azure-devops/index.ts +15 -0
- package/src/azure-devops/templates.ts +86 -0
- package/src/azure-devops/types.ts +65 -1
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/page-versions.ts +15 -0
- package/src/be/db/runtime.ts +35 -8
- package/src/be/db/skill-name-resolution.ts +36 -0
- package/src/be/db/tasks/read.ts +230 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +74 -13
- package/src/be/migrations/213_script_connections_sql.sql +79 -0
- package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
- package/src/be/migrations/216_task_resolved_effort.sql +5 -0
- package/src/be/migrations/217_chat_sdk_state.sql +104 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +12 -1
- package/src/be/model-validation.ts +5 -2
- package/src/be/models-catalog.ts +26 -1
- package/src/be/modelsdev-cache.json +198 -63
- package/src/be/modelsdev-cache.ts +9 -1
- package/src/be/script-connections.ts +47 -8
- package/src/be/script-credential-broker.ts +39 -2
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +10 -8
- package/src/be/seed/runner.ts +13 -1
- package/src/be/seed/state-db.ts +5 -0
- package/src/be/seed/types.ts +6 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
- package/src/be/seed-skills/bundled-files.generated.json +10 -18
- package/src/be/seed-skills/index.ts +107 -3
- package/src/be/sql-connections.ts +118 -0
- package/src/be/swarm-config-guard.ts +53 -0
- package/src/cli.tsx +0 -16
- package/src/commands/onboard/non-interactive.ts +4 -2
- package/src/commands/profile-sync.ts +5 -3
- package/src/commands/provider-credentials.ts +52 -6
- package/src/commands/runner.ts +196 -56
- package/src/e2b/env.ts +5 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +45 -0
- package/src/extensions/dispatcher.ts +7 -0
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/all-routes.ts +0 -1
- package/src/http/config.ts +36 -17
- package/src/http/core.ts +4 -0
- package/src/http/index.ts +14 -2
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp-oauth.ts +6 -13
- package/src/http/mcp.ts +15 -5
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +6 -5
- package/src/http/pages.ts +5 -14
- package/src/http/poll.ts +28 -68
- package/src/http/requested-by.ts +54 -0
- package/src/http/route-def.ts +7 -6
- package/src/http/script-connection-proxy.ts +51 -4
- package/src/http/script-connections.ts +5 -2
- package/src/http/scripts.ts +1 -0
- package/src/http/session-cost-recompute.ts +12 -2
- package/src/http/session-data.ts +4 -0
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +61 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +183 -2
- package/src/http/x.ts +1 -0
- package/src/integrations/chat-sdk/adapters.ts +129 -0
- package/src/integrations/chat-sdk/attachments.ts +232 -0
- package/src/integrations/chat-sdk/background.ts +36 -0
- package/src/integrations/chat-sdk/commands.ts +194 -0
- package/src/integrations/chat-sdk/config.ts +198 -0
- package/src/integrations/chat-sdk/discord-api.ts +63 -0
- package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
- package/src/integrations/chat-sdk/handlers.ts +28 -0
- package/src/integrations/chat-sdk/inbound.ts +426 -0
- package/src/integrations/chat-sdk/limits.ts +48 -0
- package/src/integrations/chat-sdk/logger.ts +81 -0
- package/src/integrations/chat-sdk/outbound.ts +123 -0
- package/src/integrations/chat-sdk/relay.ts +172 -0
- package/src/integrations/chat-sdk/router.ts +169 -0
- package/src/integrations/chat-sdk/runtime.ts +351 -0
- package/src/integrations/chat-sdk/schema.ts +245 -0
- package/src/integrations/chat-sdk/state.ts +243 -0
- package/src/integrations/chat-sdk/status.ts +244 -0
- package/src/integrations/chat-sdk/steering.ts +58 -0
- package/src/integrations/chat-sdk/teams-origin.ts +77 -0
- package/src/integrations/chat-sdk/telegram-api.ts +56 -0
- package/src/integrations/chat-sdk/templates.ts +16 -0
- package/src/integrations/chat-sdk/test-hooks.ts +37 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
- package/src/integrations/chat-sdk/typing.ts +65 -0
- package/src/jira/adf.ts +3 -1
- package/src/kv-reserved-namespaces.ts +2 -0
- package/src/oauth/mcp-wrapper.ts +10 -6
- package/src/otel-impl.ts +2 -1
- package/src/pages/version.ts +4 -5
- package/src/prompts/base-prompt.ts +111 -45
- package/src/providers/acp-target-catalog.ts +6 -0
- package/src/providers/acp-targets.ts +56 -3
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +27 -2
- package/src/providers/claude-flag-settings.ts +133 -0
- package/src/providers/claude-managed-adapter.ts +1 -1
- package/src/providers/claude-sdk-session.ts +7 -0
- package/src/providers/claude-session-events.ts +6 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +268 -37
- package/src/providers/opencode-billing.ts +86 -0
- package/src/providers/opencode-swarm-plugin.ts +51 -0
- package/src/providers/pi-mono-adapter.ts +398 -153
- package/src/providers/pricing-sources.md +37 -3
- package/src/providers/reasoning-effort.ts +27 -0
- package/src/providers/types.ts +11 -2
- package/src/rbac/legacy-policy.ts +8 -2
- package/src/rbac/permissions.ts +21 -0
- package/src/scheduler/scheduler.ts +4 -2
- package/src/scripts-runtime/api-client.ts +15 -1
- package/src/scripts-runtime/api-types.ts +9 -1
- package/src/scripts-runtime/ctx.ts +1 -1
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/mcp-client.ts +30 -19
- package/src/scripts-runtime/sdk-allowlist.ts +8 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +27 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
- package/src/server.ts +52 -18
- package/src/slack/actions.ts +29 -1
- package/src/slack/app.ts +14 -2
- package/src/slack/assistant.ts +36 -2
- package/src/slack/channel-join.ts +3 -16
- package/src/slack/commands.ts +9 -1
- package/src/slack/event-dedup.ts +1 -1
- package/src/slack/external-channel-egress.ts +113 -0
- package/src/slack/external-channel.ts +309 -0
- package/src/slack/handlers.ts +61 -0
- package/src/slack/render-v2.ts +40 -47
- package/src/slack/work-objects.ts +5 -0
- package/src/tasks/additive-buffer.ts +4 -2
- package/src/tasks/context-key.ts +94 -2
- package/src/tasks/model-pin.ts +67 -0
- package/src/tasks/worker-follow-up.ts +4 -31
- package/src/telemetry.ts +2 -1
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/apps-catalog-feedback.test.ts +310 -0
- package/src/tests/apps-catalog-inputs.test.ts +70 -0
- package/src/tests/apps-catalog-table.test.ts +113 -0
- package/src/tests/azure-devops-boards-sync.test.ts +584 -0
- package/src/tests/azure-devops-html.test.ts +13 -0
- package/src/tests/azure-devops-webhook-malformed.test.ts +4 -0
- package/src/tests/base-prompt.test.ts +58 -54
- package/src/tests/chat-sdk-commands.test.ts +191 -0
- package/src/tests/chat-sdk-config.test.ts +334 -0
- package/src/tests/chat-sdk-discord-api.test.ts +68 -0
- package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
- package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
- package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
- package/src/tests/chat-sdk-harness.ts +146 -0
- package/src/tests/chat-sdk-inbound.test.ts +363 -0
- package/src/tests/chat-sdk-relay.test.ts +470 -0
- package/src/tests/chat-sdk-reload.test.ts +128 -0
- package/src/tests/chat-sdk-shutdown.test.ts +220 -0
- package/src/tests/chat-sdk-slack.test.ts +268 -0
- package/src/tests/chat-sdk-state.test.ts +163 -0
- package/src/tests/chat-sdk-status-route.test.ts +382 -0
- package/src/tests/chat-sdk-teams.test.ts +277 -0
- package/src/tests/chat-sdk-tools.test.ts +520 -0
- package/src/tests/chat-sdk-typing.test.ts +120 -0
- package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
- package/src/tests/claude-adapter-binary.test.ts +144 -0
- package/src/tests/claude-flag-settings.test.ts +114 -0
- package/src/tests/claude-managed-adapter.test.ts +1 -1
- package/src/tests/claude-sdk-lifecycle.test.ts +148 -1
- package/src/tests/claude-sdk-process.fixture.ts +7 -0
- package/src/tests/claude-thinking-tokens.test.ts +57 -0
- package/src/tests/claude-worker-parity.test.ts +2 -7
- package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
- package/src/tests/config-refs.test.ts +66 -0
- package/src/tests/context-key.test.ts +51 -0
- package/src/tests/cors-allowlist.test.ts +25 -0
- package/src/tests/create-page-tool.test.ts +18 -0
- package/src/tests/credential-check.test.ts +139 -0
- package/src/tests/credentials.test.ts +21 -0
- package/src/tests/db-runtime.test.ts +61 -0
- package/src/tests/db-task-read-contract.test.ts +20 -0
- package/src/tests/db-task-write-contract.test.ts +10 -0
- package/src/tests/deferred-task-wake.test.ts +52 -4
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/enabled-capabilities.test.ts +20 -0
- package/src/tests/entrypoint-api-readiness.test.ts +14 -8
- package/src/tests/entrypoint-config-env-export.test.ts +5 -2
- package/src/tests/entrypoint-export-to-env.test.ts +78 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-post-bridge.test.ts +51 -0
- package/src/tests/fetch-resolved-env.test.ts +128 -0
- package/src/tests/fixtures/azure-boards/workitem.commented.json +89 -0
- package/src/tests/fixtures/azure-boards/workitem.created.json +81 -0
- package/src/tests/fixtures/azure-boards/workitem.deleted.json +70 -0
- package/src/tests/fixtures/azure-boards/workitem.restored.json +82 -0
- package/src/tests/fixtures/azure-boards/workitem.updated.json +130 -0
- package/src/tests/fixtures/catalog-feedback-definition.json.txt +860 -0
- package/src/tests/fixtures/catalog-inputs-definition.json.txt +910 -0
- package/src/tests/fixtures/catalog-table-definition.json.txt +468 -0
- package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
- package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
- package/src/tests/fixtures/runner-identity-refresh.ts +3 -0
- package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
- package/src/tests/github-handlers.test.ts +35 -0
- package/src/tests/harness-provider-resolution.test.ts +12 -0
- package/src/tests/heartbeat-checklist.test.ts +3 -2
- package/src/tests/hook-runtime-close.test.ts +0 -2
- package/src/tests/http-api-integration.test.ts +0 -25
- package/src/tests/migration-046-budgets.test.ts +17 -16
- package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
- package/src/tests/model-tier-resolution.test.ts +93 -0
- package/src/tests/models-catalog.test.ts +42 -1
- package/src/tests/node-env-runtime.test.ts +98 -0
- package/src/tests/openai-compatible-providers.test.ts +134 -0
- package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
- package/src/tests/opencode-adapter.test.ts +348 -3
- package/src/tests/opencode-billing.test.ts +28 -0
- package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
- package/src/tests/opencode-shell-env.test.ts +145 -0
- package/src/tests/openrouter-base-url.test.ts +101 -151
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/pages-browser-sdk.test.ts +76 -0
- package/src/tests/pages-feedback.test.ts +1 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
- package/src/tests/pi-mono-adapter.test.ts +385 -5
- package/src/tests/pi-shell-env.test.ts +170 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/rbac-engine.test.ts +11 -3
- package/src/tests/runner-exit-after-tasks.test.ts +0 -4
- package/src/tests/runner-identity-refresh.test.ts +3 -0
- package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
- package/src/tests/script-connections-sql.test.ts +675 -0
- package/src/tests/script-connections.test.ts +7 -1
- package/src/tests/scripts-http.test.ts +201 -1
- package/src/tests/scripts-only-gating.test.ts +2 -3
- package/src/tests/scripts-runtime.test.ts +58 -40
- package/src/tests/secret-scrubber.test.ts +86 -0
- package/src/tests/seed-skills-bundled-files.test.ts +45 -53
- package/src/tests/seed-skills-retire.test.ts +182 -0
- package/src/tests/send-task-requester-gate.test.ts +58 -0
- package/src/tests/session-cost-recompute-unit.test.ts +94 -0
- package/src/tests/skill-get-name-resolution.test.ts +137 -0
- package/src/tests/slack-channel-join.test.ts +4 -1
- package/src/tests/slack-external-channel-config.test.ts +65 -0
- package/src/tests/slack-external-channel-egress.test.ts +237 -0
- package/src/tests/slack-external-channel-guard.test.ts +280 -0
- package/src/tests/slack-external-channel-tools.test.ts +173 -0
- package/src/tests/slack-external-channel.test.ts +370 -0
- package/src/tests/slack-render-v2.test.ts +86 -10
- package/src/tests/sonnet-5-5-registry.test.ts +9 -1
- package/src/tests/spec-base-url.test.ts +16 -1
- package/src/tests/swarm-config-export-to-env.test.ts +267 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
- package/src/tests/swarm-diff.test.ts +1 -1
- package/src/tests/task-model-effort-rerun.test.ts +164 -0
- package/src/tests/task-session-prompts.test.ts +132 -0
- package/src/tests/task-tool-manifest.test.ts +14 -0
- package/src/tests/task-tools-ctx.test.ts +2 -0
- package/src/tests/tool-annotations.test.ts +6 -6
- package/src/tests/ui-logs-parser.test.ts +43 -0
- package/src/tests/used-by-route.test.ts +6 -0
- package/src/tests/vendored-openapi.test.ts +16 -1
- package/src/tests/vercel-ai-gateway.test.ts +149 -0
- package/src/tests/workflow-swarm-script.test.ts +38 -0
- package/src/tests/workflow-wait-filter.test.ts +5 -3
- package/src/tools/chat-access.ts +41 -0
- package/src/tools/chat-open-dm.ts +59 -0
- package/src/tools/chat-post.ts +74 -0
- package/src/tools/chat-read-thread.ts +100 -0
- package/src/tools/chat-reply.ts +75 -0
- package/src/tools/create-page.ts +3 -13
- package/src/tools/defer-task.ts +17 -0
- package/src/tools/model-catalog/index.ts +2 -1
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/send-task.ts +24 -0
- package/src/tools/skills/skill-get.ts +17 -9
- package/src/tools/slack-post.ts +6 -0
- package/src/tools/slack-read.ts +8 -0
- package/src/tools/slack-reply.ts +6 -0
- package/src/tools/slack-start-thread.ts +6 -0
- package/src/tools/slack-update.ts +6 -0
- package/src/tools/slack-upload-file.ts +6 -0
- package/src/tools/swarm-config/get-config.ts +2 -1
- package/src/tools/swarm-config/list-config.ts +2 -1
- package/src/tools/swarm-config/set-config.ts +16 -3
- package/src/tools/tool-config.ts +8 -6
- package/src/types.ts +12 -2
- package/src/utils/config-env-export.ts +74 -0
- package/src/utils/credentials.test.ts +92 -0
- package/src/utils/credentials.ts +52 -4
- package/src/utils/harness-provider.ts +15 -6
- package/src/utils/node-env.ts +24 -0
- package/src/utils/runtime-model-catalog.ts +11 -1
- package/src/utils/secret-scrubber.ts +71 -2
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/templates/skills/agent-browser/SKILL.md +1 -1
- package/templates/skills/agent-browser/content.md +1 -1
- package/templates/skills/apps/SKILL.md +749 -0
- package/templates/skills/apps/content.md +60 -22
- package/templates/skills/chat-sdk/config.json +14 -0
- package/templates/skills/chat-sdk/content.md +84 -0
- package/templates/skills/composio-gmail/content.md +7 -0
- package/templates/skills/composio-google-docs/content.md +1 -1
- package/templates/skills/email-domain-health/config.json +14 -0
- package/templates/skills/email-domain-health/content.md +131 -0
- package/templates/skills/kv-storage/content.md +2 -2
- package/templates/skills/pages/content.md +8 -10
- package/templates/skills/qa-loom/config.json +14 -0
- package/templates/skills/qa-loom/content.md +187 -0
- package/templates/skills/qa-loom/files/build-loom.mjs +221 -0
- package/templates/skills/qa-loom/files/run-qa.sh +57 -0
- package/templates/skills/slack-interaction/content.md +4 -0
- package/templates/skills/swarm-extensions/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +4 -2
- package/templates/skills/swarm-scripts/config.json +1 -1
- package/templates/skills/swarm-scripts/content.md +4 -2
- package/templates/skills/workflow-iterate/content.md +38 -0
- package/dist/artifact-eczsknc3.js +0 -3843
- package/dist/cli-9xsmdpdt.js +0 -56377
- package/dist/cli-bd957dgb.js +0 -153
- package/dist/cli-q51yprfq.js +0 -1104
- package/dist/cli-t1ze64h3.js +0 -811
- package/dist/index-9ch1n761.js +0 -1102
- package/src/artifact-sdk/index.ts +0 -2
- package/src/artifact-sdk/localtunnel.d.ts +0 -20
- package/src/artifact-sdk/port.ts +0 -12
- package/src/artifact-sdk/server.ts +0 -183
- package/src/artifact-sdk/tunnel.ts +0 -19
- package/src/commands/artifact.ts +0 -249
- package/src/http/ecosystem.ts +0 -77
- package/src/tests/artifact-commands.test.ts +0 -92
- package/src/tests/artifact-sdk.test.ts +0 -793
- package/src/tests/register-service-security.test.ts +0 -194
- package/src/tools/list-services.ts +0 -107
- package/src/tools/register-service.ts +0 -177
- package/src/tools/unregister-service.ts +0 -73
- package/src/tools/update-service-status.ts +0 -96
- package/templates/skills/artifacts/config.json +0 -14
- package/templates/skills/artifacts/content.md +0 -344
- package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
- package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
- package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
- package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
- package/dist/{azure-openai-responses-ap1gwxgr.js → azure-openai-responses-ygfreq4t.js} +5 -5
- package/dist/{cli-5rqrj7q0.js → cli-pw7ebjdt.js} +0 -0
- package/dist/{openai-codex-responses-33qhkkdy.js → openai-codex-responses-3bk0grnr.js} +4 -4
- package/dist/{openai-completions-s88xm3ns.js → openai-completions-5d4jpakc.js} +9 -9
- package/dist/{openai-responses-7xd2f2e2.js → openai-responses-krb51f59.js} +5 -5
- package/dist/{pi-messages-xszk613n.js → pi-messages-x9gzynm1.js} +3 -3
- /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
- /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
|
@@ -9,13 +9,28 @@
|
|
|
9
9
|
|
|
10
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
|
+
import {
|
|
13
|
+
baseUrlOverride,
|
|
14
|
+
ignoredBaseUrlVar,
|
|
15
|
+
type OpenAICompatibleProvider,
|
|
16
|
+
openAICompatibleKeyVars,
|
|
17
|
+
openAICompatibleProvider,
|
|
18
|
+
openAICompatibleProviders,
|
|
19
|
+
providerBaseUrl,
|
|
20
|
+
providerForKeyVar,
|
|
21
|
+
providerKeyVars,
|
|
22
|
+
} from "@desplega/model-catalog";
|
|
12
23
|
import type { OpenCode, OpenCodeEvent } from "@opencode/sdk";
|
|
13
24
|
import {
|
|
14
25
|
CONTEXT_FORMULA,
|
|
15
26
|
clampContextPercent,
|
|
16
27
|
getContextWindowSize,
|
|
17
28
|
} from "../utils/context-window";
|
|
18
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
openAICompatibleKeyLabels,
|
|
31
|
+
splitCredentialPool,
|
|
32
|
+
validateOpencodeCredentials,
|
|
33
|
+
} from "../utils/credentials";
|
|
19
34
|
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
20
35
|
import { fetchInstalledMcpServers } from "../utils/mcp-server-fetcher";
|
|
21
36
|
import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
|
|
@@ -24,9 +39,10 @@ import {
|
|
|
24
39
|
getOpenRouterAttributionHeaders,
|
|
25
40
|
getOpenRouterBaseUrl,
|
|
26
41
|
} from "../utils/openrouter-base-url";
|
|
27
|
-
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
42
|
+
import { registerVolatileSecret, scrubSecrets } from "../utils/secret-scrubber";
|
|
28
43
|
import { resolveSlashSkillPrompt } from "./codex-skill-resolver";
|
|
29
44
|
import { CTX_MODE_NUDGE_EVERY } from "./ctx-mode-env";
|
|
45
|
+
import { OpenRouterBilling } from "./opencode-billing";
|
|
30
46
|
import { createOpencodeSwarmPlugin, type OpencodeToolLister } from "./opencode-swarm-plugin";
|
|
31
47
|
import {
|
|
32
48
|
apiHeaders,
|
|
@@ -60,8 +76,12 @@ export type OpencodeConfig = {
|
|
|
60
76
|
provider?: Record<
|
|
61
77
|
string,
|
|
62
78
|
{
|
|
79
|
+
/** Env var names OpenCode reads the key from; `[]` stops it reading `process.env`. */
|
|
80
|
+
env?: string[];
|
|
81
|
+
npm?: string;
|
|
82
|
+
name?: string;
|
|
63
83
|
options?: Record<string, unknown>;
|
|
64
|
-
models?: Record<string, { options?: Record<string, unknown
|
|
84
|
+
models?: Record<string, { options?: Record<string, unknown>; [key: string]: unknown }>;
|
|
65
85
|
}
|
|
66
86
|
>;
|
|
67
87
|
[key: string]: unknown;
|
|
@@ -79,18 +99,41 @@ function opencodeModelToCredKey(modelStr: string | undefined): string | null {
|
|
|
79
99
|
if (provider === "anthropic") return "ANTHROPIC_API_KEY";
|
|
80
100
|
if (provider === "openrouter") return "OPENROUTER_API_KEY";
|
|
81
101
|
if (provider === "openai") return "OPENAI_API_KEY";
|
|
102
|
+
// OpenAI-compatible registry providers (`nan`, `helmcode`).
|
|
103
|
+
const direct = openAICompatibleProvider(provider);
|
|
104
|
+
if (direct) return direct.envVar;
|
|
82
105
|
}
|
|
83
106
|
return null;
|
|
84
107
|
}
|
|
85
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Env var names that satisfy `requiredKey`. The runner copies a registry
|
|
111
|
+
* key alias (`HELMCODE_API_NAN`) into its canonical var per task, but a
|
|
112
|
+
* check that reads `process.env` directly sees only the alias.
|
|
113
|
+
*/
|
|
114
|
+
function credKeyNames(requiredKey: string): readonly string[] {
|
|
115
|
+
const direct = providerForKeyVar(requiredKey);
|
|
116
|
+
return direct ? providerKeyVars(direct) : [requiredKey];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Keys that satisfy opencode when no model pins a provider. */
|
|
120
|
+
const OPENCODE_ANY_KEY_VARS = [
|
|
121
|
+
"OPENROUTER_API_KEY",
|
|
122
|
+
"ANTHROPIC_API_KEY",
|
|
123
|
+
"OPENAI_API_KEY",
|
|
124
|
+
...openAICompatibleKeyVars(),
|
|
125
|
+
];
|
|
126
|
+
|
|
86
127
|
/**
|
|
87
128
|
* Opencode is satisfied by ANY of:
|
|
88
129
|
* 1. `~/.local/share/opencode/auth.json` exists (the file `opencode auth login`
|
|
89
130
|
* writes; OpenCode 2 imports it into its credential store on start).
|
|
90
131
|
* 2. `MODEL_OVERRIDE` resolves to a provider-prefixed model — only that
|
|
91
|
-
* provider's key is required
|
|
132
|
+
* provider's key is required (a registry provider also accepts its key
|
|
133
|
+
* aliases: `nan/*` takes `HELMCODE_API_NAN`).
|
|
92
134
|
* 3. Otherwise any one of OPENROUTER_API_KEY / ANTHROPIC_API_KEY /
|
|
93
|
-
* OPENAI_API_KEY
|
|
135
|
+
* OPENAI_API_KEY / a registry provider key (canonical or alias)
|
|
136
|
+
* suffices.
|
|
94
137
|
*/
|
|
95
138
|
export function checkOpencodeCredentials(
|
|
96
139
|
env: Record<string, string | undefined>,
|
|
@@ -105,7 +148,7 @@ export function checkOpencodeCredentials(
|
|
|
105
148
|
|
|
106
149
|
const requiredKey = opencodeModelToCredKey(env.MODEL_OVERRIDE);
|
|
107
150
|
if (requiredKey) {
|
|
108
|
-
if (env[
|
|
151
|
+
if (credKeyNames(requiredKey).some((name) => env[name])) {
|
|
109
152
|
return { ready: true, missing: [], satisfiedBy: "env" };
|
|
110
153
|
}
|
|
111
154
|
return {
|
|
@@ -115,13 +158,26 @@ export function checkOpencodeCredentials(
|
|
|
115
158
|
};
|
|
116
159
|
}
|
|
117
160
|
|
|
118
|
-
if (
|
|
161
|
+
if (OPENCODE_ANY_KEY_VARS.some((name) => env[name])) {
|
|
119
162
|
return { ready: true, missing: [], satisfiedBy: "env" };
|
|
120
163
|
}
|
|
121
164
|
return {
|
|
122
165
|
ready: false,
|
|
123
|
-
missing: [
|
|
124
|
-
|
|
166
|
+
missing: [
|
|
167
|
+
"OPENROUTER_API_KEY",
|
|
168
|
+
"ANTHROPIC_API_KEY",
|
|
169
|
+
"OPENAI_API_KEY",
|
|
170
|
+
...openAICompatibleProviders().map((p) => p.envVar),
|
|
171
|
+
authFile,
|
|
172
|
+
],
|
|
173
|
+
hint: `Set one of ${[
|
|
174
|
+
"OPENROUTER_API_KEY",
|
|
175
|
+
"ANTHROPIC_API_KEY",
|
|
176
|
+
"OPENAI_API_KEY",
|
|
177
|
+
...openAICompatibleKeyLabels(),
|
|
178
|
+
].join(
|
|
179
|
+
" / ",
|
|
180
|
+
)} (any one suffices), or run \`opencode auth login\` to create ~/.local/share/opencode/auth.json.`,
|
|
125
181
|
};
|
|
126
182
|
}
|
|
127
183
|
|
|
@@ -216,9 +272,156 @@ export function applyOpenRouterBaseUrlOverride(
|
|
|
216
272
|
};
|
|
217
273
|
}
|
|
218
274
|
|
|
275
|
+
/**
|
|
276
|
+
* OpenCode provider block for an OpenAI-compatible registry provider that
|
|
277
|
+
* OpenCode does not ship (`catalog: "registry"`, e.g. `helmcode`). `apiKey` is
|
|
278
|
+
* a literal key (embedded host) or an `{env:NAME}` reference (ACP
|
|
279
|
+
* subprocess). `baseUrl` is the `<ID>_BASE_URL` override or the registry
|
|
280
|
+
* default. `env: []` keeps OpenCode from reading a key out of `process.env`.
|
|
281
|
+
* A `modelId` missing from the registry (accepted through `allowCustomModel`)
|
|
282
|
+
* gets a bare entry so OpenCode still resolves it instead of falling back to
|
|
283
|
+
* its default model.
|
|
284
|
+
*/
|
|
285
|
+
export function buildOpenAICompatibleProviderBlock(
|
|
286
|
+
provider: OpenAICompatibleProvider,
|
|
287
|
+
apiKey: string,
|
|
288
|
+
modelId?: string,
|
|
289
|
+
baseUrl: string = provider.baseUrl,
|
|
290
|
+
): NonNullable<OpencodeConfig["provider"]>[string] {
|
|
291
|
+
const models: Record<string, Record<string, unknown>> = {};
|
|
292
|
+
for (const m of provider.models) {
|
|
293
|
+
models[m.id] = {
|
|
294
|
+
name: m.name,
|
|
295
|
+
tool_call: true,
|
|
296
|
+
reasoning: m.reasoning,
|
|
297
|
+
limit: { context: m.contextWindow, output: m.maxOutputTokens },
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (modelId && !models[modelId]) models[modelId] = { name: modelId, tool_call: true };
|
|
301
|
+
return {
|
|
302
|
+
npm: provider.npm,
|
|
303
|
+
name: provider.displayName,
|
|
304
|
+
env: [],
|
|
305
|
+
options: { baseURL: baseUrl, apiKey },
|
|
306
|
+
models,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Built-in OpenCode providers whose key OpenCode reads from `process.env`:
|
|
312
|
+
* `openrouter` plus every registry provider models.dev lists
|
|
313
|
+
* (`catalog: "models.dev"`, e.g. `nan`), which OpenCode ships built in.
|
|
314
|
+
*/
|
|
315
|
+
function envBoundKeyVars(providerId: string): readonly string[] | undefined {
|
|
316
|
+
if (providerId === "openrouter") return ["OPENROUTER_API_KEY"];
|
|
317
|
+
const direct = openAICompatibleProvider(providerId);
|
|
318
|
+
return direct?.catalog === "models.dev" ? providerKeyVars(direct) : undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Registry providers OpenCode does not ship (`catalog: "registry"`); they get a full custom block. */
|
|
322
|
+
function customBlockProvider(providerId: string): OpenAICompatibleProvider | undefined {
|
|
323
|
+
const direct = openAICompatibleProvider(providerId);
|
|
324
|
+
return direct?.catalog === "registry" ? direct : undefined;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* One key from the first non-empty var in `names`. The runner's per-task env
|
|
329
|
+
* already holds one picked pool member; a value that still carries a comma
|
|
330
|
+
* pool (no runner, or `process.env`) yields its first member.
|
|
331
|
+
*/
|
|
332
|
+
function pickProviderKey(
|
|
333
|
+
env: Record<string, string | undefined>,
|
|
334
|
+
names: readonly string[],
|
|
335
|
+
): { key: string; name: string } | undefined {
|
|
336
|
+
for (const name of names) {
|
|
337
|
+
const value = env[name]?.trim();
|
|
338
|
+
if (!value) continue;
|
|
339
|
+
const key = splitCredentialPool(value)[0];
|
|
340
|
+
if (key) return { key, name };
|
|
341
|
+
}
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Hand the embedded host exactly one key for the active model's provider.
|
|
347
|
+
*
|
|
348
|
+
* Embedded OpenCode reads a built-in provider's key from the shared
|
|
349
|
+
* `process.env`, and that value beats `options.apiKey`. The entrypoint exports
|
|
350
|
+
* a credential pool there as a raw comma list, and a key saved from the
|
|
351
|
+
* dashboard after boot never reaches `process.env` at all, so both 401 without
|
|
352
|
+
* this splice. For `openrouter` and the built-in registry providers (`nan`,
|
|
353
|
+
* `vercel`) it sets `env: []` plus `options.apiKey` (and `options.baseURL`
|
|
354
|
+
* when `<ID>_BASE_URL` is set; a `baseUrlLocked` provider gets none); for registry providers OpenCode does not ship
|
|
355
|
+
* (`helmcode`) it writes the full custom provider block at
|
|
356
|
+
* `<ID>_BASE_URL` or the registry default. No splice when the key is absent
|
|
357
|
+
* (credStatus reports that).
|
|
358
|
+
*
|
|
359
|
+
* The key lives only in the in-memory config string handed to the SDK.
|
|
360
|
+
* Mutates `opencodeConfig` in place and returns the env var the key came from,
|
|
361
|
+
* or `undefined` when nothing was spliced.
|
|
362
|
+
*/
|
|
363
|
+
export function applyProviderKeySplice(
|
|
364
|
+
opencodeConfig: OpencodeConfig,
|
|
365
|
+
model: string | undefined,
|
|
366
|
+
env: Record<string, string | undefined>,
|
|
367
|
+
): { key: string; name: string } | undefined {
|
|
368
|
+
const slash = model?.indexOf("/") ?? -1;
|
|
369
|
+
if (!model || slash <= 0) return undefined;
|
|
370
|
+
const providerId = model.slice(0, slash);
|
|
371
|
+
const existing = opencodeConfig.provider?.[providerId];
|
|
372
|
+
|
|
373
|
+
const custom = customBlockProvider(providerId);
|
|
374
|
+
if (custom) {
|
|
375
|
+
const picked = pickProviderKey(env, providerKeyVars(custom));
|
|
376
|
+
if (!picked) return undefined;
|
|
377
|
+
const block = buildOpenAICompatibleProviderBlock(
|
|
378
|
+
custom,
|
|
379
|
+
picked.key,
|
|
380
|
+
model.slice(slash + 1),
|
|
381
|
+
providerBaseUrl(custom, env),
|
|
382
|
+
);
|
|
383
|
+
opencodeConfig.provider = {
|
|
384
|
+
...opencodeConfig.provider,
|
|
385
|
+
[providerId]: {
|
|
386
|
+
...existing,
|
|
387
|
+
...block,
|
|
388
|
+
options: { ...existing?.options, ...block.options },
|
|
389
|
+
models: { ...existing?.models, ...block.models },
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
return picked;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const names = envBoundKeyVars(providerId);
|
|
396
|
+
if (!names) return undefined;
|
|
397
|
+
const picked = pickProviderKey(env, names);
|
|
398
|
+
if (!picked) return undefined;
|
|
399
|
+
const direct = openAICompatibleProvider(providerId);
|
|
400
|
+
const baseURL = direct ? baseUrlOverride(direct, env) : undefined;
|
|
401
|
+
const options = { ...existing?.options, ...(baseURL ? { baseURL } : {}), apiKey: picked.key };
|
|
402
|
+
if (direct?.baseUrlLocked) {
|
|
403
|
+
// OpenCode's built-in SDK (AI Gateway) knows its endpoint; any URL here is wrong.
|
|
404
|
+
const ignored = ignoredBaseUrlVar(direct, env);
|
|
405
|
+
if (ignored || options.baseURL !== undefined) {
|
|
406
|
+
console.warn(
|
|
407
|
+
`[opencode] ${ignored ?? `provider.${providerId}.options.baseURL`} is ignored: ${direct.displayName} uses its built-in endpoint`,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
delete options.baseURL;
|
|
411
|
+
}
|
|
412
|
+
opencodeConfig.provider = {
|
|
413
|
+
...opencodeConfig.provider,
|
|
414
|
+
[providerId]: { ...existing, env: [], options },
|
|
415
|
+
};
|
|
416
|
+
return picked;
|
|
417
|
+
}
|
|
418
|
+
|
|
219
419
|
const SWARM_AGENT_NAME = "swarm";
|
|
220
420
|
const TRANSCRIPT_TAIL_CHARS = 20_000;
|
|
221
421
|
const TOOL_RESULT_PREVIEW_CHARS = 500;
|
|
422
|
+
// The last response body finishes streaming with the step; this only covers
|
|
423
|
+
// a read still in flight when the session ends.
|
|
424
|
+
const BILLING_SETTLE_MS = 2_000;
|
|
222
425
|
|
|
223
426
|
/** The swarm MCP server's name in the OpenCode config. */
|
|
224
427
|
const SWARM_MCP_SERVER = "swarm";
|
|
@@ -339,6 +542,7 @@ export class OpencodeSession implements ProviderSession {
|
|
|
339
542
|
private readonly swarm: SwarmHooksConfig,
|
|
340
543
|
private readonly cwd: string,
|
|
341
544
|
private readonly appliedReasoningEffort: ReasoningEffort | null = null,
|
|
545
|
+
private readonly billing?: OpenRouterBilling,
|
|
342
546
|
) {
|
|
343
547
|
this.completionPromise = new Promise<ProviderResult>((resolve) => {
|
|
344
548
|
this.completionResolve = resolve;
|
|
@@ -494,12 +698,13 @@ export class OpencodeSession implements ProviderSession {
|
|
|
494
698
|
this.usage = { cost: ev.data.cost, tokens: ev.data.tokens };
|
|
495
699
|
break;
|
|
496
700
|
|
|
497
|
-
case "session.execution.succeeded":
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
701
|
+
case "session.execution.succeeded":
|
|
702
|
+
void this.finish(async () => {
|
|
703
|
+
const cost = await this.buildCostData(false);
|
|
704
|
+
this.emit({ type: "result", cost, isError: false });
|
|
705
|
+
return { exitCode: 0, sessionId: this._sessionId, cost, isError: false };
|
|
706
|
+
});
|
|
501
707
|
break;
|
|
502
|
-
}
|
|
503
708
|
|
|
504
709
|
case "session.execution.failed":
|
|
505
710
|
this.fail(errorMessage(ev.data.error, "opencode session error"));
|
|
@@ -541,27 +746,34 @@ export class OpencodeSession implements ProviderSession {
|
|
|
541
746
|
}
|
|
542
747
|
|
|
543
748
|
/** Settle the session as failed; also used when the event stream dies. */
|
|
544
|
-
fail(
|
|
749
|
+
fail(rawMessage: string): void {
|
|
545
750
|
if (this.aborted || this.completed) return;
|
|
751
|
+
// SDK and provider errors can echo request details; the message reaches
|
|
752
|
+
// the runner's logs and the task's failure reason.
|
|
753
|
+
const message = scrubSecrets(rawMessage);
|
|
546
754
|
this.emit({ type: "error", message });
|
|
547
|
-
void this.finish({
|
|
755
|
+
void this.finish(async () => ({
|
|
548
756
|
exitCode: 1,
|
|
549
757
|
sessionId: this._sessionId,
|
|
550
|
-
cost: this.buildCostData(true),
|
|
758
|
+
cost: await this.buildCostData(true),
|
|
551
759
|
isError: true,
|
|
552
760
|
failureReason: message,
|
|
553
|
-
});
|
|
761
|
+
}));
|
|
554
762
|
}
|
|
555
763
|
|
|
556
|
-
private buildCostData(isError: boolean): CostData {
|
|
764
|
+
private async buildCostData(isError: boolean): Promise<CostData> {
|
|
557
765
|
const { cost, tokens } = this.usage;
|
|
766
|
+
const billed = await this.billing?.settle(BILLING_SETTLE_MS);
|
|
558
767
|
return {
|
|
559
768
|
sessionId: this._sessionId,
|
|
560
769
|
taskId: this.swarm.taskId,
|
|
561
770
|
agentId: this.swarm.agentId,
|
|
562
|
-
|
|
771
|
+
...(billed?.complete
|
|
772
|
+
? { totalCostUsd: billed.costUsd, harnessCostBilled: true }
|
|
773
|
+
: { totalCostUsd: cost }),
|
|
563
774
|
inputTokens: tokens.input,
|
|
564
|
-
|
|
775
|
+
// OpenCode 2's `output` excludes reasoning; both bill at the output rate.
|
|
776
|
+
outputTokens: tokens.output + tokens.reasoning,
|
|
565
777
|
reasoningOutputTokens: tokens.reasoning,
|
|
566
778
|
cacheReadTokens: tokens.cache.read,
|
|
567
779
|
cacheWriteTokens: tokens.cache.write,
|
|
@@ -596,10 +808,11 @@ export class OpencodeSession implements ProviderSession {
|
|
|
596
808
|
* Settle the session. Session-end work is bounded and cancellable, so the
|
|
597
809
|
* host always closes and completion always resolves.
|
|
598
810
|
*/
|
|
599
|
-
private async finish(
|
|
811
|
+
private async finish(settle: ProviderResult | (() => Promise<ProviderResult>)): Promise<void> {
|
|
600
812
|
if (this.completed) return;
|
|
601
813
|
this.completed = true;
|
|
602
814
|
const { signal } = this.sessionEnd;
|
|
815
|
+
const result = typeof settle === "function" ? await settle() : settle;
|
|
603
816
|
try {
|
|
604
817
|
if (!this.aborted) {
|
|
605
818
|
await settleWithin(this.runSessionEnd(signal), sessionHooks.budgetMs, signal);
|
|
@@ -722,6 +935,12 @@ export class OpencodeAdapter implements ProviderAdapter {
|
|
|
722
935
|
},
|
|
723
936
|
};
|
|
724
937
|
|
|
938
|
+
// One picked key for the model's provider (D3). It stays in this
|
|
939
|
+
// in-memory config; registering it lets every scrubbed egress redact it
|
|
940
|
+
// even when it never reached `process.env` (a key saved after boot).
|
|
941
|
+
const splicedKey = applyProviderKeySplice(opencodeConfig, config.model, env);
|
|
942
|
+
if (splicedKey) registerVolatileSecret(splicedKey.key, splicedKey.name);
|
|
943
|
+
|
|
725
944
|
// Splice the provider-keyed reasoning options into
|
|
726
945
|
// `provider[providerId].models[modelId].options`. `off` (and any
|
|
727
946
|
// capability-rejected level) resolves to `noop`.
|
|
@@ -762,20 +981,31 @@ export class OpencodeAdapter implements ProviderAdapter {
|
|
|
762
981
|
};
|
|
763
982
|
const { sdk, version: opencodeVersion } = await loadOpencodeSdkImpl();
|
|
764
983
|
let listTools: OpencodeToolLister | undefined;
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
984
|
+
const billing = config.model.startsWith("openrouter/") ? new OpenRouterBilling() : undefined;
|
|
985
|
+
// The config string carries the spliced provider key and the swarm MCP
|
|
986
|
+
// bearer. It goes to the SDK only; nothing logs, writes, or uploads it,
|
|
987
|
+
// and errors raised while the SDK parses it are scrubbed before they
|
|
988
|
+
// propagate to the runner.
|
|
989
|
+
let host: OpenCode.Interface;
|
|
990
|
+
try {
|
|
991
|
+
host = await sdk.OpenCode.create({
|
|
992
|
+
config: { content: JSON.stringify(opencodeConfig) },
|
|
993
|
+
plugins: [
|
|
994
|
+
createOpencodeSwarmPlugin(swarm, {
|
|
995
|
+
onToolRegistry: (lister) => {
|
|
996
|
+
listTools = lister;
|
|
997
|
+
},
|
|
998
|
+
...(billing ? { onOpenRouterResponse: (response) => billing.observe(response) } : {}),
|
|
999
|
+
}),
|
|
1000
|
+
],
|
|
1001
|
+
log: {
|
|
1002
|
+
level: "warn",
|
|
1003
|
+
emit: (entry) => console.warn(scrubSecrets(`[opencode] ${entry.message}`)),
|
|
1004
|
+
},
|
|
1005
|
+
});
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
throw new Error(scrubSecrets(`opencode host failed to start: ${errorMessage(err, "")}`));
|
|
1008
|
+
}
|
|
779
1009
|
|
|
780
1010
|
let sessionId: string;
|
|
781
1011
|
try {
|
|
@@ -786,7 +1016,7 @@ export class OpencodeAdapter implements ProviderAdapter {
|
|
|
786
1016
|
sessionId = created.id;
|
|
787
1017
|
} catch (err) {
|
|
788
1018
|
await host.close().catch(() => {});
|
|
789
|
-
throw err;
|
|
1019
|
+
throw new Error(scrubSecrets(`opencode session create failed: ${errorMessage(err, "")}`));
|
|
790
1020
|
}
|
|
791
1021
|
|
|
792
1022
|
const session = new OpencodeSession(
|
|
@@ -796,6 +1026,7 @@ export class OpencodeAdapter implements ProviderAdapter {
|
|
|
796
1026
|
swarm,
|
|
797
1027
|
config.cwd,
|
|
798
1028
|
appliedReasoningEffort,
|
|
1029
|
+
billing,
|
|
799
1030
|
);
|
|
800
1031
|
session.emitSessionInit(opencodeVersion ? { version: opencodeVersion } : undefined);
|
|
801
1032
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter's billed cost for an OpenCode 2 session.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode prices tokens from its model catalog, and the swarm pricing table
|
|
5
|
+
* holds OpenRouter's model-level list price. Both are the cheapest endpoint's
|
|
6
|
+
* rate, while OpenRouter bills the endpoint that served the request, which
|
|
7
|
+
* can cost 100x more. OpenCode asks OpenRouter for `usage: { include: true }`,
|
|
8
|
+
* so every response carries `usage.cost`, the USD OpenRouter charged. The
|
|
9
|
+
* swarm plugin hands each OpenRouter response here, and the session reports
|
|
10
|
+
* the sum as billed when every response carried one.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The billed USD in an OpenRouter chat response (SSE or JSON), or null. */
|
|
14
|
+
export function parseOpenRouterBilledCost(body: string): number | null {
|
|
15
|
+
const payloads = body.trimStart().startsWith("{")
|
|
16
|
+
? [body]
|
|
17
|
+
: body
|
|
18
|
+
.split("\n")
|
|
19
|
+
.filter((line) => line.startsWith("data:"))
|
|
20
|
+
.map((line) => line.slice(5).trim());
|
|
21
|
+
let cost: number | null = null;
|
|
22
|
+
for (const payload of payloads) {
|
|
23
|
+
if (!payload.includes('"cost"')) continue;
|
|
24
|
+
let usage: { cost?: unknown; is_byok?: unknown } | undefined;
|
|
25
|
+
try {
|
|
26
|
+
usage = (JSON.parse(payload) as { usage?: typeof usage }).usage;
|
|
27
|
+
} catch {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// A BYOK `cost` is OpenRouter's fee only; the upstream bills the rest.
|
|
31
|
+
if (usage?.is_byok === true) return null;
|
|
32
|
+
if (typeof usage?.cost === "number" && Number.isFinite(usage.cost)) cost = usage.cost;
|
|
33
|
+
}
|
|
34
|
+
return cost;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BilledCost {
|
|
38
|
+
costUsd: number;
|
|
39
|
+
/** True when every observed response reported what OpenRouter charged. */
|
|
40
|
+
complete: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class OpenRouterBilling {
|
|
44
|
+
private requests = 0;
|
|
45
|
+
private billed = 0;
|
|
46
|
+
private costUsd = 0;
|
|
47
|
+
private pending = new Set<Promise<void>>();
|
|
48
|
+
|
|
49
|
+
/** Reads a copy of `response`; the caller keeps streaming the original. */
|
|
50
|
+
observe(response: Response): void {
|
|
51
|
+
this.requests++;
|
|
52
|
+
// OpenRouter does not charge for a rejected request.
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
this.billed++;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const read = response
|
|
58
|
+
.clone()
|
|
59
|
+
.text()
|
|
60
|
+
.then((body) => {
|
|
61
|
+
const cost = parseOpenRouterBilledCost(body);
|
|
62
|
+
if (cost === null) return;
|
|
63
|
+
this.billed++;
|
|
64
|
+
this.costUsd += cost;
|
|
65
|
+
})
|
|
66
|
+
.catch(() => {})
|
|
67
|
+
.finally(() => this.pending.delete(read));
|
|
68
|
+
this.pending.add(read);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Waits up to `timeoutMs` for in-flight responses, then reports. */
|
|
72
|
+
async settle(timeoutMs: number): Promise<BilledCost | null> {
|
|
73
|
+
if (this.pending.size > 0) {
|
|
74
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
75
|
+
await Promise.race([
|
|
76
|
+
Promise.all(this.pending),
|
|
77
|
+
new Promise<void>((resolve) => {
|
|
78
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
79
|
+
}),
|
|
80
|
+
]);
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
}
|
|
83
|
+
if (this.requests === 0) return null;
|
|
84
|
+
return { costUsd: this.costUsd, complete: this.billed === this.requests };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -28,6 +28,29 @@ export type OpencodeToolLister = () => Promise<ReadonlyArray<{ readonly id: stri
|
|
|
28
28
|
export interface OpencodeSwarmPluginOptions {
|
|
29
29
|
/** Receives the tool lister once OpenCode sets the plugin up. */
|
|
30
30
|
onToolRegistry?: (listTools: OpencodeToolLister) => void;
|
|
31
|
+
/** Receives every OpenRouter model response, for the billed cost. */
|
|
32
|
+
onOpenRouterResponse?: (response: Response) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Keys OpenCode sets on each shell call, on top of its base env. */
|
|
36
|
+
const OPENCODE_SHELL_ENV_KEYS = ["TERM", "OPENCODE_TERMINAL"] as const;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Rewrites one OpenCode shell call's env, in place, to the session's own env
|
|
40
|
+
* plus the keys OpenCode set for the call. Nothing else from the process env
|
|
41
|
+
* gets through.
|
|
42
|
+
*/
|
|
43
|
+
function applyOpencodeShellEnv(
|
|
44
|
+
callEnv: Record<string, string | undefined>,
|
|
45
|
+
sessionEnv: Record<string, string | undefined>,
|
|
46
|
+
): void {
|
|
47
|
+
const keep = new Set<string>(OPENCODE_SHELL_ENV_KEYS);
|
|
48
|
+
for (const key of Object.keys(callEnv)) {
|
|
49
|
+
if (!keep.has(key)) delete callEnv[key];
|
|
50
|
+
}
|
|
51
|
+
for (const [key, value] of Object.entries(sessionEnv)) {
|
|
52
|
+
if (value !== undefined && !keep.has(key)) callEnv[key] = value;
|
|
53
|
+
}
|
|
31
54
|
}
|
|
32
55
|
|
|
33
56
|
export function createOpencodeSwarmPlugin(
|
|
@@ -39,6 +62,16 @@ export function createOpencodeSwarmPlugin(
|
|
|
39
62
|
async setup(ctx) {
|
|
40
63
|
options.onToolRegistry?.(() => ctx.tool.list());
|
|
41
64
|
|
|
65
|
+
// OpenCode runs in-process, so its shell tool spawns with process.env,
|
|
66
|
+
// which every concurrent session shares and which never sees this
|
|
67
|
+
// task's env (rows admitted or withheld by fetchResolvedEnv after boot).
|
|
68
|
+
const sessionEnv = config.env;
|
|
69
|
+
if (sessionEnv) {
|
|
70
|
+
await ctx.shell.hook("create.before", (event) => {
|
|
71
|
+
applyOpencodeShellEnv(event.env, sessionEnv);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
42
75
|
// A throw blocks the tool; OpenCode hands the message back to the model.
|
|
43
76
|
await ctx.tool.hook("execute.before", async (event) => {
|
|
44
77
|
if (!config.isLead && config.taskId) {
|
|
@@ -75,6 +108,24 @@ export function createOpencodeSwarmPlugin(
|
|
|
75
108
|
if (concurrent) event.system.push({ type: "text", text: concurrent });
|
|
76
109
|
});
|
|
77
110
|
|
|
111
|
+
// Nobody reads a headless session's title. Generating one is a second
|
|
112
|
+
// model call, on OpenCode's small model, that the session usage folds
|
|
113
|
+
// into the main model's tokens.
|
|
114
|
+
await ctx.session.hook("title", (event) => {
|
|
115
|
+
event.result = config.taskId ? `swarm task ${config.taskId}` : "swarm session";
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const onOpenRouterResponse = options.onOpenRouterResponse;
|
|
119
|
+
if (onOpenRouterResponse) {
|
|
120
|
+
await ctx.session.hook(
|
|
121
|
+
"http.response",
|
|
122
|
+
(event) => {
|
|
123
|
+
onOpenRouterResponse(event.response);
|
|
124
|
+
},
|
|
125
|
+
{ providerID: "openrouter" },
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
78
129
|
// PreCompact parity: keep the task goal in the checkpoint summary.
|
|
79
130
|
await ctx.session.hook("compaction", async (event) => {
|
|
80
131
|
if (!config.taskId) return;
|