@dan-ai-studio/dshopencodego 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +2 -2
- package/lib/index.js +17 -2
- package/lib/types/adapter.d.ts +9 -1
- package/lib/types/adapter.js +22 -2
- package/package.json +25 -23
- package/scripts/patch-pi-ai.mjs +115 -0
package/README.en.md
CHANGED
|
@@ -162,7 +162,7 @@ The `engines.dsh` field is informational for readers and package managers; **DSH
|
|
|
162
162
|
Things to keep in mind when maintaining this:
|
|
163
163
|
|
|
164
164
|
- **Required versus optional peers**: six of the eighteen are marked `optional` (`dsh-api-remotes`, `dsh-client-locale`, `dsh-client-store`, `dsh-client-ui-model-selection`, `dsh-client-ui-settings`, `dsh-client-ui-slots`). The twelve that actually block loading are `cordis`, `dsh-attachment`, `dsh-brand`, `dsh-client-ui-conversation`, `dsh-client-ui-renderer`, `dsh-credentials`, `dsh-fs`, `dsh-launch-environment`, `dsh-llm`, `dsh-timeout`, `dsh-typert-protocol`, and `dsh-typert-registry`. `dsh-llm` is the heaviest coupling — more than twenty imports across the source.
|
|
165
|
-
- **Declared range ≠ verified range**: `0.1.7-alpha.1` and `alpha.2` fall inside the declaration but were never verified here. What was verified is `0.1.7-rc.1`, `0.1.7-rc.2` (daily use),
|
|
165
|
+
- **Declared range ≠ verified range**: `0.1.7-alpha.1` and `alpha.2` fall inside the declaration but were never verified here. What was verified is `0.1.7-rc.1`, `0.1.7-rc.2` (daily use), `0.2.0-rc.1` (the dev-dependency baseline: gate verdict + typecheck + full suite), and `0.2.0-rc.2` (dev-dependency baseline + `prepareCall` generation freeze + pi-ai delta-parse patch port).
|
|
166
166
|
- **The upper bound is a tracking line**: `<0.3.0` means that the moment DSH ships `0.3.0`, this plugin must ship a new release in the same window or every user loses the plugin. The current range spans both `0.1.7` and `0.2.0` (`0.1.8` and `0.1.9` were never published, hence two segments). After changing a range, `npm test` is the verification (local mock gateway, no network, no tokens) — ranges follow what the interfaces declare, and are never "measured" with live probes.
|
|
167
167
|
- **Peer coverage is now guarded both ways**: `dsh-typert-registry`, `dsh-client-ui-conversation`, and `dsh-client-ui-renderer` were imported by the source but absent from `peerDependencies`, invisible to the gate; they are declared now, and `tests/peer-coverage.spec.ts` asserts both directions — anything the source imports must be declared, and anything declared but unreached must be removed (`dsh-settings` went that way: the settings form is actually provided by `dsh-client-ui-settings`).
|
|
168
168
|
- **Exemptions are per profile**: only a profile that ran `dsh plugin allow-version` has one, and no record means the plugin must stay inside the declared range. `dsh plugin version-exemptions` lists a profile's exemptions.
|
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
|
|
|
162
162
|
维护时需要注意:
|
|
163
163
|
|
|
164
164
|
- **必选与可选之分**:18 个 peer(17 个 `dsh-*` 加框架 `cordis`)中 6 个标了 `optional`(`dsh-api-remotes`、`dsh-client-locale`、`dsh-client-store`、`dsh-client-ui-model-selection`、`dsh-client-ui-settings`、`dsh-client-ui-slots`);真正卡住加载的是 12 个——`cordis`、`dsh-attachment`、`dsh-brand`、`dsh-client-ui-conversation`、`dsh-client-ui-renderer`、`dsh-credentials`、`dsh-fs`、`dsh-launch-environment`、`dsh-llm`、`dsh-timeout`、`dsh-typert-protocol`、`dsh-typert-registry`。其中 `dsh-llm` 是最重的一处耦合(源码 import 二十余处)。
|
|
165
|
-
- **名义范围 ≠ 实测范围**:`0.1.7-alpha.1`、`alpha.2` 落在声明范围内,但本项目没有验证过;实际验证过的是 `0.1.7-rc.1`、`0.1.7-rc.2
|
|
165
|
+
- **名义范围 ≠ 实测范围**:`0.1.7-alpha.1`、`alpha.2` 落在声明范围内,但本项目没有验证过;实际验证过的是 `0.1.7-rc.1`、`0.1.7-rc.2`(日常使用)、`0.2.0-rc.1`(开发依赖基线:门禁判定 + typecheck + 全量用例)与 `0.2.0-rc.2`(开发依赖基线 + `prepareCall` 代次冻结 + pi-ai 增量解析补丁移植)。
|
|
166
166
|
- **上界就是跟版线**:`<0.3.0` 意味着 DSH 一旦发布 `0.3.0`,本插件必须同批发新版,否则所有用户加载失败。当前范围跨 `0.1.7` 与 `0.2.0` 两代(`0.1.8`、`0.1.9` 从未发布,所以写成两段而不是一条)。调整范围后用 `npm test` 验证即可(本地 mock 网关,零网络零 token)——范围以接口声明为准,不要用线上探测来"测"出一个范围。
|
|
167
167
|
- **peer 覆盖已双向守住**:`dsh-typert-registry`、`dsh-client-ui-conversation`、`dsh-client-ui-renderer` 曾被源码 import 却未声明、门禁管不到,现已补进 `peerDependencies`;`tests/peer-coverage.spec.ts` 双向断言——源码用到的必须声明,声明了却没人用的必须删(`dsh-settings` 就这样被移除:设置表单实际由 `dsh-client-ui-settings` 提供)。
|
|
168
168
|
- **豁免是按 profile 记的**:只有显式执行过 `dsh plugin allow-version` 的 profile 才享有豁免;没有记录就代表必须落在声明范围内。用 `dsh plugin version-exemptions` 可查看当前 profile 的豁免。
|
package/lib/client.js
CHANGED
|
@@ -462,7 +462,7 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")
|
|
|
462
462
|
style.textContent = ".d7lz0W_root{color:var(--dsw-alias-label-primary);flex-direction:column;gap:1.25rem;display:flex}.d7lz0W_title{margin:0;font-size:1.05rem}.d7lz0W_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.8rem}.d7lz0W_warn{color:var(--dsw-alias-state-warn-primary);margin:0;font-size:.8rem}.d7lz0W_ok{color:var(--dsw-alias-state-success-primary);font-size:.8rem}.d7lz0W_block{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:.6rem;flex-direction:column;gap:.5rem;padding:.85rem;display:flex}.d7lz0W_row{flex-wrap:wrap;align-items:center;gap:.5rem;display:flex}.d7lz0W_row>strong{margin-right:auto}.d7lz0W_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);min-width:10rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;flex:14rem;padding:.35rem .5rem;font-size:.85rem}.d7lz0W_button{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;padding:.35rem .7rem;font-size:.8rem}.d7lz0W_button:disabled{color:var(--dsw-alias-state-idle-primary);cursor:default}.d7lz0W_number{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:5rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_select{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:100%;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_list{flex-direction:column;display:flex}.d7lz0W_item{border-top:1px solid var(--dsw-alias-border-l1);align-items:flex-start;gap:.5rem;padding:.45rem .15rem;display:flex}.d7lz0W_item input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary);margin-top:.15rem}.d7lz0W_itemBody{flex-direction:column;gap:.15rem;min-width:0;display:flex}.d7lz0W_itemMain{flex-wrap:wrap;align-items:baseline;gap:.6rem;display:flex}.d7lz0W_itemMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:.75rem;font-size:.75rem;display:flex}.d7lz0W_itemModalities{color:var(--dsw-alias-label-secondary);font-size:.75rem}.d7lz0W_name{color:var(--dsw-alias-label-primary)}.d7lz0W_mono{color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.d7lz0W_badge{color:var(--dsw-alias-brand-primary)}";
|
|
463
463
|
document.head.appendChild(style);
|
|
464
464
|
}
|
|
465
|
-
var section_default = { "row": "d7lz0W_row", "
|
|
465
|
+
var section_default = { "row": "d7lz0W_row", "itemModalities": "d7lz0W_itemModalities", "input": "d7lz0W_input", "button": "d7lz0W_button", "list": "d7lz0W_list", "root": "d7lz0W_root", "item": "d7lz0W_item", "title": "d7lz0W_title", "itemMeta": "d7lz0W_itemMeta", "itemBody": "d7lz0W_itemBody", "itemMain": "d7lz0W_itemMain", "badge": "d7lz0W_badge", "ok": "d7lz0W_ok", "number": "d7lz0W_number", "name": "d7lz0W_name", "mono": "d7lz0W_mono", "select": "d7lz0W_select", "hint": "d7lz0W_hint", "block": "d7lz0W_block", "warn": "d7lz0W_warn" };
|
|
466
466
|
|
|
467
467
|
// src/client/Section.tsx
|
|
468
468
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -963,7 +963,7 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
|
|
|
963
963
|
style.textContent = ".mMNkkW_root{align-items:center;display:inline-flex;position:relative}.mMNkkW_trigger{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;border-radius:999px;padding:.35rem .6rem;font-size:.8rem;line-height:1}.mMNkkW_trigger:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}.mMNkkW_panel{z-index:20;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-overlay);width:18rem;color:var(--dsw-alias-label-primary);border-radius:.6rem;flex-direction:column;gap:.6rem;padding:.75rem .85rem;display:flex;position:absolute;bottom:calc(100% + .5rem);right:0;box-shadow:0 8px 24px #0000002e}.mMNkkW_window{flex-direction:column;gap:.25rem;display:flex}.mMNkkW_row{justify-content:space-between;gap:.5rem;font-size:.85rem;display:flex}.mMNkkW_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.75rem}.mMNkkW_progress{width:100%;height:.35rem;accent-color:var(--dsw-alias-brand-primary)}.mMNkkW_limited{color:var(--dsw-alias-state-warn-primary);font-size:.75rem}.mMNkkW_warning{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-label-primary);border-radius:.4rem;padding:.4rem .5rem;font-size:.75rem}.mMNkkW_retry{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;align-self:flex-start;padding:.25rem .5rem;font-size:.75rem}.mMNkkW_local{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:.2rem;padding-top:.5rem;display:flex}";
|
|
964
964
|
document.head.appendChild(style);
|
|
965
965
|
}
|
|
966
|
-
var pill_default = { "
|
|
966
|
+
var pill_default = { "local": "mMNkkW_local", "row": "mMNkkW_row", "retry": "mMNkkW_retry", "panel": "mMNkkW_panel", "warning": "mMNkkW_warning", "window": "mMNkkW_window", "hint": "mMNkkW_hint", "root": "mMNkkW_root", "progress": "mMNkkW_progress", "limited": "mMNkkW_limited", "trigger": "mMNkkW_trigger" };
|
|
967
967
|
|
|
968
968
|
// src/client/UsagePill.tsx
|
|
969
969
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
package/lib/index.js
CHANGED
|
@@ -1439,6 +1439,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
|
|
|
1439
1439
|
input: ["text"]
|
|
1440
1440
|
},
|
|
1441
1441
|
overrides: config.modelProtocols,
|
|
1442
|
+
...this.options.readDocument === void 0 ? {} : { readDocument: this.options.readDocument },
|
|
1442
1443
|
observers: {
|
|
1443
1444
|
...this.options.onFallback === void 0 ? {} : { onFallback: this.options.onFallback },
|
|
1444
1445
|
...this.options.onUnconfigured === void 0 ? {} : { onUnconfigured: this.options.onUnconfigured }
|
|
@@ -1518,14 +1519,28 @@ var OpencodeGoAdapter = class extends LlmAdapter {
|
|
|
1518
1519
|
"UNSUPPORTED_REASONING_EFFORT"
|
|
1519
1520
|
);
|
|
1520
1521
|
}
|
|
1522
|
+
async prepareCall(_provider, model, _signal) {
|
|
1523
|
+
const config = this.options.config();
|
|
1524
|
+
const snapshot = await this.catalogOf(config).forModel(model);
|
|
1525
|
+
const facts = snapshot.facts.get(model);
|
|
1526
|
+
if (facts === void 0) throw new LlmError8(`opencode-go has no model "${model}"`, "UNKNOWN_MODEL");
|
|
1527
|
+
const prepared = this.modelInfo(withModelLimit(toPiModel(facts, config.baseURL), config.modelLimits));
|
|
1528
|
+
return {
|
|
1529
|
+
model: prepared,
|
|
1530
|
+
stream: (options) => this.streamWithSnapshot(options, config, snapshot)
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1521
1533
|
async *stream(options) {
|
|
1534
|
+
const config = this.options.config();
|
|
1535
|
+
const snapshot = await this.catalogOf(config).forModel(options.model);
|
|
1536
|
+
yield* this.streamWithSnapshot(options, config, snapshot);
|
|
1537
|
+
}
|
|
1538
|
+
async *streamWithSnapshot(options, config, snapshot) {
|
|
1522
1539
|
var _stack = [];
|
|
1523
1540
|
try {
|
|
1524
1541
|
if (options.stop !== void 0) {
|
|
1525
1542
|
throw new LlmError8("dshopencodego does not support GenerateOptions.stop", "UNSUPPORTED_OPTION");
|
|
1526
1543
|
}
|
|
1527
|
-
const config = this.options.config();
|
|
1528
|
-
const snapshot = await this.catalogOf(config).forModel(options.model);
|
|
1529
1544
|
const facts = snapshot.facts.get(options.model);
|
|
1530
1545
|
if (facts === void 0) throw new LlmError8(`opencode-go has no model "${options.model}"`, "UNKNOWN_MODEL");
|
|
1531
1546
|
const model = withModelLimit(toPiModel(facts, config.baseURL), config.modelLimits);
|
package/lib/types/adapter.d.ts
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
* @module @dan-ai-studio/dshopencodego/adapter
|
|
14
14
|
*/
|
|
15
15
|
import { attributionHeaders, LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
16
|
-
import type { GenerateOptions, ImageAttachmentAccess, LlmModelInfo, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm';
|
|
16
|
+
import type { GenerateOptions, ImageAttachmentAccess, LlmModelInfo, LlmResolvedModelInfo, PreparedAdapterCall, ResolvedRetryPolicy, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm';
|
|
17
17
|
import type { AttachmentStore, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
18
18
|
import { OpencodeGoCatalog } from './catalog/index.ts';
|
|
19
|
+
import type { GoDoc } from './catalog/go-doc.ts';
|
|
19
20
|
import type { OpencodeGoConfig } from './config.ts';
|
|
20
21
|
/** Image machinery the adapter reads once per request. */
|
|
21
22
|
export interface OpencodeGoImageAccess {
|
|
@@ -45,6 +46,11 @@ export interface OpencodeGoAdapterOptions {
|
|
|
45
46
|
}[]) => void;
|
|
46
47
|
/** Observe assistant history degrading to provider-neutral conversion. */
|
|
47
48
|
readonly onReplayDegrade?: (reason: string) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Read the provider documentation. Defaults to the real fetch; tests inject
|
|
51
|
+
* a fixed document so the suite stays offline. Production omits this.
|
|
52
|
+
*/
|
|
53
|
+
readonly readDocument?: () => Promise<GoDoc>;
|
|
48
54
|
/**
|
|
49
55
|
* Observe the provider's own usage for one completed call. This is the only
|
|
50
56
|
* honest source of token counts: the gateway's `/usage` endpoint reports
|
|
@@ -86,7 +92,9 @@ export declare class OpencodeGoAdapter extends LlmAdapter {
|
|
|
86
92
|
providerRetryPolicy(_provider: string): ResolvedRetryPolicy | undefined;
|
|
87
93
|
/** Validate an explicit effort against the model's own levels, without clamping. */
|
|
88
94
|
private resolveReasoningLevel;
|
|
95
|
+
prepareCall(_provider: string, model: string, _signal?: AbortSignal): Promise<PreparedAdapterCall>;
|
|
89
96
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
97
|
+
private streamWithSnapshot;
|
|
90
98
|
}
|
|
91
99
|
/** Re-exported so a composition can assert the attribution contract in one place. */
|
|
92
100
|
export { attributionHeaders };
|
package/lib/types/adapter.js
CHANGED
|
@@ -119,6 +119,7 @@ export class OpencodeGoAdapter extends LlmAdapter {
|
|
|
119
119
|
input: ['text'],
|
|
120
120
|
},
|
|
121
121
|
overrides: config.modelProtocols,
|
|
122
|
+
...this.options.readDocument === undefined ? {} : { readDocument: this.options.readDocument },
|
|
122
123
|
observers: {
|
|
123
124
|
...this.options.onFallback === undefined ? {} : { onFallback: this.options.onFallback },
|
|
124
125
|
...this.options.onUnconfigured === undefined ? {} : { onUnconfigured: this.options.onUnconfigured },
|
|
@@ -213,14 +214,33 @@ export class OpencodeGoAdapter extends LlmAdapter {
|
|
|
213
214
|
return effort;
|
|
214
215
|
throw new LlmError(`opencode-go model "${model.id}" does not support reasoning effort "${effort}"`, 'UNSUPPORTED_REASONING_EFFORT');
|
|
215
216
|
}
|
|
217
|
+
async prepareCall(_provider, model, _signal) {
|
|
218
|
+
// Freeze one generation: the config, catalog snapshot, and model facts are
|
|
219
|
+
// captured before the first await, so a settings change between preparation
|
|
220
|
+
// and dispatch cannot combine one generation's capabilities with another's
|
|
221
|
+
// endpoint. Mirrors llm-pi-ai's snapshot discipline.
|
|
222
|
+
const config = this.options.config();
|
|
223
|
+
const snapshot = await this.catalogOf(config).forModel(model);
|
|
224
|
+
const facts = snapshot.facts.get(model);
|
|
225
|
+
if (facts === undefined)
|
|
226
|
+
throw new LlmError(`opencode-go has no model "${model}"`, 'UNKNOWN_MODEL');
|
|
227
|
+
const prepared = this.modelInfo(withModelLimit(toPiModel(facts, config.baseURL), config.modelLimits));
|
|
228
|
+
return {
|
|
229
|
+
model: prepared,
|
|
230
|
+
stream: options => this.streamWithSnapshot(options, config, snapshot),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
216
233
|
async *stream(options) {
|
|
234
|
+
const config = this.options.config();
|
|
235
|
+
const snapshot = await this.catalogOf(config).forModel(options.model);
|
|
236
|
+
yield* this.streamWithSnapshot(options, config, snapshot);
|
|
237
|
+
}
|
|
238
|
+
async *streamWithSnapshot(options, config, snapshot) {
|
|
217
239
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
218
240
|
try {
|
|
219
241
|
if (options.stop !== undefined) {
|
|
220
242
|
throw new LlmError('dshopencodego does not support GenerateOptions.stop', 'UNSUPPORTED_OPTION');
|
|
221
243
|
}
|
|
222
|
-
const config = this.options.config();
|
|
223
|
-
const snapshot = await this.catalogOf(config).forModel(options.model);
|
|
224
244
|
const facts = snapshot.facts.get(options.model);
|
|
225
245
|
if (facts === undefined)
|
|
226
246
|
throw new LlmError(`opencode-go has no model "${options.model}"`, 'UNKNOWN_MODEL');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dan-ai-studio/dshopencodego",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "OpenCode Go provider for DeepSeek Harness: live gateway catalog, per-conversation session header, usage",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"lib",
|
|
34
|
+
"scripts/patch-pi-ai.mjs",
|
|
34
35
|
"cordis.patch.yml",
|
|
35
36
|
"README.md",
|
|
36
37
|
"README.en.md",
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"test": "vitest run",
|
|
60
61
|
"test:coverage": "vitest run --coverage",
|
|
61
62
|
"test:watch": "vitest",
|
|
63
|
+
"postinstall": "node scripts/patch-pi-ai.mjs",
|
|
62
64
|
"prepare": "npm run build",
|
|
63
65
|
"prepack": "npm run build"
|
|
64
66
|
},
|
|
@@ -108,28 +110,28 @@
|
|
|
108
110
|
},
|
|
109
111
|
"devDependencies": {
|
|
110
112
|
"@deepseek-ai/cordis": "4.0.4",
|
|
111
|
-
"@deepseek-ai/dsh-api-remotes": "0.2.0-rc.
|
|
112
|
-
"@deepseek-ai/dsh-attachment": "0.2.0-rc.
|
|
113
|
-
"@deepseek-ai/dsh-brand": "0.2.0-rc.
|
|
114
|
-
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.
|
|
115
|
-
"@deepseek-ai/dsh-client-store": "0.2.0-rc.
|
|
116
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.2.0-rc.
|
|
117
|
-
"@deepseek-ai/dsh-client-ui-model-selection": "0.2.0-rc.
|
|
118
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.
|
|
119
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.
|
|
120
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.
|
|
121
|
-
"@deepseek-ai/dsh-credentials": "0.2.0-rc.
|
|
122
|
-
"@deepseek-ai/dsh-fs": "0.2.0-rc.
|
|
123
|
-
"@deepseek-ai/dsh-invariants": "0.2.0-rc.
|
|
124
|
-
"@deepseek-ai/dsh-launch-environment": "0.2.0-rc.
|
|
125
|
-
"@deepseek-ai/dsh-llm": "0.2.0-rc.
|
|
126
|
-
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.
|
|
127
|
-
"@deepseek-ai/dsh-scope": "0.2.0-rc.
|
|
128
|
-
"@deepseek-ai/dsh-session": "0.2.0-rc.
|
|
129
|
-
"@deepseek-ai/dsh-settings": "0.2.0-rc.
|
|
130
|
-
"@deepseek-ai/dsh-timeout": "0.2.0-rc.
|
|
131
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.
|
|
132
|
-
"@deepseek-ai/dsh-typert-registry": "^0.2.0-rc.
|
|
113
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.0-rc.2",
|
|
114
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
|
|
115
|
+
"@deepseek-ai/dsh-brand": "0.2.0-rc.2",
|
|
116
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
|
|
117
|
+
"@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
|
|
118
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.2.0-rc.2",
|
|
119
|
+
"@deepseek-ai/dsh-client-ui-model-selection": "0.2.0-rc.2",
|
|
120
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.2",
|
|
121
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
|
|
122
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
|
|
123
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
124
|
+
"@deepseek-ai/dsh-fs": "0.2.0-rc.2",
|
|
125
|
+
"@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
|
|
126
|
+
"@deepseek-ai/dsh-launch-environment": "0.2.0-rc.2",
|
|
127
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
128
|
+
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
|
|
129
|
+
"@deepseek-ai/dsh-scope": "0.2.0-rc.2",
|
|
130
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
131
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.2",
|
|
132
|
+
"@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
|
|
133
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|
|
134
|
+
"@deepseek-ai/dsh-typert-registry": "^0.2.0-rc.2",
|
|
133
135
|
"@testing-library/dom": "^10.4.1",
|
|
134
136
|
"@testing-library/react": "^16.3.0",
|
|
135
137
|
"@types/node": "^22.20.0",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of DSH's `patches/@earendil-works__pi-ai@0.87.1.patch` for npm installs.
|
|
3
|
+
*
|
|
4
|
+
* Upstream re-parses the whole accumulated tool-call argument JSON on every
|
|
5
|
+
* stream delta (`O(n^2)` on multi-megabyte arguments and stalls the event
|
|
6
|
+
* loop). DSH removes those per-delta parses via pnpm `patchedDependencies`;
|
|
7
|
+
* this package is npm-installed, so the same removals are applied here,
|
|
8
|
+
* idempotently, to the installed copy.
|
|
9
|
+
*
|
|
10
|
+
* Only the per-delta occurrences are removed — each is anchored on its
|
|
11
|
+
* distinctive preceding accumulation line, exactly matching the upstream
|
|
12
|
+
* hunks. The final parses (toolcall_end / done handlers, which strip the
|
|
13
|
+
* scratch buffers so replay carries parsed arguments) are kept.
|
|
14
|
+
*
|
|
15
|
+
* The adapter only reads delta strings plus the finalized arguments (see
|
|
16
|
+
* `src/conversion/stream.ts`), so dropping the intermediate `arguments`
|
|
17
|
+
* assignments changes no wire behaviour — until `toolcall_end` the partials
|
|
18
|
+
* stay `{}`, exactly like the patched DSH runtime.
|
|
19
|
+
*
|
|
20
|
+
* Best-effort: a missing tree or a newer pi-ai that already dropped the lines
|
|
21
|
+
* only warns; it never fails the install.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
24
|
+
import { dirname, resolve } from 'node:path'
|
|
25
|
+
import { createRequire } from 'node:module'
|
|
26
|
+
import { fileURLToPath } from 'node:url'
|
|
27
|
+
|
|
28
|
+
const require = createRequire(import.meta.url)
|
|
29
|
+
|
|
30
|
+
/** Locate the installed pi-ai root without crossing its `exports` map. */
|
|
31
|
+
function findPiRoot() {
|
|
32
|
+
try {
|
|
33
|
+
const main = require.resolve('@earendil-works/pi-ai')
|
|
34
|
+
if (basenameIsDist(dirname(main))) return dirname(dirname(main))
|
|
35
|
+
} catch {
|
|
36
|
+
// Fall through to the layout probe below.
|
|
37
|
+
}
|
|
38
|
+
const probe = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'node_modules', '@earendil-works', 'pi-ai')
|
|
39
|
+
return existsSync(`${probe}/package.json`) ? probe : undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** True when `dir` is the package's `dist` directory. */
|
|
43
|
+
function basenameIsDist(dir) {
|
|
44
|
+
return dir.endsWith('/dist') || dir.endsWith('\\dist')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One hunk: `before` (kept) immediately followed by `removed`.
|
|
49
|
+
* Whitespace between them is flexible; nothing else is touched.
|
|
50
|
+
*/
|
|
51
|
+
const HUNKS = [
|
|
52
|
+
['dist/api/anthropic-messages.js',
|
|
53
|
+
'block.partialJson += event.delta.partial_json;',
|
|
54
|
+
'block.arguments = parseStreamingJson(block.partialJson);'],
|
|
55
|
+
['dist/api/bedrock-converse-stream.js',
|
|
56
|
+
'block.partialJson = (block.partialJson || "") + (delta.toolUse.input || "");',
|
|
57
|
+
'block.arguments = parseStreamingJson(block.partialJson);'],
|
|
58
|
+
['dist/api/mistral-conversations.js',
|
|
59
|
+
'block.partialArgs = (block.partialArgs || "") + argsDelta;',
|
|
60
|
+
'block.arguments = parseStreamingJson(block.partialArgs);'],
|
|
61
|
+
['dist/api/openai-completions.js',
|
|
62
|
+
'block.partialArgs = (block.partialArgs ?? "") + toolCall.function.arguments;',
|
|
63
|
+
'block.arguments = parseStreamingJson(block.partialArgs);'],
|
|
64
|
+
['dist/api/openai-responses-shared.js',
|
|
65
|
+
'slot.block.partialJson += event.delta;',
|
|
66
|
+
'slot.block.arguments = parseStreamingJson(slot.block.partialJson);'],
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
const escapeRegExp = text => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
70
|
+
|
|
71
|
+
const piRoot = findPiRoot()
|
|
72
|
+
if (piRoot === undefined) {
|
|
73
|
+
console.warn('[dshopencodego] patch-pi-ai: pi-ai not installed, skipping')
|
|
74
|
+
process.exit(0)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let version = 'unknown'
|
|
78
|
+
try {
|
|
79
|
+
version = JSON.parse(readFileSync(`${piRoot}/package.json`, 'utf8')).version ?? 'unknown'
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn(`[dshopencodego] patch-pi-ai: cannot read pi-ai version, skipping (${String(error)})`)
|
|
82
|
+
process.exit(0)
|
|
83
|
+
}
|
|
84
|
+
if (version !== '0.87.1') {
|
|
85
|
+
console.warn(`[dshopencodego] patch-pi-ai: pi-ai ${version} is not 0.87.1, skipping (re-check upstream pi#9265)`)
|
|
86
|
+
process.exit(0)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let patched = 0
|
|
90
|
+
let files = 0
|
|
91
|
+
for (const [rel, before, removed] of HUNKS) {
|
|
92
|
+
const path = `${piRoot}/${rel}`
|
|
93
|
+
if (!existsSync(path)) continue
|
|
94
|
+
files += 1
|
|
95
|
+
const pattern = new RegExp(`(${escapeRegExp(before)}\\n)(\\s*${escapeRegExp(removed)}\\n)`)
|
|
96
|
+
const text = readFileSync(path, 'utf8')
|
|
97
|
+
if (!pattern.test(text)) continue
|
|
98
|
+
writeFileSync(path, text.replace(pattern, '$1'))
|
|
99
|
+
patched += 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// pi-messages.js spans the delta assignment over two lines.
|
|
103
|
+
{
|
|
104
|
+
const path = `${piRoot}/dist/api/pi-messages.js`
|
|
105
|
+
if (existsSync(path)) {
|
|
106
|
+
files += 1
|
|
107
|
+
const pattern = /partial\.content\[event\.contentIndex\]\.arguments =\s*\n?\s*parseStreamingJson\(json\);\n/
|
|
108
|
+
const text = readFileSync(path, 'utf8')
|
|
109
|
+
if (pattern.test(text)) {
|
|
110
|
+
writeFileSync(path, text.replace(pattern, ''))
|
|
111
|
+
patched += 1
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
console.log(`[dshopencodego] patch-pi-ai: ${patched} hunks applied across ${files} files (idempotent)`)
|