@desplega.ai/agent-swarm 1.152.2 → 1.153.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-pfeje2v5.js → acp-adapter-ahmjxhek.js} +4 -4
- package/dist/{actions-0sttc7f5.js → actions-jzrfpkmn.js} +30 -26
- package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
- package/dist/{app-p8teva95.js → app-03na2vwb.js} +9 -6
- package/dist/{assistant-r45jnsrt.js → assistant-hfkxvhgt.js} +31 -27
- package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
- package/dist/{boot-reembed-je26h5y7.js → boot-reembed-c98xrad3.js} +8 -6
- package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-zt3qm9xn.js} +9 -7
- package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-jcqd8ksj.js} +6 -4
- package/dist/{claude-adapter-csc1mzka.js → claude-adapter-mgryg8g3.js} +17 -15
- package/dist/{claude-managed-adapter-dsmkcrjy.js → claude-managed-adapter-ea2aga6w.js} +3 -3
- package/dist/{claude-sdk-session-fcd6f078.js → claude-sdk-session-jjf4zss1.js} +17 -15
- package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
- package/dist/{cli-fm4ytsdk.js → cli-0d9nbqxf.js} +1 -1
- package/dist/cli-0x5qzvkg.js +4279 -0
- package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
- package/dist/{cli-wbyk465z.js → cli-1hwnb4hc.js} +1 -1
- package/dist/{cli-xw7vak96.js → cli-3bcmp015.js} +5 -5
- package/dist/cli-3er6gcsn.js +1849 -0
- package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
- package/dist/{cli-4ahpr4sw.js → cli-5mbmvxnn.js} +5 -5
- package/dist/{cli-xy1cchaf.js → cli-5taqtpze.js} +235 -8
- package/dist/{cli-fmy65jdn.js → cli-6ks9xf1n.js} +7 -5
- package/dist/cli-7g9s78jf.js +2827 -0
- package/dist/{cli-jew3jda5.js → cli-7pnz2v6s.js} +2 -2
- package/dist/{cli-r84ctdxn.js → cli-83wkg402.js} +2 -2
- package/dist/{cli-jvy9qgjs.js → cli-a9scg8sv.js} +4 -4
- package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
- package/dist/{cli-ht5rs63n.js → cli-b2g6mzjc.js} +15 -6
- package/dist/{cli-jbmc0r7r.js → cli-bw0dfwkr.js} +1 -1
- package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
- package/dist/{cli-wpv5rgg2.js → cli-cdq08v3r.js} +2 -2
- package/dist/cli-cgj4hc12.js +18902 -0
- package/dist/cli-d6hk10xx.js +14075 -0
- package/dist/{cli-zvgj5zg8.js → cli-djvxhnc4.js} +31 -3324
- package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
- package/dist/{cli-3bpzj8me.js → cli-eazstkad.js} +2 -2
- package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
- package/dist/cli-h38x0qhg.js +6912 -0
- package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
- package/dist/{pi-mono-adapter-jcqt71cg.js → cli-ja7mrygn.js} +67343 -101083
- package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
- package/dist/{cli-gphgnvpf.js → cli-m04pt18h.js} +2 -2
- package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
- package/dist/{cli-j4nv2wqy.js → cli-mzgyhxbe.js} +10 -4
- package/dist/{cli-qn042eh7.js → cli-n0q9b7j4.js} +46 -1870
- package/dist/cli-n7h1kqpf.js +29 -0
- package/dist/{cli-ckz5t2hb.js → cli-nq0kgyxe.js} +5 -5
- package/dist/{cli-z3wkng35.js → cli-qrqhc31j.js} +151 -125
- package/dist/{cli-56h18ng2.js → cli-r8tdds6s.js} +1 -1
- package/dist/{cli-ch38q5z4.js → cli-rj7de4b2.js} +6788 -2541
- package/dist/{cli-qd65dvr6.js → cli-s5s1f2yn.js} +56 -40
- package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
- package/dist/{cli-2nnd56zt.js → cli-tdh4hjr2.js} +3 -2
- package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
- package/dist/{cli-b9dd0h3k.js → cli-vw03hg5t.js} +3 -2
- package/dist/{cli-zvkq5csn.js → cli-ybpp1srm.js} +6 -6
- package/dist/cli-yn0a417a.js +4069 -0
- package/dist/{cli-gpqcs935.js → cli-ysc1kbet.js} +2 -2
- package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
- package/dist/cli-zcsyfw2z.js +3335 -0
- package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
- package/dist/cli-zy5w9gpt.js +230 -0
- package/dist/cli.js +26 -34
- package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-8548daap.js} +12 -11
- package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-2dad8p4x.js} +12 -11
- package/dist/{commands-0f5rxz51.js → commands-355dbpa8.js} +6 -4
- package/dist/{db-r3jxf3wy.js → db-4jjvwz1s.js} +6 -4
- package/dist/{dispatcher-06vph6q4.js → dispatcher-ca590nkz.js} +29 -25
- package/dist/dsh-adapter-rgvj6ykp.js +19 -0
- package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
- package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-xtw7tggr.js} +33 -73
- package/dist/{google-vertex-fr665tqs.js → google-vertex-zdqpms6n.js} +38 -68
- package/dist/{handlers-0hgtc910.js → handlers-2y6dqpvf.js} +32 -28
- package/dist/{hook-9kvrv1er.js → hook-0s8knepr.js} +11 -9
- package/dist/{hook-fbfhkat3.js → hook-k0gec254.js} +12 -10
- package/dist/{http-xbyx8k5j.js → http-jp2nb4zw.js} +300 -237
- package/dist/{index-tfsdxyv6.js → index-0wzg3sp3.js} +39 -24
- package/dist/{index-rnyahfn5.js → index-3mq6ce93.js} +29 -25
- package/dist/{index-jsw3aynb.js → index-42s7eezq.js} +29 -25
- package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
- package/dist/{index-gpfn1741.js → index-b56xkbc7.js} +8 -6
- package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
- package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
- package/dist/{index-sgk50mbg.js → index-s37dbevy.js} +30 -26
- package/dist/index-w097eg0x.js +5 -0
- package/dist/jiti-loader-b4837mtm.js +34 -0
- package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
- package/dist/{keepalive-s3xpr7sw.js → keepalive-9pfw7t02.js} +8 -6
- package/dist/{lead-rq4n5r16.js → lead-7vcmcdq4.js} +44 -39
- package/dist/{maintenance-f0atj1ab.js → maintenance-x277jbpd.js} +9 -7
- package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
- package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
- package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-5fwa7th2.js} +7 -5
- package/dist/{onboard-br5j1kch.js → onboard-17sv0f51.js} +103 -35
- package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
- package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
- package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
- package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
- package/dist/{otel-impl-vdqge2nt.js → otel-impl-srjy4be4.js} +4 -4
- package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
- package/dist/pi-mono-adapter-946a0sdd.js +1316 -0
- package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-vetcm563.js} +8 -6
- package/dist/{rbac-roles-jh280k9g.js → rbac-roles-cfzgr6r1.js} +8 -6
- package/dist/{rbac-roles-t78xt39b.js → rbac-roles-cmxaddmy.js} +7 -5
- package/dist/{render-v2-7p8r2ymg.js → render-v2-f9hzff72.js} +9 -6
- package/dist/{seed-pricing-hf889enh.js → seed-pricing-jggk6rsy.js} +7 -5
- package/dist/{setup-w6mb03jv.js → setup-8mfceb9t.js} +4 -5
- package/dist/virtual-modules-ax661xp0.js +54 -0
- package/dist/{work-objects-nt8gy1kj.js → work-objects-vdsdcczj.js} +32 -28
- package/dist/{worker-dwy617t0.js → worker-cd0nhyv1.js} +44 -39
- package/openapi.json +36 -14
- package/package.json +6 -6
- package/src/be/automation-preflight.ts +6 -4
- package/src/be/extensions/db.ts +35 -4
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +213 -0
- package/src/be/rbac-audit.ts +2 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/onboard/compose-generator.ts +6 -1
- package/src/commands/onboard/env-generator.ts +6 -1
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/integration-slack.tsx +56 -16
- package/src/commands/onboard/steps/review.tsx +5 -0
- package/src/commands/onboard/types.ts +6 -0
- package/src/commands/onboard.tsx +5 -1
- package/src/commands/provider-credentials.ts +14 -2
- package/src/extensions/lifecycle.ts +15 -21
- package/src/http/agents.ts +2 -2
- package/src/http/config.ts +1 -1
- package/src/http/core.ts +5 -3
- package/src/http/extensions.ts +108 -34
- package/src/http/status.ts +22 -10
- package/src/prompts/base-prompt.ts +15 -7
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/codex-models.ts +15 -1
- package/src/providers/dsh-adapter.ts +248 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/modelsdev-reasoning.json +50 -0
- package/src/rbac/legacy-policy.ts +15 -1
- package/src/rbac/types.ts +2 -0
- package/src/server.ts +7 -1
- package/src/slack/app.ts +32 -10
- package/src/slack/config.ts +29 -0
- package/src/telemetry.ts +2 -3
- package/src/tests/agents-harness-provider.test.ts +19 -0
- package/src/tests/agents-list-model-display.test.ts +60 -1
- package/src/tests/base-prompt.test.ts +25 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/codex-gpt6-release.test.ts +97 -0
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/dsh-adapter.test.ts +276 -0
- package/src/tests/e2e-nightly-report.test.ts +3 -3
- package/src/tests/e2e-redact.test.ts +2 -0
- package/src/tests/env-flag.test.ts +6 -0
- package/src/tests/extensions-db.test.ts +41 -0
- package/src/tests/extensions-http.test.ts +148 -4
- package/src/tests/extensions-lifecycle.test.ts +18 -0
- package/src/tests/extensions-mcp-tools.test.ts +9 -5
- package/src/tests/internal-ai/complete-structured.test.ts +32 -9
- package/src/tests/internal-ai/credentials.test.ts +4 -4
- package/src/tests/internal-ai/models.test.ts +20 -0
- package/src/tests/migration-046-budgets.test.ts +2 -0
- package/src/tests/model-control.test.ts +2 -2
- package/src/tests/onboard-compose.test.ts +14 -0
- package/src/tests/onboard-env.test.ts +17 -0
- package/src/tests/onboard-manifest.test.ts +12 -0
- package/src/tests/opus-5-5-registry.test.ts +48 -0
- package/src/tests/pricing-refresh.test.ts +1 -1
- package/src/tests/pricing-routes.test.ts +15 -3
- package/src/tests/provider-registration.test.ts +5 -2
- package/src/tests/rbac-admission-e2e.test.ts +54 -0
- package/src/tests/rbac-e2e-helpers.ts +2 -0
- package/src/tests/rbac-engine.test.ts +32 -0
- package/src/tests/rbac-wire-e2e.test.ts +33 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/reload-config.test.ts +33 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/slack-config.test.ts +136 -0
- package/src/tests/slack-mode-lifecycle.test.ts +69 -0
- package/src/tests/status.test.ts +22 -2
- package/src/tests/task-tool-preload-mcp.test.ts +3 -1
- package/src/tests/telemetry-init.test.ts +18 -0
- package/src/tests/workflow-llm.test.ts +3 -3
- package/src/tools/extension-common.ts +4 -0
- package/src/tools/extension-install.ts +1 -1
- package/src/tools/extension-list.ts +4 -3
- package/src/types.ts +17 -8
- package/src/utils/context-window.ts +1 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/internal-ai/complete-structured.ts +8 -0
- package/src/utils/internal-ai/models.ts +5 -5
- package/templates/schedules/daily-status-report/content.md +25 -12
- package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
- package/templates/skills/swarm-extensions/content.md +9 -5
- package/dist/cli-4wppfkcm.js +0 -16384
- package/dist/cli-55qrx3q8.js +0 -6041
- package/dist/cli-cg4vnyh4.js +0 -305
- package/dist/cli-jyetr64j.js +0 -52
- package/dist/cli-pp9ax9pa.js +0 -520
- package/dist/cli-qa66qaf0.js +0 -6076
- package/dist/cli-qghpvca2.js +0 -590
- package/dist/cli-rembwgt8.js +0 -861
- package/dist/cli-tcvj7j2r.js +0 -1971
- package/dist/cli-vfcth11k.js +0 -36
- package/dist/cli-w1d1bgkk.js +0 -1422
- package/dist/cli-y6x0gqtg.js +0 -5977
- package/dist/es5-tj3tn4jj.js +0 -844
- package/dist/index-1kq5hw2p.js +0 -467
- package/dist/index-1p8wdstt.js +0 -7
- package/dist/index-8fdjzb2f.js +0 -100
- package/dist/index-bxmwzbgw.js +0 -365
- package/dist/index-nzerxd5z.js +0 -7
- package/dist/index-pyymnzat.js +0 -1168
- package/dist/index-qv6atbab.js +0 -619
- package/dist/index-rja32qa7.js +0 -801
- package/dist/index-tzg5srfp.js +0 -211
- package/dist/index-vhncw983.js +0 -90
- package/dist/index-xk036zsk.js +0 -859
- package/dist/index-zyazbzcb.js +0 -6
- package/dist/{claude-managed-setup-48dwg9bv.js → claude-managed-setup-f5pmsyrp.js} +3 -3
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getExtensionByName,
|
|
7
7
|
insertExtensionRun,
|
|
8
8
|
listExtensionRuns,
|
|
9
|
+
listExtensionVersions,
|
|
9
10
|
setExtensionState,
|
|
10
11
|
} from "../be/extensions/db";
|
|
11
12
|
import { enqueueAuditRow, flushAuditBuffer } from "../be/rbac-audit";
|
|
@@ -259,16 +260,159 @@ describe("/api/extensions HTTP", () => {
|
|
|
259
260
|
expect((await response.json()).runs).toEqual([second, first]);
|
|
260
261
|
});
|
|
261
262
|
|
|
262
|
-
test("
|
|
263
|
-
|
|
264
|
-
const response = await install("minimal", leadId);
|
|
263
|
+
test("worker installs are owned, disabled, and cannot activate their own extension", async () => {
|
|
264
|
+
const response = await install("minimal", workerId);
|
|
265
265
|
expect(response.status).toBe(200);
|
|
266
266
|
const extension = (await response.json()).extension as {
|
|
267
267
|
enabled: boolean;
|
|
268
268
|
createdByAgentId: string | null;
|
|
269
269
|
};
|
|
270
270
|
expect(extension.enabled).toBe(false);
|
|
271
|
-
expect(extension.createdByAgentId).toBe(
|
|
271
|
+
expect(extension.createdByAgentId).toBe(workerId);
|
|
272
|
+
const stored = (await getExtensionByName("minimal"))!;
|
|
273
|
+
expect(stored.status).toBe("disabled");
|
|
274
|
+
expect(stored.agentId).toBeNull();
|
|
275
|
+
for (const operation of ["enable", "disable", "activate-version"]) {
|
|
276
|
+
expect(
|
|
277
|
+
(
|
|
278
|
+
await dispatch(`/api/extensions/${stored.id}/${operation}`, {
|
|
279
|
+
method: "POST",
|
|
280
|
+
agentId: workerId,
|
|
281
|
+
body: JSON.stringify({ version: 1 }),
|
|
282
|
+
})
|
|
283
|
+
).status,
|
|
284
|
+
).toBe(403);
|
|
285
|
+
}
|
|
286
|
+
expect(await listExtensionRuns(stored.id)).toHaveLength(0);
|
|
287
|
+
expect((await dispatch(`/api/extensions/${stored.id}`)).text).toContain(workerId);
|
|
288
|
+
expect((await dispatch("/api/extensions")).text).toContain(workerId);
|
|
289
|
+
const changed = await loadBundleFixture("minimal");
|
|
290
|
+
changed.files["hooks.ts"] += "\n// worker draft v2\n";
|
|
291
|
+
expect(
|
|
292
|
+
(
|
|
293
|
+
await dispatch("/api/extensions/install", {
|
|
294
|
+
method: "POST",
|
|
295
|
+
agentId: workerId,
|
|
296
|
+
body: JSON.stringify(changed),
|
|
297
|
+
})
|
|
298
|
+
).status,
|
|
299
|
+
).toBe(200);
|
|
300
|
+
expect(await getExtensionByName("minimal")).toMatchObject({
|
|
301
|
+
version: 2,
|
|
302
|
+
activeVersion: 1,
|
|
303
|
+
enabled: false,
|
|
304
|
+
status: "disabled",
|
|
305
|
+
createdByAgentId: workerId,
|
|
306
|
+
});
|
|
307
|
+
const versions = await listExtensionVersions(stored.id);
|
|
308
|
+
expect(versions).toHaveLength(2);
|
|
309
|
+
expect(versions.every((version) => version.changedByAgentId === workerId)).toBe(true);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("install never evaluates worker bundle code", async () => {
|
|
313
|
+
const bundle = await loadBundleFixture("minimal");
|
|
314
|
+
bundle.files["hooks.ts"] += '\nthrow new Error("must only run on activation");\n';
|
|
315
|
+
const response = await dispatch("/api/extensions/install", {
|
|
316
|
+
method: "POST",
|
|
317
|
+
agentId: workerId,
|
|
318
|
+
body: JSON.stringify(bundle),
|
|
319
|
+
});
|
|
320
|
+
expect(response.status).toBe(200);
|
|
321
|
+
const stored = (await getExtensionByName("minimal"))!;
|
|
322
|
+
expect(stored).toMatchObject({ enabled: false, status: "disabled", agentId: null });
|
|
323
|
+
expect(await listExtensionRuns(stored.id)).toHaveLength(0);
|
|
324
|
+
const activation = await dispatch(`/api/extensions/${stored.id}/enable`, { method: "POST" });
|
|
325
|
+
expect(activation.status).toBe(500);
|
|
326
|
+
expect(activation.text).toContain("must only run on activation");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("workers cannot overwrite, patch, or delete another owner's extension", async () => {
|
|
330
|
+
await install("minimal", leadId);
|
|
331
|
+
const stored = (await getExtensionByName("minimal"))!;
|
|
332
|
+
expect((await install("minimal", workerId)).status).toBe(403);
|
|
333
|
+
for (const method of ["PATCH", "DELETE"]) {
|
|
334
|
+
expect(
|
|
335
|
+
(
|
|
336
|
+
await dispatch(`/api/extensions/${stored.id}`, {
|
|
337
|
+
method,
|
|
338
|
+
agentId: workerId,
|
|
339
|
+
...(method === "PATCH" ? { body: JSON.stringify({ priority: 7 }) } : {}),
|
|
340
|
+
})
|
|
341
|
+
).status,
|
|
342
|
+
).toBe(403);
|
|
343
|
+
}
|
|
344
|
+
expect(await getExtensionByName("minimal")).toMatchObject({
|
|
345
|
+
createdByAgentId: leadId,
|
|
346
|
+
priority: 100,
|
|
347
|
+
version: 1,
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("worker owners edit and delete disabled drafts, but cannot reload live code", async () => {
|
|
352
|
+
await install("minimal", workerId);
|
|
353
|
+
const stored = (await getExtensionByName("minimal"))!;
|
|
354
|
+
const path = `/api/extensions/${stored.id}`;
|
|
355
|
+
expect(
|
|
356
|
+
(
|
|
357
|
+
await dispatch(path, {
|
|
358
|
+
method: "PATCH",
|
|
359
|
+
agentId: workerId,
|
|
360
|
+
body: JSON.stringify({ priority: 7 }),
|
|
361
|
+
})
|
|
362
|
+
).status,
|
|
363
|
+
).toBe(200);
|
|
364
|
+
expect((await dispatch(`${path}/enable`, { method: "POST", agentId: leadId })).status).toBe(
|
|
365
|
+
200,
|
|
366
|
+
);
|
|
367
|
+
expect(
|
|
368
|
+
(
|
|
369
|
+
await dispatch(path, {
|
|
370
|
+
method: "PATCH",
|
|
371
|
+
agentId: workerId,
|
|
372
|
+
body: JSON.stringify({ config: { live: true } }),
|
|
373
|
+
})
|
|
374
|
+
).status,
|
|
375
|
+
).toBe(403);
|
|
376
|
+
const changed = await loadBundleFixture("minimal");
|
|
377
|
+
changed.files["hooks.ts"] += '\nthrow new Error("inactive worker version executed");\n';
|
|
378
|
+
expect(
|
|
379
|
+
(
|
|
380
|
+
await dispatch("/api/extensions/install", {
|
|
381
|
+
method: "POST",
|
|
382
|
+
agentId: workerId,
|
|
383
|
+
body: JSON.stringify({ ...changed, config: { live: true } }),
|
|
384
|
+
})
|
|
385
|
+
).status,
|
|
386
|
+
).toBe(403);
|
|
387
|
+
expect(
|
|
388
|
+
(
|
|
389
|
+
await dispatch("/api/extensions/install", {
|
|
390
|
+
method: "POST",
|
|
391
|
+
agentId: workerId,
|
|
392
|
+
body: JSON.stringify(changed),
|
|
393
|
+
})
|
|
394
|
+
).status,
|
|
395
|
+
).toBe(200);
|
|
396
|
+
// A permitted reload must keep using version 1, never evaluate the worker draft.
|
|
397
|
+
expect(
|
|
398
|
+
(
|
|
399
|
+
await dispatch(path, {
|
|
400
|
+
method: "PATCH",
|
|
401
|
+
agentId: leadId,
|
|
402
|
+
body: JSON.stringify({ priority: 7 }),
|
|
403
|
+
})
|
|
404
|
+
).status,
|
|
405
|
+
).toBe(200);
|
|
406
|
+
expect(await getExtensionByName("minimal")).toMatchObject({
|
|
407
|
+
version: 2,
|
|
408
|
+
activeVersion: 1,
|
|
409
|
+
enabled: true,
|
|
410
|
+
configJson: "{}",
|
|
411
|
+
});
|
|
412
|
+
expect((await dispatch(path, { method: "DELETE", agentId: workerId })).status).toBe(409);
|
|
413
|
+
expect((await dispatch(`${path}/disable`, { method: "POST" })).status).toBe(200);
|
|
414
|
+
expect((await dispatch(path, { method: "DELETE", agentId: workerId })).status).toBe(200);
|
|
415
|
+
expect(await getExtensionByName("minimal")).toBeNull();
|
|
272
416
|
});
|
|
273
417
|
|
|
274
418
|
test("operator reinstall activates a new version when the extension is enabled", async () => {
|
|
@@ -81,6 +81,24 @@ describe("extension lifecycle", () => {
|
|
|
81
81
|
expect(listRegistered()).toHaveLength(0);
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
test("activation loads its immutable snapshot, not a concurrently advanced file projection", async () => {
|
|
85
|
+
const extension = await install(await loadBundleFixture("minimal"));
|
|
86
|
+
// Simulate a worker draft replacing the mutable files after activation read its row.
|
|
87
|
+
await getDbClient().run("UPDATE extension_files SET content = ? WHERE extensionId = ?", [
|
|
88
|
+
'throw new Error("unapproved draft executed");',
|
|
89
|
+
extension.id,
|
|
90
|
+
]);
|
|
91
|
+
const enabled = await enableExtension(extension.id);
|
|
92
|
+
expect(enabled).toMatchObject({ enabled: true, activeVersion: 1 });
|
|
93
|
+
expect(
|
|
94
|
+
await dispatchPre("pre.task.create", {
|
|
95
|
+
options: {},
|
|
96
|
+
description: "snapshot check",
|
|
97
|
+
origin: "rest",
|
|
98
|
+
}),
|
|
99
|
+
).toEqual({ action: "continue" });
|
|
100
|
+
});
|
|
101
|
+
|
|
84
102
|
test("activate-version swaps source and reloads an enabled extension", async () => {
|
|
85
103
|
const bundle = await loadBundleFixture("priority-a");
|
|
86
104
|
const extension = await install(bundle);
|
|
@@ -200,16 +200,20 @@ describe("extension MCP HTTP proxy tools", () => {
|
|
|
200
200
|
expect(result.structuredContent.message).toBeTruthy();
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
-
test("worker installation returns
|
|
203
|
+
test("worker installation returns ownership and a disabled draft in the shared tool envelope", async () => {
|
|
204
204
|
const tools = buildToolServer();
|
|
205
205
|
const result = (await tools.install.handler(
|
|
206
206
|
await loadBundleFixture("minimal"),
|
|
207
207
|
meta(workerId),
|
|
208
208
|
)) as ToolResult;
|
|
209
209
|
|
|
210
|
-
expect(result.isError).toBe(
|
|
211
|
-
expect(result.structuredContent).toMatchObject({
|
|
212
|
-
|
|
210
|
+
expect(result.isError).toBe(false);
|
|
211
|
+
expect(result.structuredContent).toMatchObject({
|
|
212
|
+
success: true,
|
|
213
|
+
createdByAgentId: workerId,
|
|
214
|
+
enabled: false,
|
|
215
|
+
status: "disabled",
|
|
216
|
+
});
|
|
213
217
|
});
|
|
214
218
|
|
|
215
219
|
test("lead reinstall stores an inactive version of an enabled extension", async () => {
|
|
@@ -254,7 +258,7 @@ describe("extension MCP HTTP proxy tools", () => {
|
|
|
254
258
|
const all = (await tools.list.handler({}, meta(leadId))) as ToolResult;
|
|
255
259
|
expect(all.isError).toBe(false);
|
|
256
260
|
expect(all.content[0]?.text).toContain(
|
|
257
|
-
"| Name | Version | Active | Enabled | Status | Priority | Failures |",
|
|
261
|
+
"| Name | Owner agent | Version | Active | Enabled | Status | Priority | Failures |",
|
|
258
262
|
);
|
|
259
263
|
expect(all.structuredContent).toMatchObject({ success: true });
|
|
260
264
|
expect((all.structuredContent.extensions as unknown[]).length).toBe(1);
|
|
@@ -51,7 +51,7 @@ describe("completeStructured", () => {
|
|
|
51
51
|
_credentialOverride: {
|
|
52
52
|
kind: "openrouter",
|
|
53
53
|
apiKey: "test",
|
|
54
|
-
modelDefault: "openrouter/
|
|
54
|
+
modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
55
55
|
},
|
|
56
56
|
_complete: async () => {
|
|
57
57
|
invocations++;
|
|
@@ -98,7 +98,7 @@ describe("completeStructured", () => {
|
|
|
98
98
|
_credentialOverride: {
|
|
99
99
|
kind: "openrouter",
|
|
100
100
|
apiKey: "test",
|
|
101
|
-
modelDefault: "openrouter/
|
|
101
|
+
modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
102
102
|
},
|
|
103
103
|
_complete: async () => {
|
|
104
104
|
invocations++;
|
|
@@ -111,18 +111,41 @@ describe("completeStructured", () => {
|
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
test("unknown catalog model returns null without calling complete", async () => {
|
|
115
|
+
let invocations = 0;
|
|
116
|
+
const result = await completeStructured({
|
|
117
|
+
zodSchema: ResultZodSchema,
|
|
118
|
+
toolSchema: ResultToolSchema,
|
|
119
|
+
toolName: "record_result",
|
|
120
|
+
toolDescription: "Record the result.",
|
|
121
|
+
systemPrompt: "sys",
|
|
122
|
+
userPrompt: "user",
|
|
123
|
+
_credentialOverride: {
|
|
124
|
+
kind: "openai-codex",
|
|
125
|
+
apiKey: "test",
|
|
126
|
+
modelDefault: "openai-codex/nonexistent-model",
|
|
127
|
+
},
|
|
128
|
+
_complete: async () => {
|
|
129
|
+
invocations++;
|
|
130
|
+
return makeMsg([]);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
expect(result).toBeNull();
|
|
134
|
+
expect(invocations).toBe(0);
|
|
135
|
+
});
|
|
136
|
+
|
|
114
137
|
test("passes a provider-compatible forced tool choice", async () => {
|
|
115
138
|
const credentials: ResolvedCredential[] = [
|
|
116
139
|
{
|
|
117
140
|
kind: "openrouter",
|
|
118
141
|
apiKey: "test",
|
|
119
|
-
modelDefault: "openrouter/
|
|
142
|
+
modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
120
143
|
},
|
|
121
|
-
{ kind: "openai", apiKey: "test", modelDefault: "openai/gpt-
|
|
144
|
+
{ kind: "openai", apiKey: "test", modelDefault: "openai/gpt-6-luna" },
|
|
122
145
|
{
|
|
123
146
|
kind: "openai-codex",
|
|
124
147
|
apiKey: "test",
|
|
125
|
-
modelDefault: "openai-codex/gpt-
|
|
148
|
+
modelDefault: "openai-codex/gpt-6-luna",
|
|
126
149
|
},
|
|
127
150
|
{
|
|
128
151
|
kind: "anthropic",
|
|
@@ -172,7 +195,7 @@ describe("completeStructured", () => {
|
|
|
172
195
|
_credentialOverride: {
|
|
173
196
|
kind: "openrouter",
|
|
174
197
|
apiKey: "test",
|
|
175
|
-
modelDefault: "openrouter/
|
|
198
|
+
modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
176
199
|
},
|
|
177
200
|
_complete: async () => {
|
|
178
201
|
invocations++;
|
|
@@ -210,7 +233,7 @@ describe("completeStructured", () => {
|
|
|
210
233
|
_credentialOverride: {
|
|
211
234
|
kind: "openrouter",
|
|
212
235
|
apiKey: "test",
|
|
213
|
-
modelDefault: "openrouter/
|
|
236
|
+
modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
214
237
|
},
|
|
215
238
|
_complete: async () => {
|
|
216
239
|
invocations++;
|
|
@@ -241,7 +264,7 @@ describe("completeStructured", () => {
|
|
|
241
264
|
_credentialOverride: {
|
|
242
265
|
kind: "openrouter",
|
|
243
266
|
apiKey: "test",
|
|
244
|
-
modelDefault: "openrouter/
|
|
267
|
+
modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
245
268
|
},
|
|
246
269
|
_complete: async () => {
|
|
247
270
|
invocations++;
|
|
@@ -418,7 +441,7 @@ describe("completeStructured", () => {
|
|
|
418
441
|
_credentialOverride: {
|
|
419
442
|
kind: "openrouter",
|
|
420
443
|
apiKey: "test",
|
|
421
|
-
modelDefault: "openrouter/
|
|
444
|
+
modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
422
445
|
},
|
|
423
446
|
_complete: async () =>
|
|
424
447
|
makeMsg([
|
|
@@ -30,7 +30,7 @@ describe("resolveCredential", () => {
|
|
|
30
30
|
expect(cred?.kind).toBe("openrouter");
|
|
31
31
|
if (cred?.kind === "openrouter") {
|
|
32
32
|
expect(cred.apiKey).toBe("or-1");
|
|
33
|
-
expect(cred.modelDefault).toBe("openrouter/
|
|
33
|
+
expect(cred.modelDefault).toBe("openrouter/deepseek/deepseek-v4.1-flash");
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -50,7 +50,7 @@ describe("resolveCredential", () => {
|
|
|
50
50
|
expect(cred?.kind).toBe("openai");
|
|
51
51
|
if (cred?.kind === "openai") {
|
|
52
52
|
expect(cred.apiKey).toBe("sk-o-1");
|
|
53
|
-
expect(cred.modelDefault).toBe("openai/gpt-
|
|
53
|
+
expect(cred.modelDefault).toBe("openai/gpt-6-luna");
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -79,7 +79,7 @@ describe("resolveCredential", () => {
|
|
|
79
79
|
expect(cred?.kind).toBe("openai-codex");
|
|
80
80
|
if (cred?.kind === "openai-codex") {
|
|
81
81
|
expect(cred.apiKey).toBe("example-codex-api-key-derived");
|
|
82
|
-
expect(cred.modelDefault).toBe("openai-codex/gpt-
|
|
82
|
+
expect(cred.modelDefault).toBe("openai-codex/gpt-6-luna");
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
|
@@ -101,7 +101,7 @@ describe("resolveCredential", () => {
|
|
|
101
101
|
expect(cred).toEqual({
|
|
102
102
|
kind: "openai-codex",
|
|
103
103
|
apiKey: "example-at_provider_runtime",
|
|
104
|
-
modelDefault: "openai-codex/gpt-
|
|
104
|
+
modelDefault: "openai-codex/gpt-6-luna",
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
107
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
|
|
3
|
+
import { DEFAULT_MODEL, parseModelStr } from "../../utils/internal-ai/models.js";
|
|
4
|
+
|
|
5
|
+
describe("internal-ai default models", () => {
|
|
6
|
+
for (const [kind, modelString] of Object.entries(DEFAULT_MODEL)) {
|
|
7
|
+
if (kind === "claude-cli") continue;
|
|
8
|
+
|
|
9
|
+
test(`${kind} resolves in the pinned pi-ai catalog`, () => {
|
|
10
|
+
const [provider, modelId] = parseModelStr(modelString);
|
|
11
|
+
const model = getBuiltinModel(
|
|
12
|
+
provider as Parameters<typeof getBuiltinModel>[0],
|
|
13
|
+
modelId as never,
|
|
14
|
+
);
|
|
15
|
+
expect(model).toBeDefined();
|
|
16
|
+
expect(model.id).toBe(modelId);
|
|
17
|
+
expect(model.provider).toBe(provider);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
@@ -21,6 +21,8 @@ async function removeDbFiles(path: string): Promise<void> {
|
|
|
21
21
|
|
|
22
22
|
beforeAll(() => {
|
|
23
23
|
initDb(TEST_DB_PATH);
|
|
24
|
+
// Mirror API boot: new releases are snapshot-seeded, not new migrations.
|
|
25
|
+
seedPricingFromModelsDev({ quiet: true });
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
afterAll(async () => {
|
|
@@ -505,10 +505,10 @@ describe("Model Control - Priority Resolution Logic", () => {
|
|
|
505
505
|
expect(resolveModelTier({ tier: "smart", harnessProvider: "codex" })).toBe("gpt-5.6-sol");
|
|
506
506
|
expect(resolveModelTier({ tier: "ultra", harnessProvider: "codex" })).toBe("gpt-5.6-sol");
|
|
507
507
|
expect(resolveModelTier({ tier: "smart", harnessProvider: "opencode" })).toBe(
|
|
508
|
-
"openrouter/deepseek/deepseek-v4-pro",
|
|
508
|
+
"openrouter/deepseek/deepseek-v4-pro-0813",
|
|
509
509
|
);
|
|
510
510
|
expect(resolveModelTier({ tier: "ultra", harnessProvider: "pi" })).toBe(
|
|
511
|
-
"openrouter/anthropic/claude-opus-
|
|
511
|
+
"openrouter/anthropic/claude-opus-5.5",
|
|
512
512
|
);
|
|
513
513
|
});
|
|
514
514
|
|
|
@@ -8,6 +8,11 @@ function makeState(overrides: Partial<OnboardState>): OnboardState {
|
|
|
8
8
|
return { ...INITIAL_STATE, ...overrides };
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Docker Compose env var syntax
|
|
12
|
+
const COMPOSE_SLACK_MODE = "SLACK_MODE=${SLACK_MODE:-socket}";
|
|
13
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Docker Compose env var syntax
|
|
14
|
+
const COMPOSE_SLACK_SIGNING_SECRET = "SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET}";
|
|
15
|
+
|
|
11
16
|
describe("generateCompose", () => {
|
|
12
17
|
test("uses the full preset by default and includes every official template once", () => {
|
|
13
18
|
const full = getPresetById("full");
|
|
@@ -286,6 +291,8 @@ describe("generateCompose", () => {
|
|
|
286
291
|
// Slack vars on API service
|
|
287
292
|
expect(yaml).toContain("SLACK_BOT_TOKEN");
|
|
288
293
|
expect(yaml).toContain("SLACK_APP_TOKEN");
|
|
294
|
+
expect(yaml).toContain(COMPOSE_SLACK_MODE);
|
|
295
|
+
expect(yaml).not.toContain("SLACK_SIGNING_SECRET");
|
|
289
296
|
// GitLab vars on agent services
|
|
290
297
|
expect(yaml).toContain("GITLAB_TOKEN");
|
|
291
298
|
expect(yaml).toContain("GITLAB_EMAIL");
|
|
@@ -297,6 +304,13 @@ describe("generateCompose", () => {
|
|
|
297
304
|
expect(yaml).toContain("SLACK_DISABLE=false");
|
|
298
305
|
});
|
|
299
306
|
|
|
307
|
+
test("HTTP Slack compose configuration uses the signing secret and no app token", () => {
|
|
308
|
+
const yaml = generateCompose({ ...allIntegrationsState, slackMode: "http" });
|
|
309
|
+
expect(yaml).toContain(COMPOSE_SLACK_MODE);
|
|
310
|
+
expect(yaml).toContain(COMPOSE_SLACK_SIGNING_SECRET);
|
|
311
|
+
expect(yaml).not.toContain("SLACK_APP_TOKEN");
|
|
312
|
+
});
|
|
313
|
+
|
|
300
314
|
// ── No integrations ──
|
|
301
315
|
|
|
302
316
|
test("no integrations omits integration env vars from agent services", () => {
|
|
@@ -99,9 +99,26 @@ describe("generateEnv", () => {
|
|
|
99
99
|
const env = generateEnv(state);
|
|
100
100
|
expect(env).toContain("SLACK_BOT_TOKEN=example-xoxb-slack-bot");
|
|
101
101
|
expect(env).toContain("SLACK_APP_TOKEN=example-xapp-slack-app");
|
|
102
|
+
expect(env).toContain("SLACK_MODE=socket");
|
|
103
|
+
expect(env).not.toContain("SLACK_SIGNING_SECRET=");
|
|
102
104
|
expect(env).toContain("SLACK_DISABLE=false");
|
|
103
105
|
});
|
|
104
106
|
|
|
107
|
+
test("Slack HTTP mode emits only its mode-specific credential", () => {
|
|
108
|
+
const env = generateEnv(
|
|
109
|
+
makeState({
|
|
110
|
+
integrations: { github: false, slack: true, gitlab: false, sentry: false },
|
|
111
|
+
slackMode: "http",
|
|
112
|
+
slackBotToken: "example-xoxb-slack-bot",
|
|
113
|
+
slackSigningSecret: "synthetic-signing-secret",
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
expect(env).toContain("SLACK_MODE=http");
|
|
117
|
+
expect(env).toContain("SLACK_BOT_TOKEN=example-xoxb-slack-bot");
|
|
118
|
+
expect(env).toContain("SLACK_SIGNING_SECRET=synthetic-signing-secret");
|
|
119
|
+
expect(env).not.toContain("SLACK_APP_TOKEN=");
|
|
120
|
+
});
|
|
121
|
+
|
|
105
122
|
// ── No integrations: only core section ──
|
|
106
123
|
|
|
107
124
|
test("no integrations outputs disable flags and no integration credentials", () => {
|
|
@@ -36,6 +36,7 @@ describe("generateManifest", () => {
|
|
|
36
36
|
expect(manifest).toHaveProperty("harness");
|
|
37
37
|
expect(manifest).toHaveProperty("services");
|
|
38
38
|
expect(manifest).toHaveProperty("integrations");
|
|
39
|
+
expect(manifest).toHaveProperty("slackMode");
|
|
39
40
|
expect(manifest).toHaveProperty("composePath");
|
|
40
41
|
expect(manifest).toHaveProperty("envPath");
|
|
41
42
|
expect(manifest).toHaveProperty("apiUrl");
|
|
@@ -112,6 +113,17 @@ describe("generateManifest", () => {
|
|
|
112
113
|
expect(integrations.slack).toBe(false);
|
|
113
114
|
expect(integrations.gitlab).toBe(false);
|
|
114
115
|
expect(integrations.sentry).toBe(true);
|
|
116
|
+
expect(manifest.slackMode).toBe("socket");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("records HTTP transport without persisting its signing secret", () => {
|
|
120
|
+
const manifest = generateManifest({
|
|
121
|
+
...devState,
|
|
122
|
+
slackMode: "http",
|
|
123
|
+
slackSigningSecret: "synthetic-signing-secret",
|
|
124
|
+
}) as Record<string, unknown>;
|
|
125
|
+
expect(manifest.slackMode).toBe("http");
|
|
126
|
+
expect(JSON.stringify(manifest)).not.toContain("synthetic-signing-secret");
|
|
115
127
|
});
|
|
116
128
|
|
|
117
129
|
test("all integrations false when none enabled", () => {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
findKnownModel,
|
|
4
|
+
pickDefaultModelForHarness,
|
|
5
|
+
} from "../../apps/ui/src/lib/agent-runtime-models";
|
|
6
|
+
import { loadModelsDevCache } from "../be/modelsdev-cache";
|
|
7
|
+
import { buildModelsDevSeedRows } from "../be/seed-pricing";
|
|
8
|
+
|
|
9
|
+
describe("Opus 5.5 registry", () => {
|
|
10
|
+
test("defaults to Opus 5.5 and keeps Opus 5 selectable", () => {
|
|
11
|
+
expect(pickDefaultModelForHarness("claude", [])).toBe("claude-opus-5-5");
|
|
12
|
+
expect(findKnownModel("claude-opus-5-5")).toMatchObject({
|
|
13
|
+
id: "claude-opus-5-5",
|
|
14
|
+
reasoningLevels: ["low", "medium", "high", "xhigh"],
|
|
15
|
+
});
|
|
16
|
+
expect(findKnownModel("claude-opus-5")?.id).toBe("claude-opus-5");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("seeds canonical and alias rates without changing Opus 5", () => {
|
|
20
|
+
const cache = loadModelsDevCache();
|
|
21
|
+
expect(cache).not.toBeNull();
|
|
22
|
+
const rows = buildModelsDevSeedRows(cache!);
|
|
23
|
+
for (const provider of ["claude", "claude-managed"] as const) {
|
|
24
|
+
for (const model of ["claude-opus-5-5", "opus"]) {
|
|
25
|
+
const rates = Object.fromEntries(
|
|
26
|
+
rows
|
|
27
|
+
.filter((row) => row.provider === provider && row.model === model)
|
|
28
|
+
.map((row) => [row.tokenClass, row.pricePerMillionUsd]),
|
|
29
|
+
);
|
|
30
|
+
expect(rates).toEqual({
|
|
31
|
+
input: 4,
|
|
32
|
+
output: 20,
|
|
33
|
+
cached_input: 0.2,
|
|
34
|
+
cache_write: 5,
|
|
35
|
+
cache_write_1h: 8,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
expect(
|
|
39
|
+
rows.find(
|
|
40
|
+
(row) =>
|
|
41
|
+
row.provider === provider &&
|
|
42
|
+
row.model === "claude-opus-5" &&
|
|
43
|
+
row.tokenClass === "input",
|
|
44
|
+
)?.pricePerMillionUsd,
|
|
45
|
+
).toBe(5);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -87,7 +87,7 @@ describe("models.dev runtime pricing refresh", () => {
|
|
|
87
87
|
).toBe(10);
|
|
88
88
|
expect(
|
|
89
89
|
(await getActivePricingRow("pi", "opus", "cache_write_1h", 500))?.pricePerMillionUsd,
|
|
90
|
-
).toBe(
|
|
90
|
+
).toBe(8);
|
|
91
91
|
expect(
|
|
92
92
|
(await getActivePricingRow("claude", "claude-opus-5", "cache_write", 500))
|
|
93
93
|
?.pricePerMillionUsd,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type ServerResponse,
|
|
14
14
|
} from "node:http";
|
|
15
15
|
import { closeDb, getDbClient, getLogsByEventType, initDb } from "../be/db";
|
|
16
|
+
import { seedPricingFromModelsDev } from "../be/seed-pricing";
|
|
16
17
|
import { handleCore } from "../http/core";
|
|
17
18
|
import { handlePricing } from "../http/pricing";
|
|
18
19
|
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
@@ -53,6 +54,7 @@ let port: number;
|
|
|
53
54
|
beforeAll(async () => {
|
|
54
55
|
await removeDbFiles(TEST_DB_PATH);
|
|
55
56
|
initDb(TEST_DB_PATH);
|
|
57
|
+
seedPricingFromModelsDev({ quiet: true });
|
|
56
58
|
server = createTestServer(API_KEY);
|
|
57
59
|
port = await listenOnFreePort(server);
|
|
58
60
|
});
|
|
@@ -103,13 +105,23 @@ describe("Phase 6 — /api/pricing REST surface", () => {
|
|
|
103
105
|
expect(res.status).toBe(200);
|
|
104
106
|
const body = await res.json();
|
|
105
107
|
expect(body.rows).toBeInstanceOf(Array);
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
+
// Boot seeding adds current snapshot models alongside historical rows.
|
|
109
|
+
// Check each current model rather than a fixed three-row total: some
|
|
110
|
+
// models also publish cache-write prices.
|
|
108
111
|
const seedRows = body.rows.filter(
|
|
109
112
|
(r: { provider: string; effectiveFrom: number }) =>
|
|
110
113
|
r.provider === "codex" && r.effectiveFrom === 0,
|
|
111
114
|
);
|
|
112
|
-
|
|
115
|
+
for (const model of Object.keys(CODEX_MODEL_PRICING)) {
|
|
116
|
+
for (const tokenClass of ["input", "cached_input", "output"]) {
|
|
117
|
+
expect(
|
|
118
|
+
seedRows.some(
|
|
119
|
+
(row: { model: string; tokenClass: string }) =>
|
|
120
|
+
row.model === model && row.tokenClass === tokenClass,
|
|
121
|
+
),
|
|
122
|
+
).toBe(true);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
113
125
|
});
|
|
114
126
|
|
|
115
127
|
test("GET /api/pricing/{provider}/{model}/{tokenClass} returns rows latest-first", async () => {
|
|
@@ -31,6 +31,7 @@ const modelsDevFixture = {
|
|
|
31
31
|
|
|
32
32
|
const ENTRYPOINT_EXEMPTIONS = new Set(["claude", "acp"]);
|
|
33
33
|
const ENTRYPOINT_OUTPUT: Record<string, string> = {
|
|
34
|
+
dsh: "Warning: dsh provider has no credentials yet",
|
|
34
35
|
pi: "Warning: pi provider has no credentials yet",
|
|
35
36
|
opencode: "Warning: opencode provider has no credentials yet",
|
|
36
37
|
"claude-managed": "Warning: claude-managed provider missing:",
|
|
@@ -71,9 +72,11 @@ describe("provider registration synchronization", () => {
|
|
|
71
72
|
);
|
|
72
73
|
});
|
|
73
74
|
|
|
74
|
-
test("pricing seeder emits rows for
|
|
75
|
+
test("pricing seeder emits rows for providers with cost reporting", () => {
|
|
75
76
|
const registered = new Set(buildPricingSeedRows(modelsDevFixture).map((row) => row.provider));
|
|
76
|
-
const expected = ProviderNameSchema.options.filter(
|
|
77
|
+
const expected = ProviderNameSchema.options.filter(
|
|
78
|
+
(provider) => provider !== "acp" && provider !== "dsh",
|
|
79
|
+
);
|
|
77
80
|
expect([...registered].filter((provider) => expected.includes(provider)).sort()).toEqual(
|
|
78
81
|
[...expected].sort(),
|
|
79
82
|
);
|