From f2769020fad97390f757addca759ada1487f9c3a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 20:59:19 +0000 Subject: [PATCH] build: pin the host the plugin runs on The test install now runs the host users run. - pi is pinned to 1.1.0, the version the harness's own lockfile pins, with the harness's patch applied verbatim (patches/, hash recorded by pnpm). A pnpm override holds every copy in the tree to it, not just the direct devDependency. - The @deepseek-ai packages move from 0.2.1-alpha.5 to 0.2.1-rc.2, the version a registry install of the published dsh resolves (cordis 4.0.5, loader 1.0.6, schemastery 3.18.5). The boot-tier packages the real-host harness needs next (dsh-llm, dsh-app-boot, dsh-config-editor, dsh-settings, cordis-plugin-loader) are added as devDependencies. - test/support/host-versions.ts names those versions once. test/host-versions.test.ts checks that the pi the plugin loads at runtime is 1.1.0 and the same copy the tests import, that the patch is applied, and that the installed cluster matches. - mount-protocols now checks that every catalog SDK maps to a protocol pi's exported api modules implement, and records as a characterization row that pi 1.1.0's opencode-go provider has no Google API. No src/ behaviour changes: the rc.2 builds of llm-pi-ai, cordis and schemastery are code-identical to alpha.5. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD --- AGENTS.md | 2 + CONTRIBUTING.md | 1 + docs/improvement-plan.md | 2 +- package.json | 19 +- patches/@earendil-works__pi-ai@1.1.0.patch | 73 +++ pnpm-lock.yaml | 658 ++++++++++++++++----- pnpm-workspace.yaml | 49 +- src/google-bridge.ts | 7 +- test/google-bridge.test.ts | 17 +- test/harness-channel.test.ts | 158 +++-- test/host-versions.test.ts | 221 +++++++ test/mount-protocols.test.ts | 213 +++++-- test/no-local-paths.test.ts | 6 +- test/support/host-versions.ts | 29 + 14 files changed, 1149 insertions(+), 306 deletions(-) create mode 100644 patches/@earendil-works__pi-ai@1.1.0.patch create mode 100644 test/host-versions.test.ts create mode 100644 test/support/host-versions.ts diff --git a/AGENTS.md b/AGENTS.md index 1fafe063..bb28c674 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,6 +42,8 @@ Host bundle (`lib/index.mjs`), a thin `apply` barrel over small modules: - `src/index.ts` · `src/identity.ts` · `src/lifecycle.ts` — the public barrel, the identity constants, and the `apply()` composition (one installer per side effect, in load-bearing order). The barrel is the host's contract and nothing more: `apply`, `Config`, `inject`, `name` and the config types. Everything else is internal, and `test/public-api.test.ts` pins the list, so widening it is a reviewed change. - `test/support/src.ts` — the one door tests use into `src`: each name re-exported from the module that DEFINES it, never through the barrel, so a module move edits one file. `test/architecture.test.ts` enforces both halves — every name is declared in its target, and no test outside the named exemptions (`vi.mock` targets, fresh re-imports after `vi.resetModules()`, the architecture and public-API tests, the jsdom client tests, the `reset-process-state.ts` setup file) imports a `src` path, and no jsdom test loads the door. +- `test/support/host-versions.ts` — the host the test install runs (pi, the `@deepseek-ai` cluster, cordis and its loader), named once; `test/host-versions.test.ts` checks the installed tree against it (with `pnpm-lock.yaml` as the record of what pnpm resolved, and the published peer ranges) and `test/harness-channel.test.ts` checks the declarations and the lockfile, including that `@deepseek-ai/schemastery` resolves to one copy inside the cluster's range. +- `patches/` — the harness's own pi patch, copied verbatim and applied by `patchedDependencies` in `pnpm-workspace.yaml`. Its hash is pinned by `test/host-versions.test.ts` and recorded in `pnpm-lock.yaml`, so never hand-edit it: re-copy it from the harness and re-run `pnpm install`. - `src/config.ts` · `src/config-values.ts` · `src/utils/guards.ts` — the schema and its defaults; dependency-free readers and type guards. The client bundle may import these, and it does — along with the contract and presentation modules (`usage-contract.ts`, `login-contract.ts`, `settings-copy.ts`, `settings-fields.ts`, `usage-ui.ts`, `go-limits.ts`). The rule is not a list of names: **the client may import a module that reaches nothing host-only** — no cordis context, no credential seam, no node built-in — and the bundle gate is what enforces it, by failing the build rather than by anyone remembering. An earlier version of this line named two files as the only ones allowed, which the tree had already outgrown. ## `src/utils/` — pure by construction, enforced by test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9d07091..9babbc54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,7 @@ ## Prerequisites - Node.js `>= 24` (CI runs the version in `.node-version`, and the floor in a job of its own), `pnpm` (v12). The suite refuses to start below the floor and names both versions: below it, `node:sqlite`, `Intl.DurationFormat` and the ICU strings the tests pin differ, and the failures read like regressions. +- The host versions the suite runs against (pi, the `@deepseek-ai` cluster, cordis and its loader) are named once, in `test/support/host-versions.ts`. `pnpm install` pins pi for every copy in the tree and applies the harness's own patch from `patches/` — that is what the `overrides` and `patchedDependencies` in `pnpm-workspace.yaml` do. - A checkout of this repo; for live testing, a DeepSeek Harness profile (see README for the `link:` setup) ## Commands diff --git a/docs/improvement-plan.md b/docs/improvement-plan.md index 5bd75629..74f6937f 100644 --- a/docs/improvement-plan.md +++ b/docs/improvement-plan.md @@ -22,7 +22,7 @@ Phases land as **stacked pull requests**. Each one is based on the previous phas | P1a | `fix:` shipped breakage | in review | | P1b | `chore:` hygiene | in review | | P2 | `feat:` a smaller public API | in review | -| P-T1a | `build:` pin the host | planned | +| P-T1a | `build:` pin the host | in review | | P-T1b | `test:` a harness that is the real host | planned | | P-H | `refactor:` one way to patch a host method | planned | | P-R1 | `feat:` the plugin owns the plane routes | planned | diff --git a/package.json b/package.json index c7ac6f54..d807ce0c 100644 --- a/package.json +++ b/package.json @@ -85,15 +85,20 @@ "version:major": "vp pm version major --no-git-checks" }, "dependencies": { - "@deepseek-ai/schemastery": "3.18.5-alpha.2" + "@deepseek-ai/schemastery": "3.18.5" }, "devDependencies": { - "@deepseek-ai/cordis": "4.0.5-alpha.2", - "@deepseek-ai/dsh-client-store": "0.2.1-alpha.5", - "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.5", - "@deepseek-ai/dsh-llm-pi-ai": "0.2.1-alpha.5", - "@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.5", - "@earendil-works/pi-ai": "1.3.0", + "@deepseek-ai/cordis": "4.0.5", + "@deepseek-ai/cordis-plugin-loader": "1.0.6", + "@deepseek-ai/dsh-app-boot": "0.2.1-rc.2", + "@deepseek-ai/dsh-client-store": "0.2.1-rc.2", + "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2", + "@deepseek-ai/dsh-config-editor": "0.2.1-rc.2", + "@deepseek-ai/dsh-llm": "0.2.1-rc.2", + "@deepseek-ai/dsh-llm-pi-ai": "0.2.1-rc.2", + "@deepseek-ai/dsh-settings": "0.2.1-rc.2", + "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2", + "@earendil-works/pi-ai": "1.1.0", "@testing-library/dom": "^10.4.2", "@testing-library/react": "^16.3.3", "@types/node": "^26.6.5", diff --git a/patches/@earendil-works__pi-ai@1.1.0.patch b/patches/@earendil-works__pi-ai@1.1.0.patch new file mode 100644 index 00000000..d1a3b542 --- /dev/null +++ b/patches/@earendil-works__pi-ai@1.1.0.patch @@ -0,0 +1,73 @@ +diff --git a/dist/api/anthropic-messages.js b/dist/api/anthropic-messages.js +index 38571b31e143e913c2bf59dad73382982e93664a..950e546197d8e84cb3edfca0012939f1db2488d8 100644 +--- a/dist/api/anthropic-messages.js ++++ b/dist/api/anthropic-messages.js +@@ -553,7 +553,6 @@ export const stream = (model, context, options) => { + const block = blocks[index]; + if (block && block.type === "toolCall") { + block.partialJson += event.delta.partial_json; +- block.arguments = parseStreamingJson(block.partialJson); + stream.push({ + type: "toolcall_delta", + contentIndex: index, +diff --git a/dist/api/bedrock-converse-stream.js b/dist/api/bedrock-converse-stream.js +index ca40f1b4a4e99f441503cf364822073a7a6c478a..14c204261b3f6c47f8950cf44f0fb9f86cf2cda7 100644 +--- a/dist/api/bedrock-converse-stream.js ++++ b/dist/api/bedrock-converse-stream.js +@@ -461,7 +461,6 @@ function handleContentBlockDelta(event, blocks, output, stream) { + } + else if (delta?.toolUse && block?.type === "toolCall") { + block.partialJson = (block.partialJson || "") + (delta.toolUse.input || ""); +- block.arguments = parseStreamingJson(block.partialJson); + stream.push({ type: "toolcall_delta", contentIndex: index, delta: delta.toolUse.input || "", partial: output }); + } + else if (delta?.reasoningContent) { +diff --git a/dist/api/mistral-conversations.js b/dist/api/mistral-conversations.js +index 2fc3ad343af090a919f5b71454b29f23b009fc21..64aa8a4ab2461484f22066913455518f69157857 100644 +--- a/dist/api/mistral-conversations.js ++++ b/dist/api/mistral-conversations.js +@@ -554,7 +554,6 @@ async function consumeChatStream(model, output, stream, mistralStream, onProvide + ? toolCall.function.arguments + : JSON.stringify(toolCall.function.arguments || {}); + block.partialArgs = (block.partialArgs || "") + argsDelta; +- block.arguments = parseStreamingJson(block.partialArgs); + stream.push({ + type: "toolcall_delta", + contentIndex: toolBlocksByKey.get(key), +diff --git a/dist/api/openai-completions.js b/dist/api/openai-completions.js +index a60f39352b776d71497e55b33b3334bacfbeb2f5..6c9b98e35606c52e4f0fb29d8fd0b5ba02d92e09 100644 +--- a/dist/api/openai-completions.js ++++ b/dist/api/openai-completions.js +@@ -438,7 +438,6 @@ export const stream = (model, context, options) => { + if (toolCall.function?.arguments) { + delta = toolCall.function.arguments; + block.partialArgs = (block.partialArgs ?? "") + toolCall.function.arguments; +- block.arguments = parseStreamingJson(block.partialArgs); + } + else if (toolCall.custom?.input) { + const nextInput = getCustomToolCallInput(block) + toolCall.custom.input; +diff --git a/dist/api/openai-responses-shared.js b/dist/api/openai-responses-shared.js +index f413fe5f2a1abcac39c38bbd4a77b38c349a5918..108f599ae1d985d2f465e8cdf99ce0624acea6aa 100644 +--- a/dist/api/openai-responses-shared.js ++++ b/dist/api/openai-responses-shared.js +@@ -548,7 +548,6 @@ export async function processResponsesStream(openaiStream, output, stream, model + if (!slot || slot.block.partialJson === undefined) + continue; + slot.block.partialJson += event.delta; +- slot.block.arguments = parseStreamingJson(slot.block.partialJson); + pushToolCallDelta(slot, event.delta); + } + else if (event.type === "response.function_call_arguments.done") { +diff --git a/dist/api/pi-messages.js b/dist/api/pi-messages.js +index 7da1c45de8a106a68411cf79bec75f553534a1c7..296d131b59b1cf75f2516c39e85bba72972b2c17 100644 +--- a/dist/api/pi-messages.js ++++ b/dist/api/pi-messages.js +@@ -155,8 +155,6 @@ function createEventConverter(model) { + case "toolcall_delta": { + const json = `${toolJson.get(event.contentIndex) ?? ""}${event.delta}`; + toolJson.set(event.contentIndex, json); +- partial.content[event.contentIndex].arguments = +- parseStreamingJson(json); + break; + } + case "toolcall_end": diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35a94d0b..9f471e54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,34 +163,53 @@ settings: excludeLinksFromLockfile: false overrides: + '@earendil-works/pi-ai': 1.1.0 vite@*: npm:@voidzero-dev/vite-plus-core@1.2.0 +patchedDependencies: + '@earendil-works/pi-ai@1.1.0': 66eaf960054f10441b93ac3857114e96c1901f6d183713e2f21437b007111644 + importers: .: dependencies: '@deepseek-ai/schemastery': - specifier: 3.18.5-alpha.2 - version: 3.18.5-alpha.2 + specifier: 3.18.5 + version: 3.18.5 devDependencies: '@deepseek-ai/cordis': - specifier: 4.0.5-alpha.2 - version: 4.0.5-alpha.2 + specifier: 4.0.5 + version: 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/cordis-plugin-loader': + specifier: 1.0.6 + version: 1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9) + '@deepseek-ai/dsh-app-boot': + specifier: 0.2.1-rc.2 + version: 0.2.1-rc.2(@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)) '@deepseek-ai/dsh-client-store': - specifier: 0.2.1-alpha.5 - version: 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + specifier: 0.2.1-rc.2 + version: 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) '@deepseek-ai/dsh-client-ui-primitives': - specifier: 0.2.1-alpha.5 - version: 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + specifier: 0.2.1-rc.2 + version: 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-config-editor': + specifier: 0.2.1-rc.2 + version: 0.2.1-rc.2(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-app-boot@0.2.1-rc.2(@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))) + '@deepseek-ai/dsh-llm': + specifier: 0.2.1-rc.2 + version: 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) '@deepseek-ai/dsh-llm-pi-ai': - specifier: 0.2.1-alpha.5 - version: 0.2.1-alpha.5(2dda082788d557301714a52484eab818) + specifier: 0.2.1-rc.2 + version: 0.2.1-rc.2(8200f6d81a43fd408065f697f39a99ec) + '@deepseek-ai/dsh-settings': + specifier: 0.2.1-rc.2 + version: 0.2.1-rc.2(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-app-boot@0.2.1-rc.2(@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)))(@deepseek-ai/dsh-brand@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-session@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)))(@deepseek-ai/schemastery@3.18.5)(node-addon-require-builtin@0.1.9) '@deepseek-ai/dsh-typert-protocol': - specifier: 0.2.1-alpha.5 - version: 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + specifier: 0.2.1-rc.2 + version: 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) '@earendil-works/pi-ai': - specifier: 1.3.0 - version: 1.3.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.6.5) + specifier: 1.1.0 + version: 1.1.0(patch_hash=66eaf960054f10441b93ac3857114e96c1901f6d183713e2f21437b007111644)(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.6.5) '@testing-library/dom': specifier: ^10.4.2 version: 10.4.2 @@ -416,12 +435,33 @@ packages: resolution: {integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==} engines: {node: '>=20.19.0'} - '@deepseek-ai/cordis@4.0.5-alpha.2': - resolution: {integrity: sha512-rd5Qp6PNrMk+0saWVMLUaVv08s/WBwf2TXM84ST6tppFqlHS0Yl9l/NwPrA4oVOuS/1BKCYOGlZ6zJRj+JG6IQ==} + '@deepseek-ai/cordis-plugin-group@1.0.5': + resolution: {integrity: sha512-PGYqImAydmAjmy5fl1Js6qDmk0Ivt1fipa3fOj1KyyRd0tZ9i61a2rhzHc7LaSeBb/spJ1LF4k3Pcppaq+w8fw==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/cordis-plugin-loader': ~1.0.6 + + '@deepseek-ai/cordis-plugin-include@1.0.10': + resolution: {integrity: sha512-Rdo6Uqf9WfDbfN3dsYkYj/EW+SGuWCSfthz1PoOtZYUr6qrRpqD7ND4Gj3nUYj8XhSGIo8E3xpsck6J5nM5BYQ==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/cordis-plugin-loader': ~1.0.6 + + '@deepseek-ai/cordis-plugin-loader@1.0.6': + resolution: {integrity: sha512-fr011A782oEjkJXCY4pePt2qaPpkGvem7Rmst6Rhwgg6icgwI4eN9QBRb6S/q9XOYGVQUdvL0wXUWyNxO8WipA==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 + node-addon-require-builtin: ^0.1.9 + peerDependenciesMeta: + node-addon-require-builtin: + optional: true + + '@deepseek-ai/cordis@4.0.5': + resolution: {integrity: sha512-Sktq5axy0owk9PdnivBRNOJjHEVEibCt0+TDDZcv0+UH2KS1M910fX4kn5y45YFsrpZIi1mJf/1s5VzywMUnyg==} hasBin: true peerDependencies: - '@deepseek-ai/cordis-plugin-include': ~1.0.10-alpha.2 - '@deepseek-ai/cordis-plugin-loader': ~1.0.6-alpha.2 + '@deepseek-ai/cordis-plugin-include': ~1.0.10 + '@deepseek-ai/cordis-plugin-loader': ~1.0.6 peerDependenciesMeta: '@deepseek-ai/cordis-plugin-include': optional: true @@ -431,119 +471,161 @@ packages: '@deepseek-ai/cosmokit@1.8.6': resolution: {integrity: sha512-ZSD/fN52L498LBIOYwjIOlaLv5veY6ZEwI2yzvZUKQtctn0QAPqu8tKdJ+OF5+Tv6NBq1U+wJWH+QI6OGy5EvQ==} - '@deepseek-ai/cosmokit@1.8.6-alpha.2': - resolution: {integrity: sha512-FcxdMUal80CeFURh3lPYsw9TPDMVSGpovWnA3JtTGrBG15XFli4k38z1Egso1lgvifKpIW4Hx42X6UTelLEOAA==} + '@deepseek-ai/dsh-app-boot@0.2.1-rc.2': + resolution: {integrity: sha512-kMS4f6AGOPjGsTz3UXWihl5l2Vnm8y9dIayXIF9EafV6F73vgGR+uoBAq4p8qxSq72QfUSGx2yuDP2Hjw4ktlg==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/cordis-plugin-group': ~1.0.5 + '@deepseek-ai/cordis-plugin-include': ~1.0.10 + '@deepseek-ai/cordis-plugin-loader': ~1.0.6 + '@deepseek-ai/dsh-home-paths': 0.2.1-rc.2 + '@deepseek-ai/dsh-launch-environment': 0.2.1-rc.2 + + '@deepseek-ai/dsh-atomic-write@0.2.1-rc.2': + resolution: {integrity: sha512-yCvLoL55Uoaxsi06I8hOcPgvMOhIt7w8vE/FLe3iGzvGq5OfrqkanAmt++apq069Jo6R/fP552XOhBlW8S3utg==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 + + '@deepseek-ai/dsh-attachment@0.2.1-rc.2': + resolution: {integrity: sha512-VtSSPYq6cOTZ3kLUAHkjIRq6SOjecfZ4p3lzn1dBU1EcUFJ5fOcN1pM2CLVec4ZYrvMxiB5XwtMeFDQPQ7jgpw==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/dsh-brand': 0.2.1-rc.2 - '@deepseek-ai/dsh-attachment@0.2.1-alpha.5': - resolution: {integrity: sha512-VKtULJAMMoS+t/nK8dWz9KjXouC0arFL56U65HF+pwWZbtt/nPAQphI37xjAm4hdbfkdZSUXPOeybFu7k1VtMw==} + '@deepseek-ai/dsh-authorization@0.2.1-rc.2': + resolution: {integrity: sha512-5zagVBk/p1SOWvUsT4CvHPGHRxMNVl/ClAbkIyIH43cTq0NR24EuXciWLJ7MHIDCNU+a3h+P3rm3E/WjZWp+PQ==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5 + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/dsh-credentials': 0.2.1-rc.2 + '@deepseek-ai/dsh-llm': 0.2.1-rc.2 - '@deepseek-ai/dsh-authorization@0.2.1-alpha.5': - resolution: {integrity: sha512-mt9HcWmG24m/wm3p6koPxOKXqZmV4gU14xhHMIQKF2N864AKEeyW4H3CYlLGoEK1TWRMbdqlpGB1QCeumjdMgw==} + '@deepseek-ai/dsh-brand@0.2.1-rc.2': + resolution: {integrity: sha512-T2kcygikRS8rv8cKjgho2x381EgGprv5yQmvHl4/iYbuHE19OW+SLGtHWbV27PGdSHz5U8CJxasYzFosKW4e2A==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 - '@deepseek-ai/dsh-credentials': 0.2.1-alpha.5 - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-brand@0.2.1-alpha.5': - resolution: {integrity: sha512-BfOPg8MMlmbzWq4zL3f/Dlwqk3PjsIwjQVzF+uiEzFkOdXo5nPXEqP/9mso1cn7d7eTVfO0eXkCsPD1Py9AoJQ==} + '@deepseek-ai/dsh-client-store@0.2.1-rc.2': + resolution: {integrity: sha512-cYijKdEVbvROtorEcTl6ZkHMSbi2Utkh3pklaLQuC7aQl0FgMxXsnvNpmlx6AEjJkaZB+Quwmd3tH9EKFZf0dQ==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-client-store@0.2.1-alpha.5': - resolution: {integrity: sha512-6sWqY7bTbTstwdIWvYJcqPNDwmh+4IgmMIQ66EtJcdrV7KHryQ2M8Lc3EzB3VQ4yv482yEdIViRd97GaeI/nKQ==} + '@deepseek-ai/dsh-client-ui-primitives@0.2.1-rc.2': + resolution: {integrity: sha512-3K6W2ID4sbDUDHhZHs8cNO15XDXBWAYFnEzJ/iD8ZASqidazcJActxlEf8GO6NHmbbeX0lEq0P20Ftwhovt+Mw==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 + + '@deepseek-ai/dsh-config-editor@0.2.1-rc.2': + resolution: {integrity: sha512-gVJAPbKUKMwMqLtfFufBZtidpcdhrGlRW3gcSTSOKTq3dulCcSqKDtsLhzzgP3aVfoFwrO/QaQ9XebSYx7kEhA==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/cordis-plugin-include': ~1.0.10 + '@deepseek-ai/cordis-plugin-loader': ~1.0.6 + '@deepseek-ai/dsh-app-boot': 0.2.1-rc.2 + '@deepseek-ai/dsh-hmr': 0.2.1-rc.2 + peerDependenciesMeta: + '@deepseek-ai/dsh-hmr': + optional: true - '@deepseek-ai/dsh-client-ui-primitives@0.2.1-alpha.5': - resolution: {integrity: sha512-cgkd0BPKcHY5kT0Fj8zSAG0Uin/37vyw9xJOwZQrL6xVtYXQUZWFcxWFNM5/YK7sZ7pv6mZh2hbauzIylBj5Xw==} + '@deepseek-ai/dsh-credentials@0.2.1-rc.2': + resolution: {integrity: sha512-9nCOCdjkW+puJB/ZZtnKN11BJv+TpiqnMFzfA35+yT7lL6fXXJTk3kGaajunmHqPdgKE6UFlL6mlrxP4FlbSdw==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-credentials@0.2.1-alpha.5': - resolution: {integrity: sha512-46cdmseYrJ17Thc+DSKsOM8XbjE1sUxZ+gLJL9qd1o7CKvqPIH7QpHSjm+kKk7szR8v8mZoVvKzM6TBQs0EtCA==} + '@deepseek-ai/dsh-fs@0.2.1-rc.2': + resolution: {integrity: sha512-Z6d2ZKsAiJc0FaiES8wC66vuUQMGohl9tCc2p3bAsPFYkJ0+L2zTViQ9Y5BArtRe9Br8qhNjNgLlN0jb/uwv1g==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/dsh-brand': 0.2.1-rc.2 + '@deepseek-ai/dsh-llm': 0.2.1-rc.2 + '@deepseek-ai/dsh-sandbox': 0.2.1-rc.2 - '@deepseek-ai/dsh-fs@0.2.1-alpha.5': - resolution: {integrity: sha512-o6IfZ2BFdMLlvEXwjhHTg44O5DYJDSJGntMB4ShZp25g46UawA8po5y1fcaUlWXCQf/LAfAhoXMc23ZMY5GzsQ==} + '@deepseek-ai/dsh-home-paths@0.2.1-rc.2': + resolution: {integrity: sha512-+byrn2eS9XaFt6pl1yZXIgkm7GbbzTwzX3Eo19Ew9P1xzzucw/j2HSmdFo221/GK+5PH7ct1ldeT+fAxAJ1e9w==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5 - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5 - '@deepseek-ai/dsh-sandbox': 0.2.1-alpha.5 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-launch-environment@0.2.1-alpha.5': - resolution: {integrity: sha512-uQE32L2HVZyR958OLOpXHYknLaRPmPvA0wm5jRK/reJUy+LoEa9CTSDhLgqjoC8UGiSJ52FTLQNuIqol2fpF5Q==} + '@deepseek-ai/dsh-launch-environment@0.2.1-rc.2': + resolution: {integrity: sha512-EXoLY+5i71V2uBrqa1Pk+eamwqc/6Di7N2BIvqs9GDZ09RFk6ohsqs/hObev2Z/ehWr6BYIS112c1qbf/P79Wg==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-llm-pi-ai@0.2.1-alpha.5': - resolution: {integrity: sha512-lobuWf8w2w7H5DpsVbv6+YiA5F8D1IerAUlr/EOXkGdQIPU6rV/usPIRFB2Wnvtbz4dh/FWU4F3Wb7NRTVVNFw==} + '@deepseek-ai/dsh-llm-pi-ai@0.2.1-rc.2': + resolution: {integrity: sha512-sauT3s9LwRtJTuHJL6mIZSmMjy5S1o2u9qb/MFuo1VYc8CJwTL74/RxXYLX0vpRP2Hk7TFJkJFilx5jagMecsw==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 - '@deepseek-ai/cordis-plugin-loader': ~1.0.6-alpha.2 - '@deepseek-ai/dsh-attachment': 0.2.1-alpha.5 - '@deepseek-ai/dsh-authorization': 0.2.1-alpha.5 - '@deepseek-ai/dsh-credentials': 0.2.1-alpha.5 - '@deepseek-ai/dsh-fs': 0.2.1-alpha.5 - '@deepseek-ai/dsh-launch-environment': 0.2.1-alpha.5 - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5 - '@deepseek-ai/dsh-timeout': 0.2.1-alpha.5 + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/cordis-plugin-loader': ~1.0.6 + '@deepseek-ai/dsh-attachment': 0.2.1-rc.2 + '@deepseek-ai/dsh-authorization': 0.2.1-rc.2 + '@deepseek-ai/dsh-credentials': 0.2.1-rc.2 + '@deepseek-ai/dsh-fs': 0.2.1-rc.2 + '@deepseek-ai/dsh-launch-environment': 0.2.1-rc.2 + '@deepseek-ai/dsh-llm': 0.2.1-rc.2 + '@deepseek-ai/dsh-timeout': 0.2.1-rc.2 peerDependenciesMeta: '@deepseek-ai/cordis-plugin-loader': optional: true - '@deepseek-ai/dsh-llm@0.2.1-alpha.5': - resolution: {integrity: sha512-bb8oQ37OCkc7PTjsfnmeZtVpXuXsXTEzHd7zi/l78Qt/3mwCIA8JbHDs5+tSyOzOOEPyI32Ej7llw1/B8iiQhA==} + '@deepseek-ai/dsh-llm@0.2.1-rc.2': + resolution: {integrity: sha512-3xfoMDHEpUJGcH2Lu+lNXUJYRBclYfBkdCG9Au02VzIjYAm9BrFmc/MNuaVTg0vwgvPpTsd6Fx1IeGtfmQFvlg==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-sandbox@0.2.1-alpha.5': - resolution: {integrity: sha512-D8J5pua766fJO9C4PspRjahB0E70Vb6Czx/8hyd6Dd3ozmlIvkYj+S+iQWHdc0LuORUqJV4F90taCqWlovzxLw==} + '@deepseek-ai/dsh-package-manifest@0.2.1-rc.2': + resolution: {integrity: sha512-qJsGBj+T3oGzwK5GZbewneQNhD+3ENqIrCSOUAL8GKWQsl58daMAHDyqTuetvbkzuuXezwrv4Ll/BkKhx5FrQw==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5 - '@deepseek-ai/dsh-session': 0.2.1-alpha.5 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-scope@0.2.1-alpha.5': - resolution: {integrity: sha512-zHmy+rqqvg/m7YNDAUkXxayC5sh5rbTQOo9Kv4qum4LFBTET9caLVMemrpxyE+edd3TX1BfxQ3ut3nrjBwhqpg==} + '@deepseek-ai/dsh-sandbox@0.2.1-rc.2': + resolution: {integrity: sha512-nnxNIkgi4A4tkPrTV8sbVMse3c32OT0U+mwYhLqDz/+cIm5tUGufQD+Ax0zqVeClyzXsxZo0/pVtcqDJrsp+7w==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/dsh-llm': 0.2.1-rc.2 + '@deepseek-ai/dsh-session': 0.2.1-rc.2 - '@deepseek-ai/dsh-session@0.2.1-alpha.5': - resolution: {integrity: sha512-ATXy590TD6uY8Ih3rt6jCZ9d+JctY1hR5IUdVa8Wpj5vu0Sutv/BQB5JizoIpr5fo1HBf5PICLRZ+DbChbZhhQ==} + '@deepseek-ai/dsh-scope@0.2.1-rc.2': + resolution: {integrity: sha512-CVGPI4lzmUy7O3zg8X1LavUSmD3JKFkUdykfHN7VkyWXG5oAXSvig9s5PLnIb+sxRItmSpiqPXGnT/48X1T1qg==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 - '@deepseek-ai/dsh-scope': 0.2.1-alpha.5 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-timeout@0.2.1-alpha.5': - resolution: {integrity: sha512-oNJ7cpT9XWsk0sSmdEx32kJ2G5Vu8CbPxxjr9gkR/cfVbq8Z0AOWsgM3nipZ/Pt1E8RvlSBMU23JetbK8R2qkg==} + '@deepseek-ai/dsh-session@0.2.1-rc.2': + resolution: {integrity: sha512-WDA4lIrXtG5nf1dn2wrA4UzEr7D6e8T3AdPpVSZ7eY4N4ICMDZ0RYuD4GkM+iSb7WkLojdO8ljr0etiIMEbqJg==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/dsh-scope': 0.2.1-rc.2 - '@deepseek-ai/dsh-typert-protocol@0.2.1-alpha.5': - resolution: {integrity: sha512-WkVbS2x9gsWkxwxMe72DxJKwMeOaapif0pyCC6PE82qUKvcNqpsWrLQZMEwxxikdW3MJGYOTBfeqizQq/qvX5w==} + '@deepseek-ai/dsh-settings@0.2.1-rc.2': + resolution: {integrity: sha512-CtpetovAC2ZQ1rjIFYHbu1LOcGXTulP5IhlxKqDYSDhLEQGoU2L6LnN6HpGKuU/LBCAAN88ihP+B/2E5rTNcnQ==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 + '@deepseek-ai/dsh-brand': 0.2.1-rc.2 + '@deepseek-ai/dsh-session': 0.2.1-rc.2 + '@deepseek-ai/schemastery': ~3.18.5 - '@deepseek-ai/dsh-util-crypto@0.2.1-alpha.5': - resolution: {integrity: sha512-22att1gQVYqLN1fRYym8OIIMSR1eQPlGpvA48a4ycilI2ccvvGBFHZqfh/KnARJfOumYld3CuDUIBlTFkj0jcg==} + '@deepseek-ai/dsh-timeout@0.2.1-rc.2': + resolution: {integrity: sha512-R75r+GkYP+ZhtIsIfn8MMuRXPkugJr0OyQ1Y+rauoTae2sdVfnE8v5zvQORRCHdh7MiOcmh10dQN70KjHh7qhQ==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/dsh-util-values@0.2.1-alpha.5': - resolution: {integrity: sha512-RiKa0vkqNKqwKcZkEs68kcE1HfXzvLswt+dr35xlNMsiGEhNSLirEcm8r16uGEVUIAtQEici4oI1KFKlYeXoZQ==} + '@deepseek-ai/dsh-typert-protocol@0.2.1-rc.2': + resolution: {integrity: sha512-rtsHJHGE9SWX+zOgtDbPUqz3h7r+qFm3h1WASzRJxDnULqo7JaBdIiGshNTFPmetsX+r1T69CKFdTZAJmHwMug==} peerDependencies: - '@deepseek-ai/cordis': ~4.0.5-alpha.2 + '@deepseek-ai/cordis': ~4.0.5 - '@deepseek-ai/schemastery@3.18.5-alpha.2': - resolution: {integrity: sha512-W+2TSjdeKOEKqaii9BNpw7ev2Yf8C+J74i/BE/VBx56O7BX0zxV2zQKwRY4ADLTXD5O69iJ+/Skm91/ES7iMgw==} + '@deepseek-ai/dsh-util-crypto@0.2.1-rc.2': + resolution: {integrity: sha512-V4SxvXL4ZbY5RYJckVtmo3qz4YsA52pYKvDF13+8hsDCEmQIv9YfmLRMA61pjK/i05pGMe3p1pPhAB6L+NOIEA==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 + + '@deepseek-ai/dsh-util-values@0.2.1-rc.2': + resolution: {integrity: sha512-mgnByPadOgZtoBxbwMI184li494O6sNn66/NvdqN0RIrzAJmFGpFxpjFkQtQN/yetwWhlx7Ic17+6p7eKl8DNg==} + peerDependencies: + '@deepseek-ai/cordis': ~4.0.5 - '@earendil-works/pi-ai@1.3.0': - resolution: {integrity: sha512-KaBI8x7q7sPoFUXICNGD9efHzDG+oRNwVvkA9jdIsSQWH1RjFssOlEGsbLtewUPPgCrvmz0l2KTgSLXTyj3acw==} + '@deepseek-ai/schemastery@3.18.5': + resolution: {integrity: sha512-Mnobvqf9rHsZYv4g5edj5ddxS1oF1kIdSlz0DunUgnY3LWG5F26MqvH3YOOsSaodCgyzJ16OMN9O99/s5Crtdw==} + + '@earendil-works/pi-ai@1.1.0': + resolution: {integrity: sha512-1T7LAkc/5Bvc0v6w4vAGVdCrli0o/E0pEmYKTnixu95vSFArBjvbhS/G4ZwI0RUePgf0Imcu0VyqlM4EcXxqfw==} engines: {node: '>=22.19.0'} hasBin: true @@ -551,6 +633,10 @@ packages: resolution: {integrity: sha512-ERGZr+aXjNJzoUbB7ZzHVH3g5otzMHjHLs2Fyizm12By5X+gBEhod9cJXFOk13S7M8a29Am+Mq21zm73mRJFLg==} engines: {node: '>=22.19.0'} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@exodus/bytes@1.16.0': resolution: {integrity: sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1499,6 +1585,9 @@ packages: resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} engines: {node: '>= 20'} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-escapes@7.3.0: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} @@ -1519,6 +1608,9 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -1647,6 +1739,9 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1660,6 +1755,9 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.9: + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} + fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -1790,6 +1888,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + jsdom@30.1.3: resolution: {integrity: sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} @@ -1806,6 +1908,9 @@ packages: resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} engines: {node: '>=16'} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} @@ -1944,6 +2049,72 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-addon-native-custom-loader@0.1.9: + resolution: {integrity: sha512-Zh87aubSc1UF8CzZsRws1KtSwXiPDH0tbcg8V/2OA8Tcq9yGJRESFcEOC5NGLFama5IGfC3aJhubZbrj9Un2qA==} + engines: {node: '>=20'} + + node-addon-require-builtin-darwin-arm64@0.1.9: + resolution: {integrity: sha512-n45xEILQVOLZ2hVhzRXesTMYS4odHquyI/B/IkPMPUCONa8PjNfBag6lYBH4wDrhgpLNNBwMsUD8d8O/13QPPA==} + engines: {node: '>=20'} + cpu: [arm64] + os: [darwin] + + node-addon-require-builtin-darwin-x64@0.1.9: + resolution: {integrity: sha512-Sm4Liqh1uo9zgJB2F/pACPPZdHu+fj9Qniyr1Q99dDQB0v71VgZgkAgv3K0/E1iPQBlZq9rakFkY7MGUIfWc0A==} + engines: {node: '>=20'} + cpu: [x64] + os: [darwin] + + node-addon-require-builtin-linux-arm64-gnu@0.1.9: + resolution: {integrity: sha512-ToU4wGtOD/069i2y4fJ3cjDJiiWtcjSQTZMf0YKlotb5BmTM7FjhR3X1aub8J/YjlOwl6665J/XAYuvCaDO5sA==} + engines: {node: '>=20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + node-addon-require-builtin-linux-arm64-musl@0.1.9: + resolution: {integrity: sha512-85Z5Avp++wfa9iiTIH8LC/r6qvlC/CiwpifiwIz0gYQ9PzajTSkz06gX7YDqf1oUqWYxU9uwNEOmo7mktHOOfA==} + engines: {node: '>=20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + node-addon-require-builtin-linux-x64-gnu@0.1.9: + resolution: {integrity: sha512-fh3K3/JvBfSYwU8vEOpp4Wdp4N/jX0EnZYq5GsaftlQin1FVqgve/yDNoh+asxBMOQyHOG8F4dXlV1WCierrfg==} + engines: {node: '>=20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + node-addon-require-builtin-linux-x64-musl@0.1.9: + resolution: {integrity: sha512-B6fu/5xmbRxnifM7nWz0IESkYnUl8dwFZmRDMsxdhzv2nb+EtBRFAXg4lf73qhX3yoZD4SnWJILBQMlIAwBCzw==} + engines: {node: '>=20'} + cpu: [x64] + os: [linux] + libc: [musl] + + node-addon-require-builtin-win32-arm64-msvc@0.1.9: + resolution: {integrity: sha512-FOQuzNe8B1BANu886rJgdbQuRxPGUQmewkykw87wSr32U0ibQ3AWGuUM/Gh7LG4RekjFaifj7gEI/tq2+6GU6g==} + engines: {node: '>=20'} + cpu: [arm64] + os: [win32] + + node-addon-require-builtin-win32-ia32-msvc@0.1.9: + resolution: {integrity: sha512-N29P0kmIBT6hnkNNFYzxn+o9ydT6OYZe9RFAi8dSxV1rNSul1FlxtuKWYe8w2sQpbQGYuGDEtpOwdeFtq2iFTg==} + engines: {node: '>=20 <23'} + cpu: [ia32] + os: [win32] + + node-addon-require-builtin-win32-x64-msvc@0.1.9: + resolution: {integrity: sha512-+i6N4A7m7txjbw8pDYESdWXiKO9EEVq0SzTzwPS0WZuV4F1MzQnaDJwJJnO7T6zAc0BvqHXBOkpharCrUjFhbQ==} + engines: {node: '>=20'} + cpu: [x64] + os: [win32] + + node-addon-require-builtin@0.1.9: + resolution: {integrity: sha512-BCxXCMZDs/VLLh64SwKNw/me7Qx1I7Be4qxWwYVRRm8tJoOB8fL7qg6CmXhSXeUqsKt72t8RaKCX4EN+5nocVg==} + engines: {node: '>=20'} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -2689,68 +2860,126 @@ snapshots: '@csstools/css-tokenizer@4.0.2': {} - '@deepseek-ai/cordis@4.0.5-alpha.2': + '@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/cordis-plugin-loader': 1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9) + + '@deepseek-ai/cordis-plugin-include@1.0.10(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/cordis-plugin-loader': 1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9) + '@deepseek-ai/cosmokit': 1.8.6 + js-yaml: 4.3.2 + + '@deepseek-ai/cordis-plugin-loader@1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9)': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/cosmokit': 1.8.6 + '@deepseek-ai/schemastery': 3.18.5 + optionalDependencies: + node-addon-require-builtin: 0.1.9 + + '@deepseek-ai/cordis@4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)': dependencies: '@deepseek-ai/cosmokit': 1.8.6 '@standard-schema/spec': 1.1.0 + optionalDependencies: + '@deepseek-ai/cordis-plugin-include': 1.0.10(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/cordis-plugin-loader': 1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9) '@deepseek-ai/cosmokit@1.8.6': {} - '@deepseek-ai/cosmokit@1.8.6-alpha.2': {} + '@deepseek-ai/dsh-app-boot@0.2.1-rc.2(@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/cordis-plugin-group': 1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/cordis-plugin-include': 1.0.10(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/cordis-plugin-loader': 1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9) + '@deepseek-ai/dsh-atomic-write': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-home-paths': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-launch-environment': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-package-manifest': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@eslint-community/regexpp': 4.12.2 + ajv: 8.20.0 + js-yaml: 4.3.2 + node-addon-require-builtin: 0.1.9 + resolve.exports: 2.0.3 + semver: 7.8.5 + + '@deepseek-ai/dsh-atomic-write@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + + '@deepseek-ai/dsh-attachment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-brand@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-brand': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + + '@deepseek-ai/dsh-authorization@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-credentials@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-credentials': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-llm': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) - '@deepseek-ai/dsh-attachment@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-brand@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))': + '@deepseek-ai/dsh-brand@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/dsh-authorization@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-credentials@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))': + '@deepseek-ai/dsh-client-store@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-credentials': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/dsh-brand@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-client-ui-primitives@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/dsh-client-store@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-config-editor@0.2.1-rc.2(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-app-boot@0.2.1-rc.2(@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)))': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/cordis-plugin-include': 1.0.10(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/cordis-plugin-loader': 1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9) + '@deepseek-ai/dsh-app-boot': 0.2.1-rc.2(@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)) + '@deepseek-ai/dsh-atomic-write': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + js-yaml: 4.3.2 + yaml: 2.9.1 - '@deepseek-ai/dsh-client-ui-primitives@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-credentials@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-brand': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) - '@deepseek-ai/dsh-credentials@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-fs@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-brand@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-sandbox@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-session@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))))': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-brand': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-llm': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-sandbox': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-session@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))) - '@deepseek-ai/dsh-fs@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-brand@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-sandbox@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-session@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-scope@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))))': + '@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-sandbox': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-session@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-scope@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))) + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/dsh-launch-environment@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/dsh-llm-pi-ai@0.2.1-alpha.5(2dda082788d557301714a52484eab818)': + '@deepseek-ai/dsh-llm-pi-ai@0.2.1-rc.2(8200f6d81a43fd408065f697f39a99ec)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-attachment': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-brand@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)) - '@deepseek-ai/dsh-authorization': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-credentials@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)) - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-credentials': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-fs': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-brand@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-sandbox@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-session@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-scope@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)))) - '@deepseek-ai/dsh-launch-environment': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-timeout': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-util-values': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/schemastery': 3.18.5-alpha.2 - '@earendil-works/pi-ai': 1.3.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.6.5) + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-attachment': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-brand@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)) + '@deepseek-ai/dsh-authorization': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-credentials@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)) + '@deepseek-ai/dsh-brand': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-credentials': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-fs': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-brand@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-sandbox@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-session@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)))) + '@deepseek-ai/dsh-launch-environment': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-llm': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-timeout': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-util-values': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/schemastery': 3.18.5 + '@earendil-works/pi-ai': 1.1.0(patch_hash=66eaf960054f10441b93ac3857114e96c1901f6d183713e2f21437b007111644)(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.6.5) + optionalDependencies: + '@deepseek-ai/cordis-plugin-loader': 1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9) transitivePeerDependencies: - '@aws-sdk/credential-provider-node' - '@modelcontextprotocol/sdk' @@ -2764,60 +2993,81 @@ snapshots: - ws - zod - '@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-timeout': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-typert-protocol': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-util-crypto': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-util-values': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/schemastery': 3.18.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-brand': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-timeout': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-typert-protocol': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-util-crypto': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-util-values': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/schemastery': 3.18.5 zod: 4.4.3 - '@deepseek-ai/dsh-sandbox@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-llm@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))(@deepseek-ai/dsh-session@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-scope@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)))': + '@deepseek-ai/dsh-package-manifest@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-session': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-scope@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)) - '@deepseek-ai/dsh-util-values': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/dsh-scope@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-sandbox@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-llm@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-session@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)))': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-llm': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-session': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)) + '@deepseek-ai/dsh-util-values': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) - '@deepseek-ai/dsh-session@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)(@deepseek-ai/dsh-scope@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2))': + '@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-llm': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-scope': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) - '@deepseek-ai/dsh-util-values': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + + '@deepseek-ai/dsh-session@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-brand': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-llm': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-scope': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-util-values': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + + '@deepseek-ai/dsh-settings@0.2.1-rc.2(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-app-boot@0.2.1-rc.2(@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)))(@deepseek-ai/dsh-brand@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-session@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)))(@deepseek-ai/schemastery@3.18.5)(node-addon-require-builtin@0.1.9)': + dependencies: + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/cordis-plugin-loader': 1.0.6(@deepseek-ai/cordis@4.0.5)(node-addon-require-builtin@0.1.9) + '@deepseek-ai/cosmokit': 1.8.6 + '@deepseek-ai/dsh-brand': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/dsh-config-editor': 0.2.1-rc.2(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-app-boot@0.2.1-rc.2(@deepseek-ai/cordis-plugin-group@1.0.5(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6)(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-home-paths@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))(@deepseek-ai/dsh-launch-environment@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5))) + '@deepseek-ai/dsh-session': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)(@deepseek-ai/dsh-scope@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)) + '@deepseek-ai/dsh-util-values': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) + '@deepseek-ai/schemastery': 3.18.5 + yaml: 2.9.1 + transitivePeerDependencies: + - '@deepseek-ai/cordis-plugin-include' + - '@deepseek-ai/dsh-app-boot' + - '@deepseek-ai/dsh-hmr' + - node-addon-require-builtin - '@deepseek-ai/dsh-timeout@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-timeout@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/dsh-typert-protocol@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-typert-protocol@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 - '@deepseek-ai/dsh-brand': 0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2) + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) + '@deepseek-ai/dsh-brand': 0.2.1-rc.2(@deepseek-ai/cordis@4.0.5) zod: 4.6.5 - '@deepseek-ai/dsh-util-crypto@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-util-crypto@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/dsh-util-values@0.2.1-alpha.5(@deepseek-ai/cordis@4.0.5-alpha.2)': + '@deepseek-ai/dsh-util-values@0.2.1-rc.2(@deepseek-ai/cordis@4.0.5)': dependencies: - '@deepseek-ai/cordis': 4.0.5-alpha.2 + '@deepseek-ai/cordis': 4.0.5(@deepseek-ai/cordis-plugin-include@1.0.10)(@deepseek-ai/cordis-plugin-loader@1.0.6) - '@deepseek-ai/schemastery@3.18.5-alpha.2': + '@deepseek-ai/schemastery@3.18.5': dependencies: - '@deepseek-ai/cosmokit': 1.8.6-alpha.2 + '@deepseek-ai/cosmokit': 1.8.6 '@standard-schema/spec': 1.1.0 - '@earendil-works/pi-ai@1.3.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.6.5)': + '@earendil-works/pi-ai@1.1.0(patch_hash=66eaf960054f10441b93ac3857114e96c1901f6d183713e2f21437b007111644)(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.6.5)': dependencies: '@anthropic-ai/sdk': 0.129.0(zod@4.6.5) '@aws-sdk/client-bedrock-runtime': 3.1127.0 @@ -2844,6 +3094,8 @@ snapshots: '@earendil-works/pi-telemetry@1.3.0': {} + '@eslint-community/regexpp@4.12.2': {} + '@exodus/bytes@1.16.0': {} '@google/genai@2.21.0': @@ -3456,6 +3708,13 @@ snapshots: agent-base@9.0.0: {} + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.9 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -3468,6 +3727,8 @@ snapshots: ansi-styles@6.2.3: {} + argparse@2.0.1: {} + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -3580,6 +3841,8 @@ snapshots: extend@3.0.2: {} + fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3596,6 +3859,8 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 + fast-uri@3.1.9: {} + fast-wrap-ansi@0.2.2: dependencies: fast-string-width: 3.0.2 @@ -3735,6 +4000,10 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + jsdom@30.1.3: dependencies: '@asamuzakjp/css-color': 7.1.3 @@ -3769,6 +4038,8 @@ snapshots: '@babel/runtime': 7.29.7 ts-algebra: 2.0.0 + json-schema-traverse@1.0.0: {} + jsonc-parser@3.3.1: {} jwa@2.0.1: @@ -3884,6 +4155,67 @@ snapshots: nanoid@3.3.20: {} + node-addon-native-custom-loader@0.1.9: {} + + node-addon-require-builtin-darwin-arm64@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin-darwin-x64@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin-linux-arm64-gnu@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin-linux-arm64-musl@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin-linux-x64-gnu@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin-linux-x64-musl@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin-win32-arm64-msvc@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin-win32-ia32-msvc@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin-win32-x64-msvc@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optional: true + + node-addon-require-builtin@0.1.9: + dependencies: + node-addon-native-custom-loader: 0.1.9 + optionalDependencies: + node-addon-require-builtin-darwin-arm64: 0.1.9 + node-addon-require-builtin-darwin-x64: 0.1.9 + node-addon-require-builtin-linux-arm64-gnu: 0.1.9 + node-addon-require-builtin-linux-arm64-musl: 0.1.9 + node-addon-require-builtin-linux-x64-gnu: 0.1.9 + node-addon-require-builtin-linux-x64-musl: 0.1.9 + node-addon-require-builtin-win32-arm64-msvc: 0.1.9 + node-addon-require-builtin-win32-ia32-msvc: 0.1.9 + node-addon-require-builtin-win32-x64-msvc: 0.1.9 + node-domexception@1.0.0: {} node-fetch@3.3.2: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4dd0553b..a58b8c3d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,8 @@ -# Neither is used at runtime here. They arrive as dependencies of -# @earendil-works/pi-ai, which this repo only imports in a test that reads its -# protocol table, so their build scripts stay off rather than being approved. +# Both arrive as dependencies of @earendil-works/pi-ai, which this repo pins to +# the harness's own copy (the overrides/patchedDependencies below); the bridge +# loads it at runtime from the host and the suite imports it directly. Neither +# package's build script is needed, so their builds stay off rather than being +# approved. allowBuilds: "@google/genai": false protobufjs: false @@ -8,30 +10,27 @@ catalog: vite: npm:@voidzero-dev/vite-plus-core@1.2.0 vite-plus: 1.2.0 # pnpm refuses any version published inside the last 24h (its default -# `minimumReleaseAge`), and the pi seam this repo follows rides fresh cuts: -# the 0.2.1-alpha.5 cluster is hours old. These are the exact versions that -# window is lifted for — the wrapper the bridge's pin has to satisfy and the -# harness packages it pulls — pinned, not wildcards, so the next alpha bump -# has to make its own case. What proves them is `pnpm run release:gate`, not -# their age. -minimumReleaseAgeExclude: - - "@deepseek-ai/dsh-brand@0.2.1-alpha.5" - - "@deepseek-ai/dsh-llm-pi-ai@0.2.1-alpha.5" - - "@deepseek-ai/dsh-util-values@0.2.1-alpha.5" - - "@deepseek-ai/dsh-attachment@0.2.1-alpha.5" - - "@deepseek-ai/dsh-authorization@0.2.1-alpha.5" - - "@deepseek-ai/dsh-credentials@0.2.1-alpha.5" - - "@deepseek-ai/dsh-fs@0.2.1-alpha.5" - - "@deepseek-ai/dsh-launch-environment@0.2.1-alpha.5" - - "@deepseek-ai/dsh-llm@0.2.1-alpha.5" - - "@deepseek-ai/dsh-sandbox@0.2.1-alpha.5" - - "@deepseek-ai/dsh-scope@0.2.1-alpha.5" - - "@deepseek-ai/dsh-session@0.2.1-alpha.5" - - "@deepseek-ai/dsh-timeout@0.2.1-alpha.5" - - "@deepseek-ai/dsh-typert-protocol@0.2.1-alpha.5" - - "@deepseek-ai/dsh-util-crypto@0.2.1-alpha.5" +# `minimumReleaseAge`). Nothing pinned here currently needs that window lifted: +# every exact version this repo installs is older than it. When a future exact +# version is refused for age, it goes in a `minimumReleaseAgeExclude:` list +# here, as `name@version` rather than a wildcard, so each bump makes its own +# case. What proves a version is `pnpm run release:gate`, not its age. +# +# The harness's own lockfile pins `@earendil-works/pi-ai` at 1.1.0 and patches +# it (D14). The override is the one statement that reaches every copy in the +# tree, whatever a dependent's range asks for. Today the exact root +# devDependency alone already pulls `dsh-llm-pi-ai`'s `^1.1.0` onto 1.1.0 (pnpm +# prefers a version a direct dependency pins); the override is what still holds +# when a dependent's range stops admitting 1.1.0 or a new one asks for another +# pi. (A registry install of the published host has no such pin, and npm floats +# the wrapper's range to the newest 1.x.) The patch is the harness's file, +# verbatim, and pnpm records its hash in the lockfile; +# `test/host-versions.test.ts` checks both. overrides: + "@earendil-works/pi-ai": "1.1.0" vite@*: "catalog:" +patchedDependencies: + "@earendil-works/pi-ai@1.1.0": patches/@earendil-works__pi-ai@1.1.0.patch peerDependencyRules: allowAny: - vite diff --git a/src/google-bridge.ts b/src/google-bridge.ts index fc9385ba..99f52541 100644 --- a/src/google-bridge.ts +++ b/src/google-bridge.ts @@ -30,9 +30,10 @@ * and deliberately not declared as a dependency. * * **Why not a dependency.** The harness wrapper (`dsh-llm-pi-ai`) already - * depends on `@earendil-works/pi-ai` with a RANGE (`^1.1.0`), so the host - * installs exactly the pi its own release was tested against — 1.3.0 for - * `0.2.1-alpha.5`, whatever the next harness line moves it to. Declaring the + * depends on `@earendil-works/pi-ai` with a RANGE (`^1.1.0`). The harness's + * own lockfile pins that to 1.1.0 plus its patch; a registry install of the + * published host resolves the range to the newest 1.x; this repo's dev install + * pins 1.1.0 (`test/support/host-versions.ts`). Declaring the * same package as an exact dependency here puts a SECOND pi in the tree and * turns every harness bump into a version-skew bug: the worst measured shape is * a degraded Google route whose error names the model rather than the version. diff --git a/test/google-bridge.test.ts b/test/google-bridge.test.ts index 16e051cb..4d79e977 100644 --- a/test/google-bridge.test.ts +++ b/test/google-bridge.test.ts @@ -63,12 +63,13 @@ interface ProviderLike { } /** - * The pi runtime the bridge consumes, built from THIS repo's devDependency. + * The pi runtime the bridge consumes, built from the root pi the suite imports. * * A deployment gets the harness's own pi (`loadHostPi` finds it through - * `dsh-llm-pi-ai`), and the shape is identical either way — these tests exercise - * the contract, not a version. `pi-runtime.test.ts` proves the loader finds the - * host's copy in the real tree. + * `dsh-llm-pi-ai`). Under the pin (`test/support/host-versions.ts`) the root pi + * and the wrapper's are one copy, and these tests exercise the contract, not a + * version. `host-versions.test.ts` (its first two cases) proves the loader + * finds the host's copy in the real tree and that it is this same module. */ const testPi = (): PiRuntime => ({ getBuiltinModels: (plane) => @@ -457,9 +458,11 @@ describe("google-bridge · the pi copy it loads", () => { it("declares no pi of its own at runtime, and loads the host's", async () => { // The rule this replaced a version pin with: the plugin must NOT ship a pi. // The wrapper (`dsh-llm-pi-ai`) already depends on `@earendil-works/pi-ai` - // with a range, so the host installs the pi its own release was tested - // against. A second copy here would put two versions in one tree and turn - // every harness bump into a version-skew bug. + // with a range (`^1.1.0`): the harness's own lockfile pins that to 1.1.0 + // plus its patch, a registry install of the published host floats it to the + // newest 1.x, and this repo's dev install pins 1.1.0. A second copy here + // would put two versions in one tree and turn every harness bump into a + // version-skew bug. const ours = JSON.parse(readFileSync("package.json", "utf8")) as { dependencies?: Record; devDependencies?: Record; diff --git a/test/harness-channel.test.ts b/test/harness-channel.test.ts index 47d4b512..672fefe8 100644 --- a/test/harness-channel.test.ts +++ b/test/harness-channel.test.ts @@ -9,8 +9,10 @@ * packages on rc. Compile-time deps made that invisible; the test below is what * makes it visible. * - * The rule is not "the newest channel": it is "the channel the host runs". The - * running host is `dsh@0.2.1-alpha.5`, so the tools follow alpha. + * The rule is not "the newest channel": it is "the host users run". That host + * is a registry install of `@deepseek-ai/dsh`, which resolves the cluster at + * `0.2.1-rc.2` (D22, `HOST_VERSIONS`). The alpha pin this file used to assert + * came from the host a previous session happened to run, not from that. * * @module test/harness-channel */ @@ -19,6 +21,8 @@ import { readFileSync } from "node:fs"; import semver from "semver"; import { describe, expect, it } from "vitest"; +import { HOST_VERSIONS } from "./support/host-versions.ts"; + interface Manifest { dependencies?: Record; devDependencies?: Record; @@ -28,54 +32,115 @@ interface Manifest { const manifest = (): Manifest => JSON.parse(readFileSync("package.json", "utf8")) as Manifest; -/** The channel of a version string, or `undefined` for an untagged release. */ -const channelOf = (spec: string): string | undefined => { - const match = /-([a-z]+)\./u.exec(spec); - return match?.[1]; -}; - describe("harness channel alignment", () => { - const HOST_CHANNEL = "alpha"; + /** + * The exact version a harness package is pinned to: cordis and its loader at + * what the published host resolves, on their own untagged lines; every + * `dsh-*` on the cluster's tag (`HOST_VERSIONS.dsh`). For the two + * client-bundle packages, `dsh-client-store` and `dsh-client-ui-primitives`, + * that tag is grounded in the host's build manifest rather than an installed + * copy: the published host does not install them, they are devDependencies + * its frontend bundle was built against at exactly this tag + * (`dsh-web-frontend/package.json`). + */ + const expectedFor = (name: string): string | undefined => { + if (name === "@deepseek-ai/cordis") { + return HOST_VERSIONS.cordis; + } + if (name === "@deepseek-ai/cordis-plugin-loader") { + return HOST_VERSIONS.loader; + } + return name.startsWith("@deepseek-ai/dsh-") ? HOST_VERSIONS.dsh : undefined; + }; - it("every harness dependency is pinned on the host's channel", () => { - const deps = { + /** Every `@deepseek-ai/*` dependency whose release moves with the host. */ + const harnessDeps = (): [string, string][] => + Object.entries({ ...manifest().dependencies, ...manifest().devDependencies, - }; - const harness = Object.entries(deps).filter(([name]) => - name.startsWith("@deepseek-ai/") + }).filter( + // `schemastery` is a plain library rather than a harness surface: it + // follows the cluster's `~3.18.x` dependency range rather than the + // cluster's tag, so it is checked against that range (in the lockfile + // case below), not against HOST_VERSIONS. + ([name]) => + name.startsWith("@deepseek-ai/") && name !== "@deepseek-ai/schemastery" ); + + it("every harness dependency is pinned exactly to the published host's cluster versions", () => { + // Exact, not a range: rc sorts above alpha, so a caret range written + // against one channel silently floats onto another. The previous form of + // this check required every spec to be `-alpha.N`, which encoded the host + // a previous session ran; the published host resolves rc.2, and cordis + // 4.0.5 and the loader's 1.0.6 carry no channel at all. + const harness = harnessDeps(); expect(harness.length).toBeGreaterThan(0); for (const [name, spec] of harness) { - // `schemastery` is a plain library rather than a harness surface, and its - // version is an independent line; the check is about the harness packages - // whose releases move together. - if (name === "@deepseek-ai/schemastery") { - continue; - } - const channel = channelOf(spec); - expect(channel, `${name} is pinned with no channel: ${spec}`).toBe( - HOST_CHANNEL - ); + // Fail closed on a harness package with no rule, and say where the rule + // goes, rather than reporting a spurious "expected … to be undefined". + const expected = expectedFor(name); + expect( + expected, + `${name} is a @deepseek-ai dependency with no rule in expectedFor / HOST_VERSIONS (test/support/host-versions.ts); add one or exclude it deliberately` + ).toBeDefined(); + expect(spec, name).toBe(expected); } }); - it("no pinned spec can float onto another channel", () => { - // A caret range written against alpha resolves to rc, because rc sorts - // higher. Every spec whose channel matters is therefore EXACT, and the peer - // range names alpha at both bounds. - const deps = { - ...manifest().dependencies, - ...manifest().devDependencies, - }; - for (const [name, spec] of Object.entries(deps)) { - if (!name.startsWith("@deepseek-ai/") || name.endsWith("schemastery")) { - continue; - } - expect(spec, `${name} must be exact, not a range: ${spec}`).toMatch( - /^\d+\.\d+\.\d+-alpha\.\d+$/u - ); - } + it("every dsh package in the resolved tree is on the published host's version", () => { + // The declarations above cannot see a transitive dependency: a cluster + // package nobody here names (`dsh-brand`, `dsh-util-values`, …) pulled in + // at a second version is exactly the split cluster this file is about. The + // lockfile's `packages:` section is what pnpm resolved, transitive entries + // included, so every `@deepseek-ai/dsh-*` key there must carry the pin. + const lock = readFileSync("pnpm-lock.yaml", "utf8"); + const sections = lock.split("\npackages:\n"); + const packages = (sections.at(-1) ?? "").split("\nsnapshots:\n")[0] ?? ""; + const resolved = [ + ...packages.matchAll( + /^ {2}'(@deepseek-ai\/dsh-[a-z0-9-]+)@([^'(]+)':/gmu + ), + ].map(([, name, version]) => [name ?? "", version ?? ""] as const); + // A check that read no entries would otherwise pass by finding nothing. + expect(resolved.length).toBeGreaterThan(0); + const off = resolved + .filter(([, version]) => version !== HOST_VERSIONS.dsh) + .map(([name, version]) => `${name}@${version}`); + expect(off).toEqual([]); + + // The plugin's one runtime library the cluster also depends on resolves to + // ONE copy: the version this plugin declares, inside the range the + // installed wrapper declares. A host bump onto another schemastery line + // would otherwise leave two copies of the library the Config schema is + // built with, and nothing else here would notice. + const schemastery = [ + ...packages.matchAll(/^ {2}'@deepseek-ai\/schemastery@([^'(]+)':/gmu), + ].map(([, version]) => version ?? ""); + expect(schemastery).toHaveLength(1); + expect(schemastery[0]).toBe( + manifest().dependencies?.["@deepseek-ai/schemastery"] + ); + const wrapper = JSON.parse( + readFileSync( + "node_modules/@deepseek-ai/dsh-llm-pi-ai/package.json", + "utf8" + ) + ) as Manifest; + // Read from both declaration kinds (dsh-settings already declares + // schemastery as a peer), and require that one was found: semver reads an + // empty range as `*`, so a missing declaration would otherwise pass here + // without checking anything. + const range = { + ...wrapper.peerDependencies, + ...wrapper.dependencies, + }["@deepseek-ai/schemastery"]; + expect( + range, + "dsh-llm-pi-ai no longer declares @deepseek-ai/schemastery (dependencies or peerDependencies)" + ).toBeTruthy(); + expect(semver.satisfies(schemastery[0] ?? "", range ?? ""), range).toBe( + true + ); }); it("the typert peer range covers the 0.2.1 line on BOTH channels", () => { @@ -96,11 +161,16 @@ describe("harness channel alignment", () => { manifest().peerDependencies?.["@deepseek-ai/dsh-typert-protocol"]; expect(peer).toBeDefined(); + // The published builds the code is verified on, plus the pinned host by + // construction, so a host bump outside the range fails here. const admitted = [ - "0.2.1-alpha.1", - "0.2.1-alpha.5", - "0.2.1-rc.1", - "0.2.1-rc.2", + ...new Set([ + "0.2.1-alpha.1", + "0.2.1-alpha.5", + "0.2.1-rc.1", + "0.2.1-rc.2", + HOST_VERSIONS.dsh, + ]), ]; for (const version of admitted) { expect( diff --git a/test/host-versions.test.ts b/test/host-versions.test.ts new file mode 100644 index 00000000..81dcf528 --- /dev/null +++ b/test/host-versions.test.ts @@ -0,0 +1,221 @@ +/** + * The install under test is the host users run: pi at the harness's pin with + * the harness's patch applied, and the `@deepseek-ai` cluster at the version + * the published host resolves (`test/support/host-versions.ts`). + * + * The cases check the INSTALLED tree rather than the declarations alone: a pin + * that `package.json` states but a stray range defeats would leave a second pi + * in `node_modules`, and the suite would quietly test that one. What each reads: + * + * - the installed tree — the pi `loadHostPi` resolves and its identity with the + * root import, the `package.json` versions under `node_modules`, and the + * applied hunks in pi's `dist/`; + * - pnpm's resolution record, `pnpm-lock.yaml` — the pi snapshot count and the + * patch hash pnpm recorded; + * - the published ranges — this plugin's `peerDependencies` and the installed + * wrapper's own pi range, each checked against the versions installed. + * + * @module test/host-versions + */ +import { createHash } from "node:crypto"; +import { readFileSync, realpathSync } from "node:fs"; +import { join } from "node:path"; + +import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all"; +import semver from "semver"; +import { describe, expect, it } from "vitest"; + +import { HOST_VERSIONS } from "./support/host-versions.ts"; +import { loadHostPi } from "./support/src.ts"; + +const ROOT = join(import.meta.dirname, ".."); + +const versionOf = (dir: string): string => { + const manifest = JSON.parse( + readFileSync(join(dir, "package.json"), "utf8") + ) as { version?: string }; + return manifest.version ?? ""; +}; + +const installed = (name: string): string => + versionOf(join(ROOT, "node_modules", name)); + +/** The installed pi's package root, through the root symlink. */ +const piRoot = (): string => + realpathSync(join(ROOT, "node_modules", "@earendil-works", "pi-ai")); + +/** pnpm names a patch file after the version it patches. */ +const PATCH = join( + ROOT, + "patches", + `@earendil-works__pi-ai@${HOST_VERSIONS.pi}.patch` +); + +/** The `patch_hash` the harness's own lockfile records for this patch. */ +const HARNESS_PATCH_HASH = + "66eaf960054f10441b93ac3857114e96c1901f6d183713e2f21437b007111644"; + +describe("host pin", () => { + it("the pi the bridge loads at runtime is the harness's pin", async () => { + // `loadHostPi` resolves the wrapper and walks up from its real location to + // the first pi it finds — the copy the wrapper itself would load. + const pi = await loadHostPi(); + expect(pi).toBeDefined(); + expect(pi?.version).toBe(HOST_VERSIONS.pi); + }); + + it("the root pi the tests import is that same copy, and the only one", async () => { + // `loadHostPi` hands pi's function through untouched, so identity proves + // one module instance — the tests and the bridge read one file. + const pi = await loadHostPi(); + expect(pi?.getBuiltinModels).toBe(getBuiltinModels); + expect(versionOf(piRoot())).toBe(HOST_VERSIONS.pi); + // The link the wrapper's own lookup reaches (the walk `installedPiRoot` + // takes from its real location) is the root's copy too. + const wrapper = realpathSync( + join(ROOT, "node_modules", "@deepseek-ai", "dsh-llm-pi-ai") + ); + expect( + realpathSync(join(wrapper, "..", "..", "@earendil-works", "pi-ai")) + ).toBe(piRoot()); + // And the lockfile records exactly one pi snapshot, the patched pin. This + // reads what pnpm RESOLVED, not `node_modules/.pnpm`: pnpm keeps an + // orphaned virtual-store directory until `modules-cache-max-age` (7 days) + // expires, so a directory count would read a stale cache from a pre-pin + // branch as a broken pin. A snapshot key also carries the peer suffix, so a + // second 1.1.0 under a different peer context counts as a second copy. + // Mutation check, run against a scratch copy of the manifests: dropping the + // global `overrides` entry does NOT go red here, because pnpm resolves the + // wrapper's `^1.1.0` to the version the exact root devDependency already + // pins (a fresh resolve without a lockfile included). What does produce a + // second copy is a dependent asking for another pi, e.g. the scoped override + // `"@deepseek-ai/dsh-llm-pi-ai>@earendil-works/pi-ai": "1.4.0"`: the lockfile + // then holds a 1.1.0 and a 1.4.0 snapshot, and the wrapper's sibling link + // resolves to 1.4.0 while the root stays on 1.1.0 — this count, the link + // check above and `loadHostPi`'s version all read the split. + const lock = readFileSync(join(ROOT, "pnpm-lock.yaml"), "utf8"); + const snapshots = lock.split("\nsnapshots:\n").at(-1) ?? ""; + const piSnapshots = [ + ...snapshots.matchAll(/^ {2}'@earendil-works\/pi-ai@([^']+)':/gmu), + ].map((match) => match[1] ?? ""); + expect(piSnapshots).toHaveLength(1); + expect( + piSnapshots[0]?.startsWith( + `${HOST_VERSIONS.pi}(patch_hash=${HARNESS_PATCH_HASH})` + ), + piSnapshots[0] + ).toBe(true); + const locked = new Set( + [...lock.matchAll(/@earendil-works\/pi-ai@(\d+\.\d+\.\d+)/gu)].map( + (match) => match[1] + ) + ); + expect([...locked]).toEqual([HOST_VERSIONS.pi]); + }); + + it("the dsh cluster is pinned to the published host's cluster versions", () => { + // Every `@deepseek-ai/dsh-*` this repo declares, read from the manifest so + // a new cluster package is covered without editing this list. They share + // the cluster's tag; the two client-bundle packages (dsh-client-store, + // dsh-client-ui-primitives) are not installed by the published host, and + // follow the tag its frontend was built against. + const manifest = JSON.parse( + readFileSync(join(ROOT, "package.json"), "utf8") + ) as { + dependencies?: Record; + devDependencies?: Record; + peerDependencies?: Record; + }; + const cluster = Object.keys({ + ...manifest.dependencies, + ...manifest.devDependencies, + }).filter((name) => name.startsWith("@deepseek-ai/dsh-")); + expect(cluster.length).toBeGreaterThan(0); + for (const name of cluster) { + expect(installed(name), name).toBe(HOST_VERSIONS.dsh); + } + expect(installed("@deepseek-ai/cordis")).toBe(HOST_VERSIONS.cordis); + expect(installed("@deepseek-ai/cordis-plugin-loader")).toBe( + HOST_VERSIONS.loader + ); + // The ranges this plugin publishes admit the host it is tested on: a bump + // that moves the install outside a peer range would otherwise ship as an + // install error on exactly the host users run. + const peers = Object.entries(manifest.peerDependencies ?? {}); + expect(peers.length).toBeGreaterThan(0); + for (const [name, range] of peers) { + const version = installed(name); + expect( + semver.satisfies(version, range), + `${name}@${version} vs ${range}` + ).toBe(true); + } + // The pi pin sits inside the wrapper's own range: a legal resolution of + // what the wrapper declares, not a forced skew. + const wrapper = JSON.parse( + readFileSync( + join(ROOT, "node_modules/@deepseek-ai/dsh-llm-pi-ai/package.json"), + "utf8" + ) + ) as { + dependencies?: Record; + peerDependencies?: Record; + }; + // Either declaration kind, and one must exist: semver reads an empty range + // as `*`, so a missing declaration would otherwise pass without a check. + const range = { + ...wrapper.peerDependencies, + ...wrapper.dependencies, + }["@earendil-works/pi-ai"]; + expect( + range, + "dsh-llm-pi-ai no longer declares @earendil-works/pi-ai (dependencies or peerDependencies)" + ).toBeTruthy(); + expect(semver.satisfies(HOST_VERSIONS.pi, range ?? ""), range).toBe(true); + }); + + it("the harness's patch is committed verbatim, recorded by pnpm, and applied to the installed pi", () => { + // Byte-for-byte the harness's file: its hash is the one the harness's own + // lockfile records, so this is a copy, not a re-derivation. + const patch = readFileSync(PATCH); + expect(createHash("sha256").update(patch).digest("hex")).toBe( + HARNESS_PATCH_HASH + ); + const files = [ + ...patch.toString("utf8").matchAll(/^diff --git a\/(\S+)/gmu), + ].map((match) => match[1]); + expect(files).toEqual([ + "dist/api/anthropic-messages.js", + "dist/api/bedrock-converse-stream.js", + "dist/api/mistral-conversations.js", + "dist/api/openai-completions.js", + "dist/api/openai-responses-shared.js", + "dist/api/pi-messages.js", + ]); + const lock = readFileSync(join(ROOT, "pnpm-lock.yaml"), "utf8"); + expect(lock).toContain( + `'@earendil-works/pi-ai@${HOST_VERSIONS.pi}': ${HARNESS_PATCH_HASH}` + ); + + // Applied: each hunk deletes a streaming re-parse of a tool call's + // arguments. Unpatched, pi-messages.js carries one beside this context + // line, and anthropic-messages.js one right after its partial_json append. + const messages = readFileSync( + join(piRoot(), "dist/api/pi-messages.js"), + "utf8" + ); + expect(messages).toContain("toolJson.set(event.contentIndex, json);"); + expect(messages).not.toContain("parseStreamingJson(json)"); + const anthropic = readFileSync( + join(piRoot(), "dist/api/anthropic-messages.js"), + "utf8" + ); + const anchor = anthropic.indexOf( + "block.partialJson += event.delta.partial_json;" + ); + expect(anchor).toBeGreaterThan(-1); + expect(anthropic.slice(anchor, anchor + 160)).not.toContain( + "parseStreamingJson" + ); + }); +}); diff --git a/test/mount-protocols.test.ts b/test/mount-protocols.test.ts index b2f0f0bc..5b071dfa 100644 --- a/test/mount-protocols.test.ts +++ b/test/mount-protocols.test.ts @@ -3,10 +3,13 @@ import { googleGenerativeAIApi } from "@earendil-works/pi-ai/api/google-generati import { builtinProviders } from "@earendil-works/pi-ai/providers/all"; import { describe, expect, it } from "vitest"; +import { HOST_VERSIONS } from "./support/host-versions.ts"; import { + GOOGLE_SDK, OPENCODE_GO_CATALOG, OPENCODE_ZEN_CATALOG, PROTOCOL_FOR_SDK, + protocolFor, ROUTE_FOR_PLANE_PROTOCOL, SELF_MOUNTED_ROUTES, UNSERVED_PLANE_SDKS, @@ -15,20 +18,24 @@ import { /** * The protocols this plugin mounts must be ones the harness's own pi-ai layer - * can build. + * can build, and every SDK the catalog names must map to one of them. * - * `dsh-llm-pi-ai` constructs a hand-declared route from `PROTOCOLS`, and that - * table has three entries. A route naming anything else is refused outright, so - * the plugin's whole routing layer is bounded by this list - and nothing checked - * it: the unit suite mounts against a stand-in that accepts any `api`, and the - * e2e suite talks to the gateway without going through the mount at all. + * Two claims, each against the real package rather than a stand-in: * - * That is how `mistral-conversations` shipped. It is in `COMPAT_GATES`, which - * looks like a protocol registry and is not one, and the real table refuses it. + * - The routes the WRAPPER mounts may only name a protocol in + * `dsh-llm-pi-ai`'s own `PROTOCOLS` table, which refuses anything else + * outright. That is how `mistral-conversations` once shipped: it is in + * `COMPAT_GATES`, which looks like a protocol registry and is not one. + * - Every catalog row resolves, through `protocolFor` (which reads + * `GATEWAY_PROTOCOL_FOR_SDK`), to a protocol pi's exported `api/*` modules + * implement — independent of which models any of pi's own gateway providers + * happen to list. The plugin's own SDK table, `PROTOCOL_FOR_SDK` + * (`src/responses-routes.ts`), is checked against the same modules as a + * second step. In this file "the wrapper's table" means only + * `dsh-llm-pi-ai`'s `PROTOCOLS`. * - * `it.fails` while that route is still declared: it passes only while the gap is - * real, and turns red the moment the mapping is corrected, which is the signal - * to make it a plain assertion. + * The pi and harness versions these run against are named once, in + * `test/support/host-versions.ts`. */ describe("mount · the protocols the harness can build", () => { it("names only protocols dsh-llm-pi-ai supports, for the routes it mounts", () => { @@ -114,63 +121,161 @@ describe("mount · what pi-ai's own gateway providers cover", () => { expect(apis.size).toBeGreaterThan(1); } }); + + it(`characterization (F10): pi ${HOST_VERSIONS.pi}'s opencode-go provider has no google-generative-ai`, () => { + // A characterization of the pinned pi (`HOST_VERSIONS.pi`), not a + // requirement: its Go provider lists no model on the Google protocol, + // while the gateway's Go catalog does carry one. That gap is what the + // bridge serves today. pi 1.3.0 added a Go Google row + // (`gemini-4-argon-contributor`), which is why the provider-based routing + // check that used to stand below passed on the newer pi and not this one. + const go = providers.find((p) => p.id === "opencode-go"); + const apis = [ + ...new Set((go?.getModels() ?? []).map((model) => model.api)), + ].toSorted(); + expect(apis).toEqual([ + "anthropic-messages", + "openai-completions", + "openai-responses", + ]); + expect(OPENCODE_GO_CATALOG.some((m) => m.provider_npm === GOOGLE_SDK)).toBe( + true + ); + }); }); /** - * Every catalogued model, against the protocols its plane's provider implements. + * Every catalogued model, against the protocols pi's own `api/*` modules + * implement. * * The per-model `api` in our catalog is the vendor's own SDK (`@ai-sdk/…`), and - * `PROTOCOL_FOR_SDK` turns it into a wire protocol. This walks the whole catalog + * `protocolFor` turns it into a wire protocol — `openai-completions` for a row + * that names no SDK, the provider's own default. This walks the whole catalog * rather than a sample, because the failure this catches is one model in a * refreshed catalog naming an SDK nobody has a protocol for - and a picker that * offers it looks exactly like a picker that does not. * - * A model is fine when its plane's pi-ai provider has at least one model on the - * same protocol, since a provider only carries protocols it can speak. Models - * whose SDK is a recorded decision (`UNSERVED_SDKS`) are skipped: that is the - * plugin saying so, not a gap. + * The claim is against pi's exported implementations, resolved from pi itself + * (`@earendil-works/pi-ai/api/.lazy`), not a provider's model list + * and not a table kept here. An earlier version asked each plane's pi provider + * whether it carried a model on the same protocol, which made the answer a + * fact about pi's catalog: pi 1.3.0 listed a Go Gemini model and so passed, + * while the pinned pi 1.1.0 lists none (characterized above, F10) although the + * bridge serves those models through the same `google-generative-ai` module. + * Models whose SDK is a recorded decision (`UNSERVED_SDKS`) are skipped: that + * is the plugin saying so, not a gap. Plane-level decisions in + * `UNSERVED_PLANE_SDKS` likewise, so the rows here are the rows the picker + * offers (`isServableModel`, src/models-discovery.ts) and `test/catalog.test.ts` + * walks. */ -describe("routing · every catalogued model has a protocol its plane can serve", () => { - const protocolsOf = (id: string): Set => - new Set( - ( - builtinProviders() - .find((p) => p.id === id) - ?.getModels() ?? [] - ).map((model) => model.api) +describe("routing · every catalog SDK maps to a protocol pi's api/* modules implement", () => { + /** + * Whether pi ships `api/.lazy` with one `*Api` factory whose + * implementation has both stream entry points the adapter delegates to. + */ + const implemented = async (protocol: string): Promise => { + let module: Record; + try { + module = (await import( + /* @vite-ignore */ `@earendil-works/pi-ai/api/${protocol}.lazy` + )) as Record; + } catch { + return false; + } + const factories = Object.entries(module) + .filter(([name]) => name.endsWith("Api")) + .map(([, value]) => value); + const [factory] = factories; + if (factories.length !== 1 || typeof factory !== "function") { + return false; + } + const api = (factory as () => unknown)() as { + stream?: unknown; + streamSimple?: unknown; + }; + return ( + typeof api.stream === "function" && typeof api.streamSimple === "function" ); + }; - const planes = [ - { catalog: OPENCODE_ZEN_CATALOG, id: "opencode" }, - { catalog: OPENCODE_GO_CATALOG, id: "opencode-go" }, - ]; + /** Every catalog row the plugin offers, with the protocol it resolves to. */ + const served = () => + ( + [ + ["opencode", OPENCODE_ZEN_CATALOG], + ["opencode-go", OPENCODE_GO_CATALOG], + ] as const + ).flatMap(([plane, catalog]) => + catalog + .filter( + (model) => + model.provider_npm === undefined || + // The `${plane} + ${sdk}` key is the one `isServableModel` reads + // (the route ids `opencode` / `opencode-go`). The set is empty + // today; an entry that withholds a protocol on every plane then + // turns the "four protocols" constant below red, for review. + !( + UNSERVED_SDKS.has(model.provider_npm) || + UNSERVED_PLANE_SDKS.has(`${plane} + ${model.provider_npm}`) + ) + ) + .map((model) => ({ model, plane, protocol: protocolFor(model) })) + ); - it("names no protocol its plane's provider cannot speak", () => { - const unsupported: string[] = []; - let checked = 0; - for (const { catalog, id } of planes) { - const protocols = protocolsOf(id); - for (const model of catalog) { - const sdk = model.provider_npm; - // No SDK means the provider's own default, which is always servable. - if ( - sdk === undefined || - UNSERVED_SDKS.has(sdk) || - UNSERVED_PLANE_SDKS.has(`${id} + ${sdk}`) - ) { - continue; - } - checked += 1; - const protocol = PROTOCOL_FOR_SDK[sdk]; - if (protocol === undefined || !protocols.has(protocol)) { - unsupported.push( - `${id} ${model.id}: ${sdk} -> ${protocol ?? "no protocol"}` - ); - } - } - } + it("names no SDK whose protocol pi does not implement", async () => { + const rows = served(); // A check that read no models would otherwise pass by finding nothing. - expect(checked).toBeGreaterThan(0); + expect(rows.length).toBeGreaterThan(0); + const protocols = [ + ...new Set( + [ + ...rows.map((row) => row.protocol), + ...Object.values(PROTOCOL_FOR_SDK), + ].filter((name) => name !== undefined) + ), + ]; + const verdicts = new Map( + await Promise.all( + protocols.map(async (name) => [name, await implemented(name)] as const) + ) + ); + const unsupported = rows + .filter( + (row) => + row.protocol === undefined || verdicts.get(row.protocol) !== true + ) + .map( + (row) => + `${row.plane} ${row.model.id}: ${row.model.provider_npm ?? "(default)"} -> ${row.protocol ?? "no protocol"}` + ); expect(unsupported).toEqual([]); + // The plugin's own SDK table (`PROTOCOL_FOR_SDK`, src/responses-routes.ts) + // names nothing pi lacks either. + expect( + Object.values(PROTOCOL_FOR_SDK).filter( + (protocol) => verdicts.get(protocol) !== true + ) + ).toEqual([]); + }); + + it("reaches the four protocols the plugin serves (a constant, labelled as one)", () => { + // The set the catalog resolves to today, including the SDK-less default. + // A fifth protocol is a deliberate change, reviewed here; whether pi + // implements it is the case above, which reads pi rather than this list. + expect( + [...new Set(served().map((row) => row.protocol))].toSorted((a, b) => + (a ?? "").localeCompare(b ?? "") + ) + ).toEqual([ + "anthropic-messages", + "google-generative-ai", + "openai-completions", + "openai-responses", + ]); + }); + + it("answers false for a protocol pi does not ship", async () => { + // The negative control: an unknown module is not read as implemented. + expect(await implemented("no-such-protocol")).toBe(false); }); }); diff --git a/test/no-local-paths.test.ts b/test/no-local-paths.test.ts index f2525efe..38ee0381 100644 --- a/test/no-local-paths.test.ts +++ b/test/no-local-paths.test.ts @@ -24,12 +24,14 @@ import { CARD_FIELDS } from "./support/src.ts"; const ROOT = new URL("..", import.meta.url).pathname; /** Directories a third party can read: the tree, plus the build output. */ -const SCANNED = ["src", "test", "docs", "scripts", "lib"]; +const SCANNED = ["src", "test", "docs", "scripts", "lib", "patches"]; const SCANNED_FILES = [ "AGENTS.md", + "CONTRIBUTING.md", "README.md", "package.json", "cordis.patch.yml", + "pnpm-workspace.yaml", ]; /** A home prefix followed by a name that is not the rule's placeholder. */ @@ -53,7 +55,7 @@ const walk = async (dir: string): Promise => { const info = await stat(full); if (info.isDirectory()) { found.push(...(await walk(full))); - } else if (/\.(?:ts|tsx|js|mjs|md|json|ya?ml)$/u.test(entry)) { + } else if (/\.(?:ts|tsx|js|mjs|md|json|ya?ml|patch)$/u.test(entry)) { found.push(full); } } diff --git a/test/support/host-versions.ts b/test/support/host-versions.ts new file mode 100644 index 00000000..edaf8911 --- /dev/null +++ b/test/support/host-versions.ts @@ -0,0 +1,29 @@ +/** + * The host this repository's test install runs, named once. + * + * - `pi` is the `@earendil-works/pi-ai` the harness's own lockfile pins, and + * patches with the hunks committed under `patches/` (D14). A registry install + * of the published host resolves the wrapper's `^1.1.0` to the newest 1.x + * instead; this pin is the version the harness itself builds and tests on. + * - `dsh`, `cordis` and `loader` are what a registry install of + * `@deepseek-ai/dsh` resolves today (D22) — `dsh-llm`, `dsh-llm-pi-ai` and the + * rest of the cluster on one tag, `@deepseek-ai/cordis` and + * `@deepseek-ai/cordis-plugin-loader` on their own untagged lines. Re-measured + * by installing the published host into a throwaway prefix and reading the + * installed `package.json` versions. + * + * `pnpm-workspace.yaml` (overrides, patchedDependencies) and `package.json` are + * what install these; `test/host-versions.test.ts` checks the installed tree + * (with `pnpm-lock.yaml` as the record of what pnpm resolved, and the published + * peer ranges) against this record, and `test/harness-channel.test.ts` checks + * the declarations and the lockfile, so a host bump is a deliberate change to + * both sides. + * + * @module test/support/host-versions + */ +export const HOST_VERSIONS = { + cordis: "4.0.5", + dsh: "0.2.1-rc.2", + loader: "1.0.6", + pi: "1.1.0", +} as const;