Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/improvement-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
19 changes: 12 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
73 changes: 73 additions & 0 deletions patches/@earendil-works__pi-ai@1.1.0.patch
Original file line number Diff line number Diff line change
@@ -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":
Loading
Loading