@desplega.ai/agent-swarm 1.165.0 → 1.167.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/dist/acp-adapter-zpvy6321.js +34 -0
- package/dist/{actions-jna0b3wm.js → actions-fq2zrwtf.js} +30 -34
- package/dist/{amp-adapter-0eve81pz.js → amp-adapter-5gbk7501.js} +7 -7
- package/dist/{anthropic-messages-hst62ws9.js → anthropic-messages-13yn502s.js} +4 -4
- package/dist/{app-75hm5cfa.js → app-nctetn1e.js} +11 -11
- package/dist/{assistant-yzd51j33.js → assistant-0aq8gjvj.js} +31 -35
- package/dist/{azure-openai-responses-mh8tvf78.js → azure-openai-responses-9st34pfz.js} +5 -5
- package/dist/{boot-reembed-z158xq32.js → boot-reembed-1ze5853n.js} +14 -16
- package/dist/{boot-reembed-2ex2mcsh.js → boot-reembed-fmp5ww69.js} +15 -16
- package/dist/boot-scrub-sweep-cbp8xd4e.js +212 -0
- package/dist/{boot-seeds-2bmnh18p.js → boot-seeds-22tx8a53.js} +13 -12
- package/dist/{claude-adapter-jbj1mycz.js → claude-adapter-0jhgeh1d.js} +25 -25
- package/dist/{claude-managed-adapter-17ggz09t.js → claude-managed-adapter-tzwxy803.js} +5 -5
- package/dist/{claude-managed-setup-ptm1nzm2.js → claude-managed-setup-nrhfx5b0.js} +5 -5
- package/dist/{claude-sdk-session-9re1g7r2.js → claude-sdk-session-wh77rh83.js} +2632 -2614
- package/dist/{cli-zsh093hk.js → cli-014vns2y.js} +63 -1
- package/dist/{cli-qvfk83fy.js → cli-13xwcqvj.js} +2 -2
- package/dist/{cli-8gpbwy09.js → cli-1yrcfa5s.js} +5 -5
- package/dist/{cli-xtgtyafv.js → cli-577bxkf6.js} +212 -451
- package/dist/{cli-ma3zvh39.js → cli-5qkcdv8c.js} +6118 -5874
- package/dist/{cli-cwxhbf43.js → cli-5t5my0h8.js} +85 -2
- package/dist/{cli-zvckv750.js → cli-69egj654.js} +2 -2
- package/dist/{cli-9xb89xca.js → cli-6bpvv8h4.js} +9 -9
- package/dist/{cli-90v942q1.js → cli-6qxmjpem.js} +8 -8
- package/dist/{cli-9866bd07.js → cli-6vfz44sr.js} +1 -1
- package/dist/{cli-32bca7nr.js → cli-7avy4v8w.js} +1 -1
- package/dist/{cli-xb3c6aat.js → cli-7mhy3ths.js} +21 -18
- package/dist/{cli-q38n4bq0.js → cli-95zs84qw.js} +3 -3
- package/dist/cli-a35hzdve.js +33 -0
- package/dist/{cli-x3x0qwky.js → cli-bxqez1p0.js} +51 -23
- package/dist/{cli-prt9emct.js → cli-c2tpw9ck.js} +3 -3
- package/dist/{cli-0n4fasz0.js → cli-c8r99akr.js} +16 -16
- package/dist/{cli-5anb0ev3.js → cli-cexctgn5.js} +89 -26
- package/dist/{cli-bns5c8k9.js → cli-cf2vgn9d.js} +105 -91
- package/dist/{cli-ng97fjq7.js → cli-cmxkbzzs.js} +33 -7
- package/dist/{cli-nexwh8zg.js → cli-g7k7zbcz.js} +12 -5
- package/dist/{cli-mmzwwptd.js → cli-hf5s9pgf.js} +1 -1
- package/dist/{cli-nvvfd8bq.js → cli-jbzbr36n.js} +4091 -3793
- package/dist/{cli-zn2v3t8v.js → cli-jvmtxqtc.js} +27 -15
- package/dist/{cli-pkke6geq.js → cli-km4x66j0.js} +83 -65
- package/dist/cli-kw0jcfje.js +516 -0
- package/dist/{cli-28smfvbn.js → cli-m7vmeepg.js} +32 -19
- package/dist/{cli-hpryr6em.js → cli-mb75wpxf.js} +1 -1
- package/dist/{cli-qdv8kgz7.js → cli-mkdsy1nq.js} +14018 -3333
- package/dist/{cli-b0f70z24.js → cli-mn8qkd8v.js} +1 -1
- package/dist/{cli-420bdgw0.js → cli-mv2520nm.js} +1 -1
- package/dist/{cli-n9ehc28t.js → cli-mxry91sa.js} +31 -10
- package/dist/{cli-wfajtmf7.js → cli-mxvnnv5q.js} +596 -13
- package/dist/cli-nkqjj42q.js +104 -0
- package/dist/{cli-e7t4snpa.js → cli-p8rt8spp.js} +5 -5
- package/dist/{cli-gh5yqzpe.js → cli-pkj1w46r.js} +12 -7
- package/dist/cli-q1vnz8wv.js +96 -0
- package/dist/{cli-fj86xr3c.js → cli-qd97v6z2.js} +2 -2
- package/dist/{cli-ba66wyhy.js → cli-qzmzwegq.js} +60 -20
- package/dist/{cli-5b2fjjzh.js → cli-r7eeddkq.js} +14 -9
- package/dist/{cli-8ssjez7e.js → cli-rhy7z1rp.js} +22 -11
- package/dist/{cli-4mevd1hf.js → cli-s9y81s7j.js} +5 -5
- package/dist/{cli-59jn5twa.js → cli-tc5zec7v.js} +7 -7
- package/dist/{cli-ttbsse3f.js → cli-tmhsxhb8.js} +1 -1
- package/dist/{cli-k6xx3pve.js → cli-tx2zvn23.js} +4 -4
- package/dist/{cli-pjxnr1jn.js → cli-ty4bzkqr.js} +5 -5
- package/dist/{cli-nfygw434.js → cli-v1pwxreq.js} +55 -52
- package/dist/{cli-ftry7975.js → cli-v1w7pbd8.js} +3 -2
- package/dist/{cli-10zmp1p7.js → cli-vr5avem2.js} +27 -32
- package/dist/{cli-bzr0ftht.js → cli-wnb9b2g3.js} +19 -0
- package/dist/cli-wt7ccy4r.js +2654 -0
- package/dist/{cli-50eg13rj.js → cli-wydz3feg.js} +549 -321
- package/dist/{cli-krqm5f11.js → cli-xezn9nkh.js} +16 -13
- package/dist/{cli-8kt0bmgx.js → cli-xf713zz0.js} +2 -2
- package/dist/{cli-x7afk5wm.js → cli-xvc3q9br.js} +1 -1
- package/dist/{cli-9s3h836g.js → cli-xvdgasgy.js} +1 -1
- package/dist/{cli-j0pdrmwg.js → cli-y0kjpfm9.js} +3 -3
- package/dist/{cli-6zcfw0x7.js → cli-y195hv1a.js} +1 -1
- package/dist/{cli-amwhs8h2.js → cli-ykt8f7va.js} +22 -9
- package/dist/{cli-1kzfvd1z.js → cli-ymmc1zxg.js} +15 -11
- package/dist/{cli-xnzrzets.js → cli-ypgba1a8.js} +2 -2
- package/dist/{cli-bdv39kgj.js → cli-z1dqhc9y.js} +31 -20
- package/dist/{cli-fw6vdcq5.js → cli-zp67qg5x.js} +34 -1
- package/dist/{cli-qbksc292.js → cli-zy39d433.js} +2 -0
- package/dist/cli.js +36 -32
- package/dist/{cloudflare-workers-ai-system-one-tt484t61.js → cloudflare-workers-ai-system-one-sta45jf9.js} +7 -5
- package/dist/{codex-adapter-3qnxshje.js → codex-adapter-cgesnjkr.js} +20 -20
- package/dist/{codex-login-brkxhhdg.js → codex-login-g1cyt5zm.js} +2 -2
- package/dist/{codex-session-runner-612wnapp.js → codex-session-runner-8kd7ddhr.js} +20 -20
- package/dist/{commands-2ybfcxkj.js → commands-h6zaaxym.js} +11 -10
- package/dist/{cursor-adapter-p3jgdehv.js → cursor-adapter-v8mm6bp5.js} +7 -7
- package/dist/{db-f4ans0jj.js → db-qg3myn53.js} +13 -10
- package/dist/{devin-adapter-dhzt01ff.js → devin-adapter-bffjj5n4.js} +2 -2
- package/dist/{dispatcher-pk182apd.js → dispatcher-7vnwj9qw.js} +28 -32
- package/dist/{dsh-adapter-7twaeps1.js → dsh-adapter-tz6hfp5r.js} +7 -7
- package/dist/{e2b-y8t3jzyz.js → e2b-nd50eccg.js} +1 -1
- package/dist/{easter-egg-3d-mtpnw06s.js → easter-egg-3d-66zxervv.js} +2 -2
- package/dist/{event-bus-w7ac4n3c.js → event-bus-vnfthafg.js} +2 -2
- package/dist/{execute-s4ccjq3n.js → execute-h4jgevyw.js} +61 -11
- package/dist/{google-generative-ai-d6nphg3q.js → google-generative-ai-b8zxrpax.js} +5 -5
- package/dist/{google-vertex-cpshkref.js → google-vertex-nrdd697t.js} +5 -5
- package/dist/grok-adapter-jdt2dz2s.js +39 -0
- package/dist/{handlers-g2wnr4t9.js → handlers-2qtkpncr.js} +31 -35
- package/dist/{hook-f7jt9vnm.js → hook-51yfcgsv.js} +48 -39
- package/dist/{hook-bbdpnkb4.js → hook-qnbdgwvd.js} +15 -17
- package/dist/{http-w40q4w94.js → http-jsmtag08.js} +536 -231
- package/dist/{index-056j5kzn.js → index-2t9kesn1.js} +13 -12
- package/dist/{index-cj1r9q83.js → index-bt8wkv1w.js} +29 -33
- package/dist/{index-7n70jwbb.js → index-cx1sb9bq.js} +42 -35
- package/dist/{index-9bfssf54.js → index-ekywyv2v.js} +28 -32
- package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
- package/dist/{index-k8xc083e.js → index-t4g06w93.js} +28 -32
- package/dist/{keepalive-1tj10tf4.js → keepalive-4aezx4bj.js} +16 -12
- package/dist/{lead-st0bg7v4.js → lead-r44bkpm5.js} +52 -56
- package/dist/{llama-cpp-classify-tgj35k8d.js → llama-cpp-classify-6erb7wwv.js} +3 -1
- package/dist/{maintenance-nz71zats.js → maintenance-grz5jrzh.js} +12 -13
- package/dist/{mistral-conversations-8445ddjn.js → mistral-conversations-059y51zt.js} +4 -4
- package/dist/{node-f0ykebbv.js → node-qm9eb6z0.js} +25 -7
- package/dist/{oauth-refresh-sweep-aefzwvvq.js → oauth-refresh-sweep-wm4ctjfj.js} +15 -11
- package/dist/{onboard-kgpr9mbn.js → onboard-v60zgqf0.js} +2 -2
- package/dist/{openai-codex-responses-9ynm88zy.js → openai-codex-responses-jzxkta9b.js} +8 -7
- package/dist/{openai-completions-nhqwjmaw.js → openai-completions-663yncfq.js} +4 -4
- package/dist/openai-decisions-vj9nkpf8.js +162 -0
- package/dist/{openai-responses-xx1c4kgm.js → openai-responses-m3g43rrt.js} +5 -5
- package/dist/{opencode-adapter-0c8e70dk.js → opencode-adapter-yjyqynzf.js} +8 -8
- package/dist/{openrouter-images-8yx3487g.js → openrouter-images-kr60n6b9.js} +1 -1
- package/dist/{otel-impl-x8zp4y5a.js → otel-impl-ghh4dyf8.js} +2 -2
- package/dist/{pi-messages-vxdt8v7j.js → pi-messages-xszk613n.js} +1 -1
- package/dist/{pi-mono-adapter-t280ah5c.js → pi-mono-adapter-0eg8kw2c.js} +69 -52
- package/dist/{pricing-refresh-9e0q7cwk.js → pricing-refresh-mjrqvnvw.js} +12 -11
- package/dist/{rbac-roles-790j3ga8.js → rbac-roles-axv4d74x.js} +11 -10
- package/dist/{rbac-roles-kcp6zge2.js → rbac-roles-fd5tskj9.js} +11 -10
- package/dist/{render-v2-tch4nefj.js → render-v2-k50j33qd.js} +11 -11
- package/dist/{runtime-v2t1r79e.js → runtime-h9ha1mq6.js} +4 -4
- package/dist/{setup-rcm303ns.js → setup-4syh0pd2.js} +2 -2
- package/dist/{typesafe-system-one-datfr82p.js → typesafe-system-one-e988vty7.js} +7 -5
- package/dist/{virtual-modules-wm8mxdxx.js → virtual-modules-ftr9kwe8.js} +11 -11
- package/dist/{work-objects-ctg7t70w.js → work-objects-jcdbg8rr.js} +32 -36
- package/dist/{worker-5dp0xm55.js → worker-x8j8c9sx.js} +52 -56
- package/dist/{x-v3yw66zs.js → x-ma4gnfgy.js} +2 -2
- package/openapi.json +5 -2
- package/package.json +15 -11
- package/src/be/automation-preflight.ts +9 -4
- package/src/be/boot-scrub-sweep.ts +302 -0
- package/src/be/crypto/key-bootstrap.ts +1 -1
- package/src/be/db/approvals.ts +15 -1
- package/src/be/db/codex-oauth-device-flows.ts +40 -0
- package/src/be/db/config-secrets.ts +31 -0
- package/src/be/db/runtime.ts +4 -0
- package/src/be/db/script-runs.ts +15 -0
- package/src/be/db/tasks/write.ts +1 -1
- package/src/be/db-queries/mcp-oauth.ts +5 -0
- package/src/be/db-queries/oauth.ts +77 -0
- package/src/be/db.ts +162 -59
- package/src/be/events.ts +17 -1
- package/src/be/memory/index-content.ts +2 -2
- package/src/be/memory/providers/openai-embedding.ts +7 -2
- package/src/be/memory/providers/sqlite-store.ts +63 -11
- package/src/be/memory/raters/llm-summarizer.ts +7 -2
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
- package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
- package/src/be/modelsdev-cache.json +61 -0
- package/src/be/scripts/db.ts +28 -4
- package/src/be/scripts/source-secrets.ts +37 -0
- package/src/be/scrub-json.ts +32 -0
- package/src/be/sealed-json.ts +59 -0
- package/src/be/secret-registry.ts +131 -0
- package/src/be/seed-pricing.ts +84 -53
- package/src/be/swarm-config-guard.ts +13 -0
- package/src/be/workflow-replay.ts +77 -0
- package/src/commands/provider-credentials.ts +28 -5
- package/src/commands/runner.ts +7 -12
- package/src/heartbeat/heartbeat.ts +91 -19
- package/src/hooks/hook.ts +72 -27
- package/src/http/codex-oauth-device.ts +8 -10
- package/src/http/db-query-bounded.ts +3 -0
- package/src/http/db-query-shared.ts +107 -15
- package/src/http/db-query.ts +10 -2
- package/src/http/index.ts +23 -6
- package/src/http/mcp.ts +5 -0
- package/src/http/onboarding.ts +2 -0
- package/src/http/script-runs.ts +9 -1
- package/src/http/scripts.ts +15 -0
- package/src/http/workflows.ts +3 -2
- package/src/oauth/mcp-wrapper.ts +1 -1
- package/src/providers/acp-adapter.ts +25 -11
- package/src/providers/acp-target-catalog.ts +35 -1
- package/src/providers/acp-targets.ts +78 -7
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +26 -0
- package/src/providers/codex-adapter.ts +30 -3
- package/src/providers/codex-oauth/auth-json.ts +21 -4
- package/src/providers/codex-oauth/types.ts +2 -0
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +10 -1
- package/src/providers/grok-adapter.ts +18 -2
- package/src/providers/opencode-adapter.ts +21 -5
- package/src/providers/pi-mono-extension.ts +56 -25
- package/src/providers/pricing-sources.md +2 -0
- package/src/script-workflows/supervisor.ts +11 -2
- package/src/scripts-runtime/eval-harness.ts +2 -12
- package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
- package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
- package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
- package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
- package/src/scripts-runtime/executors/quickjs.ts +196 -0
- package/src/scripts-runtime/executors/registry.ts +2 -0
- package/src/scripts-runtime/executors/sourcemap.ts +79 -0
- package/src/scripts-runtime/script-error-hints.ts +14 -0
- package/src/server.ts +66 -4
- package/src/tests/acp-adapter.test.ts +277 -12
- package/src/tests/boot-scrub-sweep.test.ts +390 -0
- package/src/tests/check-text-columns.test.ts +145 -0
- package/src/tests/ci-unit-test-report.test.ts +73 -0
- package/src/tests/claude-default-route.test.ts +75 -0
- package/src/tests/claude-hook-output.test.ts +233 -0
- package/src/tests/claude-sdk-process.fixture.ts +13 -2
- package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +4 -2
- package/src/tests/codex-adapter.test.ts +166 -26
- package/src/tests/codex-oauth-auth-timestamps.test.ts +76 -0
- package/src/tests/codex-oauth-device.test.ts +23 -37
- package/src/tests/codex-oauth.test.ts +4 -1
- package/src/tests/context-window.test.ts +2 -2
- package/src/tests/credential-check.test.ts +51 -1
- package/src/tests/db-query-denylist.test.ts +202 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/dsh-adapter.test.ts +23 -1
- package/src/tests/enabled-tools.test.ts +269 -0
- package/src/tests/entrypoint-acp-target-binary.test.ts +31 -1
- package/src/tests/entrypoint-codex-oauth-seed.test.ts +12 -0
- package/src/tests/events-scrub.test.ts +88 -0
- package/src/tests/fixtures/secret-corpus.ts +320 -0
- package/src/tests/grok-adapter.test.ts +13 -0
- package/src/tests/haiku-5-5-pricing.test.ts +38 -0
- package/src/tests/heartbeat-checklist.test.ts +158 -1
- package/src/tests/onboarding.test.ts +44 -0
- package/src/tests/opencode-adapter.test.ts +66 -3
- package/src/tests/opencode-plugin.test.ts +37 -0
- package/src/tests/openrouter-attribution-call-sites.test.ts +204 -0
- package/src/tests/openrouter-base-url.test.ts +30 -39
- package/src/tests/pi-mono-extension.test.ts +161 -2
- package/src/tests/pricing-refresh.test.ts +70 -1
- package/src/tests/reasoning-effort.test.ts +3 -1
- package/src/tests/script-connections.test.ts +5 -0
- package/src/tests/script-executor-conformance.test.ts +3 -0
- package/src/tests/script-executor-quickjs.test.ts +442 -0
- package/src/tests/script-executor-registry.test.ts +6 -1
- package/src/tests/script-runs-http.test.ts +88 -1
- package/src/tests/script-runs-scrub.test.ts +275 -0
- package/src/tests/scripts-http.test.ts +82 -1
- package/src/tests/scrubbed-text-12e.test.ts +289 -0
- package/src/tests/sealed-key-guard.test.ts +166 -0
- package/src/tests/secret-canary-sweep.test.ts +958 -0
- package/src/tests/secret-registry.test.ts +355 -0
- package/src/tests/secret-rules.test.ts +230 -0
- package/src/tests/secret-scrubber.test.ts +3 -2
- package/src/tests/telemetry-trigger.test.ts +12 -1
- package/src/tests/workflow-llm.test.ts +14 -0
- package/src/tests/workflow-replay.test.ts +518 -0
- package/src/tests/workflow-scrub.test.ts +242 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +33 -0
- package/src/tools/db-query.ts +1 -1
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/request-human-input.ts +7 -2
- package/src/tools/workflows/get-workflow-run.ts +3 -2
- package/src/types.ts +1 -1
- package/src/utils/enabled-tools.ts +27 -0
- package/src/utils/internal-ai/complete-structured.ts +11 -3
- package/src/utils/openrouter-base-url.ts +23 -0
- package/src/utils/secret-rules.generated.ts +2055 -0
- package/src/utils/secret-scrubber.ts +363 -29
- package/src/workflows/engine.ts +39 -4
- package/src/workflows/executors/raw-llm.ts +1 -0
- package/src/workflows/executors/system-one-decision.ts +2 -0
- package/src/workflows/executors/validate.ts +1 -0
- package/src/workflows/executors/workflow-llm.ts +9 -2
- package/src/workflows/recovery.ts +10 -1
- package/src/workflows/resume.ts +62 -3
- package/src/workflows/retry-poller.ts +3 -0
- package/templates/skills/agent-browser/SKILL.md +2 -0
- package/templates/skills/agent-browser/content.md +2 -0
- package/templates/skills/code-quality/content.md +1 -0
- package/templates/skills/heartbeat-runbook/content.md +1 -1
- package/templates/skills/work-on-task/SKILL.md +4 -0
- package/templates/skills/work-on-task/content.md +4 -0
- package/templates/skills/workflow-iterate/content.md +9 -0
- package/dist/acp-adapter-w6zws6me.js +0 -23
- package/dist/boot-scrub-logs-246yn076.js +0 -99
- package/dist/cli-3a4m1aws.js +0 -572
- package/dist/cli-3dpj7atn.js +0 -11
- package/dist/cli-a87b04k8.js +0 -951
- package/dist/cli-cvk6yzw5.js +0 -40
- package/dist/cli-gvcgmx69.js +0 -183
- package/dist/cli-h1spv8v4.js +0 -427
- package/dist/cli-jqt9hrnz.js +0 -168
- package/dist/cli-yhmwmv0g.js +0 -9281
- package/dist/grok-adapter-yt9pab3k.js +0 -29
- package/src/be/boot-scrub-logs.ts +0 -132
- package/dist/{codex-hook-d2rkvf39.js → codex-hook-15dgc4cb.js} +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const ACP_TARGET_IDS = ["opencode", "gemini", "custom"] as const;
|
|
1
|
+
export const ACP_TARGET_IDS = ["opencode", "gemini", "copilot", "custom"] as const;
|
|
2
2
|
|
|
3
3
|
export type AcpTarget = (typeof ACP_TARGET_IDS)[number];
|
|
4
4
|
|
|
@@ -61,6 +61,40 @@ export const ACP_TARGET_CATALOG: readonly AcpTargetCatalogEntry[] = [
|
|
|
61
61
|
],
|
|
62
62
|
knobs: [{ id: "model", label: "Model", category: "model" }],
|
|
63
63
|
},
|
|
64
|
+
{
|
|
65
|
+
id: "copilot",
|
|
66
|
+
label: "GitHub Copilot CLI",
|
|
67
|
+
description:
|
|
68
|
+
"GitHub Copilot CLI's ACP server, launched with `copilot --acp`. Uses a Copilot-entitled COPILOT_GITHUB_TOKEN, or a BYOK provider via COPILOT_PROVIDER_*.",
|
|
69
|
+
command: "copilot",
|
|
70
|
+
args: ["--acp"],
|
|
71
|
+
envKeys: [
|
|
72
|
+
"COPILOT_GITHUB_TOKEN",
|
|
73
|
+
"COPILOT_GH_HOST",
|
|
74
|
+
"GH_HOST",
|
|
75
|
+
"COPILOT_HOME",
|
|
76
|
+
"COPILOT_MODEL",
|
|
77
|
+
"COPILOT_AUTO_TIER",
|
|
78
|
+
"COPILOT_OFFLINE",
|
|
79
|
+
"COPILOT_PROVIDER_BASE_URL",
|
|
80
|
+
"COPILOT_PROVIDER_TYPE",
|
|
81
|
+
"COPILOT_PROVIDER_API_KEY",
|
|
82
|
+
"COPILOT_PROVIDER_API_KEY_COMMAND",
|
|
83
|
+
"COPILOT_PROVIDER_BEARER_TOKEN",
|
|
84
|
+
"COPILOT_PROVIDER_WIRE_API",
|
|
85
|
+
"COPILOT_PROVIDER_TRANSPORT",
|
|
86
|
+
"COPILOT_PROVIDER_AZURE_API_VERSION",
|
|
87
|
+
"COPILOT_PROVIDER_MODEL_ID",
|
|
88
|
+
"COPILOT_PROVIDER_WIRE_MODEL",
|
|
89
|
+
"COPILOT_PROVIDER_MAX_PROMPT_TOKENS",
|
|
90
|
+
"COPILOT_PROVIDER_MAX_OUTPUT_TOKENS",
|
|
91
|
+
"COPILOT_PROVIDER_HEADERS",
|
|
92
|
+
"HTTP_PROXY",
|
|
93
|
+
"HTTPS_PROXY",
|
|
94
|
+
"NO_PROXY",
|
|
95
|
+
],
|
|
96
|
+
knobs: [{ id: "model", label: "Model", category: "model" }],
|
|
97
|
+
},
|
|
64
98
|
{
|
|
65
99
|
id: "custom",
|
|
66
100
|
label: "Custom",
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { mkdtemp } from "node:fs/promises";
|
|
1
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { normalizeGrokModel } from "@desplega/model-catalog";
|
|
5
|
+
import type { Config } from "@opencode-ai/sdk";
|
|
5
6
|
import { clampContextPercent, getContextWindowSize } from "../utils/context-window";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
ACP_TARGET_IDS,
|
|
9
|
+
type AcpTarget,
|
|
10
|
+
getAcpTargetCatalogEntry,
|
|
11
|
+
isAcpTarget,
|
|
12
|
+
} from "./acp-target-catalog";
|
|
13
|
+
import { applyOpenRouterBaseUrlOverride } from "./opencode-adapter";
|
|
7
14
|
import { applyReasoningEffort } from "./reasoning-effort";
|
|
8
15
|
import type { CostData, CostModelUsage, ProviderEvent, ProviderSessionConfig } from "./types";
|
|
9
16
|
|
|
@@ -14,6 +21,8 @@ export interface AcpTargetProfile {
|
|
|
14
21
|
env(config: ProviderSessionConfig): Record<string, string>;
|
|
15
22
|
configuredOptions(config: ProviderSessionConfig): Record<string, string | boolean>;
|
|
16
23
|
writeSystemPromptArtifact(config: ProviderSessionConfig): Promise<void>;
|
|
24
|
+
/** Removes what writeSystemPromptArtifact wrote. Called once the session ends or fails to start. */
|
|
25
|
+
cleanupSystemPromptArtifact?(config: ProviderSessionConfig): Promise<void>;
|
|
17
26
|
/** Target-specific `session/new` `_meta`. Targets without one send none. */
|
|
18
27
|
sessionMeta?(config: ProviderSessionConfig): Record<string, unknown> | undefined;
|
|
19
28
|
/** A clearer message for a target error whose own text misleads, else undefined. */
|
|
@@ -117,8 +126,8 @@ function configuredOptions(config: ProviderSessionConfig): Record<string, string
|
|
|
117
126
|
return options;
|
|
118
127
|
}
|
|
119
128
|
|
|
120
|
-
function
|
|
121
|
-
|
|
129
|
+
function withOpencodeConfig(env: Record<string, string>, model: string): void {
|
|
130
|
+
const key = "OPENCODE_CONFIG_CONTENT";
|
|
122
131
|
let value: Record<string, unknown> = {};
|
|
123
132
|
const existing = env[key];
|
|
124
133
|
if (existing) {
|
|
@@ -131,7 +140,9 @@ function withModelInJsonEnv(env: Record<string, string>, key: string, model: str
|
|
|
131
140
|
console.warn(`\x1b[33m[acp]\x1b[0m Replacing invalid ${key} JSON for model fallback`);
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
|
-
|
|
143
|
+
if (model.trim()) value.model = model.trim();
|
|
144
|
+
applyOpenRouterBaseUrlOverride(value as Config, env);
|
|
145
|
+
env[key] = JSON.stringify(value);
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
function parseCommand(command: string, args: string | undefined): string[] {
|
|
@@ -196,7 +207,8 @@ const opencodeTargetProfile: AcpTargetProfile = {
|
|
|
196
207
|
const env = baseTargetEnv(config);
|
|
197
208
|
const entry = getAcpTargetCatalogEntry("opencode");
|
|
198
209
|
copyEnvKeys(config, env, entry.envKeys);
|
|
199
|
-
|
|
210
|
+
copyEnvKeys(config, env, ["OPENROUTER_APP_ATTRIBUTION"]);
|
|
211
|
+
withOpencodeConfig(env, config.model);
|
|
200
212
|
return env;
|
|
201
213
|
},
|
|
202
214
|
configuredOptions,
|
|
@@ -238,6 +250,63 @@ const geminiTargetProfile: AcpTargetProfile = {
|
|
|
238
250
|
},
|
|
239
251
|
};
|
|
240
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Copilot CLI has no system-prompt flag. Under `--acp` it reads custom
|
|
255
|
+
* instructions from the git root, the cwd, and every directory listed in
|
|
256
|
+
* COPILOT_CUSTOM_INSTRUCTIONS_DIRS. Verified against 1.0.93: from those extra
|
|
257
|
+
* directories it loads `.github/instructions/*.instructions.md`, but not
|
|
258
|
+
* `AGENTS.md`. So the swarm prompt goes into a per-task directory outside the
|
|
259
|
+
* repo, and the repo's own AGENTS.md is never touched.
|
|
260
|
+
*/
|
|
261
|
+
export function copilotInstructionsDir(config: ProviderSessionConfig): string {
|
|
262
|
+
const key = new Bun.CryptoHasher("sha256")
|
|
263
|
+
.update(`${config.agentId}:${config.taskId}:${config.cwd}`)
|
|
264
|
+
.digest("hex")
|
|
265
|
+
.slice(0, 16);
|
|
266
|
+
return join(tmpdir(), "agent-swarm-copilot-instructions", key);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export const COPILOT_INSTRUCTIONS_FILE = join(
|
|
270
|
+
".github",
|
|
271
|
+
"instructions",
|
|
272
|
+
"agent-swarm.instructions.md",
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const copilotTargetProfile: AcpTargetProfile = {
|
|
276
|
+
target: "copilot",
|
|
277
|
+
command() {
|
|
278
|
+
const entry = getAcpTargetCatalogEntry("copilot");
|
|
279
|
+
return [entry.command!, ...(entry.args ?? [])];
|
|
280
|
+
},
|
|
281
|
+
env(config) {
|
|
282
|
+
const env = baseTargetEnv(config);
|
|
283
|
+
const entry = getAcpTargetCatalogEntry("copilot");
|
|
284
|
+
copyEnvKeys(config, env, entry.envKeys);
|
|
285
|
+
if (config.model.trim()) env.COPILOT_MODEL = config.model.trim();
|
|
286
|
+
// The image pins the CLI; a self-update would run an unpinned binary.
|
|
287
|
+
env.COPILOT_AUTO_UPDATE = "false";
|
|
288
|
+
const operatorDirs = readEnv(config, "COPILOT_CUSTOM_INSTRUCTIONS_DIRS")?.trim();
|
|
289
|
+
env.COPILOT_CUSTOM_INSTRUCTIONS_DIRS = [copilotInstructionsDir(config), operatorDirs]
|
|
290
|
+
.filter(Boolean)
|
|
291
|
+
.join(",");
|
|
292
|
+
return env;
|
|
293
|
+
},
|
|
294
|
+
configuredOptions,
|
|
295
|
+
async writeSystemPromptArtifact(config) {
|
|
296
|
+
const dir = copilotInstructionsDir(config);
|
|
297
|
+
await rm(dir, { recursive: true, force: true });
|
|
298
|
+
if (!config.systemPrompt) return;
|
|
299
|
+
await mkdir(join(dir, ".github", "instructions"), { recursive: true });
|
|
300
|
+
await Bun.write(
|
|
301
|
+
join(dir, COPILOT_INSTRUCTIONS_FILE),
|
|
302
|
+
`---\napplyTo: "**"\n---\n\n${config.systemPrompt}\n`,
|
|
303
|
+
);
|
|
304
|
+
},
|
|
305
|
+
async cleanupSystemPromptArtifact(config) {
|
|
306
|
+
await rm(copilotInstructionsDir(config), { recursive: true, force: true });
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
|
|
241
310
|
/**
|
|
242
311
|
* Grok CLI env keys passed through to `grok agent stdio`. `GROK_HOME` is the
|
|
243
312
|
* per-session state dir the grok adapter creates; the rest switch off the
|
|
@@ -471,7 +540,7 @@ export function resolveAcpTarget(config: ProviderSessionConfig): AcpTargetProfil
|
|
|
471
540
|
const target = readEnv(config, "ACP_TARGET") ?? "custom";
|
|
472
541
|
if (!isAcpTarget(target)) {
|
|
473
542
|
throw new AcpTargetResolutionError(
|
|
474
|
-
`Unsupported ACP target "${target}". Supported targets:
|
|
543
|
+
`Unsupported ACP target "${target}". Supported targets: ${ACP_TARGET_IDS.join(", ")}.`,
|
|
475
544
|
);
|
|
476
545
|
}
|
|
477
546
|
switch (target) {
|
|
@@ -479,6 +548,8 @@ export function resolveAcpTarget(config: ProviderSessionConfig): AcpTargetProfil
|
|
|
479
548
|
return opencodeTargetProfile;
|
|
480
549
|
case "gemini":
|
|
481
550
|
return geminiTargetProfile;
|
|
551
|
+
case "copilot":
|
|
552
|
+
return copilotTargetProfile;
|
|
482
553
|
case "custom":
|
|
483
554
|
return customTargetProfile;
|
|
484
555
|
}
|
|
@@ -44,7 +44,7 @@ import type {
|
|
|
44
44
|
* default; the image installs exactly this version and the per-task settings
|
|
45
45
|
* set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
|
|
46
46
|
*/
|
|
47
|
-
export const AMP_PACKAGE = "@ampcode/cli@0.0.
|
|
47
|
+
export const AMP_PACKAGE = "@ampcode/cli@0.0.1791460855-g1f688c";
|
|
48
48
|
|
|
49
49
|
/** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
|
|
50
50
|
const AMP_MCP_SERVER_NAME = "agent-swarm";
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import { withFileLock } from "../utils/file-lock";
|
|
35
35
|
import { fetchInstalledMcpServers } from "../utils/mcp-server-fetcher";
|
|
36
36
|
import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
|
|
37
|
+
import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
|
|
37
38
|
import {
|
|
38
39
|
detachedProcessGroup,
|
|
39
40
|
registerProcessGroup,
|
|
@@ -738,6 +739,30 @@ export function buildClaudeCodeRuntimeEnv(
|
|
|
738
739
|
};
|
|
739
740
|
}
|
|
740
741
|
|
|
742
|
+
/**
|
|
743
|
+
* `ANTHROPIC_CUSTOM_HEADERS` ("Name: value" per line) with OpenRouter app
|
|
744
|
+
* attribution appended, when `ANTHROPIC_BASE_URL` points at openrouter.ai.
|
|
745
|
+
* The operator's lines are kept in order (they may carry gateway
|
|
746
|
+
* credentials); only lines naming one of our headers are replaced. Returns
|
|
747
|
+
* `{}` when nothing changes, so the inherited value stays byte-identical.
|
|
748
|
+
*
|
|
749
|
+
* Exported for unit testing.
|
|
750
|
+
*/
|
|
751
|
+
export function claudeOpenRouterAttributionEnv(env: Record<string, string | undefined>): {
|
|
752
|
+
ANTHROPIC_CUSTOM_HEADERS?: string;
|
|
753
|
+
} {
|
|
754
|
+
const headers = getOpenRouterAttributionHeaders(env.ANTHROPIC_BASE_URL ?? "", env);
|
|
755
|
+
const names = new Set(Object.keys(headers).map((name) => name.toLowerCase()));
|
|
756
|
+
if (names.size === 0) return {};
|
|
757
|
+
const kept = (env.ANTHROPIC_CUSTOM_HEADERS ?? "").split("\n").filter((line) => {
|
|
758
|
+
const colon = line.indexOf(":");
|
|
759
|
+
const name = colon === -1 ? "" : line.slice(0, colon).trim().toLowerCase();
|
|
760
|
+
return line.trim() !== "" && !names.has(name);
|
|
761
|
+
});
|
|
762
|
+
const ours = Object.entries(headers).map(([name, value]) => `${name}: ${value}`);
|
|
763
|
+
return { ANTHROPIC_CUSTOM_HEADERS: [...kept, ...ours].join("\n") };
|
|
764
|
+
}
|
|
765
|
+
|
|
741
766
|
export function buildClaudeSessionEnvironment(
|
|
742
767
|
config: ProviderSessionConfig,
|
|
743
768
|
model: string,
|
|
@@ -751,6 +776,7 @@ export function buildClaudeSessionEnvironment(
|
|
|
751
776
|
env: {
|
|
752
777
|
ENABLE_PROMPT_CACHING_1H: "1",
|
|
753
778
|
...sourceEnv,
|
|
779
|
+
...claudeOpenRouterAttributionEnv(sourceEnv),
|
|
754
780
|
...buildClaudeCodeRuntimeEnv(sourceEnv),
|
|
755
781
|
...buildClaudeCodeOtelEnv(sourceEnv),
|
|
756
782
|
...(reasoningApplication.kind === "claude-env" ? reasoningApplication.env : {}),
|
|
@@ -147,6 +147,8 @@ export type ThreadEvent =
|
|
|
147
147
|
usage?: Usage;
|
|
148
148
|
}
|
|
149
149
|
| { type: "turn.interrupted"; usage?: Usage }
|
|
150
|
+
/** Latest single-request usage: a context-window snapshot, not billable usage. */
|
|
151
|
+
| { type: "context.updated"; usage: Usage; contextWindow?: number }
|
|
150
152
|
| { type: "error"; message: string; codexErrorInfo?: string | null; willRetry?: boolean };
|
|
151
153
|
|
|
152
154
|
interface CodexThread {
|
|
@@ -833,8 +835,25 @@ class AppServerCodexThread implements CodexThread {
|
|
|
833
835
|
}
|
|
834
836
|
break;
|
|
835
837
|
case "thread/tokenUsage/updated": {
|
|
836
|
-
const tokenUsage = params.tokenUsage as
|
|
838
|
+
const tokenUsage = params.tokenUsage as
|
|
839
|
+
| { total?: unknown; last?: unknown; modelContextWindow?: unknown }
|
|
840
|
+
| undefined;
|
|
841
|
+
// `total` is cumulative for the thread and drives cost accounting at
|
|
842
|
+
// turn end. `last` is the most recent model request, which is the
|
|
843
|
+
// only figure that reflects what currently sits in the context window.
|
|
837
844
|
this.latestTotalUsage = normalizeUsage(tokenUsage?.total);
|
|
845
|
+
const last = normalizeUsage(tokenUsage?.last);
|
|
846
|
+
if (last) {
|
|
847
|
+
push({
|
|
848
|
+
type: "context.updated",
|
|
849
|
+
usage: last,
|
|
850
|
+
contextWindow:
|
|
851
|
+
typeof tokenUsage?.modelContextWindow === "number" &&
|
|
852
|
+
tokenUsage.modelContextWindow > 0
|
|
853
|
+
? tokenUsage.modelContextWindow
|
|
854
|
+
: undefined,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
838
857
|
break;
|
|
839
858
|
}
|
|
840
859
|
case "error": {
|
|
@@ -1394,6 +1413,9 @@ export class CodexSession implements ProviderSession {
|
|
|
1394
1413
|
case "turn.interrupted":
|
|
1395
1414
|
this.accountUsage(event.usage);
|
|
1396
1415
|
break;
|
|
1416
|
+
case "context.updated":
|
|
1417
|
+
this.emitContextUsage(event.usage, event.contextWindow ?? this.contextWindow);
|
|
1418
|
+
break;
|
|
1397
1419
|
case "error": {
|
|
1398
1420
|
const { message } = this.formatTerminalError(event.message, event.codexErrorInfo);
|
|
1399
1421
|
if (event.willRetry === true) {
|
|
@@ -1420,7 +1442,12 @@ export class CodexSession implements ProviderSession {
|
|
|
1420
1442
|
this.accumulatedUsage.reasoning_output_tokens + usage.reasoning_output_tokens,
|
|
1421
1443
|
}
|
|
1422
1444
|
: usage;
|
|
1445
|
+
// No context_usage here: a turn's usage sums every model request in the
|
|
1446
|
+
// turn, so it measures consumption, not window occupancy. Context comes
|
|
1447
|
+
// from the per-request `context.updated` snapshots instead.
|
|
1448
|
+
}
|
|
1423
1449
|
|
|
1450
|
+
private emitContextUsage(usage: Usage, contextWindow: number): void {
|
|
1424
1451
|
// Codex input tokens already include the cached read and write subsets.
|
|
1425
1452
|
// Passing either subset again would inflate context usage.
|
|
1426
1453
|
const contextUsed = computeContextUsedUnified({
|
|
@@ -1432,8 +1459,8 @@ export class CodexSession implements ProviderSession {
|
|
|
1432
1459
|
this.emit({
|
|
1433
1460
|
type: "context_usage",
|
|
1434
1461
|
contextUsedTokens: contextUsed,
|
|
1435
|
-
contextTotalTokens:
|
|
1436
|
-
contextPercent: clampContextPercent(contextUsed,
|
|
1462
|
+
contextTotalTokens: contextWindow,
|
|
1463
|
+
contextPercent: clampContextPercent(contextUsed, contextWindow) ?? 0,
|
|
1437
1464
|
outputTokens: usage.output_tokens,
|
|
1438
1465
|
contextFormula: CONTEXT_FORMULA,
|
|
1439
1466
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Conversion utilities between CodexOAuthCredentials (our internal type)
|
|
3
3
|
* and ~/.codex/auth.json (the format the Codex CLI reads natively).
|
|
4
4
|
*
|
|
5
|
-
* The Codex CLI expects auth.json
|
|
5
|
+
* The Codex CLI expects these native auth.json fields:
|
|
6
6
|
* {
|
|
7
7
|
* "auth_mode": "chatgpt",
|
|
8
8
|
* "OPENAI_API_KEY": null,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* },
|
|
15
15
|
* "last_refresh": "<ISO 8601>"
|
|
16
16
|
* }
|
|
17
|
+
* Swarm also writes optional `expires` metadata for lossless conversion.
|
|
17
18
|
*
|
|
18
19
|
* Note: `id_token` is set to the `access_token` value because the token
|
|
19
20
|
* exchange endpoint doesn't return a separate `id_token`. This matches
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
*/
|
|
24
25
|
|
|
25
26
|
import { codexPlanFromClaim } from "../../utils/subscription-plans.js";
|
|
26
|
-
import { extractChatgptPlanType, extractChatgptUserId } from "./flow.js";
|
|
27
|
+
import { decodeJwt, extractChatgptPlanType, extractChatgptUserId } from "./flow.js";
|
|
27
28
|
import type { CodexAuthJson, CodexOAuthCredentials } from "./types.js";
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -96,15 +97,31 @@ export function credentialsToAuthJson(
|
|
|
96
97
|
refresh_token: includeRefreshToken ? creds.refresh : "",
|
|
97
98
|
account_id: creds.accountId,
|
|
98
99
|
},
|
|
99
|
-
last_refresh: new Date(
|
|
100
|
+
last_refresh: new Date().toISOString(),
|
|
101
|
+
// Swarm-only metadata keeps opaque-token round trips lossless. Codex
|
|
102
|
+
// ignores this field and may drop it when it rewrites auth.json.
|
|
103
|
+
expires: creds.expires,
|
|
100
104
|
};
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
export function authJsonToCredentials(auth: CodexAuthJson): CodexOAuthCredentials {
|
|
108
|
+
// A CLI refresh may replace the access token and discard our metadata.
|
|
109
|
+
// Prefer its JWT expiry, which describes the token actually in the file.
|
|
110
|
+
const exp = decodeJwt(auth.tokens.access_token)?.exp;
|
|
111
|
+
const jwtExpires = typeof exp === "number" ? exp * 1000 : NaN;
|
|
112
|
+
const expires =
|
|
113
|
+
Number.isFinite(jwtExpires) && jwtExpires >= 0
|
|
114
|
+
? jwtExpires
|
|
115
|
+
: typeof auth.expires === "number" && Number.isFinite(auth.expires)
|
|
116
|
+
? auth.expires
|
|
117
|
+
: new Date(auth.last_refresh).getTime();
|
|
118
|
+
|
|
104
119
|
return {
|
|
105
120
|
access: auth.tokens.access_token,
|
|
106
121
|
refresh: auth.tokens.refresh_token,
|
|
107
|
-
|
|
122
|
+
// Legacy swarm files put expiry in last_refresh; keep that fallback
|
|
123
|
+
// only when neither the token nor the explicit metadata carries it.
|
|
124
|
+
expires,
|
|
108
125
|
accountId: auth.tokens.account_id,
|
|
109
126
|
};
|
|
110
127
|
}
|
|
@@ -35,7 +35,7 @@ import type {
|
|
|
35
35
|
} from "./types";
|
|
36
36
|
|
|
37
37
|
/** Pinned in package.json and Dockerfile.worker; bump all three together. */
|
|
38
|
-
export const CURSOR_SDK_VERSION = "1.0.
|
|
38
|
+
export const CURSOR_SDK_VERSION = "1.0.37";
|
|
39
39
|
|
|
40
40
|
/** MCP server name; Cursor reports its tools as `mcp` calls with this `providerIdentifier`. */
|
|
41
41
|
const CURSOR_MCP_SERVER_NAME = "agent-swarm";
|
|
@@ -9,7 +9,10 @@ import {
|
|
|
9
9
|
} from "../utils/context-window";
|
|
10
10
|
import { addDshStepUsage, type DshStepUsage, normalizeDshStepUsage } from "../utils/dsh-usage";
|
|
11
11
|
import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getOpenRouterAttributionHeaders,
|
|
14
|
+
getOpenRouterBaseUrl,
|
|
15
|
+
} from "../utils/openrouter-base-url";
|
|
13
16
|
import {
|
|
14
17
|
detachedProcessGroup,
|
|
15
18
|
registerProcessGroup,
|
|
@@ -342,6 +345,9 @@ export class DshAdapter implements ProviderAdapter {
|
|
|
342
345
|
const directory = await mkdtemp(join(tmpdir(), "swarm-dsh-"));
|
|
343
346
|
try {
|
|
344
347
|
const patchPath = join(directory, "patch.json");
|
|
348
|
+
const openRouterHeaders = openrouter
|
|
349
|
+
? getOpenRouterAttributionHeaders(getOpenRouterBaseUrl(env), env)
|
|
350
|
+
: {};
|
|
345
351
|
const runtimeInstanceId = swarmRuntimeInstanceId();
|
|
346
352
|
// JSON is valid YAML. Values stay data, including arbitrary system prompts.
|
|
347
353
|
await writeFile(
|
|
@@ -364,6 +370,9 @@ export class DshAdapter implements ProviderAdapter {
|
|
|
364
370
|
openrouter: {
|
|
365
371
|
apiKeyEnv: keyEnv,
|
|
366
372
|
baseURL: getOpenRouterBaseUrl(env),
|
|
373
|
+
...(Object.keys(openRouterHeaders).length > 0
|
|
374
|
+
? { headers: openRouterHeaders }
|
|
375
|
+
: {}),
|
|
367
376
|
api: "openai-completions",
|
|
368
377
|
// Declare the selected ID even if the bundled catalog predates it.
|
|
369
378
|
models: [
|
|
@@ -3,7 +3,10 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { normalizeGrokModel } from "@desplega/model-catalog";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getOpenRouterAttributionHeaders,
|
|
8
|
+
getOpenRouterBaseUrl,
|
|
9
|
+
} from "../utils/openrouter-base-url";
|
|
7
10
|
import { registerVolatileSecret } from "../utils/secret-scrubber";
|
|
8
11
|
import { ACPAdapter } from "./acp-adapter";
|
|
9
12
|
import { grokTargetProfile, isGrokOpenRouterModel } from "./acp-targets";
|
|
@@ -64,6 +67,8 @@ export interface GrokCustomModel {
|
|
|
64
67
|
baseUrl: string;
|
|
65
68
|
/** Env var holding the endpoint's API key. */
|
|
66
69
|
envKey: string;
|
|
70
|
+
/** Sent verbatim on every request to the endpoint (`extra_headers`). */
|
|
71
|
+
headers?: Record<string, string>;
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
/** `openrouter/<vendor>/<id>` -> a `[model."openrouter/<vendor>/<id>"]` block on OpenRouter. */
|
|
@@ -73,14 +78,24 @@ export function grokOpenRouterModel(
|
|
|
73
78
|
): GrokCustomModel | null {
|
|
74
79
|
const id = model.trim();
|
|
75
80
|
if (!isGrokOpenRouterModel(id)) return null;
|
|
81
|
+
const baseUrl = getOpenRouterBaseUrl(env);
|
|
76
82
|
return {
|
|
77
83
|
id,
|
|
78
84
|
model: id.slice("openrouter/".length),
|
|
79
|
-
baseUrl
|
|
85
|
+
baseUrl,
|
|
80
86
|
envKey: "OPENROUTER_API_KEY",
|
|
87
|
+
headers: getOpenRouterAttributionHeaders(baseUrl, env),
|
|
81
88
|
};
|
|
82
89
|
}
|
|
83
90
|
|
|
91
|
+
/** `key = { "k" = "v", ... }`, or nothing for an empty map. JSON strings are valid TOML basic strings. */
|
|
92
|
+
function tomlInlineTable(key: string, entries: Record<string, string> | undefined): string[] {
|
|
93
|
+
const pairs = Object.entries(entries ?? {});
|
|
94
|
+
if (pairs.length === 0) return [];
|
|
95
|
+
const body = pairs.map(([k, v]) => `${JSON.stringify(k)} = ${JSON.stringify(v)}`).join(", ");
|
|
96
|
+
return [`${key} = { ${body} }`];
|
|
97
|
+
}
|
|
98
|
+
|
|
84
99
|
/** `$GROK_HOME/config.toml` for one swarm session. */
|
|
85
100
|
export function buildGrokConfigToml(
|
|
86
101
|
disabledPlugins: readonly string[],
|
|
@@ -95,6 +110,7 @@ export function buildGrokConfigToml(
|
|
|
95
110
|
`base_url = ${JSON.stringify(customModel.baseUrl)}`,
|
|
96
111
|
`env_key = ${JSON.stringify(customModel.envKey)}`,
|
|
97
112
|
`api_backend = "chat_completions"`,
|
|
113
|
+
...tomlInlineTable("extra_headers", customModel.headers),
|
|
98
114
|
]
|
|
99
115
|
: [];
|
|
100
116
|
return [
|
|
@@ -20,7 +20,11 @@ import {
|
|
|
20
20
|
import { validateOpencodeCredentials } from "../utils/credentials";
|
|
21
21
|
import { fetchInstalledMcpServers } from "../utils/mcp-server-fetcher";
|
|
22
22
|
import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
DEFAULT_OPENROUTER_BASE_URL,
|
|
25
|
+
getOpenRouterAttributionHeaders,
|
|
26
|
+
getOpenRouterBaseUrl,
|
|
27
|
+
} from "../utils/openrouter-base-url";
|
|
24
28
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
25
29
|
import { resolveSlashSkillPrompt } from "./codex-skill-resolver";
|
|
26
30
|
import { CTX_MODE_NUDGE_EVERY } from "./ctx-mode-env";
|
|
@@ -177,22 +181,34 @@ function formatUnknownError(err: unknown): string {
|
|
|
177
181
|
* Route OpenRouter traffic through the configured gateway (see
|
|
178
182
|
* src/utils/openrouter-base-url.ts). opencode's bundled openrouter provider
|
|
179
183
|
* honors `provider.openrouter.options.baseURL`. Mutates `opencodeConfig` in
|
|
180
|
-
* place
|
|
181
|
-
* openrouter.ai behavior is preserved. Exported for tests.
|
|
184
|
+
* place, and attribute direct OpenRouter requests to Agent Swarm.
|
|
182
185
|
*/
|
|
183
186
|
export function applyOpenRouterBaseUrlOverride(
|
|
184
187
|
opencodeConfig: Config,
|
|
185
188
|
env: Record<string, string | undefined> = process.env,
|
|
186
189
|
): void {
|
|
187
190
|
const openRouterBaseUrl = getOpenRouterBaseUrl(env as NodeJS.ProcessEnv);
|
|
188
|
-
|
|
191
|
+
const headers = getOpenRouterAttributionHeaders(openRouterBaseUrl, env);
|
|
192
|
+
const overrideBaseUrl = openRouterBaseUrl !== DEFAULT_OPENROUTER_BASE_URL;
|
|
193
|
+
if (!overrideBaseUrl && Object.keys(headers).length === 0) return;
|
|
189
194
|
opencodeConfig.provider = {
|
|
190
195
|
...opencodeConfig.provider,
|
|
191
196
|
openrouter: {
|
|
192
197
|
...opencodeConfig.provider?.openrouter,
|
|
193
198
|
options: {
|
|
194
199
|
...opencodeConfig.provider?.openrouter?.options,
|
|
195
|
-
baseURL: openRouterBaseUrl,
|
|
200
|
+
...(overrideBaseUrl ? { baseURL: openRouterBaseUrl } : {}),
|
|
201
|
+
...(Object.keys(headers).length > 0
|
|
202
|
+
? {
|
|
203
|
+
headers: {
|
|
204
|
+
...(opencodeConfig.provider?.openrouter?.options?.headers as
|
|
205
|
+
| Record<string, string>
|
|
206
|
+
| undefined),
|
|
207
|
+
...headers,
|
|
208
|
+
"X-Title": "Agent Swarm",
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
: {}),
|
|
196
212
|
},
|
|
197
213
|
},
|
|
198
214
|
};
|
|
@@ -11,6 +11,7 @@ import { buildRatingsFromLlm, fetchRetrievalsForTask, postRatings } from "../be/
|
|
|
11
11
|
import { checkToolLoop, clearToolHistory } from "../hooks/tool-loop-detection";
|
|
12
12
|
import { summarizeSession as runSummarize } from "../utils/internal-ai";
|
|
13
13
|
import { getMemoryRaterNames } from "../utils/memory-raters";
|
|
14
|
+
import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
|
|
14
15
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
15
16
|
|
|
16
17
|
export interface SwarmHooksConfig {
|
|
@@ -421,6 +422,24 @@ export async function summarizeSessionForPi(
|
|
|
421
422
|
}
|
|
422
423
|
}
|
|
423
424
|
|
|
425
|
+
/** `customType` of the advisory messages this extension sends to the model. */
|
|
426
|
+
export const SWARM_NUDGE_CUSTOM_TYPE = "swarm-nudge";
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Deliver an advisory nudge to the model. pi runs extensions in-process and
|
|
430
|
+
* never feeds their stdout to the model, so a console.log here only reaches
|
|
431
|
+
* the worker log. `sendMessage` steers the text in after the current tool
|
|
432
|
+
* batch while the agent is streaming, and otherwise appends it to the session
|
|
433
|
+
* so the next prompt carries it.
|
|
434
|
+
*/
|
|
435
|
+
function sendNudge(pi: Parameters<ExtensionFactory>[0], text: string): void {
|
|
436
|
+
pi.sendMessage({
|
|
437
|
+
customType: SWARM_NUDGE_CUSTOM_TYPE,
|
|
438
|
+
content: [{ type: "text", text }],
|
|
439
|
+
display: false,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
424
443
|
/**
|
|
425
444
|
* Create the swarm hooks extension factory for pi-mono.
|
|
426
445
|
*
|
|
@@ -431,6 +450,13 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
431
450
|
return (pi) => {
|
|
432
451
|
let lastContextPostTime = 0;
|
|
433
452
|
|
|
453
|
+
pi.on("before_provider_headers", (event, ctx) => {
|
|
454
|
+
Object.assign(
|
|
455
|
+
event.headers,
|
|
456
|
+
getOpenRouterAttributionHeaders(ctx.model?.baseUrl ?? "", config.env),
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
434
460
|
// === session_start → SessionStart ===
|
|
435
461
|
pi.on("session_start", async (_event, _ctx) => {
|
|
436
462
|
// Installed MCP servers: pi's MCP extension connects them in the
|
|
@@ -458,7 +484,7 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
458
484
|
if (config.isLead) {
|
|
459
485
|
const ctx = await fetchConcurrentContext(config);
|
|
460
486
|
if (ctx) {
|
|
461
|
-
|
|
487
|
+
sendNudge(pi, ctx);
|
|
462
488
|
}
|
|
463
489
|
}
|
|
464
490
|
});
|
|
@@ -498,7 +524,7 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
498
524
|
}
|
|
499
525
|
|
|
500
526
|
if (loopResult.severity === "warning" && loopResult.reason) {
|
|
501
|
-
|
|
527
|
+
sendNudge(pi, `Warning: ${loopResult.reason}`);
|
|
502
528
|
}
|
|
503
529
|
}
|
|
504
530
|
|
|
@@ -586,7 +612,8 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
586
612
|
|
|
587
613
|
// Reminders
|
|
588
614
|
if (config.isLead && event.toolName?.endsWith("send-task")) {
|
|
589
|
-
|
|
615
|
+
sendNudge(
|
|
616
|
+
pi,
|
|
590
617
|
"Task sent successfully. Monitor progress using the get-task-details tool periodically.",
|
|
591
618
|
);
|
|
592
619
|
}
|
|
@@ -594,29 +621,30 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
594
621
|
return undefined;
|
|
595
622
|
});
|
|
596
623
|
|
|
597
|
-
// ===
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
pi.on("
|
|
601
|
-
if (!config.taskId) return
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
reminder.push(`Current Progress: ${taskDetails.progress}`);
|
|
613
|
-
}
|
|
614
|
-
reminder.push("=== Continue working on this task after compaction ===");
|
|
615
|
-
console.log(reminder.join("\n"));
|
|
624
|
+
// === session_compact → PreCompact ===
|
|
625
|
+
// Re-anchor the model on its task once compaction has summarized the
|
|
626
|
+
// history. `context` fires before every LLM call, so it cannot carry this.
|
|
627
|
+
pi.on("session_compact", async () => {
|
|
628
|
+
if (!config.taskId) return;
|
|
629
|
+
|
|
630
|
+
const taskDetails = await fetchTaskDetails(config);
|
|
631
|
+
if (taskDetails) {
|
|
632
|
+
const reminder = [
|
|
633
|
+
"=== GOAL REMINDER (injected before context compaction) ===",
|
|
634
|
+
`Task ID: ${taskDetails.id}`,
|
|
635
|
+
`Task: ${taskDetails.task}`,
|
|
636
|
+
];
|
|
637
|
+
if (taskDetails.progress) {
|
|
638
|
+
reminder.push(`Current Progress: ${taskDetails.progress}`);
|
|
616
639
|
}
|
|
617
|
-
|
|
618
|
-
|
|
640
|
+
reminder.push("=== Continue working on this task after compaction ===");
|
|
641
|
+
sendNudge(pi, reminder.join("\n"));
|
|
619
642
|
}
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// === context (before every LLM call) ===
|
|
646
|
+
pi.on("context", async (_event, ctx) => {
|
|
647
|
+
if (!config.taskId) return undefined;
|
|
620
648
|
|
|
621
649
|
// Report context usage as a compaction event
|
|
622
650
|
const usage = ctx.getContextUsage?.();
|
|
@@ -644,7 +672,10 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
644
672
|
const { cancelled, reason } = await isTaskCancelled(config);
|
|
645
673
|
if (cancelled) {
|
|
646
674
|
const cancelReason = reason || "Task cancelled by lead or creator";
|
|
647
|
-
|
|
675
|
+
// "handled" skips the turn; the nudge lands in the session for the
|
|
676
|
+
// next one instead of paying for a turn on a cancelled task.
|
|
677
|
+
sendNudge(
|
|
678
|
+
pi,
|
|
648
679
|
`🛑 TASK CANCELLED: ${cancelReason}. Stop working and use store-progress to acknowledge.`,
|
|
649
680
|
);
|
|
650
681
|
return { action: "handled" as const };
|