@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,15 +1,8 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { createServer, type Server } from "node:http";
|
|
3
3
|
import { decryptSecret, encryptSecret, getEncryptionKey } from "../be/crypto";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
getDbClient,
|
|
7
|
-
getKv,
|
|
8
|
-
getSwarmConfigs,
|
|
9
|
-
initDb,
|
|
10
|
-
upsertKv,
|
|
11
|
-
upsertSwarmConfig,
|
|
12
|
-
} from "../be/db";
|
|
4
|
+
import { closeDb, getDbClient, getSwarmConfigs, initDb, upsertSwarmConfig } from "../be/db";
|
|
5
|
+
import { getCodexDeviceFlow, upsertCodexDeviceFlow } from "../be/db/codex-oauth-device-flows";
|
|
13
6
|
import type { OnboardingState } from "../be/onboarding";
|
|
14
7
|
import { handleCodexOAuthDevice } from "../http/codex-oauth-device";
|
|
15
8
|
import { handleCore } from "../http/core";
|
|
@@ -97,7 +90,7 @@ afterAll(async () => {
|
|
|
97
90
|
});
|
|
98
91
|
|
|
99
92
|
beforeEach(async () => {
|
|
100
|
-
await getDbClient().run("DELETE FROM
|
|
93
|
+
await getDbClient().run("DELETE FROM codex_oauth_device_flows");
|
|
101
94
|
await getDbClient().run("DELETE FROM swarm_config");
|
|
102
95
|
fetchImpl = async () => {
|
|
103
96
|
throw new Error("Unexpected fetch");
|
|
@@ -221,8 +214,8 @@ describe("Codex device HTTP routes", () => {
|
|
|
221
214
|
|
|
222
215
|
const started = await startFlow();
|
|
223
216
|
expect(Number.isNaN(Date.parse(started.expiresAt))).toBe(false);
|
|
224
|
-
const encrypted = await
|
|
225
|
-
expect(encrypted?.
|
|
217
|
+
const encrypted = await getCodexDeviceFlow(started.flowId);
|
|
218
|
+
expect(encrypted?.state).not.toContain("device-auth-full");
|
|
226
219
|
|
|
227
220
|
const poll = await request(`/api/codex-oauth/device/${started.flowId}/poll`);
|
|
228
221
|
expect(await poll.json()).toEqual({ status: "complete", slot: 1 });
|
|
@@ -318,17 +311,15 @@ describe("Codex device HTTP routes", () => {
|
|
|
318
311
|
const secondResponse = await request(path);
|
|
319
312
|
const secondBody = await secondResponse.json();
|
|
320
313
|
const callsBeforeRelease = { pollCalls, exchangeCalls };
|
|
321
|
-
const entry = await
|
|
322
|
-
const state = JSON.parse(decryptSecret(
|
|
314
|
+
const entry = await getCodexDeviceFlow(started.flowId);
|
|
315
|
+
const state = JSON.parse(decryptSecret(entry!.state, getEncryptionKey())) as Record<
|
|
323
316
|
string,
|
|
324
317
|
unknown
|
|
325
318
|
>;
|
|
326
319
|
state.pollLeaseExpiresAt = Date.now() - 1;
|
|
327
|
-
await
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
value: encryptSecret(JSON.stringify(state), getEncryptionKey()),
|
|
331
|
-
valueType: "string",
|
|
320
|
+
await upsertCodexDeviceFlow({
|
|
321
|
+
flowId: started.flowId,
|
|
322
|
+
state: encryptSecret(JSON.stringify(state), getEncryptionKey()),
|
|
332
323
|
expiresAt: Number(state.expiresAt),
|
|
333
324
|
});
|
|
334
325
|
releaseExchange();
|
|
@@ -359,19 +350,17 @@ describe("Codex device HTTP routes", () => {
|
|
|
359
350
|
}) as typeof fetch;
|
|
360
351
|
|
|
361
352
|
const started = await startFlow();
|
|
362
|
-
const entry = await
|
|
363
|
-
const state = JSON.parse(decryptSecret(
|
|
353
|
+
const entry = await getCodexDeviceFlow(started.flowId);
|
|
354
|
+
const state = JSON.parse(decryptSecret(entry!.state, getEncryptionKey())) as Record<
|
|
364
355
|
string,
|
|
365
356
|
unknown
|
|
366
357
|
>;
|
|
367
358
|
state.lastPolledAt = Date.now() - 10_000;
|
|
368
359
|
state.pollLeaseId = crypto.randomUUID();
|
|
369
360
|
state.pollLeaseExpiresAt = Date.now() - 1;
|
|
370
|
-
await
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
value: encryptSecret(JSON.stringify(state), getEncryptionKey()),
|
|
374
|
-
valueType: "string",
|
|
361
|
+
await upsertCodexDeviceFlow({
|
|
362
|
+
flowId: started.flowId,
|
|
363
|
+
state: encryptSecret(JSON.stringify(state), getEncryptionKey()),
|
|
375
364
|
expiresAt: Number(state.expiresAt),
|
|
376
365
|
});
|
|
377
366
|
|
|
@@ -380,9 +369,9 @@ describe("Codex device HTTP routes", () => {
|
|
|
380
369
|
});
|
|
381
370
|
expect(pollCalls).toBe(1);
|
|
382
371
|
|
|
383
|
-
const recovered = await
|
|
372
|
+
const recovered = await getCodexDeviceFlow(started.flowId);
|
|
384
373
|
const recoveredState = JSON.parse(
|
|
385
|
-
decryptSecret(
|
|
374
|
+
decryptSecret(recovered!.state, getEncryptionKey()),
|
|
386
375
|
) as Record<string, unknown>;
|
|
387
376
|
expect(recoveredState.pollLeaseId).toBeNull();
|
|
388
377
|
expect(recoveredState.pollLeaseExpiresAt).toBeNull();
|
|
@@ -403,17 +392,15 @@ describe("Codex device HTTP routes", () => {
|
|
|
403
392
|
}) as typeof fetch;
|
|
404
393
|
|
|
405
394
|
const started = await startFlow();
|
|
406
|
-
const entry = await
|
|
407
|
-
const state = JSON.parse(decryptSecret(
|
|
395
|
+
const entry = await getCodexDeviceFlow(started.flowId);
|
|
396
|
+
const state = JSON.parse(decryptSecret(entry!.state, getEncryptionKey())) as Record<
|
|
408
397
|
string,
|
|
409
398
|
unknown
|
|
410
399
|
>;
|
|
411
400
|
state.expiresAt = Date.now() - 1;
|
|
412
|
-
await
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
value: encryptSecret(JSON.stringify(state), getEncryptionKey()),
|
|
416
|
-
valueType: "string",
|
|
401
|
+
await upsertCodexDeviceFlow({
|
|
402
|
+
flowId: started.flowId,
|
|
403
|
+
state: encryptSecret(JSON.stringify(state), getEncryptionKey()),
|
|
417
404
|
expiresAt: Number(state.expiresAt),
|
|
418
405
|
});
|
|
419
406
|
|
|
@@ -482,8 +469,7 @@ describe("Codex device HTTP routes", () => {
|
|
|
482
469
|
}) as typeof fetch;
|
|
483
470
|
await getDbClient().run(
|
|
484
471
|
`CREATE TRIGGER fail_device_flow_write
|
|
485
|
-
BEFORE INSERT ON
|
|
486
|
-
WHEN NEW.namespace = 'codex-oauth-device'
|
|
472
|
+
BEFORE INSERT ON codex_oauth_device_flows
|
|
487
473
|
BEGIN
|
|
488
474
|
SELECT RAISE(ABORT, 'forced local write failure');
|
|
489
475
|
END`,
|
|
@@ -355,6 +355,7 @@ describe("credentialsToAuthJson", () => {
|
|
|
355
355
|
accountId: "c724a178-abc",
|
|
356
356
|
};
|
|
357
357
|
|
|
358
|
+
const before = Date.now();
|
|
358
359
|
const authJson = credentialsToAuthJson(creds);
|
|
359
360
|
expect(authJson.auth_mode).toBe("chatgpt");
|
|
360
361
|
expect(authJson.OPENAI_API_KEY).toBeNull();
|
|
@@ -362,7 +363,9 @@ describe("credentialsToAuthJson", () => {
|
|
|
362
363
|
expect(authJson.tokens.refresh_token).toBe(creds.refresh);
|
|
363
364
|
expect(authJson.tokens.account_id).toBe(creds.accountId);
|
|
364
365
|
expect(authJson.tokens.id_token).toBe(creds.access);
|
|
365
|
-
expect(authJson.last_refresh)
|
|
366
|
+
expect(Date.parse(authJson.last_refresh)).toBeGreaterThanOrEqual(before);
|
|
367
|
+
expect(Date.parse(authJson.last_refresh)).toBeLessThanOrEqual(Date.now());
|
|
368
|
+
expect(authJson.expires).toBe(creds.expires);
|
|
366
369
|
});
|
|
367
370
|
});
|
|
368
371
|
|
|
@@ -32,9 +32,9 @@ describe("getContextWindowSize", () => {
|
|
|
32
32
|
expect(getContextWindowSize("sonnet")).toBe(1_000_000);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
test("returns 200K for haiku
|
|
35
|
+
test("returns 200K for Haiku 4.5 and 1M for the haiku shortname (Haiku 5.5)", () => {
|
|
36
36
|
expect(getContextWindowSize("claude-haiku-4-5")).toBe(200_000);
|
|
37
|
-
expect(getContextWindowSize("haiku")).toBe(
|
|
37
|
+
expect(getContextWindowSize("haiku")).toBe(1_000_000);
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
test("reads OpenAI windows from the catalog", () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
buildCredStatusReport,
|
|
4
|
+
CREDENTIAL_PROVIDER_CHECKERS,
|
|
4
5
|
checkProviderCredentials,
|
|
5
6
|
isBedrockMode,
|
|
6
7
|
isCredCheckDisabled,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
shouldRefreshBedrockStatus,
|
|
9
10
|
validateProviderCredentials,
|
|
10
11
|
} from "../commands/provider-credentials";
|
|
12
|
+
import { ACP_TARGET_IDS } from "../providers/acp-target-catalog";
|
|
11
13
|
import { checkClaudeCredentials } from "../providers/claude-adapter";
|
|
12
14
|
import { checkClaudeManagedCredentials } from "../providers/claude-managed-adapter";
|
|
13
15
|
import { checkCodexCredentials } from "../providers/codex-adapter";
|
|
@@ -660,6 +662,54 @@ describe("checkOpencodeCredentials", () => {
|
|
|
660
662
|
});
|
|
661
663
|
});
|
|
662
664
|
|
|
665
|
+
describe("ACP readiness", () => {
|
|
666
|
+
test.each(["typo", "", " \t "])("rejects unsupported target %j", async (target) => {
|
|
667
|
+
const status = await checkProviderCredentials("acp", {
|
|
668
|
+
ACP_TARGET: target,
|
|
669
|
+
ACP_TARGET_COMMAND: "custom-acp --stdio",
|
|
670
|
+
});
|
|
671
|
+
expect(status.ready).toBe(false);
|
|
672
|
+
expect(status.missing).toEqual(["ACP_TARGET"]);
|
|
673
|
+
expect(status.hint).toContain("ACP_TARGET");
|
|
674
|
+
for (const supported of ACP_TARGET_IDS) expect(status.hint).toContain(supported);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test.each([undefined, "custom"])("requires a command for target %s", async (target) => {
|
|
678
|
+
const status = await CREDENTIAL_PROVIDER_CHECKERS.acp({ ACP_TARGET: target });
|
|
679
|
+
expect(status.ready).toBe(false);
|
|
680
|
+
expect(status.missing).toEqual(["ACP_TARGET_COMMAND"]);
|
|
681
|
+
expect(status.hint).toContain("ACP_TARGET_COMMAND");
|
|
682
|
+
expect(status.hint).toContain("ACP_COMMAND");
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test.each(["", " \t "])("rejects a blank custom command %j", async (command) => {
|
|
686
|
+
const status = await checkProviderCredentials("acp", {
|
|
687
|
+
ACP_TARGET: "custom",
|
|
688
|
+
ACP_TARGET_COMMAND: command,
|
|
689
|
+
ACP_COMMAND: "legacy-acp",
|
|
690
|
+
});
|
|
691
|
+
expect(status.ready).toBe(false);
|
|
692
|
+
expect(status.missing).toEqual(["ACP_TARGET_COMMAND"]);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test.each(["ACP_TARGET_COMMAND", "ACP_COMMAND"])("accepts a command from %s", async (key) => {
|
|
696
|
+
for (const target of [undefined, "custom"]) {
|
|
697
|
+
const status = await checkProviderCredentials("acp", {
|
|
698
|
+
ACP_TARGET: target,
|
|
699
|
+
[key]: "custom-acp --stdio",
|
|
700
|
+
});
|
|
701
|
+
expect(status).toEqual({ ready: true, missing: [], satisfiedBy: "sdk-delegated" });
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
test.each(
|
|
706
|
+
ACP_TARGET_IDS.filter((target) => target !== "custom"),
|
|
707
|
+
)("keeps preset %s ready without a custom command", async (target) => {
|
|
708
|
+
const status = await checkProviderCredentials("acp", { ACP_TARGET: target });
|
|
709
|
+
expect(status).toEqual({ ready: true, missing: [], satisfiedBy: "sdk-delegated" });
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
|
|
663
713
|
// ─── dispatcher ──────────────────────────────────────────────────────────────
|
|
664
714
|
|
|
665
715
|
describe("checkProviderCredentials dispatcher", () => {
|
|
@@ -720,7 +770,7 @@ describe("checkProviderCredentials dispatcher", () => {
|
|
|
720
770
|
).ready,
|
|
721
771
|
).toBe(true);
|
|
722
772
|
|
|
723
|
-
const acpStatus = await checkProviderCredentials("acp", {});
|
|
773
|
+
const acpStatus = await checkProviderCredentials("acp", { ACP_TARGET: "opencode" });
|
|
724
774
|
expect(acpStatus.ready).toBe(true);
|
|
725
775
|
expect(acpStatus.satisfiedBy).toBe("sdk-delegated");
|
|
726
776
|
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { createServer as createHttpServer, type Server } from "node:http";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { closeDb, getDb, initDb, upsertKv } from "../be/db";
|
|
6
|
+
import { upsertCodexDeviceFlow } from "../be/db/codex-oauth-device-flows";
|
|
7
|
+
import {
|
|
8
|
+
DbQueryDeniedTableError,
|
|
9
|
+
executeReadOnlyQueryGated,
|
|
10
|
+
handleDbQuery,
|
|
11
|
+
} from "../http/db-query";
|
|
12
|
+
import { executeReadOnlyQuery } from "../http/db-query-shared";
|
|
13
|
+
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
14
|
+
import { listenOnFreePort } from "./test-net";
|
|
15
|
+
|
|
16
|
+
// A real on-disk database: the bounded path spawns a child process that opens
|
|
17
|
+
// its own connection, so an in-memory test template would be invisible to it.
|
|
18
|
+
const DB_PATH = join(tmpdir(), `db-query-denylist-${process.pid}-${Date.now()}.sqlite`);
|
|
19
|
+
|
|
20
|
+
const testGlobals = globalThis as typeof globalThis & {
|
|
21
|
+
__testMigrationTemplate?: Uint8Array;
|
|
22
|
+
};
|
|
23
|
+
let savedTemplate: Uint8Array | undefined;
|
|
24
|
+
|
|
25
|
+
// Built at runtime so no secret-shaped literal lands in the repo.
|
|
26
|
+
const syntheticToken = () => `gh${"p"}_${"K".repeat(36)}`;
|
|
27
|
+
|
|
28
|
+
async function removeDb(): Promise<void> {
|
|
29
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
30
|
+
try {
|
|
31
|
+
await Bun.file(`${DB_PATH}${suffix}`).delete();
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
savedTemplate = testGlobals.__testMigrationTemplate;
|
|
38
|
+
testGlobals.__testMigrationTemplate = undefined;
|
|
39
|
+
closeDb();
|
|
40
|
+
await removeDb();
|
|
41
|
+
initDb(DB_PATH);
|
|
42
|
+
const db = getDb();
|
|
43
|
+
db.run("CREATE VIEW denylist_test_view AS SELECT * FROM oauth_authorizations");
|
|
44
|
+
db.run("CREATE TABLE denylist_test_notes (id INTEGER PRIMARY KEY, body TEXT)");
|
|
45
|
+
db.run("INSERT INTO denylist_test_notes (body) VALUES (?)", [
|
|
46
|
+
`deploy used ${syntheticToken()} from the env dump`,
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterAll(async () => {
|
|
51
|
+
closeDb();
|
|
52
|
+
testGlobals.__testMigrationTemplate = savedTemplate;
|
|
53
|
+
await removeDb();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const DENIED_QUERIES: Array<[string, string, string]> = [
|
|
57
|
+
["direct read", "SELECT * FROM oauth_authorizations", "oauth_authorizations"],
|
|
58
|
+
["alias", "SELECT a.accessToken FROM oauth_authorizations AS a", "oauth_authorizations"],
|
|
59
|
+
["CTE", "WITH t AS (SELECT * FROM oauth_authorizations) SELECT * FROM t", "oauth_authorizations"],
|
|
60
|
+
[
|
|
61
|
+
"join",
|
|
62
|
+
"SELECT n.body, p.codeVerifier FROM denylist_test_notes n JOIN oauth_pending p ON 1 = 1",
|
|
63
|
+
"oauth_pending",
|
|
64
|
+
],
|
|
65
|
+
["subquery", "SELECT * FROM (SELECT * FROM user_tokens)", "user_tokens"],
|
|
66
|
+
["view", "SELECT * FROM denylist_test_view", "oauth_authorizations"],
|
|
67
|
+
["scalar subquery", "SELECT (SELECT clientSecret FROM oauth_apps LIMIT 1) AS s", "oauth_apps"],
|
|
68
|
+
["count only", "SELECT COUNT(*) FROM session_tokens", "session_tokens"],
|
|
69
|
+
["mixed case", "select * from Script_Apis", "script_apis"],
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
describe("db-query credential-table denylist", () => {
|
|
73
|
+
for (const [label, sql, table] of DENIED_QUERIES) {
|
|
74
|
+
test(`in-process path rejects a ${label}`, () => {
|
|
75
|
+
expect(() => executeReadOnlyQuery(sql)).toThrow(DbQueryDeniedTableError);
|
|
76
|
+
expect(() => executeReadOnlyQuery(sql)).toThrow(table);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
test("bounded path rejects before spawning a child", async () => {
|
|
81
|
+
for (const [, sql, table] of DENIED_QUERIES) {
|
|
82
|
+
await expect(executeReadOnlyQueryGated(sql, [], 5_000, 10)).rejects.toThrow(table);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("allows swarm_config, EXPLAIN and a table name in a string literal", async () => {
|
|
87
|
+
const config = await executeReadOnlyQueryGated("SELECT key FROM swarm_config", [], 5_000, 10);
|
|
88
|
+
expect(config.columns).toEqual(["key"]);
|
|
89
|
+
|
|
90
|
+
const explained = executeReadOnlyQuery("EXPLAIN QUERY PLAN SELECT * FROM oauth_authorizations");
|
|
91
|
+
expect(explained.columns.length).toBeGreaterThan(0);
|
|
92
|
+
|
|
93
|
+
const literal = executeReadOnlyQuery("SELECT 'oauth_authorizations' AS name");
|
|
94
|
+
expect(literal.rows).toEqual([["oauth_authorizations"]]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("POST /api/db-query", () => {
|
|
99
|
+
let server: Server;
|
|
100
|
+
let baseUrl = "";
|
|
101
|
+
|
|
102
|
+
beforeAll(async () => {
|
|
103
|
+
server = createHttpServer(async (req, res) => {
|
|
104
|
+
const handled = await handleDbQuery(
|
|
105
|
+
req,
|
|
106
|
+
res,
|
|
107
|
+
getPathSegments(req.url || ""),
|
|
108
|
+
parseQueryParams(req.url || ""),
|
|
109
|
+
);
|
|
110
|
+
if (!handled) {
|
|
111
|
+
res.writeHead(404);
|
|
112
|
+
res.end();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
baseUrl = `http://localhost:${await listenOnFreePort(server)}`;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
afterAll(async () => {
|
|
119
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
async function post(sql: string) {
|
|
123
|
+
const res = await fetch(`${baseUrl}/api/db-query`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: { "Content-Type": "application/json" },
|
|
126
|
+
body: JSON.stringify({ sql }),
|
|
127
|
+
});
|
|
128
|
+
return { status: res.status, body: (await res.json()) as Record<string, unknown> };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
test("rejects a credential table with a clear 400", async () => {
|
|
132
|
+
const { status, body } = await post("SELECT * FROM oauth_authorizations");
|
|
133
|
+
expect(status).toBe(400);
|
|
134
|
+
expect(String(body.error)).toContain("oauth_authorizations");
|
|
135
|
+
expect(String(body.error)).toContain("credentials");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("redacts a secret stored in an allowed table and keeps the context", async () => {
|
|
139
|
+
const { status, body } = await post("SELECT body FROM denylist_test_notes");
|
|
140
|
+
expect(status).toBe(200);
|
|
141
|
+
const serialized = JSON.stringify(body.rows);
|
|
142
|
+
expect(serialized).not.toContain(syntheticToken());
|
|
143
|
+
expect(serialized).toContain("[REDACTED:");
|
|
144
|
+
expect(serialized).toContain("from the env dump");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("denies Codex device-login state and keeps general KV readable", async () => {
|
|
148
|
+
const marker = `device-state-${crypto.randomUUID()}`;
|
|
149
|
+
await upsertCodexDeviceFlow({
|
|
150
|
+
flowId: crypto.randomUUID(),
|
|
151
|
+
state: marker,
|
|
152
|
+
expiresAt: Date.now() + 60_000,
|
|
153
|
+
});
|
|
154
|
+
await upsertKv({
|
|
155
|
+
namespace: "denylist-test",
|
|
156
|
+
key: "note",
|
|
157
|
+
value: "plain kv value",
|
|
158
|
+
valueType: "string",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
for (const sql of [
|
|
162
|
+
"SELECT * FROM codex_oauth_device_flows",
|
|
163
|
+
"SELECT f.state FROM codex_oauth_device_flows f JOIN kv_entries k ON 1",
|
|
164
|
+
]) {
|
|
165
|
+
const { status, body } = await post(sql);
|
|
166
|
+
expect(status).toBe(400);
|
|
167
|
+
expect(String(body.error)).toContain("codex_oauth_device_flows");
|
|
168
|
+
expect(JSON.stringify(body)).not.toContain(marker);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const kv = await post("SELECT namespace, value FROM kv_entries");
|
|
172
|
+
expect(kv.status).toBe(200);
|
|
173
|
+
const rows = JSON.stringify(kv.body.rows);
|
|
174
|
+
expect(rows).toContain("plain kv value");
|
|
175
|
+
expect(rows).not.toContain("codex-oauth-device");
|
|
176
|
+
expect(rows).not.toContain(marker);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("migration 202 moves legacy kv device flows out of kv_entries", async () => {
|
|
180
|
+
const flowId = crypto.randomUUID();
|
|
181
|
+
const marker = `legacy-state-${crypto.randomUUID()}`;
|
|
182
|
+
await upsertKv({
|
|
183
|
+
namespace: "codex-oauth-device",
|
|
184
|
+
key: flowId,
|
|
185
|
+
value: marker,
|
|
186
|
+
valueType: "string",
|
|
187
|
+
expiresAt: Date.now() + 60_000,
|
|
188
|
+
});
|
|
189
|
+
const migration = await Bun.file(
|
|
190
|
+
join(import.meta.dir, "../be/migrations/202_codex_oauth_device_flows.sql"),
|
|
191
|
+
).text();
|
|
192
|
+
getDb().exec(migration);
|
|
193
|
+
|
|
194
|
+
const kv = await post("SELECT key FROM kv_entries WHERE namespace = 'codex-oauth-device'");
|
|
195
|
+
expect(kv.status).toBe(200);
|
|
196
|
+
expect(kv.body.rows).toEqual([]);
|
|
197
|
+
const moved = getDb()
|
|
198
|
+
.query("SELECT state FROM codex_oauth_device_flows WHERE flow_id = ?")
|
|
199
|
+
.get(flowId) as { state: string } | null;
|
|
200
|
+
expect(moved?.state).toBe(marker);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -9,7 +9,10 @@ import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
|
|
|
9
9
|
import { DEFAULT_MODEL_TIER_MAP } from "../types";
|
|
10
10
|
import { getModelAwareCredentialVars } from "../utils/credentials";
|
|
11
11
|
import { resolveHarnessProvider } from "../utils/harness-provider";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_OPENROUTER_BASE_URL,
|
|
14
|
+
OPENROUTER_APP_ATTRIBUTION_HEADERS,
|
|
15
|
+
} from "../utils/openrouter-base-url";
|
|
13
16
|
|
|
14
17
|
const directories: string[] = [];
|
|
15
18
|
afterEach(async () => {
|
|
@@ -166,6 +169,7 @@ describe("dsh harness", () => {
|
|
|
166
169
|
openrouter: {
|
|
167
170
|
apiKeyEnv: "OPENROUTER_API_KEY",
|
|
168
171
|
baseURL: DEFAULT_OPENROUTER_BASE_URL,
|
|
172
|
+
headers: OPENROUTER_APP_ATTRIBUTION_HEADERS,
|
|
169
173
|
api: "openai-completions",
|
|
170
174
|
models: [{ id: modelId }],
|
|
171
175
|
},
|
|
@@ -316,6 +320,24 @@ describe("dsh harness", () => {
|
|
|
316
320
|
model: "deepseek/deepseek-v4.1-flash",
|
|
317
321
|
});
|
|
318
322
|
expect(patch[2].config.providers.openrouter.baseURL).toBe("https://gateway.example/proxy/v1");
|
|
323
|
+
// A gateway gets no OpenRouter app attribution.
|
|
324
|
+
expect(patch[2].config.providers.openrouter).not.toHaveProperty("headers");
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("OPENROUTER_APP_ATTRIBUTION=false drops the attribution headers", async () => {
|
|
328
|
+
const config = await fixture();
|
|
329
|
+
config.model = "openrouter/vendor/new-model";
|
|
330
|
+
config.env = {
|
|
331
|
+
...config.env,
|
|
332
|
+
DEEPSEEK_API_KEY: "",
|
|
333
|
+
OPENROUTER_API_KEY: "openrouter-test-key",
|
|
334
|
+
OPENROUTER_APP_ATTRIBUTION: "false",
|
|
335
|
+
};
|
|
336
|
+
const session = await new DshAdapter().createSession(config);
|
|
337
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
338
|
+
const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
|
|
339
|
+
expect(patch[2].config.providers.openrouter.baseURL).toBe(DEFAULT_OPENROUTER_BASE_URL);
|
|
340
|
+
expect(patch[2].config.providers.openrouter).not.toHaveProperty("headers");
|
|
319
341
|
});
|
|
320
342
|
|
|
321
343
|
test("bare models keep direct DeepSeek when both keys exist", async () => {
|