@desplega.ai/agent-swarm 1.173.0 → 2.1.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-ccm9xrep.js → acp-adapter-kr4abrsk.js} +17 -16
- package/dist/{actions-sgeyv08k.js → actions-eh4g6w2r.js} +39 -34
- package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-7h7wdt8q.js} +8 -7
- package/dist/{app-km9qnv2f.js → app-7k8s9w5p.js} +18 -14
- package/dist/{assistant-5r8tcvvd.js → assistant-wswt2y7p.js} +40 -35
- package/dist/{boot-reembed-16rv9843.js → boot-reembed-9apm9qw6.js} +10 -9
- package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-whzbdthj.js} +10 -9
- package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-ndzmvmgt.js} +11 -10
- package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-e9sta0yt.js} +16 -13
- package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-4mqd3r6r.js} +19 -18
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-9wswb52m.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-tpe1effx.js} +6 -4
- package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-q49ghmh9.js} +12402 -11850
- package/dist/{cli-ne7smj1b.js → cli-01xvj1rq.js} +10 -10
- package/dist/{cli-nwrdx840.js → cli-035vekcw.js} +3 -3
- package/dist/{cli-9xf7fdtp.js → cli-0pd8hzhc.js} +29 -6
- package/dist/{cli-tmhsxhb8.js → cli-0veb92a3.js} +28 -6
- package/dist/{cli-c3rk1v0x.js → cli-10jesr25.js} +3 -3
- package/dist/{cli-6thk4jz3.js → cli-1y7yywmj.js} +6 -6
- package/dist/{cli-6r2f7edk.js → cli-20krdhz5.js} +47 -19
- package/dist/{cli-rfs4vddv.js → cli-29rja109.js} +39 -20
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-na2pxe4f.js → cli-2pwa0q4d.js} +15 -4
- package/dist/{cli-7a93cv8c.js → cli-4mengw46.js} +441 -43
- package/dist/{cli-94b48jhk.js → cli-5twdke5x.js} +16 -14
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-w1apjecb.js → cli-711vwbxn.js} +2 -2
- package/dist/{cli-r4fqfpwf.js → cli-78vv7446.js} +50 -27
- package/dist/{cli-srqq3gc5.js → cli-7n6s5man.js} +5 -5
- package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
- package/dist/{cli-2bw3w47j.js → cli-8wwxw8a3.js} +1 -1
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-ywm1azg9.js → cli-9jzdj90z.js} +8 -4
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/cli-cvv9k0hh.js +10143 -0
- package/dist/{cli-anzdk0z4.js → cli-d9286m9h.js} +2 -2
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-gmdajfw5.js → cli-gxzkg1de.js} +5020 -1729
- package/dist/{cli-451ks6aw.js → cli-h1arqn31.js} +1 -1
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-jpc4r224.js → cli-hqr88ew6.js} +19 -3
- package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
- package/dist/{cli-cd0hv9nh.js → cli-j67mh2vg.js} +2 -2
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-d603qf86.js → cli-ksphgh48.js} +10 -8
- package/dist/{cli-3nxdmjrc.js → cli-mgz4nbgg.js} +4 -4
- package/dist/{cli-saqyage0.js → cli-mz5as5ha.js} +179 -27
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-ppy4nnnh.js → cli-n8ndw8j7.js} +2 -2
- package/dist/{cli-1w0vj3gw.js → cli-naqzr17j.js} +7 -7
- package/dist/{cli-0a7mm3zp.js → cli-nm1p8jfr.js} +25 -21
- package/dist/{cli-wrh08zjd.js → cli-p3zbc1rw.js} +5 -18
- package/dist/{cli-hsem16hb.js → cli-pe05f8yk.js} +29 -6
- package/dist/{cli-5wpw4rab.js → cli-qmzsbkvy.js} +5 -5
- package/dist/{cli-bv2etw2y.js → cli-qnm4e9e4.js} +99 -590
- package/dist/{cli-1x7da0de.js → cli-qqy4q48b.js} +8 -8
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-5er5gjbm.js → cli-r35rw5de.js} +15 -15
- package/dist/{cli-v3zhnxsb.js → cli-r610p9bs.js} +2 -2
- package/dist/{cli-7jykv7ef.js → cli-rc6pf5kv.js} +1 -1
- package/dist/{cli-x119kdba.js → cli-rdxm77k3.js} +1 -1
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-0p3enk35.js → cli-s69nfyeh.js} +9 -1
- package/dist/{cli-0kxf81jj.js → cli-sawv63e8.js} +2 -2
- package/dist/{cli-e3pmyt39.js → cli-sg19gbf0.js} +1 -1
- package/dist/{cli-d161g69q.js → cli-shk7r5cs.js} +57 -6
- package/dist/{cli-7pz3b7x5.js → cli-sksetb2q.js} +36 -1
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-db3m6zdt.js → cli-t02z59f2.js} +1 -1
- package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
- package/dist/{cli-s8z6x4b4.js → cli-v7g551te.js} +225 -113
- package/dist/{cli-rfz2ve55.js → cli-vgqzp4em.js} +3341 -955
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/cli-wfnemr1t.js +86 -0
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-qm1448f9.js → cli-wms6x9db.js} +8 -8
- package/dist/{cli-tadfgd0t.js → cli-x1r545x8.js} +33 -4
- package/dist/{cli-xc7p7s3g.js → cli-xczsgshn.js} +6 -6
- package/dist/{cli-fmhj1ehj.js → cli-y3167wt1.js} +882 -271
- package/dist/{cli-r0byd61h.js → cli-yjmqpxt3.js} +17 -16
- package/dist/{cli-81r1wjyv.js → cli-z780ec7c.js} +1 -1
- package/dist/{cli-phcqp1b9.js → cli-zm9t2vde.js} +2 -2
- package/dist/cli.js +34 -49
- package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-4z16v9n0.js} +15 -14
- package/dist/{codex-login-841kv5fk.js → codex-login-pkepy2j1.js} +3 -2
- package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-rvms9y2x.js} +15 -14
- package/dist/{commands-z8fq9qna.js → commands-2a9rtyat.js} +11 -10
- package/dist/{controller-hq5ggpcp.js → controller-3k3madbn.js} +5 -4
- package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-v9yn8zb9.js} +42 -37
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-98gbhyzn.js} +7 -6
- package/dist/{db-qfpq4bt7.js → db-eywwsnjw.js} +30 -9
- package/dist/{db-yyhqqrbf.js → db-ncypk9np.js} +12 -11
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-zeq8b9gz.js} +3 -2
- package/dist/{dispatcher-x0x7qfcq.js → dispatcher-w94kc575.js} +38 -33
- package/dist/{docker-f503k9p0.js → docker-b1e4cqab.js} +3 -2
- package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-47ztej36.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-bbt8ey45.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-dp804sen.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-j2jdg3qb.js} +3 -2
- package/dist/{external-channel-h1fjecwe.js → external-channel-zrkg8ar9.js} +11 -10
- package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
- package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
- package/dist/{grok-adapter-e7mr287e.js → grok-adapter-s9ncp9vy.js} +18 -17
- package/dist/{handlers-znb9n7d4.js → handlers-kqftvjgw.js} +41 -36
- package/dist/{heartbeat-bm4qkn8d.js → heartbeat-9q96p8v9.js} +43 -38
- package/dist/{hook-254r4mza.js → hook-56qe10dp.js} +11 -23
- package/dist/{hook-xaa3q80s.js → hook-yy6rw42k.js} +12 -11
- package/dist/{http-3dgrgp8y.js → http-r2dnb8f8.js} +2411 -275
- package/dist/{index-fx3jqq1e.js → index-3gtmkhc6.js} +38 -33
- package/dist/{index-bdsqa799.js → index-3k5hkz7r.js} +43 -38
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-g2byveez.js → index-5e101240.js} +38 -33
- package/dist/{index-2ybk7khy.js → index-5wyfjc2c.js} +845 -683
- 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-jd7sejm2.js → index-vxvhxxbn.js} +10 -9
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{keepalive-6ywhpja1.js → keepalive-psj6j54c.js} +12 -11
- package/dist/{lead-cw45n21s.js → lead-hajz4688.js} +61 -56
- package/dist/{maintenance-ss1a1ge9.js → maintenance-ps02sb5m.js} +10 -9
- package/dist/{node-fkajaq96.js → node-mwb5r697.js} +2 -1
- package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-0284h93b.js} +11 -10
- package/dist/{onboard-8hzdhc1k.js → onboard-rbf6bg01.js} +11 -3
- package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-13dcatgq.js} +18 -13
- package/dist/{otel-impl-e6mnvc4v.js → otel-impl-zvqcbhp7.js} +242 -868
- package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-xxy47q4v.js} +244 -106
- package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-kefx9v3t.js} +12 -11
- package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-3m36rxm2.js} +11 -10
- package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-z76xv0hq.js} +12 -11
- package/dist/{render-v2-907cy6sa.js → render-v2-wz73bs25.js} +18 -14
- package/dist/{resume-42stcaz0.js → resume-k4wzge8w.js} +25 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-adbc1c10.js → setup-2fp2pypp.js} +2 -2
- package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
- package/dist/{work-objects-wpj8ck95.js → work-objects-6k0ywz3m.js} +41 -36
- package/dist/{worker-bzdra9rf.js → worker-5v3rxsdh.js} +61 -56
- package/dist/{x-qfmygqqv.js → x-4mtdeym1.js} +3 -2
- package/openapi.json +1279 -329
- package/package.json +15 -15
- package/src/agentmail/handlers.ts +21 -11
- package/src/apps/definition.ts +16 -0
- package/src/azure-devops/api.ts +52 -2
- package/src/azure-devops/boards-sync.ts +50 -20
- package/src/azure-devops/index.ts +1 -0
- package/src/azure-devops/outbound.ts +95 -0
- package/src/be/boot-seeds.ts +11 -0
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/model-profiles.ts +315 -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 +237 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +87 -36
- 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/migrations/219_relabel_pi_context_compactions.sql +22 -0
- package/src/be/migrations/220_model_profiles.sql +137 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +155 -10
- package/src/be/model-validation.ts +12 -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-credential-broker.ts +39 -2
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +22 -8
- package/src/be/secret-registry.ts +6 -2
- 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-model-profiles.ts +41 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
- 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-scripts/index.ts +9 -0
- package/src/be/seed-skills/bundled-files.generated.json +0 -18
- package/src/be/seed-skills/index.ts +104 -3
- 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/provider-credentials.ts +52 -6
- package/src/commands/runner.ts +193 -71
- package/src/e2b/env.ts +5 -0
- package/src/extensions/catalog.generated.json +17 -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/loader.ts +10 -5
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/heartbeat.ts +2 -1
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/agents.ts +49 -2
- package/src/http/all-routes.ts +1 -1
- package/src/http/apps.ts +30 -3
- package/src/http/config.ts +62 -19
- package/src/http/core.ts +8 -0
- package/src/http/index.ts +25 -3
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp.ts +15 -5
- package/src/http/model-profiles.ts +303 -0
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +13 -7
- package/src/http/pages.ts +3 -13
- package/src/http/poll.ts +42 -71
- package/src/http/requested-by.ts +54 -0
- package/src/http/script-runs.ts +1 -1
- package/src/http/scripts.ts +26 -2
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +79 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +160 -3
- 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/{artifact-sdk → pages}/feedback-overlay.ts +65 -6
- 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 +1 -1
- package/src/providers/claude-flag-settings.ts +17 -5
- package/src/providers/claude-managed-adapter.ts +1 -1
- package/src/providers/claude-session-events.ts +6 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +243 -26
- package/src/providers/opencode-swarm-plugin.ts +31 -0
- package/src/providers/pi-mono-adapter.ts +398 -153
- package/src/providers/pi-mono-extension.ts +17 -3
- package/src/providers/pricing-sources.md +33 -2
- package/src/providers/reasoning-effort.ts +37 -0
- package/src/providers/types.ts +6 -2
- package/src/rbac/legacy-policy.ts +16 -2
- package/src/rbac/permissions.ts +26 -0
- package/src/scheduler/deferred-task-waits.ts +18 -5
- package/src/scheduler/scheduler.ts +11 -3
- package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
- package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
- package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
- package/src/scripts-runtime/extract-args-schema.ts +9 -8
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/quickjs-compat.ts +464 -0
- package/src/scripts-runtime/sdk-allowlist.ts +12 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +39 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +66 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +39 -15
- package/src/server.ts +62 -19
- package/src/slack/blocks.ts +4 -2
- package/src/slack/event-dedup.ts +1 -1
- package/src/slack/reaction-shortcode.ts +14 -0
- package/src/slack/render-v2.ts +12 -7
- package/src/slack/responses.ts +5 -4
- package/src/slack/thread-buffer.ts +41 -22
- package/src/tasks/additive-buffer.ts +4 -2
- package/src/tasks/context-key.ts +60 -1
- package/src/tasks/model-pin.ts +67 -0
- package/src/tasks/worker-follow-up.ts +15 -33
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/agentmail-filters.test.ts +47 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/apps-spike2.test.ts +35 -0
- package/src/tests/azure-devops-outbound.test.ts +420 -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 +9 -4
- package/src/tests/claude-flag-settings.test.ts +11 -3
- package/src/tests/claude-managed-adapter.test.ts +1 -1
- package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
- 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 +19 -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 +22 -0
- package/src/tests/db-task-write-contract.test.ts +11 -0
- package/src/tests/deferred-task-wake.test.ts +78 -1
- 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/env-flag.test.ts +1 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-catalog.test.ts +1 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-loader.test.ts +15 -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/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-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/heartbeat-jev-gate-credentials.test.ts +168 -0
- package/src/tests/heartbeat-jev-gate.test.ts +385 -0
- 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-profile-backcompat.test.ts +327 -0
- package/src/tests/model-profile-providers.test.ts +302 -0
- package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
- package/src/tests/model-profiles-api.test.ts +614 -0
- package/src/tests/model-tier-resolution.test.ts +93 -0
- package/src/tests/models-catalog.test.ts +42 -1
- 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 +216 -2
- 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-keys.test.ts +121 -0
- package/src/tests/pages-feedback.test.ts +4 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-context-compaction.test.ts +193 -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/profile-claim.test.ts +269 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/quickjs-compat.test.ts +213 -0
- package/src/tests/rbac-engine.test.ts +32 -3
- package/src/tests/runner-exit-after-tasks.test.ts +0 -4
- package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
- package/src/tests/script-connections.test.ts +6 -0
- package/src/tests/script-executor-conformance.test.ts +252 -1
- package/src/tests/script-executor-quickjs.test.ts +96 -0
- package/src/tests/script-runs-http.test.ts +89 -0
- package/src/tests/scripts-http.test.ts +217 -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 +96 -0
- package/src/tests/seed-model-profiles.test.ts +174 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- 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 +46 -0
- package/src/tests/skill-get-name-resolution.test.ts +137 -0
- package/src/tests/skill-invocations.test.ts +4 -2
- package/src/tests/slack-blocks.test.ts +36 -0
- package/src/tests/slack-inline-output.test.ts +14 -0
- package/src/tests/slack-reaction-config.test.ts +20 -0
- package/src/tests/slack-render-v2.test.ts +37 -0
- package/src/tests/slack-thread-buffer.test.ts +98 -1
- package/src/tests/sonnet-5-5-registry.test.ts +9 -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/vercel-ai-gateway.test.ts +149 -0
- package/src/tests/workflow-llm.test.ts +61 -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/model-catalog/index.ts +2 -1
- package/src/tools/model-profiles/list-model-profiles.ts +54 -0
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/request-human-input.ts +11 -3
- package/src/tools/script-apis.ts +5 -1
- package/src/tools/script-upsert.ts +4 -1
- package/src/tools/send-task.ts +48 -1
- package/src/tools/skills/skill-get.ts +17 -9
- 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/system-one-decide.ts +104 -0
- package/src/tools/task-action.ts +22 -1
- package/src/tools/tool-config.ts +14 -6
- package/src/types.ts +48 -3
- 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/runtime-model-catalog.ts +11 -1
- package/src/utils/secret-scrubber.ts +86 -10
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/utils/vcs-repo-match.ts +29 -0
- package/src/workflows/executors/raw-llm.ts +8 -3
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/src/workflows/executors/system-one-decision.ts +21 -11
- package/src/workflows/executors/workflow-llm.ts +22 -0
- package/src/workflows/index.ts +21 -12
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/comms-adherence-gate/README.md +88 -0
- package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
- package/templates/extensions/comms-adherence-gate/manifest.json +8 -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 +8 -1
- package/templates/skills/apps/content.md +8 -1
- 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/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-42qhyvwx.js +0 -33
- package/dist/cli-bd957dgb.js +0 -153
- package/dist/cli-q51yprfq.js +0 -1104
- package/dist/cli-t1ze64h3.js +0 -811
- package/dist/cli-tcc4wd5d.js +0 -56471
- 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/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
|
@@ -267,6 +267,99 @@ describe("claim-time resolution via /api/poll", () => {
|
|
|
267
267
|
});
|
|
268
268
|
});
|
|
269
269
|
|
|
270
|
+
// A row that already ran carries that run's model: the session report wrote it
|
|
271
|
+
// into `model`, and the claim wrote `resolvedModel`/`modelSource`. Re-claiming
|
|
272
|
+
// it must resolve from the config in force NOW, not replay the previous run.
|
|
273
|
+
describe("re-claim after a previous run", () => {
|
|
274
|
+
async function markRan(
|
|
275
|
+
taskId: string,
|
|
276
|
+
fields: { model: string; resolvedModel?: string; modelSource?: string },
|
|
277
|
+
): Promise<void> {
|
|
278
|
+
await getDbClient().run(
|
|
279
|
+
`UPDATE agent_tasks SET model = ?, resolvedModel = ?, modelSource = ?,
|
|
280
|
+
claudeSessionId = 'previous-session', status = 'pending' WHERE id = ?`,
|
|
281
|
+
[fields.model, fields.resolvedModel ?? null, fields.modelSource ?? null, taskId],
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
test("an unpinned task drops the previous run's model, so the agent's current config applies", async () => {
|
|
286
|
+
const worker = await createAgent({
|
|
287
|
+
name: "rerun-unpinned",
|
|
288
|
+
isLead: false,
|
|
289
|
+
status: "idle",
|
|
290
|
+
maxTasks: 1,
|
|
291
|
+
harnessProvider: "claude",
|
|
292
|
+
});
|
|
293
|
+
const task = await createTaskExtended("plain work", { agentId: worker.id });
|
|
294
|
+
// First run: no pin, MODEL_OVERRIDE was opus, the session reported it.
|
|
295
|
+
await markRan(task.id, { model: "claude-opus-5-5" });
|
|
296
|
+
|
|
297
|
+
const trigger = await callPoll(worker.id);
|
|
298
|
+
|
|
299
|
+
expect(trigger?.type).toBe("task_assigned");
|
|
300
|
+
// No `model` in the trigger: the worker falls through to MODEL_OVERRIDE.
|
|
301
|
+
expect(trigger?.task.model).toBeUndefined();
|
|
302
|
+
expect(trigger?.task.resolvedModel).toBeUndefined();
|
|
303
|
+
expect(await taskRow(task.id)).toEqual({
|
|
304
|
+
resolvedModel: null,
|
|
305
|
+
modelSource: null,
|
|
306
|
+
modelAlias: null,
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("a tier task re-resolves the tier after the tier config changed", async () => {
|
|
311
|
+
const worker = await createAgent({
|
|
312
|
+
name: "rerun-tier",
|
|
313
|
+
isLead: false,
|
|
314
|
+
status: "idle",
|
|
315
|
+
maxTasks: 1,
|
|
316
|
+
harnessProvider: "claude",
|
|
317
|
+
});
|
|
318
|
+
const task = await createTaskExtended("smart work", { agentId: worker.id, modelTier: "smart" });
|
|
319
|
+
await markRan(task.id, {
|
|
320
|
+
model: "claude-opus-5-5",
|
|
321
|
+
resolvedModel: "claude-opus-5-5",
|
|
322
|
+
modelSource: "tier-config",
|
|
323
|
+
});
|
|
324
|
+
process.env.MODEL_TIER_CLAUDE_SMART = "claude-sonnet-5-5";
|
|
325
|
+
|
|
326
|
+
const trigger = await callPoll(worker.id, header({}));
|
|
327
|
+
|
|
328
|
+
expect(trigger?.task.model).toBeUndefined();
|
|
329
|
+
expect(trigger?.task.resolvedModel).toBe("claude-sonnet-5-5");
|
|
330
|
+
expect(await taskRow(task.id)).toEqual({
|
|
331
|
+
resolvedModel: "claude-sonnet-5-5",
|
|
332
|
+
modelSource: "tier-config",
|
|
333
|
+
modelAlias: null,
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("a task-pinned model stays pinned on re-claim", async () => {
|
|
338
|
+
const worker = await createAgent({
|
|
339
|
+
name: "rerun-pinned",
|
|
340
|
+
isLead: false,
|
|
341
|
+
status: "idle",
|
|
342
|
+
maxTasks: 1,
|
|
343
|
+
harnessProvider: "claude",
|
|
344
|
+
});
|
|
345
|
+
const task = await createTaskExtended("pinned work", {
|
|
346
|
+
agentId: worker.id,
|
|
347
|
+
model: "claude-sonnet-5-5",
|
|
348
|
+
});
|
|
349
|
+
await markRan(task.id, {
|
|
350
|
+
model: "claude-sonnet-5-5",
|
|
351
|
+
resolvedModel: "claude-sonnet-5-5",
|
|
352
|
+
modelSource: "model",
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const trigger = await callPoll(worker.id);
|
|
356
|
+
|
|
357
|
+
expect(trigger?.task.model).toBe("claude-sonnet-5-5");
|
|
358
|
+
expect(trigger?.task.resolvedModel).toBe("claude-sonnet-5-5");
|
|
359
|
+
expect((await taskRow(task.id))?.modelSource).toBe("model");
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
270
363
|
describe("claim-time harness guard", () => {
|
|
271
364
|
test("a directed task pinning an Anthropic model on a Codex worker fails fast with a tagged reason", async () => {
|
|
272
365
|
const worker = await createAgent({
|
|
@@ -11,14 +11,17 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
14
|
+
import { reasoningLevelsForModel } from "@desplega/model-catalog";
|
|
14
15
|
import {
|
|
15
16
|
buildModelsCatalog,
|
|
17
|
+
type CatalogProviderId,
|
|
16
18
|
getModelsCatalog,
|
|
17
19
|
PINNED_MODELSDEV_ENTRIES,
|
|
18
20
|
resetModelsCatalogForTests,
|
|
19
21
|
updateLiveModelsCatalog,
|
|
20
22
|
} from "../be/models-catalog";
|
|
21
23
|
import type { ModelsDevCache } from "../be/modelsdev-cache";
|
|
24
|
+
import { getContextWindowSize } from "../utils/context-window";
|
|
22
25
|
|
|
23
26
|
afterEach(() => {
|
|
24
27
|
resetModelsCatalogForTests();
|
|
@@ -82,6 +85,14 @@ describe("getModelsCatalog", () => {
|
|
|
82
85
|
expect(Object.keys(result.providers.openrouter?.models ?? {}).length).toBeGreaterThan(0);
|
|
83
86
|
expect(Object.keys(result.providers.anthropic?.models ?? {}).length).toBeGreaterThan(0);
|
|
84
87
|
expect(Object.keys(result.providers.opencode?.models ?? {}).length).toBeGreaterThan(0);
|
|
88
|
+
expect(result.providers.nan?.models["glm5.3-flash"]).toBeDefined();
|
|
89
|
+
expect(Object.keys(result.providers.helmcode?.models ?? {}).sort()).toEqual([
|
|
90
|
+
"deepseek-v4-flash",
|
|
91
|
+
"gemma4",
|
|
92
|
+
"glm5.3",
|
|
93
|
+
"glm5.3-flash",
|
|
94
|
+
"qwen3.6",
|
|
95
|
+
]);
|
|
85
96
|
});
|
|
86
97
|
|
|
87
98
|
test("prefers live data after an update", () => {
|
|
@@ -99,7 +110,7 @@ describe("getModelsCatalog", () => {
|
|
|
99
110
|
const result = getModelsCatalog();
|
|
100
111
|
for (const entryPath of PINNED_MODELSDEV_ENTRIES) {
|
|
101
112
|
const slashIndex = entryPath.indexOf("/");
|
|
102
|
-
const providerId = entryPath.slice(0, slashIndex) as
|
|
113
|
+
const providerId = entryPath.slice(0, slashIndex) as CatalogProviderId;
|
|
103
114
|
const modelId = entryPath.slice(slashIndex + 1);
|
|
104
115
|
expect(result.providers[providerId]?.models[modelId]).toBeDefined();
|
|
105
116
|
}
|
|
@@ -118,3 +129,33 @@ describe("getModelsCatalog", () => {
|
|
|
118
129
|
expect(result.providers.anthropic?.models["claude-mythos-5"]?.name).toBe("Live Mythos");
|
|
119
130
|
});
|
|
120
131
|
});
|
|
132
|
+
|
|
133
|
+
describe("nan and helmcode sections", () => {
|
|
134
|
+
test("the registry-synthesized helmcode section survives a live payload that lacks it", () => {
|
|
135
|
+
// LIVE_CACHE has no helmcode provider: the registry section is refilled.
|
|
136
|
+
updateLiveModelsCatalog(LIVE_CACHE);
|
|
137
|
+
const helmcode = getModelsCatalog().providers.helmcode;
|
|
138
|
+
expect(helmcode?.id).toBe("helmcode");
|
|
139
|
+
expect(helmcode?.name).toBe("Helmcode");
|
|
140
|
+
expect(helmcode?.models["glm5.3"]?.limit?.context).toBe(1_048_576);
|
|
141
|
+
expect(helmcode?.models["qwen3.6"]?.limit?.context).toBe(262_144);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("reasoning levels resolve for helmcode and nan ids on pi and opencode", () => {
|
|
145
|
+
const catalog = getModelsCatalog().providers;
|
|
146
|
+
for (const harness of ["pi", "opencode"]) {
|
|
147
|
+
expect(
|
|
148
|
+
reasoningLevelsForModel(harness, "helmcode/glm5.3-flash", catalog).length,
|
|
149
|
+
).toBeGreaterThan(0);
|
|
150
|
+
expect(reasoningLevelsForModel(harness, "nan/glm5.3-flash", catalog).length).toBeGreaterThan(
|
|
151
|
+
0,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("context windows come from the catalog sections", () => {
|
|
157
|
+
expect(getContextWindowSize("helmcode/glm5.3")).toBe(1_048_576);
|
|
158
|
+
expect(getContextWindowSize("helmcode/gemma4")).toBe(262_144);
|
|
159
|
+
expect(getContextWindowSize("nan/qwen3.6")).toBe(262_144);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
baseUrlEnvVar,
|
|
4
|
+
OPENAI_COMPATIBLE_PROVIDERS,
|
|
5
|
+
openAICompatibleProviderIds,
|
|
6
|
+
providerBaseUrl,
|
|
7
|
+
providerForKeyVar,
|
|
8
|
+
providerForModel,
|
|
9
|
+
providerKeyFromEnv,
|
|
10
|
+
withRegistryCatalogSections,
|
|
11
|
+
} from "@desplega/model-catalog";
|
|
12
|
+
import { loadModelsDevCache } from "../be/modelsdev-cache";
|
|
13
|
+
import snapshot from "../be/modelsdev-cache.json";
|
|
14
|
+
import { normalizeProviderKeyAliases } from "../utils/credentials";
|
|
15
|
+
|
|
16
|
+
type SnapshotModel = {
|
|
17
|
+
name: string;
|
|
18
|
+
reasoning?: boolean;
|
|
19
|
+
tool_call?: boolean;
|
|
20
|
+
modalities?: { input?: string[] };
|
|
21
|
+
limit?: { context?: number; output?: number };
|
|
22
|
+
};
|
|
23
|
+
type SnapshotProvider = {
|
|
24
|
+
api?: string;
|
|
25
|
+
env?: string[];
|
|
26
|
+
npm?: string;
|
|
27
|
+
models: Record<string, SnapshotModel>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("openai-compatible provider table", () => {
|
|
31
|
+
test("providerForModel routes by exact prefix only", () => {
|
|
32
|
+
expect(providerForModel("nan/glm5.3-flash")?.id).toBe("nan");
|
|
33
|
+
expect(providerForModel("helmcode/gemma4")?.id).toBe("helmcode");
|
|
34
|
+
expect(providerForModel("openrouter/nan/glm5.3")).toBeUndefined();
|
|
35
|
+
expect(providerForModel("glm5.3")).toBeUndefined();
|
|
36
|
+
expect(providerForModel("nanx/glm5.3")).toBeUndefined();
|
|
37
|
+
expect(providerForModel("toString/x")).toBeUndefined();
|
|
38
|
+
expect(providerForModel(undefined)).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("providerForKeyVar finds canonical and alias key vars", () => {
|
|
42
|
+
expect(providerForKeyVar("NAN_API_KEY")?.id).toBe("nan");
|
|
43
|
+
expect(providerForKeyVar("HELMCODE_API_NAN")?.id).toBe("nan");
|
|
44
|
+
expect(providerForKeyVar("HELMCODE_API_KEY")?.id).toBe("helmcode");
|
|
45
|
+
expect(providerForKeyVar("OPENROUTER_API_KEY")).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("<ID>_BASE_URL overrides the default base URL", () => {
|
|
49
|
+
const nan = OPENAI_COMPATIBLE_PROVIDERS.nan;
|
|
50
|
+
const helmcode = OPENAI_COMPATIBLE_PROVIDERS.helmcode;
|
|
51
|
+
expect(baseUrlEnvVar(nan)).toBe("NAN_BASE_URL");
|
|
52
|
+
expect(baseUrlEnvVar(helmcode)).toBe("HELMCODE_BASE_URL");
|
|
53
|
+
expect(providerBaseUrl(nan, {})).toBe("https://api.nan.builders/v1");
|
|
54
|
+
expect(providerBaseUrl(nan, { NAN_BASE_URL: " " })).toBe("https://api.nan.builders/v1");
|
|
55
|
+
expect(providerBaseUrl(helmcode, { HELMCODE_BASE_URL: "http://127.0.0.1:9/v1/" })).toBe(
|
|
56
|
+
"http://127.0.0.1:9/v1",
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("providerKeyFromEnv prefers the canonical var, then the alias", () => {
|
|
61
|
+
const nan = OPENAI_COMPATIBLE_PROVIDERS.nan;
|
|
62
|
+
expect(providerKeyFromEnv(nan, { HELMCODE_API_NAN: "alias" })).toBe("alias");
|
|
63
|
+
expect(providerKeyFromEnv(nan, { NAN_API_KEY: "canon", HELMCODE_API_NAN: "alias" })).toBe(
|
|
64
|
+
"canon",
|
|
65
|
+
);
|
|
66
|
+
expect(providerKeyFromEnv(nan, { NAN_API_KEY: " " })).toBeUndefined();
|
|
67
|
+
expect(providerKeyFromEnv(OPENAI_COMPATIBLE_PROVIDERS.helmcode, { NAN_API_KEY: "k" })).toBe(
|
|
68
|
+
undefined,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("normalizeProviderKeyAliases copies an alias only into an empty canonical var", () => {
|
|
73
|
+
const aliasOnly: Record<string, string | undefined> = { HELMCODE_API_NAN: "alias" };
|
|
74
|
+
normalizeProviderKeyAliases(aliasOnly);
|
|
75
|
+
expect(aliasOnly.NAN_API_KEY).toBe("alias");
|
|
76
|
+
const both: Record<string, string | undefined> = {
|
|
77
|
+
NAN_API_KEY: "canon",
|
|
78
|
+
HELMCODE_API_NAN: "alias",
|
|
79
|
+
};
|
|
80
|
+
normalizeProviderKeyAliases(both);
|
|
81
|
+
expect(both.NAN_API_KEY).toBe("canon");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("registry-sourced sections are synthesized, never hand-written in the snapshot", () => {
|
|
85
|
+
const raw = snapshot as unknown as Record<string, unknown>;
|
|
86
|
+
for (const id of openAICompatibleProviderIds()) {
|
|
87
|
+
const entry = OPENAI_COMPATIBLE_PROVIDERS[id];
|
|
88
|
+
if (entry.catalog === "registry") expect(raw[id]).toBeUndefined();
|
|
89
|
+
else expect(raw[id]).toBeDefined();
|
|
90
|
+
}
|
|
91
|
+
// The server loader carries the synthesized sections.
|
|
92
|
+
expect(loadModelsDevCache()?.helmcode?.models?.["glm5.3"]?.limit?.context).toBe(1048576);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// The registry duplicates the models.dev sections of `catalog: "models.dev"`
|
|
96
|
+
// entries (the pi/opencode blocks need limits the worker cannot read from
|
|
97
|
+
// the catalog store); keep the two from drifting. Registry-sourced sections
|
|
98
|
+
// are built from the registry, so they agree by construction.
|
|
99
|
+
const merged = withRegistryCatalogSections(
|
|
100
|
+
snapshot as unknown as Record<string, SnapshotProvider | undefined>,
|
|
101
|
+
);
|
|
102
|
+
for (const id of openAICompatibleProviderIds()) {
|
|
103
|
+
test(`${id} matches its catalog section`, () => {
|
|
104
|
+
const entry = OPENAI_COMPATIBLE_PROVIDERS[id];
|
|
105
|
+
const section = merged[id];
|
|
106
|
+
expect(section).toBeDefined();
|
|
107
|
+
if (!section) return;
|
|
108
|
+
// OpenCode's built-in AI Gateway SDK has no OpenAI-compatible `api` URL.
|
|
109
|
+
if (entry.npm === "@ai-sdk/gateway") expect(section.api).toBeUndefined();
|
|
110
|
+
else expect(section.api).toBe(entry.baseUrl);
|
|
111
|
+
expect(section.env).toEqual([entry.envVar]);
|
|
112
|
+
expect(section.npm).toBe(entry.npm);
|
|
113
|
+
for (const model of entry.models) {
|
|
114
|
+
const snap = section.models[model.id];
|
|
115
|
+
expect(snap, `${id}/${model.id} missing from snapshot`).toBeDefined();
|
|
116
|
+
if (!snap) continue;
|
|
117
|
+
expect({
|
|
118
|
+
name: model.name,
|
|
119
|
+
contextWindow: model.contextWindow,
|
|
120
|
+
maxOutputTokens: model.maxOutputTokens,
|
|
121
|
+
reasoning: model.reasoning,
|
|
122
|
+
input: [...model.input],
|
|
123
|
+
}).toEqual({
|
|
124
|
+
name: snap.name,
|
|
125
|
+
contextWindow: snap.limit?.context,
|
|
126
|
+
maxOutputTokens: snap.limit?.output,
|
|
127
|
+
reasoning: snap.reasoning,
|
|
128
|
+
input: (snap.modalities?.input ?? []).filter((m) => m === "text" || m === "image"),
|
|
129
|
+
});
|
|
130
|
+
expect(snap.tool_call).toBe(true);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof that adding an OpenAI-compatible provider takes one registry entry:
|
|
3
|
+
* a fake `testgw` entry must reach every derived surface
|
|
4
|
+
* (`fixtures/openai-compatible-registry-extension.child.ts`).
|
|
5
|
+
*
|
|
6
|
+
* The proof mocks the registry module, and its consumers cache derivations of
|
|
7
|
+
* the table at import (integration cards, picker constants) that no mock
|
|
8
|
+
* restore can undo. It runs in its own `bun test` child so this process, and
|
|
9
|
+
* every file sharing it in a single-process run, keeps the real table.
|
|
10
|
+
*/
|
|
11
|
+
import { expect, test } from "bun:test";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { modelGroupsForHarness } from "../../apps/ui/src/lib/agent-runtime-models";
|
|
14
|
+
import { INTEGRATIONS } from "../../apps/ui/src/lib/integrations-catalog";
|
|
15
|
+
import { CHILD_PROCESS_TEST_BUDGET_MS, expectChildOk, runChild } from "./test-proc";
|
|
16
|
+
|
|
17
|
+
const REPO_ROOT = join(import.meta.dir, "../..");
|
|
18
|
+
const CHILD = "./src/tests/fixtures/openai-compatible-registry-extension.child.ts";
|
|
19
|
+
|
|
20
|
+
test(
|
|
21
|
+
"a testgw registry entry reaches every derived surface (child process)",
|
|
22
|
+
async () => {
|
|
23
|
+
const result = expectChildOk(
|
|
24
|
+
await runChild([process.execPath, "test", CHILD], {
|
|
25
|
+
cwd: REPO_ROOT,
|
|
26
|
+
env: { ...process.env },
|
|
27
|
+
}),
|
|
28
|
+
"registry extension proof",
|
|
29
|
+
);
|
|
30
|
+
// bun test reports to stderr; 0 fail and every case ran.
|
|
31
|
+
const report = `${result.stdout}\n${result.stderr}`;
|
|
32
|
+
expect(report).toMatch(/\b6 pass\b/);
|
|
33
|
+
expect(report).toMatch(/\b0 fail\b/);
|
|
34
|
+
},
|
|
35
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
test("this process still sees the real registry only", () => {
|
|
39
|
+
expect(INTEGRATIONS.some((def) => def.id === "testgw")).toBe(false);
|
|
40
|
+
const providers = modelGroupsForHarness("pi", [], {}).map((g) => g.provider);
|
|
41
|
+
expect(providers).not.toContain("TestGW");
|
|
42
|
+
});
|
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
* config wiring, steering, abort, and stream-death handling.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
10
|
+
import { afterAll, afterEach, beforeAll, describe, expect, spyOn, test } from "bun:test";
|
|
11
11
|
import type { OpenCodeEvent } from "@opencode/sdk";
|
|
12
12
|
import { normalizeSessionLogs, type SessionLogRecord } from "../../apps/ui/src/logs-parser";
|
|
13
13
|
import {
|
|
14
14
|
_setOpencodeSdkLoaderForTests,
|
|
15
15
|
_setOpencodeSessionHooksForTests,
|
|
16
16
|
applyOpenRouterBaseUrlOverride,
|
|
17
|
+
applyProviderKeySplice,
|
|
17
18
|
OpencodeAdapter,
|
|
18
19
|
type OpencodeSession,
|
|
19
20
|
resolveOpencodeSdkSpecifier,
|
|
20
21
|
} from "../providers/opencode-adapter";
|
|
21
22
|
import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
|
|
23
|
+
import { clearVolatileSecretsForTesting } from "../utils/secret-scrubber";
|
|
22
24
|
|
|
23
25
|
const SESSION_ID = "ses_test";
|
|
24
26
|
|
|
@@ -128,13 +130,14 @@ function makeFakeHost(events: OpenCodeEvent[], opts: FakeHostOptions = {}) {
|
|
|
128
130
|
return host;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
function installHost(host: FakeHost) {
|
|
133
|
+
function installHost(host: FakeHost, onCreate?: (options: Record<string, unknown>) => void) {
|
|
132
134
|
_setOpencodeSdkLoaderForTests(async () => ({
|
|
133
135
|
version: "2.0.28",
|
|
134
136
|
sdk: {
|
|
135
137
|
OpenCode: {
|
|
136
138
|
create: async (options: Record<string, unknown>) => {
|
|
137
139
|
host.createOptions = options;
|
|
140
|
+
onCreate?.(options);
|
|
138
141
|
// Set the plugins up the way OpenCode does, with a stub context.
|
|
139
142
|
const plugins = options.plugins as Array<{ setup: (ctx: unknown) => Promise<void> }>;
|
|
140
143
|
for (const plugin of plugins) {
|
|
@@ -143,6 +146,8 @@ function installHost(host: FakeHost) {
|
|
|
143
146
|
hook: async () => {},
|
|
144
147
|
list: async () => host.tools.map((id) => ({ id })),
|
|
145
148
|
},
|
|
149
|
+
// Registered when the session config carries a per-task env.
|
|
150
|
+
shell: { hook: async () => {} },
|
|
146
151
|
session: {
|
|
147
152
|
hook: async (
|
|
148
153
|
name: string,
|
|
@@ -841,3 +846,212 @@ describe("OpenRouter attribution config", () => {
|
|
|
841
846
|
expect(config).toEqual({});
|
|
842
847
|
});
|
|
843
848
|
});
|
|
849
|
+
|
|
850
|
+
describe("provider key splice (one picked key per task)", () => {
|
|
851
|
+
const PICKED = "sk-picked-member-0123456789";
|
|
852
|
+
const POOL = "sk-pool-member-aaaaaaaaaa,sk-pool-member-bbbbbbbbbb";
|
|
853
|
+
const SPLICE_VARS = [
|
|
854
|
+
"NAN_API_KEY",
|
|
855
|
+
"HELMCODE_API_NAN",
|
|
856
|
+
"HELMCODE_API_KEY",
|
|
857
|
+
"OPENROUTER_API_KEY",
|
|
858
|
+
"OPENROUTER_BASE_URL",
|
|
859
|
+
];
|
|
860
|
+
const saved: Record<string, string | undefined> = {};
|
|
861
|
+
beforeAll(() => {
|
|
862
|
+
for (const key of SPLICE_VARS) saved[key] = process.env[key];
|
|
863
|
+
});
|
|
864
|
+
afterEach(() => {
|
|
865
|
+
for (const key of SPLICE_VARS) {
|
|
866
|
+
if (saved[key] === undefined) delete process.env[key];
|
|
867
|
+
else process.env[key] = saved[key];
|
|
868
|
+
}
|
|
869
|
+
clearVolatileSecretsForTesting();
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
test("nan: env [] and the per-task key, even with a pool in process.env", async () => {
|
|
873
|
+
process.env.NAN_API_KEY = POOL;
|
|
874
|
+
const { host } = await runSession(
|
|
875
|
+
[succeeded],
|
|
876
|
+
testConfig({ model: "nan/glm5.3-flash", env: { NAN_API_KEY: PICKED } }),
|
|
877
|
+
);
|
|
878
|
+
const nan = configOf(host).provider.nan;
|
|
879
|
+
expect(nan.env).toEqual([]);
|
|
880
|
+
expect(nan.options.apiKey).toBe(PICKED);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test("nan: the HELMCODE_API_NAN alias is read when NAN_API_KEY is empty", () => {
|
|
884
|
+
const config = {};
|
|
885
|
+
const picked = applyProviderKeySplice(config, "nan/qwen3.6", { HELMCODE_API_NAN: PICKED });
|
|
886
|
+
expect(picked).toEqual({ key: PICKED, name: "HELMCODE_API_NAN" });
|
|
887
|
+
expect(config).toEqual({ provider: { nan: { env: [], options: { apiKey: PICKED } } } });
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test("a pool that reaches the splice unpicked yields its first member", () => {
|
|
891
|
+
const config = {};
|
|
892
|
+
applyProviderKeySplice(config, "nan/qwen3.6", {
|
|
893
|
+
NAN_API_KEY: ` ${PICKED} , sk-other-0000000000`,
|
|
894
|
+
});
|
|
895
|
+
expect(config).toEqual({ provider: { nan: { env: [], options: { apiKey: PICKED } } } });
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("helmcode: full custom block with limits, reasoning options merged in", async () => {
|
|
899
|
+
const { host } = await runSession(
|
|
900
|
+
[succeeded],
|
|
901
|
+
testConfig({
|
|
902
|
+
model: "helmcode/glm5.3",
|
|
903
|
+
reasoningEffort: "high",
|
|
904
|
+
env: { HELMCODE_API_KEY: PICKED },
|
|
905
|
+
}),
|
|
906
|
+
);
|
|
907
|
+
const helmcode = configOf(host).provider.helmcode;
|
|
908
|
+
expect(helmcode).toMatchObject({
|
|
909
|
+
npm: "@ai-sdk/openai-compatible",
|
|
910
|
+
name: "Helmcode",
|
|
911
|
+
env: [],
|
|
912
|
+
options: { baseURL: "https://api.helmcode.com/v1", apiKey: PICKED },
|
|
913
|
+
});
|
|
914
|
+
expect(helmcode.models["glm5.3"]).toEqual({
|
|
915
|
+
name: "GLM-5.3",
|
|
916
|
+
tool_call: true,
|
|
917
|
+
reasoning: true,
|
|
918
|
+
limit: { context: 1048576, output: 32768 },
|
|
919
|
+
options: { reasoningEffort: "high" },
|
|
920
|
+
});
|
|
921
|
+
expect(Object.keys(helmcode.models)).toEqual([
|
|
922
|
+
"deepseek-v4-flash",
|
|
923
|
+
"glm5.3",
|
|
924
|
+
"glm5.3-flash",
|
|
925
|
+
"qwen3.6",
|
|
926
|
+
"gemma4",
|
|
927
|
+
]);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
test("helmcode: a model missing from the table still gets an entry", () => {
|
|
931
|
+
const config: Record<string, any> = {};
|
|
932
|
+
applyProviderKeySplice(config, "helmcode/brand-new", { HELMCODE_API_KEY: PICKED });
|
|
933
|
+
expect(config.provider.helmcode.models["brand-new"]).toEqual({
|
|
934
|
+
name: "brand-new",
|
|
935
|
+
tool_call: true,
|
|
936
|
+
});
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
test("openrouter gateway: baseURL kept next to env [] and the picked key", async () => {
|
|
940
|
+
process.env.OPENROUTER_API_KEY = POOL;
|
|
941
|
+
const { host } = await runSession(
|
|
942
|
+
[succeeded],
|
|
943
|
+
testConfig({
|
|
944
|
+
model: "openrouter/deepseek/deepseek-v4-flash",
|
|
945
|
+
env: { OPENROUTER_API_KEY: PICKED, OPENROUTER_BASE_URL: "http://127.0.0.1:1234/v1" },
|
|
946
|
+
}),
|
|
947
|
+
);
|
|
948
|
+
expect(configOf(host).provider.openrouter).toEqual({
|
|
949
|
+
env: [],
|
|
950
|
+
options: { apiKey: PICKED, baseURL: "http://127.0.0.1:1234/v1" },
|
|
951
|
+
});
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
test("openrouter host: attribution headers kept next to env [] and the picked key", async () => {
|
|
955
|
+
const { host } = await runSession(
|
|
956
|
+
[succeeded],
|
|
957
|
+
testConfig({
|
|
958
|
+
model: "openrouter/deepseek/deepseek-v4-flash",
|
|
959
|
+
env: { OPENROUTER_API_KEY: PICKED },
|
|
960
|
+
}),
|
|
961
|
+
);
|
|
962
|
+
const openrouter = configOf(host).provider.openrouter;
|
|
963
|
+
expect(openrouter.env).toEqual([]);
|
|
964
|
+
expect(openrouter.options.apiKey).toBe(PICKED);
|
|
965
|
+
expect(openrouter.options.headers).toMatchObject({ "X-Title": "Agent Swarm" });
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
test("no splice without a key, or for providers outside the splice", () => {
|
|
969
|
+
for (const model of ["nan/qwen3.6", "helmcode/glm5.3", "openrouter/x/y"]) {
|
|
970
|
+
const config = {};
|
|
971
|
+
expect(applyProviderKeySplice(config, model, {})).toBeUndefined();
|
|
972
|
+
expect(config).toEqual({});
|
|
973
|
+
}
|
|
974
|
+
for (const model of ["anthropic/claude-sonnet-4-5", "openai/gpt-5", "glm5.3", "", undefined]) {
|
|
975
|
+
const config = {};
|
|
976
|
+
applyProviderKeySplice(config, model, {
|
|
977
|
+
ANTHROPIC_API_KEY: PICKED,
|
|
978
|
+
OPENAI_API_KEY: PICKED,
|
|
979
|
+
NAN_API_KEY: PICKED,
|
|
980
|
+
});
|
|
981
|
+
expect(config).toEqual({});
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
describe("spliced key never reaches logs, events, or errors", () => {
|
|
987
|
+
// Not in process.env: the key a dashboard save delivers to the per-task env only.
|
|
988
|
+
const SENTINEL = "sk-nan-sentinel-do-not-log-4f9c2e";
|
|
989
|
+
let captured: string[] = [];
|
|
990
|
+
let spies: Array<ReturnType<typeof spyOn>> = [];
|
|
991
|
+
beforeAll(() => {
|
|
992
|
+
delete process.env.NAN_API_KEY;
|
|
993
|
+
});
|
|
994
|
+
afterEach(() => {
|
|
995
|
+
for (const spy of spies) spy.mockRestore();
|
|
996
|
+
spies = [];
|
|
997
|
+
captured = [];
|
|
998
|
+
clearVolatileSecretsForTesting();
|
|
999
|
+
});
|
|
1000
|
+
function captureConsole() {
|
|
1001
|
+
for (const method of ["log", "info", "warn", "error", "debug"] as const) {
|
|
1002
|
+
spies.push(
|
|
1003
|
+
spyOn(console, method).mockImplementation((...args: unknown[]) => {
|
|
1004
|
+
captured.push(args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" "));
|
|
1005
|
+
}),
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
test("SDK log lines and a provider failure that echo the config are scrubbed", async () => {
|
|
1011
|
+
captureConsole();
|
|
1012
|
+
const host = makeFakeHost([
|
|
1013
|
+
ev("session.execution.failed", {
|
|
1014
|
+
sessionID: SESSION_ID,
|
|
1015
|
+
error: { message: `401 Invalid API key ${SENTINEL}` },
|
|
1016
|
+
}),
|
|
1017
|
+
]);
|
|
1018
|
+
installHost(host, (options) => {
|
|
1019
|
+
const content = (options.config as { content: string }).content;
|
|
1020
|
+
const log = options.log as { emit: (entry: { message: string }) => void };
|
|
1021
|
+
log.emit({ message: `config: ${content}` });
|
|
1022
|
+
});
|
|
1023
|
+
const session = (await new OpencodeAdapter().createSession(
|
|
1024
|
+
testConfig({ model: "nan/glm5.3-flash", env: { NAN_API_KEY: SENTINEL } }),
|
|
1025
|
+
)) as OpencodeSession;
|
|
1026
|
+
const emitted: ProviderEvent[] = [];
|
|
1027
|
+
session.onEvent((e) => emitted.push(e));
|
|
1028
|
+
const result = await session.waitForCompletion();
|
|
1029
|
+
|
|
1030
|
+
// Positive control: the key did reach the SDK config.
|
|
1031
|
+
expect(configOf(host).provider.nan.options.apiKey).toBe(SENTINEL);
|
|
1032
|
+
expect(captured.some((line) => line.includes("[opencode] config:"))).toBe(true);
|
|
1033
|
+
expect(result.isError).toBe(true);
|
|
1034
|
+
|
|
1035
|
+
for (const line of captured) expect(line).not.toContain(SENTINEL);
|
|
1036
|
+
expect(JSON.stringify(emitted)).not.toContain(SENTINEL);
|
|
1037
|
+
expect(result.failureReason ?? "").not.toContain(SENTINEL);
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
test("a host start failure that echoes the config is scrubbed before it propagates", async () => {
|
|
1041
|
+
captureConsole();
|
|
1042
|
+
const host = makeFakeHost([]);
|
|
1043
|
+
installHost(host, (options) => {
|
|
1044
|
+
const content = (options.config as { content: string }).content;
|
|
1045
|
+
throw new Error(`invalid config ${content}`);
|
|
1046
|
+
});
|
|
1047
|
+
const error = await new OpencodeAdapter()
|
|
1048
|
+
.createSession(testConfig({ model: "nan/glm5.3-flash", env: { NAN_API_KEY: SENTINEL } }))
|
|
1049
|
+
.then(
|
|
1050
|
+
() => undefined,
|
|
1051
|
+
(err: unknown) => err as Error,
|
|
1052
|
+
);
|
|
1053
|
+
expect(error?.message).toStartWith("opencode host failed to start: invalid config");
|
|
1054
|
+
expect(error?.message).not.toContain(SENTINEL);
|
|
1055
|
+
for (const line of captured) expect(line).not.toContain(SENTINEL);
|
|
1056
|
+
});
|
|
1057
|
+
});
|