@desplega.ai/agent-swarm 1.166.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-qz3m692w.js → actions-fq2zrwtf.js} +25 -25
- package/dist/{amp-adapter-j1xbmsqz.js → amp-adapter-5gbk7501.js} +4 -4
- package/dist/{anthropic-messages-hst62ws9.js → anthropic-messages-13yn502s.js} +4 -4
- package/dist/{app-43vrdmv8.js → app-nctetn1e.js} +10 -9
- package/dist/{assistant-ertffmka.js → assistant-0aq8gjvj.js} +26 -26
- package/dist/{azure-openai-responses-mh8tvf78.js → azure-openai-responses-9st34pfz.js} +5 -5
- package/dist/{boot-reembed-998jtpdg.js → boot-reembed-1ze5853n.js} +9 -8
- package/dist/{boot-reembed-s7k7c6v2.js → boot-reembed-fmp5ww69.js} +9 -8
- package/dist/{boot-scrub-sweep-nepsk4t1.js → boot-scrub-sweep-cbp8xd4e.js} +10 -9
- package/dist/{boot-seeds-vvzs1zhk.js → boot-seeds-22tx8a53.js} +12 -11
- package/dist/{claude-adapter-2zvmekrp.js → claude-adapter-0jhgeh1d.js} +24 -22
- package/dist/{claude-managed-adapter-s49wbk8t.js → claude-managed-adapter-tzwxy803.js} +4 -4
- package/dist/{claude-managed-setup-cvhqq8qn.js → claude-managed-setup-nrhfx5b0.js} +3 -3
- package/dist/{claude-sdk-session-kkbxtw5q.js → claude-sdk-session-wh77rh83.js} +22 -22
- package/dist/{cli-zsh093hk.js → cli-014vns2y.js} +63 -1
- package/dist/{cli-qvfk83fy.js → cli-13xwcqvj.js} +2 -2
- package/dist/{cli-dw2q9f7g.js → cli-1yrcfa5s.js} +4 -4
- package/dist/{cli-jvj6a0cv.js → cli-577bxkf6.js} +27 -17
- package/dist/{cli-zy7vwsne.js → cli-5qkcdv8c.js} +16 -9
- package/dist/{cli-cwxhbf43.js → cli-5t5my0h8.js} +85 -2
- package/dist/{cli-y59h2pk9.js → cli-69egj654.js} +1 -1
- package/dist/{cli-68r1j4sg.js → cli-6bpvv8h4.js} +3 -3
- package/dist/{cli-cpfmqz3n.js → cli-6qxmjpem.js} +5 -5
- package/dist/{cli-aqcey741.js → cli-6vfz44sr.js} +1 -1
- package/dist/{cli-32bca7nr.js → cli-7avy4v8w.js} +1 -1
- package/dist/{cli-f2n17zhp.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-gt8azyet.js → cli-c2tpw9ck.js} +1 -1
- package/dist/{cli-ecknp7p1.js → cli-c8r99akr.js} +10 -10
- package/dist/{cli-3qn4r334.js → cli-cexctgn5.js} +20 -14
- package/dist/{cli-vzc73wet.js → cli-cf2vgn9d.js} +87 -69
- package/dist/{cli-ng97fjq7.js → cli-cmxkbzzs.js} +33 -7
- package/dist/{cli-nexwh8zg.js → cli-g7k7zbcz.js} +12 -5
- package/dist/{cli-nvvfd8bq.js → cli-jbzbr36n.js} +4091 -3793
- package/dist/{cli-3ddybbbx.js → cli-jvmtxqtc.js} +26 -14
- package/dist/{cli-pkke6geq.js → cli-km4x66j0.js} +83 -65
- package/dist/{cli-j2jbr8af.js → cli-kw0jcfje.js} +1 -1
- package/dist/{cli-8t229cmr.js → cli-m7vmeepg.js} +30 -17
- package/dist/{cli-yshaazs1.js → cli-mkdsy1nq.js} +62 -20
- package/dist/{cli-b0f70z24.js → cli-mn8qkd8v.js} +1 -1
- package/dist/{cli-0qjcsxgz.js → cli-mxry91sa.js} +30 -9
- package/dist/cli-nkqjj42q.js +104 -0
- package/dist/{cli-rggdq8wq.js → cli-p8rt8spp.js} +3 -3
- package/dist/{cli-v2a8x45d.js → cli-pkj1w46r.js} +10 -5
- package/dist/cli-q1vnz8wv.js +96 -0
- package/dist/{cli-fj86xr3c.js → cli-qd97v6z2.js} +2 -2
- package/dist/{cli-66zh26sp.js → cli-qzmzwegq.js} +57 -17
- package/dist/{cli-p4ztb80h.js → cli-r7eeddkq.js} +13 -8
- package/dist/{cli-8ssjez7e.js → cli-rhy7z1rp.js} +22 -11
- package/dist/{cli-4mevd1hf.js → cli-s9y81s7j.js} +5 -5
- package/dist/{cli-h3x613ze.js → cli-tc5zec7v.js} +6 -6
- package/dist/{cli-ttbsse3f.js → cli-tmhsxhb8.js} +1 -1
- package/dist/{cli-0nhdwxd5.js → cli-tx2zvn23.js} +3 -3
- package/dist/{cli-mwegs4af.js → cli-ty4bzkqr.js} +3 -3
- package/dist/{cli-ysnyhws9.js → cli-v1pwxreq.js} +54 -51
- package/dist/{cli-feh3dkah.js → cli-v1w7pbd8.js} +2 -1
- package/dist/{cli-10zmp1p7.js → cli-vr5avem2.js} +27 -32
- package/dist/{cli-bzr0ftht.js → cli-wnb9b2g3.js} +19 -0
- package/dist/{cli-df56tqdw.js → cli-wydz3feg.js} +108 -57
- package/dist/{cli-krqm5f11.js → cli-xezn9nkh.js} +16 -13
- package/dist/{cli-8kt0bmgx.js → cli-xf713zz0.js} +2 -2
- package/dist/{cli-x7kpr72a.js → cli-y0kjpfm9.js} +2 -2
- package/dist/{cli-6zcfw0x7.js → cli-y195hv1a.js} +1 -1
- package/dist/{cli-fn697bnt.js → cli-ykt8f7va.js} +21 -8
- package/dist/{cli-4r1sjfc4.js → cli-ymmc1zxg.js} +8 -8
- package/dist/{cli-8kgvmkak.js → cli-ypgba1a8.js} +1 -1
- package/dist/{cli-rsy933yz.js → cli-z1dqhc9y.js} +30 -19
- package/dist/{cli-qbksc292.js → cli-zy39d433.js} +2 -0
- package/dist/cli.js +26 -26
- package/dist/{cloudflare-workers-ai-system-one-tt484t61.js → cloudflare-workers-ai-system-one-sta45jf9.js} +7 -5
- package/dist/{codex-adapter-dw1wxqt8.js → codex-adapter-cgesnjkr.js} +18 -18
- package/dist/{codex-login-terbdsqq.js → codex-login-g1cyt5zm.js} +1 -1
- package/dist/{codex-session-runner-3cgb8y1g.js → codex-session-runner-8kd7ddhr.js} +18 -18
- package/dist/{commands-mhxam6z2.js → commands-h6zaaxym.js} +9 -8
- package/dist/{cursor-adapter-2qda3st7.js → cursor-adapter-v8mm6bp5.js} +5 -5
- package/dist/{db-sjks418k.js → db-qg3myn53.js} +9 -8
- package/dist/{dispatcher-1es759rn.js → dispatcher-7vnwj9qw.js} +24 -24
- package/dist/{dsh-adapter-bek4cyw9.js → dsh-adapter-tz6hfp5r.js} +5 -5
- package/dist/{easter-egg-3d-mtpnw06s.js → easter-egg-3d-66zxervv.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-ar9cw9jg.js → grok-adapter-jdt2dz2s.js} +18 -8
- package/dist/{handlers-1gkbhktr.js → handlers-2qtkpncr.js} +27 -27
- package/dist/{hook-ds2zxq9v.js → hook-51yfcgsv.js} +42 -31
- package/dist/{hook-vxa3tcsy.js → hook-qnbdgwvd.js} +14 -14
- package/dist/{http-3h21z9md.js → http-jsmtag08.js} +320 -98
- package/dist/{index-9b65j1m5.js → index-2t9kesn1.js} +9 -8
- package/dist/{index-dkfxpkmm.js → index-bt8wkv1w.js} +25 -25
- package/dist/{index-f8xnqrye.js → index-cx1sb9bq.js} +33 -24
- package/dist/{index-bn6cy8ps.js → index-ekywyv2v.js} +24 -24
- package/dist/{index-n99fycq7.js → index-t4g06w93.js} +24 -24
- package/dist/{keepalive-3xspa9jw.js → keepalive-4aezx4bj.js} +11 -10
- package/dist/{lead-6qphc124.js → lead-r44bkpm5.js} +47 -47
- package/dist/{llama-cpp-classify-tgj35k8d.js → llama-cpp-classify-6erb7wwv.js} +3 -1
- package/dist/{maintenance-0teem1bb.js → maintenance-grz5jrzh.js} +9 -8
- package/dist/{mistral-conversations-8445ddjn.js → mistral-conversations-059y51zt.js} +4 -4
- package/dist/{node-9sg7ajby.js → node-qm9eb6z0.js} +1 -1
- package/dist/{oauth-refresh-sweep-sh9q25xp.js → oauth-refresh-sweep-wm4ctjfj.js} +10 -9
- package/dist/{onboard-54qmnqxe.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-wjfa2wvd.js → opencode-adapter-yjyqynzf.js} +7 -7
- package/dist/{openrouter-images-8yx3487g.js → openrouter-images-kr60n6b9.js} +1 -1
- package/dist/{otel-impl-ycqzp2a1.js → otel-impl-ghh4dyf8.js} +1 -1
- package/dist/{pi-messages-vxdt8v7j.js → pi-messages-xszk613n.js} +1 -1
- package/dist/{pi-mono-adapter-e77e9zst.js → pi-mono-adapter-0eg8kw2c.js} +68 -51
- package/dist/{pricing-refresh-atnkngqs.js → pricing-refresh-mjrqvnvw.js} +11 -10
- package/dist/{rbac-roles-j78wdksm.js → rbac-roles-axv4d74x.js} +10 -9
- package/dist/{rbac-roles-9pjphh9q.js → rbac-roles-fd5tskj9.js} +9 -8
- package/dist/{render-v2-pxjs49fx.js → render-v2-k50j33qd.js} +10 -9
- package/dist/{runtime-v2t1r79e.js → runtime-h9ha1mq6.js} +4 -4
- package/dist/{setup-ymej0xtx.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-3nh4ymzb.js → work-objects-jcdbg8rr.js} +27 -27
- package/dist/{worker-qa9fkz5s.js → worker-x8j8c9sx.js} +47 -47
- package/openapi.json +1 -1
- package/package.json +8 -8
- package/src/be/memory/providers/openai-embedding.ts +7 -2
- package/src/be/memory/raters/llm-summarizer.ts +7 -2
- package/src/be/modelsdev-cache.json +61 -0
- package/src/be/seed-pricing.ts +84 -53
- package/src/be/swarm-config-guard.ts +13 -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/mcp.ts +5 -0
- package/src/http/onboarding.ts +2 -0
- package/src/providers/acp-targets.ts +9 -4
- 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/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/server.ts +63 -3
- package/src/tests/acp-adapter.test.ts +152 -12
- 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/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.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/dsh-adapter.test.ts +23 -1
- package/src/tests/enabled-tools.test.ts +269 -0
- package/src/tests/entrypoint-codex-oauth-seed.test.ts +12 -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/telemetry-trigger.test.ts +12 -1
- package/src/tests/workflow-llm.test.ts +14 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +33 -0
- package/src/tools/request-human-input.ts +7 -2
- 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/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/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-yzfxn8vj.js +0 -23
- package/dist/cli-3dpj7atn.js +0 -11
- package/dist/cli-gvcgmx69.js +0 -183
- package/dist/{cli-jhm6hzjn.js → cli-mxvnnv5q.js} +7 -7
- package/dist/{codex-hook-d2rkvf39.js → codex-hook-15dgc4cb.js} +1 -1
|
@@ -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
|
});
|
|
@@ -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 () => {
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
spyOn,
|
|
9
|
+
test,
|
|
10
|
+
} from "bun:test";
|
|
11
|
+
import { unlink } from "node:fs/promises";
|
|
12
|
+
import {
|
|
13
|
+
createServer as createHttpServer,
|
|
14
|
+
type IncomingMessage,
|
|
15
|
+
type Server,
|
|
16
|
+
type ServerResponse,
|
|
17
|
+
} from "node:http";
|
|
18
|
+
import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
19
|
+
import { closeDb, createAgent, getDbClient, initDb, upsertSwarmConfig } from "../be/db";
|
|
20
|
+
import { validateConfigValue } from "../be/swarm-config-guard";
|
|
21
|
+
import {
|
|
22
|
+
_resetAutoReloadForTests,
|
|
23
|
+
flushPendingIntegrationsReload,
|
|
24
|
+
loadGlobalConfigsIntoEnv,
|
|
25
|
+
} from "../http/core";
|
|
26
|
+
import { handleMcp } from "../http/mcp";
|
|
27
|
+
import { createServer } from "../server";
|
|
28
|
+
import { parseEnabledTools } from "../utils/enabled-tools";
|
|
29
|
+
import { listenOnFreePort } from "./test-net";
|
|
30
|
+
|
|
31
|
+
const TEST_DB_PATH = "./test-enabled-tools.sqlite";
|
|
32
|
+
|
|
33
|
+
async function removeDbFiles(path: string): Promise<void> {
|
|
34
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
35
|
+
try {
|
|
36
|
+
await unlink(path + suffix);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function toolNames(opts: Parameters<typeof createServer>[0]): Promise<string[]> {
|
|
44
|
+
const server = (await createServer(opts)) as unknown as {
|
|
45
|
+
_registeredTools: Record<string, unknown>;
|
|
46
|
+
};
|
|
47
|
+
return Object.keys(server._registeredTools).sort();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseMcpPayload(text: string): unknown {
|
|
51
|
+
const trimmed = text.trim();
|
|
52
|
+
if (trimmed.startsWith("event:") || trimmed.startsWith("data:")) {
|
|
53
|
+
return JSON.parse(
|
|
54
|
+
trimmed
|
|
55
|
+
.split("\n")
|
|
56
|
+
.filter((line) => line.startsWith("data:"))
|
|
57
|
+
.map((line) => line.slice("data:".length).trim())
|
|
58
|
+
.join("\n"),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return JSON.parse(trimmed);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let httpServer: Server;
|
|
65
|
+
let baseUrl: string;
|
|
66
|
+
let savedEnv: string | undefined;
|
|
67
|
+
let savedCapabilities: string | undefined;
|
|
68
|
+
|
|
69
|
+
async function mcpPost(agentId: string, body: Record<string, unknown>, sessionId?: string) {
|
|
70
|
+
const headers: Record<string, string> = {
|
|
71
|
+
Accept: "application/json, text/event-stream",
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"X-Agent-ID": agentId,
|
|
74
|
+
};
|
|
75
|
+
if (sessionId) headers["mcp-session-id"] = sessionId;
|
|
76
|
+
const response = await fetch(`${baseUrl}/mcp`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers,
|
|
79
|
+
body: JSON.stringify(body),
|
|
80
|
+
});
|
|
81
|
+
const text = await response.text();
|
|
82
|
+
return { response, payload: text ? parseMcpPayload(text) : null };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function listSessionTools(agentId: string): Promise<string[]> {
|
|
86
|
+
const init = await mcpPost(agentId, {
|
|
87
|
+
jsonrpc: "2.0",
|
|
88
|
+
id: 1,
|
|
89
|
+
method: "initialize",
|
|
90
|
+
params: {
|
|
91
|
+
protocolVersion: "2024-11-05",
|
|
92
|
+
clientInfo: { name: "enabled-tools", version: "1" },
|
|
93
|
+
capabilities: {},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
const sessionId = init.response.headers.get("mcp-session-id");
|
|
97
|
+
if (!sessionId) throw new Error("missing MCP session ID");
|
|
98
|
+
await mcpPost(agentId, { jsonrpc: "2.0", method: "notifications/initialized" }, sessionId);
|
|
99
|
+
const listed = await mcpPost(
|
|
100
|
+
agentId,
|
|
101
|
+
{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
|
|
102
|
+
sessionId,
|
|
103
|
+
);
|
|
104
|
+
return (listed.payload as { result: { tools: Array<{ name: string }> } }).result.tools
|
|
105
|
+
.map((tool) => tool.name)
|
|
106
|
+
.sort();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
beforeAll(async () => {
|
|
110
|
+
// Drain any config reload a previous suite left queued (see
|
|
111
|
+
// scripts-only-gating.test.ts) before touching the global config rows.
|
|
112
|
+
const originalSlackDisable = process.env.SLACK_DISABLE;
|
|
113
|
+
process.env.SLACK_DISABLE = "true";
|
|
114
|
+
await flushPendingIntegrationsReload();
|
|
115
|
+
_resetAutoReloadForTests();
|
|
116
|
+
if (originalSlackDisable === undefined) delete process.env.SLACK_DISABLE;
|
|
117
|
+
else process.env.SLACK_DISABLE = originalSlackDisable;
|
|
118
|
+
|
|
119
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
120
|
+
closeDb();
|
|
121
|
+
initDb(TEST_DB_PATH);
|
|
122
|
+
|
|
123
|
+
const transports: Record<string, StreamableHTTPServerTransport> = {};
|
|
124
|
+
const sessionAgents: Record<string, string> = {};
|
|
125
|
+
httpServer = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
126
|
+
if (await handleMcp(req, res, transports, {}, sessionAgents)) return;
|
|
127
|
+
res.writeHead(404);
|
|
128
|
+
res.end("Not Found");
|
|
129
|
+
});
|
|
130
|
+
baseUrl = `http://127.0.0.1:${await listenOnFreePort(httpServer, "127.0.0.1")}`;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
afterAll(async () => {
|
|
134
|
+
await new Promise<void>((resolve) => httpServer.close(() => resolve()));
|
|
135
|
+
closeDb();
|
|
136
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
savedEnv = process.env.SWARM_ENABLED_TOOLS;
|
|
141
|
+
delete process.env.SWARM_ENABLED_TOOLS;
|
|
142
|
+
// Pin the default capability set; worker containers export their own.
|
|
143
|
+
savedCapabilities = process.env.CAPABILITIES;
|
|
144
|
+
delete process.env.CAPABILITIES;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
afterEach(async () => {
|
|
148
|
+
await getDbClient().run("DELETE FROM swarm_config WHERE key = 'SWARM_ENABLED_TOOLS'");
|
|
149
|
+
// Un-inject the global row so it cannot leak into later suites.
|
|
150
|
+
await loadGlobalConfigsIntoEnv(true);
|
|
151
|
+
if (savedEnv === undefined) delete process.env.SWARM_ENABLED_TOOLS;
|
|
152
|
+
else process.env.SWARM_ENABLED_TOOLS = savedEnv;
|
|
153
|
+
if (savedCapabilities === undefined) delete process.env.CAPABILITIES;
|
|
154
|
+
else process.env.CAPABILITIES = savedCapabilities;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("parseEnabledTools", () => {
|
|
158
|
+
test.each([
|
|
159
|
+
[undefined, undefined],
|
|
160
|
+
["", undefined],
|
|
161
|
+
[" ", undefined],
|
|
162
|
+
[" , ,", undefined],
|
|
163
|
+
["[]", undefined],
|
|
164
|
+
["get-tasks, store-progress", ["get-tasks", "store-progress"]],
|
|
165
|
+
["get-tasks,get-tasks,", ["get-tasks"]],
|
|
166
|
+
['["get-tasks", " store-progress "]', ["get-tasks", "store-progress"]],
|
|
167
|
+
] as Array<[string | undefined, string[] | undefined]>)("parses %#", (value, expected) => {
|
|
168
|
+
expect(parseEnabledTools(value)).toEqual(expected);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("rejects a JSON value that is not an array of strings", () => {
|
|
172
|
+
expect(() => parseEnabledTools("[1, 2]")).toThrow();
|
|
173
|
+
expect(() => parseEnabledTools("[not json")).toThrow();
|
|
174
|
+
expect(validateConfigValue("SWARM_ENABLED_TOOLS", "[1]")).toContain("SWARM_ENABLED_TOOLS");
|
|
175
|
+
expect(validateConfigValue("SWARM_ENABLED_TOOLS", "get-tasks,nope")).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("SWARM_ENABLED_TOOLS on the MCP server", () => {
|
|
180
|
+
test("unset keeps the capability-driven surface", async () => {
|
|
181
|
+
const unset = await toolNames({});
|
|
182
|
+
expect(unset).toContain("send-task");
|
|
183
|
+
// messaging is a default-disabled capability
|
|
184
|
+
expect(unset).not.toContain("post-message");
|
|
185
|
+
expect(await toolNames({ enabledTools: undefined })).toEqual(unset);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("set registers exactly the listed tools, ignoring capabilities", async () => {
|
|
189
|
+
expect(await toolNames({ enabledTools: "get-tasks, post-message" })).toEqual([
|
|
190
|
+
"get-tasks",
|
|
191
|
+
"post-message",
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("reads the deployment env when no session value is passed", async () => {
|
|
196
|
+
process.env.SWARM_ENABLED_TOOLS = "store-progress";
|
|
197
|
+
expect(await toolNames({})).toEqual(["store-progress"]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("overrides scripts-only mode", async () => {
|
|
201
|
+
expect(await toolNames({ scriptsOnly: true, enabledTools: "get-tasks" })).toEqual([
|
|
202
|
+
"get-tasks",
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("does not affect the full-surface bridge server", async () => {
|
|
207
|
+
const tools = await toolNames({ fullSurface: true, enabledTools: "get-tasks" });
|
|
208
|
+
expect(tools).toContain("post-message");
|
|
209
|
+
expect(tools.length).toBeGreaterThan(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("warns once on unknown names and keeps the known ones", async () => {
|
|
213
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
214
|
+
try {
|
|
215
|
+
const value = "get-tasks, no-such-tool-a1, no-such-tool-b2";
|
|
216
|
+
expect(await toolNames({ enabledTools: value })).toEqual(["get-tasks"]);
|
|
217
|
+
await toolNames({ enabledTools: value });
|
|
218
|
+
const messages = warn.mock.calls.map((call) => String(call[0]));
|
|
219
|
+
const unknown = messages.filter((m) => m.includes("unknown tools"));
|
|
220
|
+
expect(unknown).toEqual([
|
|
221
|
+
"[MCP] SWARM_ENABLED_TOOLS names unknown tools, ignoring them: no-such-tool-a1, no-such-tool-b2",
|
|
222
|
+
]);
|
|
223
|
+
} finally {
|
|
224
|
+
warn.mockRestore();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("an empty value logs and behaves as unset", async () => {
|
|
229
|
+
const unset = await toolNames({});
|
|
230
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
231
|
+
try {
|
|
232
|
+
expect(await toolNames({ enabledTools: " , " })).toEqual(unset);
|
|
233
|
+
expect(await toolNames({ enabledTools: "" })).toEqual(unset);
|
|
234
|
+
expect(
|
|
235
|
+
warn.mock.calls.some((call) => String(call[0]).includes("lists no tools; ignoring it")),
|
|
236
|
+
).toBe(true);
|
|
237
|
+
} finally {
|
|
238
|
+
warn.mockRestore();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("an agent-scoped value beats the swarm-wide one, per session", async () => {
|
|
243
|
+
const scoped = await createAgent({ name: "allowlist-agent", isLead: false, status: "idle" });
|
|
244
|
+
const neighbor = await createAgent({
|
|
245
|
+
name: "allowlist-neighbor",
|
|
246
|
+
isLead: false,
|
|
247
|
+
status: "idle",
|
|
248
|
+
});
|
|
249
|
+
await upsertSwarmConfig({ scope: "global", key: "SWARM_ENABLED_TOOLS", value: "get-tasks" });
|
|
250
|
+
await upsertSwarmConfig({
|
|
251
|
+
scope: "agent",
|
|
252
|
+
scopeId: scoped.id,
|
|
253
|
+
key: "SWARM_ENABLED_TOOLS",
|
|
254
|
+
value: "store-progress,post-message",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(await listSessionTools(scoped.id)).toEqual(["post-message", "store-progress"]);
|
|
258
|
+
expect(await listSessionTools(neighbor.id)).toEqual(["get-tasks"]);
|
|
259
|
+
|
|
260
|
+
// Reloadable: a config change applies on the next session, no restart.
|
|
261
|
+
await upsertSwarmConfig({
|
|
262
|
+
scope: "agent",
|
|
263
|
+
scopeId: scoped.id,
|
|
264
|
+
key: "SWARM_ENABLED_TOOLS",
|
|
265
|
+
value: "store-progress",
|
|
266
|
+
});
|
|
267
|
+
expect(await listSessionTools(scoped.id)).toEqual(["store-progress"]);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -119,6 +119,7 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
119
119
|
test(
|
|
120
120
|
"blanks refresh_token for a flat {access,refresh,accountId,expires} input",
|
|
121
121
|
async () => {
|
|
122
|
+
const before = Math.floor(Date.now() / 1000) * 1000;
|
|
122
123
|
const result = (await runJqFilter(filter, {
|
|
123
124
|
access: "example-access-tok",
|
|
124
125
|
refresh: "example-live-refresh-tok",
|
|
@@ -126,6 +127,8 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
126
127
|
expires: 1_800_000_000_000,
|
|
127
128
|
})) as {
|
|
128
129
|
auth_mode: string;
|
|
130
|
+
last_refresh: string;
|
|
131
|
+
expires: number;
|
|
129
132
|
tokens: {
|
|
130
133
|
id_token: string;
|
|
131
134
|
access_token: string;
|
|
@@ -139,6 +142,9 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
139
142
|
expect(result.tokens.id_token).toBe("example-access-tok");
|
|
140
143
|
expect(result.tokens.access_token).toBe("example-access-tok");
|
|
141
144
|
expect(result.tokens.account_id).toBe("acct-456");
|
|
145
|
+
expect(Date.parse(result.last_refresh)).toBeGreaterThanOrEqual(before);
|
|
146
|
+
expect(Date.parse(result.last_refresh)).toBeLessThanOrEqual(Date.now());
|
|
147
|
+
expect(result.expires).toBe(1_800_000_000_000);
|
|
142
148
|
},
|
|
143
149
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
144
150
|
);
|
|
@@ -182,6 +188,7 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
182
188
|
test(
|
|
183
189
|
"preserves refresh_token (from the flat `refresh` field) for a flat input on the standalone path",
|
|
184
190
|
async () => {
|
|
191
|
+
const before = Math.floor(Date.now() / 1000) * 1000;
|
|
185
192
|
const result = (await runJqFilter(
|
|
186
193
|
filter,
|
|
187
194
|
{
|
|
@@ -193,9 +200,14 @@ describe("docker-entrypoint.sh: codex_oauth boot-seed jq transform", () => {
|
|
|
193
200
|
true,
|
|
194
201
|
)) as {
|
|
195
202
|
tokens: { refresh_token: string };
|
|
203
|
+
last_refresh: string;
|
|
204
|
+
expires: number;
|
|
196
205
|
};
|
|
197
206
|
|
|
198
207
|
expect(result.tokens.refresh_token).toBe("example-live-refresh-tok");
|
|
208
|
+
expect(Date.parse(result.last_refresh)).toBeGreaterThanOrEqual(before);
|
|
209
|
+
expect(Date.parse(result.last_refresh)).toBeLessThanOrEqual(Date.now());
|
|
210
|
+
expect(result.expires).toBe(1_800_000_000_000);
|
|
199
211
|
},
|
|
200
212
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
201
213
|
);
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
buildGrokConfigToml,
|
|
26
26
|
checkGrokCredentials,
|
|
27
27
|
GrokAdapter,
|
|
28
|
+
grokOpenRouterModel,
|
|
28
29
|
installedClaudePluginNames,
|
|
29
30
|
} from "../providers/grok-adapter";
|
|
30
31
|
import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
|
|
@@ -487,6 +488,7 @@ describe("GrokAdapter", () => {
|
|
|
487
488
|
'base_url = "https://openrouter.ai/api/v1"',
|
|
488
489
|
'env_key = "OPENROUTER_API_KEY"',
|
|
489
490
|
'api_backend = "chat_completions"',
|
|
491
|
+
'extra_headers = { "HTTP-Referer" = "https://agent-swarm.dev", "X-OpenRouter-Title" = "Agent Swarm", "X-OpenRouter-Categories" = "personal-agent,cloud-agent" }',
|
|
490
492
|
].join("\n"),
|
|
491
493
|
);
|
|
492
494
|
} finally {
|
|
@@ -495,6 +497,17 @@ describe("GrokAdapter", () => {
|
|
|
495
497
|
}
|
|
496
498
|
});
|
|
497
499
|
|
|
500
|
+
test("OpenRouter app attribution rides only on direct openrouter.ai models", () => {
|
|
501
|
+
const toml = (env: Record<string, string>) =>
|
|
502
|
+
buildGrokConfigToml([], grokOpenRouterModel("openrouter/acme/thing", env));
|
|
503
|
+
expect(toml({})).toContain('extra_headers = { "HTTP-Referer" = "https://agent-swarm.dev"');
|
|
504
|
+
expect(toml({ OPENROUTER_BASE_URL: "https://gateway.example/v1" })).not.toContain(
|
|
505
|
+
"extra_headers",
|
|
506
|
+
);
|
|
507
|
+
expect(toml({ OPENROUTER_APP_ATTRIBUTION: "false" })).not.toContain("extra_headers");
|
|
508
|
+
expect(buildGrokConfigToml([], null)).not.toContain("extra_headers");
|
|
509
|
+
});
|
|
510
|
+
|
|
498
511
|
test("an xai/-qualified model runs as the bare xAI id", async () => {
|
|
499
512
|
const dir = makeTempDir();
|
|
500
513
|
const grok = writeFakeGrok(dir, "ok");
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { loadModelsDevCache } from "../be/modelsdev-cache";
|
|
3
|
+
import { buildModelsDevSeedRows } from "../be/seed-pricing";
|
|
4
|
+
|
|
5
|
+
function ratesFor(provider: string, model: string) {
|
|
6
|
+
const cache = loadModelsDevCache();
|
|
7
|
+
expect(cache).not.toBeNull();
|
|
8
|
+
return Object.fromEntries(
|
|
9
|
+
buildModelsDevSeedRows(cache!)
|
|
10
|
+
.filter((row) => row.provider === provider && row.model === model)
|
|
11
|
+
.map((row) => [row.tokenClass, row.pricePerMillionUsd]),
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("Haiku 5.5 pricing seed", () => {
|
|
16
|
+
test("seeds Anthropic rates for the claude harnesses, including the 1h cache write", () => {
|
|
17
|
+
for (const provider of ["claude", "claude-managed"]) {
|
|
18
|
+
expect(ratesFor(provider, "claude-haiku-5-5")).toEqual({
|
|
19
|
+
input: 0.1,
|
|
20
|
+
output: 0.5,
|
|
21
|
+
cached_input: 0.01,
|
|
22
|
+
cache_write: 0.125,
|
|
23
|
+
cache_write_1h: 0.2,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("seeds the same rates without a 1h class for amp and cursor", () => {
|
|
29
|
+
for (const provider of ["amp", "cursor"]) {
|
|
30
|
+
expect(ratesFor(provider, "claude-haiku-5-5")).toEqual({
|
|
31
|
+
input: 0.1,
|
|
32
|
+
output: 0.5,
|
|
33
|
+
cached_input: 0.01,
|
|
34
|
+
cache_write: 0.125,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|