dsh-image-gen 0.6.10 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +351 -43
- package/lib/client.js.map +1 -1
- package/lib/index.js +761 -1076
- package/lib/types/client/browser-image-utils.d.ts +9 -0
- package/lib/types/client/gallery-store.d.ts +4 -0
- package/lib/types/client/gallery-view.d.ts +32 -2
- package/lib/types/client/index.d.ts +9 -2
- package/lib/types/client/settings-write.d.ts +4 -0
- package/lib/types/client/tl/studio-tl-canvas.d.ts +7 -0
- package/lib/types/config.d.ts +7 -1
- package/lib/types/shared.d.ts +6 -0
- package/package.json +20 -20
- package/lib/types/client/canvas/canvas-bridge.d.ts +0 -10
- package/lib/types/client/canvas/canvas-model.d.ts +0 -129
- package/lib/types/client/canvas/canvas-nodes.d.ts +0 -23
- package/lib/types/client/canvas/canvas-store.d.ts +0 -29
- package/lib/types/client/canvas/canvas-style.d.ts +0 -1
- package/lib/types/client/canvas/canvas-view.d.ts +0 -8
- package/lib/types/client/canvas/rf-base-css.d.ts +0 -2
- package/lib/types/client/studio-chat-panel.d.ts +0 -23
- package/lib/types/client/tl/diagram-landing.d.ts +0 -75
- package/lib/types/client/tl/diagram-layout.d.ts +0 -76
- package/lib/types/client/tl/diagram-measure.d.ts +0 -40
- package/lib/types/diagram.d.ts +0 -42
- package/lib/types/studio-chat.d.ts +0 -48
package/lib/index.js
CHANGED
|
@@ -5,861 +5,7 @@ import { lstat, mkdir, readFile, readdir, realpath, rename, stat, unlink, writeF
|
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import http from "node:http";
|
|
8
|
-
//#region
|
|
9
|
-
var __defProp = Object.defineProperty;
|
|
10
|
-
var __exportAll = (all, no_symbols) => {
|
|
11
|
-
let target = {};
|
|
12
|
-
for (var name in all) __defProp(target, name, {
|
|
13
|
-
get: all[name],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
17
|
-
return target;
|
|
18
|
-
};
|
|
19
|
-
//#endregion
|
|
20
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-value_005f00f2ec004257b269f27fd5e936b4/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
|
|
21
|
-
/** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
|
|
22
|
-
/**
|
|
23
|
-
* Mark an unreachable closed-union branch.
|
|
24
|
-
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
25
|
-
* @param context - optional switch-site label included in the failure message.
|
|
26
|
-
* @returns never; a runtime value that escaped its type always throws.
|
|
27
|
-
*/
|
|
28
|
-
function assertNever(value, context) {
|
|
29
|
-
const rendered = JSON.stringify(value) ?? String(value);
|
|
30
|
-
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
31
|
-
}
|
|
32
|
-
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
33
|
-
function hasIntrinsicConstructor$1(prototype, name) {
|
|
34
|
-
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
35
|
-
if (typeof constructor !== "function") return false;
|
|
36
|
-
try {
|
|
37
|
-
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
38
|
-
} catch {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
43
|
-
function isIntrinsicObjectPrototype$1(value) {
|
|
44
|
-
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor$1(value, "Object");
|
|
45
|
-
}
|
|
46
|
-
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
47
|
-
function hasPlainArrayPrototype$1(value) {
|
|
48
|
-
const prototype = Object.getPrototypeOf(value);
|
|
49
|
-
if (!Array.isArray(prototype) || !hasIntrinsicConstructor$1(prototype, "Array")) return false;
|
|
50
|
-
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
51
|
-
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype$1(objectPrototype);
|
|
52
|
-
}
|
|
53
|
-
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
54
|
-
function hasPlainObjectPrototype(value) {
|
|
55
|
-
const prototype = Object.getPrototypeOf(value);
|
|
56
|
-
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype$1(prototype);
|
|
57
|
-
}
|
|
58
|
-
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
59
|
-
function enumerableStringKeys(value) {
|
|
60
|
-
const keys = Reflect.ownKeys(value);
|
|
61
|
-
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
62
|
-
return keys;
|
|
63
|
-
}
|
|
64
|
-
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
65
|
-
function walkJsonValue(value, detach) {
|
|
66
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
67
|
-
let root;
|
|
68
|
-
const assign = (destination, item) => {
|
|
69
|
-
if (destination === void 0) return;
|
|
70
|
-
if (destination.kind === "root") root = item;
|
|
71
|
-
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
72
|
-
else Object.defineProperty(destination.target, destination.key, {
|
|
73
|
-
value: item,
|
|
74
|
-
enumerable: true,
|
|
75
|
-
configurable: true,
|
|
76
|
-
writable: true
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
const tasks = [{
|
|
80
|
-
kind: "visit",
|
|
81
|
-
value,
|
|
82
|
-
...detach ? { destination: { kind: "root" } } : {}
|
|
83
|
-
}];
|
|
84
|
-
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
85
|
-
if (task.kind === "leave") {
|
|
86
|
-
ancestors.delete(task.source);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (task.kind === "array-item") {
|
|
90
|
-
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
91
|
-
tasks.push({
|
|
92
|
-
kind: "visit",
|
|
93
|
-
value: task.source[task.index],
|
|
94
|
-
...task.target === void 0 ? {} : { destination: {
|
|
95
|
-
kind: "array",
|
|
96
|
-
target: task.target,
|
|
97
|
-
index: task.index
|
|
98
|
-
} }
|
|
99
|
-
});
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (task.kind === "object-property") {
|
|
103
|
-
tasks.push({
|
|
104
|
-
kind: "visit",
|
|
105
|
-
value: task.source[task.key],
|
|
106
|
-
...task.target === void 0 ? {} : { destination: {
|
|
107
|
-
kind: "object",
|
|
108
|
-
target: task.target,
|
|
109
|
-
key: task.key
|
|
110
|
-
} }
|
|
111
|
-
});
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
const current = task.value;
|
|
115
|
-
if (current === null) {
|
|
116
|
-
assign(task.destination, null);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (typeof current === "boolean" || typeof current === "string") {
|
|
120
|
-
assign(task.destination, current);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (typeof current === "number") {
|
|
124
|
-
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
125
|
-
assign(task.destination, current);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (typeof current !== "object") return void 0;
|
|
129
|
-
if (ancestors.has(current)) return void 0;
|
|
130
|
-
if (Array.isArray(current)) {
|
|
131
|
-
if (!hasPlainArrayPrototype$1(current)) return void 0;
|
|
132
|
-
const length = current.length;
|
|
133
|
-
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
134
|
-
const target = detach ? [] : void 0;
|
|
135
|
-
if (target !== void 0) assign(task.destination, target);
|
|
136
|
-
ancestors.add(current);
|
|
137
|
-
tasks.push({
|
|
138
|
-
kind: "leave",
|
|
139
|
-
source: current
|
|
140
|
-
});
|
|
141
|
-
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
142
|
-
kind: "array-item",
|
|
143
|
-
source: current,
|
|
144
|
-
index,
|
|
145
|
-
...target === void 0 ? {} : { target }
|
|
146
|
-
});
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
150
|
-
const keys = enumerableStringKeys(current);
|
|
151
|
-
if (keys === void 0) return void 0;
|
|
152
|
-
const target = detach ? {} : void 0;
|
|
153
|
-
if (target !== void 0) assign(task.destination, target);
|
|
154
|
-
ancestors.add(current);
|
|
155
|
-
tasks.push({
|
|
156
|
-
kind: "leave",
|
|
157
|
-
source: current
|
|
158
|
-
});
|
|
159
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
160
|
-
const key = keys[index];
|
|
161
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
162
|
-
if (key === void 0) return void 0;
|
|
163
|
-
tasks.push({
|
|
164
|
-
kind: "object-property",
|
|
165
|
-
source: current,
|
|
166
|
-
key,
|
|
167
|
-
...target === void 0 ? {} : { target }
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return detach ? root : true;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Validate and detach lossless JSON in one read per property.
|
|
175
|
-
* @param value - candidate value to validate and detach.
|
|
176
|
-
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
177
|
-
*/
|
|
178
|
-
function snapshotJsonValue(value) {
|
|
179
|
-
return walkJsonValue(value, true);
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
|
|
183
|
-
* @param value - candidate value to test.
|
|
184
|
-
* @returns whether the value survives a JSON round trip without loss.
|
|
185
|
-
*/
|
|
186
|
-
function isJsonValue(value) {
|
|
187
|
-
return walkJsonValue(value, false) === true;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Compare JSON-compatible values structurally.
|
|
191
|
-
* @param a - one JSON-compatible value.
|
|
192
|
-
* @param b - the other JSON-compatible value.
|
|
193
|
-
* @returns whether both values contain the same JSON data.
|
|
194
|
-
*/
|
|
195
|
-
function deepEqualJson(a, b) {
|
|
196
|
-
if (a === b) return true;
|
|
197
|
-
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
198
|
-
if (Array.isArray(a) || Array.isArray(b)) {
|
|
199
|
-
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
200
|
-
return a.every((entry, index) => deepEqualJson(entry, b[index]));
|
|
201
|
-
}
|
|
202
|
-
const left = a;
|
|
203
|
-
const right = b;
|
|
204
|
-
const keys = Object.keys(left);
|
|
205
|
-
if (keys.length !== Object.keys(right).length) return false;
|
|
206
|
-
return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
210
|
-
* @param value - value to freeze.
|
|
211
|
-
* @returns the same value after every reachable enumerable child is frozen.
|
|
212
|
-
*/
|
|
213
|
-
function deepFreeze(value) {
|
|
214
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
215
|
-
const pending = [{
|
|
216
|
-
kind: "visit",
|
|
217
|
-
node: value
|
|
218
|
-
}];
|
|
219
|
-
while (pending.length > 0) {
|
|
220
|
-
const task = pending.pop();
|
|
221
|
-
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
222
|
-
if (task === void 0) continue;
|
|
223
|
-
if (task.kind === "property") {
|
|
224
|
-
pending.push({
|
|
225
|
-
kind: "visit",
|
|
226
|
-
node: task.source[task.key]
|
|
227
|
-
});
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
const node = task.node;
|
|
231
|
-
if (node === null || typeof node !== "object") continue;
|
|
232
|
-
if (node instanceof AbortSignal) continue;
|
|
233
|
-
if (seen.has(node)) continue;
|
|
234
|
-
seen.add(node);
|
|
235
|
-
Object.freeze(node);
|
|
236
|
-
const keys = Object.keys(node);
|
|
237
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
238
|
-
const key = keys[index];
|
|
239
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
240
|
-
if (key === void 0) continue;
|
|
241
|
-
pending.push({
|
|
242
|
-
kind: "property",
|
|
243
|
-
source: node,
|
|
244
|
-
key
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return value;
|
|
249
|
-
}
|
|
250
|
-
//#endregion
|
|
251
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-settings@0_935f3a2ac52a1f8e83ba61ff4169507a/node_modules/@deepseek-ai/dsh-settings/lib/index.js
|
|
252
|
-
var lib_exports = /* @__PURE__ */ __exportAll({
|
|
253
|
-
SettingsConflictError: () => SettingsConflictError,
|
|
254
|
-
SettingsProvider: () => SettingsProvider,
|
|
255
|
-
default: () => SettingsProvider,
|
|
256
|
-
redactSecrets: () => redactSecrets$1
|
|
257
|
-
});
|
|
258
|
-
/**
|
|
259
|
-
* Structural secret redaction for settings values. `role('secret')` fields are
|
|
260
|
-
* removed from a value before it crosses a wire boundary; a sidecar records
|
|
261
|
-
* each schema-declared secret position and whether it currently holds a value,
|
|
262
|
-
* so a configuration surface can render a write-only input without ever
|
|
263
|
-
* receiving the secret itself.
|
|
264
|
-
* @module @deepseek-ai/dsh-settings/redact
|
|
265
|
-
*/
|
|
266
|
-
/** Whether a value is a plain data object the walker may recurse into. */
|
|
267
|
-
function isRecord$1(value) {
|
|
268
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
269
|
-
}
|
|
270
|
-
function walk(node, value, path, secrets) {
|
|
271
|
-
if (node === void 0) return value;
|
|
272
|
-
if (node.meta?.role === "secret") {
|
|
273
|
-
secrets.push({
|
|
274
|
-
path,
|
|
275
|
-
set: value !== void 0
|
|
276
|
-
});
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
switch (node.type) {
|
|
280
|
-
case "object": {
|
|
281
|
-
const properties = node.dict ?? {};
|
|
282
|
-
const source = isRecord$1(value) ? value : void 0;
|
|
283
|
-
const rebuilt = {};
|
|
284
|
-
if (source !== void 0) for (const [key, entry] of Object.entries(source)) {
|
|
285
|
-
if (key in properties) continue;
|
|
286
|
-
rebuilt[key] = entry;
|
|
287
|
-
}
|
|
288
|
-
for (const [key, child] of Object.entries(properties)) {
|
|
289
|
-
const stripped = walk(child, source?.[key], [...path, key], secrets);
|
|
290
|
-
if (stripped !== void 0) rebuilt[key] = stripped;
|
|
291
|
-
}
|
|
292
|
-
return source === void 0 && Object.keys(rebuilt).length === 0 ? value : rebuilt;
|
|
293
|
-
}
|
|
294
|
-
case "dict": {
|
|
295
|
-
if (!isRecord$1(value)) return value;
|
|
296
|
-
const rebuilt = {};
|
|
297
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
298
|
-
const stripped = walk(node.inner, entry, [...path, key], secrets);
|
|
299
|
-
if (stripped !== void 0) rebuilt[key] = stripped;
|
|
300
|
-
}
|
|
301
|
-
return rebuilt;
|
|
302
|
-
}
|
|
303
|
-
case "array":
|
|
304
|
-
if (!Array.isArray(value)) return value;
|
|
305
|
-
return value.map((entry, index) => walk(node.inner, entry, [...path, String(index)], secrets));
|
|
306
|
-
default: return value;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Remove every `role('secret')` field a schema declares from a value. The
|
|
311
|
-
* walker follows `object`, `dict`, and `array` containers; a secret must be
|
|
312
|
-
* declared directly on a field reachable through those containers (a secret
|
|
313
|
-
* buried inside a union branch or transform is not reachable and must not be
|
|
314
|
-
* modeled that way). The input is never mutated.
|
|
315
|
-
* @param schema - live schemastery schema describing the value.
|
|
316
|
-
* @param value - the value to strip; `undefined` yields an empty record with
|
|
317
|
-
* object-property secret slots still enumerated.
|
|
318
|
-
* @returns the stripped detached value and the ordered secret positions.
|
|
319
|
-
*/
|
|
320
|
-
function redactSecrets$1(schema, value) {
|
|
321
|
-
const secrets = [];
|
|
322
|
-
return {
|
|
323
|
-
value: walk(schema, value, [], secrets),
|
|
324
|
-
secrets
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Service Definition for the user-settings capability seam (`ctx.settings`). Providers store one raw document of
|
|
329
|
-
* per-namespace sections; plugins register a namespace schema and read the
|
|
330
|
-
* resolved value, which layers schema defaults, the registrant's composition
|
|
331
|
-
* `base`, and the user document section, in that order.
|
|
332
|
-
* @module @deepseek-ai/dsh-settings
|
|
333
|
-
*/
|
|
334
|
-
const NAMESPACE_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
335
|
-
function parseSettingsNamespace(value) {
|
|
336
|
-
if (!NAMESPACE_PATTERN.test(value)) throw new TypeError(`settings namespace "${value}" must match ${String(NAMESPACE_PATTERN)}`);
|
|
337
|
-
return value;
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* A write refused because the namespace moved since the caller read it. The
|
|
341
|
-
* Service Definition's serialized write queue orders writes; it cannot tell a fresh writer
|
|
342
|
-
* from one holding a stale snapshot, which is what this reports.
|
|
343
|
-
*/
|
|
344
|
-
var SettingsConflictError = class extends Error {
|
|
345
|
-
/** Stable machine code for wire layers mapping this to their own taxonomy. */
|
|
346
|
-
code = "SETTINGS_CONFLICT";
|
|
347
|
-
/** The revision the write expected. */
|
|
348
|
-
expected;
|
|
349
|
-
/** The revision the namespace actually stands at. */
|
|
350
|
-
actual;
|
|
351
|
-
/**
|
|
352
|
-
* @param ns - the namespace whose write was refused.
|
|
353
|
-
* @param expected - the revision the caller sent.
|
|
354
|
-
* @param actual - the revision now stored.
|
|
355
|
-
*/
|
|
356
|
-
constructor(ns, expected, actual) {
|
|
357
|
-
super(`settings namespace "${ns}" changed since it was read (expected revision ${String(expected)}, now ${String(actual)})`);
|
|
358
|
-
this.name = "SettingsConflictError";
|
|
359
|
-
this.expected = expected;
|
|
360
|
-
this.actual = actual;
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
/** Whether a value is a plain data object (not an array, null, or class instance). */
|
|
364
|
-
function isPlainObject(value) {
|
|
365
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
366
|
-
const proto = Object.getPrototypeOf(value);
|
|
367
|
-
return proto === Object.prototype || proto === null;
|
|
368
|
-
}
|
|
369
|
-
/** Apply one path op to a detached section, returning the next section. */
|
|
370
|
-
function applyPathOp(section, op) {
|
|
371
|
-
const [head, ...rest] = op.path;
|
|
372
|
-
if (head === void 0) {
|
|
373
|
-
if (op.op === "unset") return {};
|
|
374
|
-
if (!isPlainObject(op.value)) throw new TypeError("settings mutate: setting the section root requires a plain object");
|
|
375
|
-
return { ...op.value };
|
|
376
|
-
}
|
|
377
|
-
if (rest.length === 0) {
|
|
378
|
-
if (op.op === "set") return {
|
|
379
|
-
...section,
|
|
380
|
-
[head]: op.value
|
|
381
|
-
};
|
|
382
|
-
const { [head]: _removed, ...kept } = section;
|
|
383
|
-
return kept;
|
|
384
|
-
}
|
|
385
|
-
const child = section[head];
|
|
386
|
-
if (!isPlainObject(child)) {
|
|
387
|
-
if (op.op === "unset") return section;
|
|
388
|
-
return {
|
|
389
|
-
...section,
|
|
390
|
-
[head]: applyPathOp({}, {
|
|
391
|
-
...op,
|
|
392
|
-
path: rest
|
|
393
|
-
})
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
return {
|
|
397
|
-
...section,
|
|
398
|
-
[head]: applyPathOp(child, {
|
|
399
|
-
...op,
|
|
400
|
-
path: rest
|
|
401
|
-
})
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
/** Human label for a value that lossless JSON cannot represent (numbers reject inline). */
|
|
405
|
-
function describeRejected(value) {
|
|
406
|
-
if (value === void 0) return "undefined";
|
|
407
|
-
if (typeof value === "object" && value !== null) {
|
|
408
|
-
const name = Object.getPrototypeOf(value)?.constructor?.name;
|
|
409
|
-
return name === void 0 || name === "Object" ? "a non-plain object" : `a ${name}`;
|
|
410
|
-
}
|
|
411
|
-
return `a ${typeof value}`;
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Detach and validate one write input in a single walk before persistence:
|
|
415
|
-
* only JSON data (plain objects, arrays, strings, finite numbers,
|
|
416
|
-
* booleans, `null`) may reach a provider document. `structuredClone` alone
|
|
417
|
-
* would admit Dates, Maps, BigInts, and cycles that YAML/JSON storage then
|
|
418
|
-
* silently distorts on the reload round-trip. `undefined` entries in objects
|
|
419
|
-
* are skipped — the same sparse-patch semantics as {@link mergeLayers} — while
|
|
420
|
-
* an `undefined` array entry is rejected rather than coerced.
|
|
421
|
-
* @param root - plain-object write input (caller-checked).
|
|
422
|
-
* @param reject - builds the validation error from a value label and its `$`-rooted path.
|
|
423
|
-
* @returns the detached JSON-compatible clone.
|
|
424
|
-
*/
|
|
425
|
-
function cloneJsonShaped(root, reject) {
|
|
426
|
-
const visiting = /* @__PURE__ */ new WeakSet();
|
|
427
|
-
const clone = (value, path) => {
|
|
428
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
429
|
-
if (typeof value === "number") {
|
|
430
|
-
if (!Number.isFinite(value)) throw reject("a non-finite number", path);
|
|
431
|
-
return value;
|
|
432
|
-
}
|
|
433
|
-
if (Array.isArray(value)) {
|
|
434
|
-
if (visiting.has(value)) throw reject("a circular reference", path);
|
|
435
|
-
visiting.add(value);
|
|
436
|
-
const entries = value.map((entry, index) => clone(entry, `${path}[${index}]`));
|
|
437
|
-
visiting.delete(value);
|
|
438
|
-
return entries;
|
|
439
|
-
}
|
|
440
|
-
if (isPlainObject(value)) {
|
|
441
|
-
if (visiting.has(value)) throw reject("a circular reference", path);
|
|
442
|
-
visiting.add(value);
|
|
443
|
-
const out = {};
|
|
444
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
445
|
-
if (entry === void 0) continue;
|
|
446
|
-
out[key] = clone(entry, `${path}.${key}`);
|
|
447
|
-
}
|
|
448
|
-
visiting.delete(value);
|
|
449
|
-
return out;
|
|
450
|
-
}
|
|
451
|
-
throw reject(describeRejected(value), path);
|
|
452
|
-
};
|
|
453
|
-
return clone(root, "$");
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Layer `over` onto `under`: plain objects merge recursively, every other
|
|
457
|
-
* value (arrays included) replaces the lower layer wholesale. `over` never
|
|
458
|
-
* carries `undefined` entries — sections come from parsed documents and write
|
|
459
|
-
* snapshots pass {@link cloneJsonShaped}, which strips them so a sparse patch
|
|
460
|
-
* cannot erase lower keys.
|
|
461
|
-
*/
|
|
462
|
-
function mergeLayers(under, over) {
|
|
463
|
-
if (over === void 0) return under;
|
|
464
|
-
if (!isPlainObject(under) || !isPlainObject(over)) return over;
|
|
465
|
-
const merged = { ...under };
|
|
466
|
-
for (const [key, value] of Object.entries(over)) merged[key] = key in merged ? mergeLayers(merged[key], value) : value;
|
|
467
|
-
return merged;
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* Abstract settings service. Providers implement raw-document storage
|
|
471
|
-
* (`load`/`persist`) and push external changes through {@link Settings.publish};
|
|
472
|
-
* the base class owns namespace registration, resolution, validation, change
|
|
473
|
-
* detection, and the `settings/updated` commit event.
|
|
474
|
-
*/
|
|
475
|
-
var SettingsProvider = class extends Service {
|
|
476
|
-
registrations = /* @__PURE__ */ new Map();
|
|
477
|
-
/** Latest published raw document; empty until the provider's first publish. */
|
|
478
|
-
document = {};
|
|
479
|
-
/** Per-namespace write chains; settled tails, so a failure never poisons the queue. */
|
|
480
|
-
writeQueues = /* @__PURE__ */ new Map();
|
|
481
|
-
/** In-flight watcher invocation segments, drained by the dispose teardown. */
|
|
482
|
-
pendingTails = /* @__PURE__ */ new Set();
|
|
483
|
-
/** Set at service dispose: refuse new writes while queued ones drain. */
|
|
484
|
-
stopped = false;
|
|
485
|
-
/** Opaque read of {@link stopped}: control flow cannot narrow it across awaits. */
|
|
486
|
-
isStopped() {
|
|
487
|
-
return this.stopped;
|
|
488
|
-
}
|
|
489
|
-
constructor(ctx) {
|
|
490
|
-
super(ctx, "settings");
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Load the provider's document once and publish it before the service
|
|
494
|
-
* becomes injectable, and register the write-drain teardown. Providers with
|
|
495
|
-
* their own init (watchers, connections) delegate here first via
|
|
496
|
-
* `yield* super[Service.init]()`; their disposers then run before the drain.
|
|
497
|
-
*/
|
|
498
|
-
async *[Service.init]() {
|
|
499
|
-
yield async () => {
|
|
500
|
-
this.stopped = true;
|
|
501
|
-
await Promise.allSettled([...this.writeQueues.values(), ...this.pendingTails]);
|
|
502
|
-
};
|
|
503
|
-
this.publish(await this.load());
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Absolute path of the provider's user-editable document, when its storage
|
|
507
|
-
* is one local file. Configuration surfaces use this only as availability
|
|
508
|
-
* metadata; the guarded open operation resolves the path again Host-side.
|
|
509
|
-
* Non-file providers leave it undefined and expose no open-document affordance.
|
|
510
|
-
* @returns the absolute local document path, or undefined for non-file storage.
|
|
511
|
-
*/
|
|
512
|
-
get documentPath() {}
|
|
513
|
-
/**
|
|
514
|
-
* Prepare the provider's user-editable document for a native editor. File
|
|
515
|
-
* providers may materialize an absent document before returning its path;
|
|
516
|
-
* non-file providers return undefined.
|
|
517
|
-
* @returns the absolute local document path, or undefined for non-file storage.
|
|
518
|
-
*/
|
|
519
|
-
prepareDocument() {
|
|
520
|
-
return Promise.resolve(this.documentPath);
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Register a namespace schema and receive its owner scope. The registration
|
|
524
|
-
* is an effect on the calling plugin's fiber: disposing that fiber removes
|
|
525
|
-
* the namespace and its observers. An invalid stored section fails the
|
|
526
|
-
* registration itself — the earliest point where the schema can judge it.
|
|
527
|
-
* @param ns - unique namespace; duplicate registration fails loud.
|
|
528
|
-
* @param schema - schemastery schema resolving this namespace's value.
|
|
529
|
-
* @param options - composition `base` layer and effect timing.
|
|
530
|
-
* @returns the owner scope for reads, observation, and updates.
|
|
531
|
-
* @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
|
|
532
|
-
*/
|
|
533
|
-
register(ns, schema, options) {
|
|
534
|
-
const parsedNs = parseSettingsNamespace(ns);
|
|
535
|
-
if (this.registrations.has(parsedNs)) throw new Error(`settings namespace "${parsedNs}" is already registered`);
|
|
536
|
-
const registration = {
|
|
537
|
-
ns: parsedNs,
|
|
538
|
-
schema,
|
|
539
|
-
base: options?.base,
|
|
540
|
-
applies: options?.applies ?? "live",
|
|
541
|
-
...options?.validate === void 0 ? {} : { validate: options.validate },
|
|
542
|
-
resolved: deepFreeze(this.resolve(schema, options?.base, this.section(parsedNs), options?.validate)),
|
|
543
|
-
revision: 0,
|
|
544
|
-
watchers: /* @__PURE__ */ new Set()
|
|
545
|
-
};
|
|
546
|
-
this.ctx.effect(() => {
|
|
547
|
-
this.registrations.set(parsedNs, registration);
|
|
548
|
-
return () => this.registrations.delete(parsedNs);
|
|
549
|
-
}, `settings.register(${JSON.stringify(String(parsedNs))})`);
|
|
550
|
-
return {
|
|
551
|
-
get: () => registration.resolved,
|
|
552
|
-
watch: (callback) => {
|
|
553
|
-
const watcher = {
|
|
554
|
-
callback,
|
|
555
|
-
tail: Promise.resolve(),
|
|
556
|
-
active: true
|
|
557
|
-
};
|
|
558
|
-
registration.watchers.add(watcher);
|
|
559
|
-
return () => {
|
|
560
|
-
watcher.active = false;
|
|
561
|
-
registration.watchers.delete(watcher);
|
|
562
|
-
};
|
|
563
|
-
},
|
|
564
|
-
update: (patch) => this.update(parsedNs, patch),
|
|
565
|
-
replace: (section) => this.replace(parsedNs, section)
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Attach one optional-settings consumer to this provider. The consumer
|
|
570
|
-
* registers its composition entry as the base layer while this provider is
|
|
571
|
-
* present, then falls back to that entry if the provider detaches.
|
|
572
|
-
* @param owner - consumer context whose unload suppresses fallback work.
|
|
573
|
-
* @param ns - consumer-owned settings namespace.
|
|
574
|
-
* @param schema - schema resolving the namespace.
|
|
575
|
-
* @param entry - composition entry used as the base and fallback value.
|
|
576
|
-
* @param hooks - source sink, change notification, and optional validation.
|
|
577
|
-
* @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
|
|
578
|
-
*/
|
|
579
|
-
installSection(owner, ns, schema, entry, hooks) {
|
|
580
|
-
const scope = this.register(ns, schema, {
|
|
581
|
-
base: entry,
|
|
582
|
-
...hooks.validate === void 0 ? {} : { validate: hooks.validate }
|
|
583
|
-
});
|
|
584
|
-
hooks.setSource(() => scope.get());
|
|
585
|
-
this.ctx.effect(() => () => {
|
|
586
|
-
if (isUnloading(owner)) return;
|
|
587
|
-
hooks.setSource(() => entry);
|
|
588
|
-
hooks.onChange();
|
|
589
|
-
});
|
|
590
|
-
hooks.onChange();
|
|
591
|
-
scope.watch(() => {
|
|
592
|
-
if (isUnloading(owner)) return;
|
|
593
|
-
hooks.onChange();
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* Describe every registered namespace for configuration surfaces, including
|
|
598
|
-
* the composition `base` and raw user layers so a form can mark which fields
|
|
599
|
-
* the user overrode (presence in `user`) and what a reset returns to.
|
|
600
|
-
* @param options - redaction switch; wire surfaces must redact.
|
|
601
|
-
* @returns one descriptor per registered namespace, in registration order.
|
|
602
|
-
*/
|
|
603
|
-
describe(options) {
|
|
604
|
-
return [...this.registrations.values()].map((registration) => {
|
|
605
|
-
let user;
|
|
606
|
-
try {
|
|
607
|
-
user = this.section(registration.ns);
|
|
608
|
-
} catch {
|
|
609
|
-
user = void 0;
|
|
610
|
-
}
|
|
611
|
-
const base = registration.base === void 0 ? void 0 : structuredClone(registration.base);
|
|
612
|
-
const detachedUser = user === void 0 ? void 0 : structuredClone(user);
|
|
613
|
-
const descriptor = {
|
|
614
|
-
ns: registration.ns,
|
|
615
|
-
schema: registration.schema.toJSON(),
|
|
616
|
-
value: registration.resolved,
|
|
617
|
-
revision: registration.revision,
|
|
618
|
-
...base === void 0 ? {} : { base },
|
|
619
|
-
...detachedUser === void 0 ? {} : { user: detachedUser },
|
|
620
|
-
applies: registration.applies
|
|
621
|
-
};
|
|
622
|
-
if (options?.redactSecrets !== true) return descriptor;
|
|
623
|
-
const schema = registration.schema;
|
|
624
|
-
const redacted = redactSecrets$1(schema, registration.resolved);
|
|
625
|
-
return {
|
|
626
|
-
...descriptor,
|
|
627
|
-
value: redacted.value,
|
|
628
|
-
...base === void 0 ? {} : { base: redactSecrets$1(schema, base).value },
|
|
629
|
-
...detachedUser === void 0 ? {} : { user: redactSecrets$1(schema, detachedUser).value },
|
|
630
|
-
secrets: redacted.secrets
|
|
631
|
-
};
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Read one registered namespace's resolved value.
|
|
636
|
-
* @param ns - the namespace to read.
|
|
637
|
-
* @returns the resolved value, or `undefined` while unregistered.
|
|
638
|
-
* @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
|
|
639
|
-
*/
|
|
640
|
-
get(ns) {
|
|
641
|
-
return this.registrations.get(parseSettingsNamespace(ns))?.resolved;
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Merge a patch into one registered namespace's user layer, validate the
|
|
645
|
-
* resolved candidate, persist through the provider, then commit and emit.
|
|
646
|
-
* A validation failure rejects before anything is persisted. Writes to one
|
|
647
|
-
* namespace are serialized: concurrent updates apply in call order, each
|
|
648
|
-
* merging over the previous write's committed section.
|
|
649
|
-
* @param ns - the registered namespace to update.
|
|
650
|
-
* @param patch - plain-object patch over the user section.
|
|
651
|
-
* @param expectedRevision - the descriptor `revision` the caller read; a
|
|
652
|
-
* namespace that moved past it rejects with {@link SettingsConflictError}.
|
|
653
|
-
* @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
|
|
654
|
-
*/
|
|
655
|
-
async update(ns, patch, expectedRevision) {
|
|
656
|
-
return this.write(parseSettingsNamespace(ns), patch, "merge", expectedRevision);
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Replace one registered namespace's user section wholesale, validate,
|
|
660
|
-
* persist, then commit and emit. Keys absent from `section` fall back to the
|
|
661
|
-
* composition `base` and schema defaults — this is the removal/reset path a
|
|
662
|
-
* merge-only patch cannot express (`replace({})` re-inherits everything).
|
|
663
|
-
* @param ns - the registered namespace to replace.
|
|
664
|
-
* @param section - the complete next user section.
|
|
665
|
-
* @param expectedRevision - the descriptor `revision` the caller read; a
|
|
666
|
-
* namespace that moved past it rejects with {@link SettingsConflictError}.
|
|
667
|
-
* @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
|
|
668
|
-
*/
|
|
669
|
-
async replace(ns, section, expectedRevision) {
|
|
670
|
-
return this.write(parseSettingsNamespace(ns), section, "replace", expectedRevision);
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* Apply path-addressed edits to one registered namespace's user section,
|
|
674
|
-
* validate, persist, then commit and emit. The ops are applied to the
|
|
675
|
-
* section as it stands when the write reaches the front of the queue, so a
|
|
676
|
-
* caller never has to restate fields it did not touch — and, crucially,
|
|
677
|
-
* cannot delete fields it never saw. This is the write path for any caller
|
|
678
|
-
* holding a redacted view; `replace` remains the wholesale reset.
|
|
679
|
-
* @param ns - the registered namespace to edit.
|
|
680
|
-
* @param ops - ordered path edits; later ops observe earlier ones.
|
|
681
|
-
* @param expectedRevision - the descriptor `revision` the caller read; a
|
|
682
|
-
* namespace that moved past it rejects with {@link SettingsConflictError}.
|
|
683
|
-
* @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
|
|
684
|
-
*/
|
|
685
|
-
async mutate(ns, ops, expectedRevision) {
|
|
686
|
-
const parsedNs = parseSettingsNamespace(ns);
|
|
687
|
-
if (!Array.isArray(ops)) throw new TypeError(`settings mutate for "${parsedNs}" must be an array of path ops`);
|
|
688
|
-
for (const op of ops) {
|
|
689
|
-
if (!isPlainObject(op) || op["op"] !== "set" && op["op"] !== "unset") throw new TypeError(`settings mutate for "${parsedNs}" ops must be {op:'set'|'unset', path}`);
|
|
690
|
-
if (!Array.isArray(op["path"]) || op["path"].some((part) => typeof part !== "string")) throw new TypeError(`settings mutate for "${parsedNs}" op paths must be arrays of strings`);
|
|
691
|
-
}
|
|
692
|
-
return this.write(parsedNs, ops, "mutate", expectedRevision);
|
|
693
|
-
}
|
|
694
|
-
/** Validate a write, then queue it on the namespace's serialized write chain. */
|
|
695
|
-
write(ns, input, mode, expectedRevision) {
|
|
696
|
-
const verb = mode === "merge" ? "update" : mode === "replace" ? "replace" : "mutate";
|
|
697
|
-
const registration = this.registrations.get(ns);
|
|
698
|
-
if (registration === void 0) throw new Error(`settings namespace "${ns}" is not registered`);
|
|
699
|
-
if (this.isStopped()) throw new Error(`settings service is disposed: "${ns}" cannot be written`);
|
|
700
|
-
if (!this.writable) throw new Error(`settings provider is read-only: "${ns}" cannot be updated in-process`);
|
|
701
|
-
let payload;
|
|
702
|
-
if (mode === "mutate") payload = { ops: input };
|
|
703
|
-
else {
|
|
704
|
-
if (!isPlainObject(input)) throw new TypeError(`settings ${verb} for "${ns}" must be a plain object`);
|
|
705
|
-
payload = input;
|
|
706
|
-
}
|
|
707
|
-
const snapshot = cloneJsonShaped(payload, (label, path) => /* @__PURE__ */ new TypeError(`settings ${verb} for "${ns}" must contain only JSON-compatible data (found ${label} at ${path})`));
|
|
708
|
-
const run = (this.writeQueues.get(ns) ?? Promise.resolve()).catch(() => void 0).then(async () => {
|
|
709
|
-
if (this.isStopped()) throw new Error(`settings service was disposed before the queued "${ns}" ${verb} ran`);
|
|
710
|
-
if (this.registrations.get(ns) !== registration) throw new Error(`settings namespace "${ns}" registration was disposed before the queued ${verb} ran`);
|
|
711
|
-
const current = this.section(ns) ?? {};
|
|
712
|
-
if (expectedRevision !== void 0 && expectedRevision !== registration.revision) throw new SettingsConflictError(ns, expectedRevision, registration.revision);
|
|
713
|
-
const section = mode === "merge" ? mergeLayers(current, snapshot) : mode === "replace" ? snapshot : snapshot["ops"].reduce(applyPathOp, current);
|
|
714
|
-
const next = deepFreeze(this.resolve(registration.schema, registration.base, section, registration.validate));
|
|
715
|
-
await this.persist(ns, section);
|
|
716
|
-
this.document[ns] = section;
|
|
717
|
-
if (this.registrations.get(ns) === registration && !this.isStopped()) {
|
|
718
|
-
this.bumpRevision(registration, current, section);
|
|
719
|
-
this.commit(registration, next, "update");
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
this.writeQueues.set(ns, run);
|
|
723
|
-
return run;
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Provider hook: commit a complete raw document observed in storage. Each
|
|
727
|
-
* registered namespace re-resolves; an invalid section keeps that
|
|
728
|
-
* namespace's last good value and warns, other namespaces still commit.
|
|
729
|
-
* @param doc - the detached raw document (unregistered sections preserved).
|
|
730
|
-
* @param source - change origin; defaults to `provider`.
|
|
731
|
-
*/
|
|
732
|
-
publish(doc, source = "provider") {
|
|
733
|
-
const before = /* @__PURE__ */ new Map();
|
|
734
|
-
for (const registration of this.registrations.values()) try {
|
|
735
|
-
before.set(registration.ns, this.section(registration.ns));
|
|
736
|
-
} catch {
|
|
737
|
-
before.set(registration.ns, void 0);
|
|
738
|
-
}
|
|
739
|
-
this.document = doc;
|
|
740
|
-
for (const registration of this.registrations.values()) {
|
|
741
|
-
let next;
|
|
742
|
-
try {
|
|
743
|
-
next = deepFreeze(this.resolve(registration.schema, registration.base, this.section(registration.ns), registration.validate));
|
|
744
|
-
} catch (error) {
|
|
745
|
-
this.ctx.logger.warn("settings: keeping last good \"%s\" after invalid stored section", registration.ns);
|
|
746
|
-
this.ctx.logger.warn(error);
|
|
747
|
-
continue;
|
|
748
|
-
}
|
|
749
|
-
this.bumpRevision(registration, before.get(registration.ns), this.section(registration.ns));
|
|
750
|
-
this.commit(registration, next, source);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
/** Read one namespace's raw user section, rejecting non-object sections. */
|
|
754
|
-
section(ns) {
|
|
755
|
-
const section = this.document[ns];
|
|
756
|
-
if (section === void 0) return void 0;
|
|
757
|
-
if (!isPlainObject(section)) throw new TypeError(`settings section "${ns}" must be an object of keys`);
|
|
758
|
-
return section;
|
|
759
|
-
}
|
|
760
|
-
/** Resolve one namespace value: schema defaults, then `base`, then the user layer. */
|
|
761
|
-
resolve(schema, base, section, validate) {
|
|
762
|
-
const value = schema(mergeLayers(base, section));
|
|
763
|
-
validate?.(value);
|
|
764
|
-
return value;
|
|
765
|
-
}
|
|
766
|
-
/**
|
|
767
|
-
* Advance a namespace's revision when its RAW section changed, and announce
|
|
768
|
-
* it. Deliberately independent of {@link commit}'s resolved-value equality:
|
|
769
|
-
* storing an override equal to the composition base leaves the resolved
|
|
770
|
-
* value alone but changes what the document says, which is exactly what a
|
|
771
|
-
* configuration surface must re-read.
|
|
772
|
-
*/
|
|
773
|
-
bumpRevision(registration, before, after) {
|
|
774
|
-
if (deepEqualJson(before, after)) return;
|
|
775
|
-
registration.revision += 1;
|
|
776
|
-
this.emitDocumentUpdated(registration.ns, registration.revision);
|
|
777
|
-
}
|
|
778
|
-
/** Contained fan-out of `settings/document-updated`, mirroring {@link commit}'s. */
|
|
779
|
-
emitDocumentUpdated(ns, revision) {
|
|
780
|
-
let invariantFailure;
|
|
781
|
-
const args = [
|
|
782
|
-
"settings/document-updated",
|
|
783
|
-
ns,
|
|
784
|
-
revision
|
|
785
|
-
];
|
|
786
|
-
for (const listener of this.ctx.events.dispatch("emit", args)) try {
|
|
787
|
-
const returned = listener(ns, revision);
|
|
788
|
-
if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
|
|
789
|
-
this.warnListenerFailure(ns, error);
|
|
790
|
-
});
|
|
791
|
-
} catch (error) {
|
|
792
|
-
if (error?.code === "INVARIANT") {
|
|
793
|
-
invariantFailure ??= error;
|
|
794
|
-
continue;
|
|
795
|
-
}
|
|
796
|
-
this.warnListenerFailure(ns, error);
|
|
797
|
-
}
|
|
798
|
-
if (invariantFailure !== void 0) throw invariantFailure;
|
|
799
|
-
}
|
|
800
|
-
/** Commit a resolved value when changed: swap, notify watchers, emit the event. */
|
|
801
|
-
commit(registration, next, source) {
|
|
802
|
-
const prev = registration.resolved;
|
|
803
|
-
if (deepEqualJson(next, prev)) return;
|
|
804
|
-
registration.resolved = next;
|
|
805
|
-
for (const watcher of [...registration.watchers]) {
|
|
806
|
-
const segment = watcher.tail.then(() => {
|
|
807
|
-
if (!watcher.active || this.isStopped()) return;
|
|
808
|
-
return watcher.callback(next, prev);
|
|
809
|
-
}).then(() => void 0, (error) => {
|
|
810
|
-
this.warnWatcherFailure(registration.ns, error);
|
|
811
|
-
});
|
|
812
|
-
watcher.tail = segment;
|
|
813
|
-
this.pendingTails.add(segment);
|
|
814
|
-
segment.then(() => this.pendingTails.delete(segment));
|
|
815
|
-
}
|
|
816
|
-
let invariantFailure;
|
|
817
|
-
const args = [
|
|
818
|
-
"settings/updated",
|
|
819
|
-
registration.ns,
|
|
820
|
-
next,
|
|
821
|
-
prev,
|
|
822
|
-
source
|
|
823
|
-
];
|
|
824
|
-
for (const listener of this.ctx.events.dispatch("emit", args)) try {
|
|
825
|
-
const returned = listener(registration.ns, next, prev, source);
|
|
826
|
-
if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
|
|
827
|
-
this.warnListenerFailure(registration.ns, error);
|
|
828
|
-
});
|
|
829
|
-
} catch (error) {
|
|
830
|
-
if (error?.code === "INVARIANT") {
|
|
831
|
-
invariantFailure ??= error;
|
|
832
|
-
continue;
|
|
833
|
-
}
|
|
834
|
-
this.warnListenerFailure(registration.ns, error);
|
|
835
|
-
}
|
|
836
|
-
if (invariantFailure !== void 0) throw invariantFailure;
|
|
837
|
-
}
|
|
838
|
-
/** Contained-watcher diagnostic shared by the sync and async failure paths. */
|
|
839
|
-
warnWatcherFailure(ns, error) {
|
|
840
|
-
this.ctx.logger.warn("settings: watcher for \"%s\" failed", ns);
|
|
841
|
-
this.ctx.logger.warn(error);
|
|
842
|
-
}
|
|
843
|
-
/** Contained-listener diagnostic shared by the sync and async failure paths. */
|
|
844
|
-
warnListenerFailure(ns, error) {
|
|
845
|
-
this.ctx.logger.warn("settings: a settings/updated listener for \"%s\" failed", ns);
|
|
846
|
-
this.ctx.logger.warn(error);
|
|
847
|
-
}
|
|
848
|
-
};
|
|
849
|
-
/**
|
|
850
|
-
* Value mirror of the `FiberState` members {@link isUnloading} compares
|
|
851
|
-
* against: a const enum has no runtime object to import, and the value is
|
|
852
|
-
* needed at runtime (same rationale as the CLI boot driver's mirror).
|
|
853
|
-
*/
|
|
854
|
-
const FIBER_DISPOSED = 4;
|
|
855
|
-
const FIBER_UNLOADING = 5;
|
|
856
|
-
/** Whether the consumer's own fiber is tearing down (not just losing the settings service). */
|
|
857
|
-
function isUnloading(ctx) {
|
|
858
|
-
const state = ctx.fiber.state;
|
|
859
|
-
return state === FIBER_UNLOADING || state === FIBER_DISPOSED;
|
|
860
|
-
}
|
|
861
|
-
//#endregion
|
|
862
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1._7f15c7653b2619acc206cea5c06e49b3/node_modules/@deepseek-ai/dsh-scope/lib/index.js
|
|
8
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1._0271d3a82adfe807020938592f267574/node_modules/@deepseek-ai/dsh-scope/lib/index.js
|
|
863
9
|
/**
|
|
864
10
|
* Shared insertion-ordered storage and effect ownership for scope-aware registries.
|
|
865
11
|
*
|
|
@@ -1138,7 +284,7 @@ function scopeTarget(base, key) {
|
|
|
1138
284
|
return carrier;
|
|
1139
285
|
}
|
|
1140
286
|
//#endregion
|
|
1141
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-typert-
|
|
287
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-typert-pro_3c0b69ae3886dc169eb7f5273170dc50/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
|
|
1142
288
|
/** The one Remote failure class shared by owners, the Gateway, and consumers. */
|
|
1143
289
|
/**
|
|
1144
290
|
* One Remote call failure: a real Error carrying its stable code and typed
|
|
@@ -1182,7 +328,10 @@ function isTypertRemoteSegment(value) {
|
|
|
1182
328
|
}
|
|
1183
329
|
const REMOTE_METHOD_DESCRIPTOR = "@deepseek-ai/dsh-typert-protocol/remote-methods";
|
|
1184
330
|
/**
|
|
1185
|
-
* Bind one visible Service field to a Cordis key and Remote namespace.
|
|
331
|
+
* Bind one visible Service field to a Cordis key and Remote namespace. A
|
|
332
|
+
* service that owns a Cordis Context also gives its tree `ctx.invocation`,
|
|
333
|
+
* `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
|
|
334
|
+
* a Host composition to read it.
|
|
1186
335
|
* @param service - owning Service instance, normally `this`.
|
|
1187
336
|
* @param serviceKey - exact Cordis service key.
|
|
1188
337
|
* @param options - optional distinct wire namespace.
|
|
@@ -1192,6 +341,8 @@ function bindTypertRemote(service, serviceKey, options = {}) {
|
|
|
1192
341
|
validateName("service key", serviceKey);
|
|
1193
342
|
const namespace = options.namespace ?? serviceKey;
|
|
1194
343
|
validateName("namespace", namespace);
|
|
344
|
+
const ctx = Reflect.get(service, "ctx");
|
|
345
|
+
if (ctx instanceof Context) provideInvocationAccessor(ctx);
|
|
1195
346
|
return Object.freeze({
|
|
1196
347
|
service,
|
|
1197
348
|
serviceKey,
|
|
@@ -1213,6 +364,16 @@ var TypertRemoteService = class extends Service {
|
|
|
1213
364
|
this.typertRemote = bindTypertRemote(this, this.name, options);
|
|
1214
365
|
}
|
|
1215
366
|
};
|
|
367
|
+
/**
|
|
368
|
+
* Make `ctx.invocation` read as `undefined` outside a Remote call instead of the
|
|
369
|
+
* reflect service's "cannot get property" error; a call-derived Context shadows
|
|
370
|
+
* the accessor with its own property. The first Remote Service constructed in a
|
|
371
|
+
* tree registers it on the root, where it outlives any one Service.
|
|
372
|
+
*/
|
|
373
|
+
function provideInvocationAccessor(ctx) {
|
|
374
|
+
if (Object.hasOwn(ctx.root.reflect.props, "invocation")) return;
|
|
375
|
+
ctx.root.accessor("invocation", { get: () => void 0 });
|
|
376
|
+
}
|
|
1216
377
|
function Remote(methodExportOrOptions, context) {
|
|
1217
378
|
if (typeof methodExportOrOptions === "string") {
|
|
1218
379
|
validateName("Remote export name", methodExportOrOptions);
|
|
@@ -1222,68 +383,280 @@ function Remote(methodExportOrOptions, context) {
|
|
|
1222
383
|
if (remoteOptionMode(methodExportOrOptions) !== "stream" || Reflect.ownKeys(methodExportOrOptions).length !== 1) throw new TypeError("typert-protocol: Remote options must contain exactly mode: \"stream\"");
|
|
1223
384
|
return remoteDecorator({ kind: "direct" }, "stream");
|
|
1224
385
|
}
|
|
1225
|
-
if (context === void 0) throw new TypeError("typert-protocol: Remote decorator context is missing");
|
|
1226
|
-
addMarkerInitializer(context, { kind: "direct" });
|
|
1227
|
-
}
|
|
1228
|
-
function remoteOptionMode(options) {
|
|
1229
|
-
return Reflect.get(options, "mode");
|
|
1230
|
-
}
|
|
1231
|
-
function remoteDecorator(invocation, mode, exportName) {
|
|
1232
|
-
return function(_method, context) {
|
|
1233
|
-
addMarkerInitializer(context, invocation, mode, exportName);
|
|
1234
|
-
};
|
|
386
|
+
if (context === void 0) throw new TypeError("typert-protocol: Remote decorator context is missing");
|
|
387
|
+
addMarkerInitializer(context, { kind: "direct" });
|
|
388
|
+
}
|
|
389
|
+
function remoteOptionMode(options) {
|
|
390
|
+
return Reflect.get(options, "mode");
|
|
391
|
+
}
|
|
392
|
+
function remoteDecorator(invocation, mode, exportName) {
|
|
393
|
+
return function(_method, context) {
|
|
394
|
+
addMarkerInitializer(context, invocation, mode, exportName);
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function readRemoteMethodDescriptor(prototype) {
|
|
398
|
+
const property = Object.getOwnPropertyDescriptor(prototype, REMOTE_METHOD_DESCRIPTOR);
|
|
399
|
+
if (property === void 0) return void 0;
|
|
400
|
+
const descriptor = property.value;
|
|
401
|
+
if (descriptor === null || typeof descriptor !== "object") throw new TypeError("typert-protocol: Remote method descriptor must be an object");
|
|
402
|
+
const version = Reflect.get(descriptor, "version");
|
|
403
|
+
if (version !== 1) throw new TypeError(`typert-protocol: unsupported Remote method descriptor version ${String(version)}`);
|
|
404
|
+
const methods = Reflect.get(descriptor, "methods");
|
|
405
|
+
if (!Array.isArray(methods)) throw new TypeError("typert-protocol: Remote method descriptor methods must be an array");
|
|
406
|
+
return descriptor;
|
|
407
|
+
}
|
|
408
|
+
function addMarkerInitializer(context, invocation, mode, exportName) {
|
|
409
|
+
if (context.private || context.static || typeof context.name !== "string") throw new TypeError("typert-protocol: Remote decorators require a public instance method with a string name");
|
|
410
|
+
const method = context.name;
|
|
411
|
+
context.addInitializer(function() {
|
|
412
|
+
const prototype = Object.getPrototypeOf(this);
|
|
413
|
+
if (prototype === null) throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`);
|
|
414
|
+
mark(prototype, method, invocation, mode, exportName);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
function mark(prototype, method, invocation, mode, exportName) {
|
|
418
|
+
const descriptor = readRemoteMethodDescriptor(prototype);
|
|
419
|
+
const marker = Object.freeze({
|
|
420
|
+
method,
|
|
421
|
+
...exportName === void 0 || exportName === method ? {} : { exportName },
|
|
422
|
+
...mode === void 0 ? {} : { mode },
|
|
423
|
+
invocation: Object.freeze(invocation)
|
|
424
|
+
});
|
|
425
|
+
const current = descriptor?.methods.find((candidate) => candidate.method === method);
|
|
426
|
+
if (current !== void 0) {
|
|
427
|
+
if (current.exportName === marker.exportName && current.mode === marker.mode && sameInvocation(current.invocation, invocation)) return;
|
|
428
|
+
throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`);
|
|
429
|
+
}
|
|
430
|
+
Object.defineProperty(prototype, REMOTE_METHOD_DESCRIPTOR, {
|
|
431
|
+
configurable: true,
|
|
432
|
+
value: Object.freeze({
|
|
433
|
+
version: 1,
|
|
434
|
+
methods: Object.freeze([...descriptor?.methods ?? [], marker])
|
|
435
|
+
})
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function sameInvocation(left, right) {
|
|
439
|
+
if (left.kind === "direct") return right.kind === "direct";
|
|
440
|
+
if (right.kind === "direct") return false;
|
|
441
|
+
return left.context === right.context;
|
|
442
|
+
}
|
|
443
|
+
function validateName(subject, value) {
|
|
444
|
+
if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
|
|
445
|
+
}
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-value_1dc92f68acf2742e77b3d5d44dd1e11b/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
|
|
448
|
+
/** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
|
|
449
|
+
/**
|
|
450
|
+
* Mark an unreachable closed-union branch.
|
|
451
|
+
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
452
|
+
* @param context - optional switch-site label included in the failure message.
|
|
453
|
+
* @returns never; a runtime value that escaped its type always throws.
|
|
454
|
+
*/
|
|
455
|
+
function assertNever(value, context) {
|
|
456
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
457
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
458
|
+
}
|
|
459
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
460
|
+
function hasIntrinsicConstructor$1(prototype, name) {
|
|
461
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
462
|
+
if (typeof constructor !== "function") return false;
|
|
463
|
+
try {
|
|
464
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
465
|
+
} catch {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
470
|
+
function isIntrinsicObjectPrototype$1(value) {
|
|
471
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor$1(value, "Object");
|
|
472
|
+
}
|
|
473
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
474
|
+
function hasPlainArrayPrototype$1(value) {
|
|
475
|
+
const prototype = Object.getPrototypeOf(value);
|
|
476
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor$1(prototype, "Array")) return false;
|
|
477
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
478
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype$1(objectPrototype);
|
|
479
|
+
}
|
|
480
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
481
|
+
function hasPlainObjectPrototype(value) {
|
|
482
|
+
const prototype = Object.getPrototypeOf(value);
|
|
483
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype$1(prototype);
|
|
484
|
+
}
|
|
485
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
486
|
+
function enumerableStringKeys(value) {
|
|
487
|
+
const keys = Reflect.ownKeys(value);
|
|
488
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
489
|
+
return keys;
|
|
490
|
+
}
|
|
491
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
492
|
+
function walkJsonValue(value, detach) {
|
|
493
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
494
|
+
let root;
|
|
495
|
+
const assign = (destination, item) => {
|
|
496
|
+
if (destination === void 0) return;
|
|
497
|
+
if (destination.kind === "root") root = item;
|
|
498
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
499
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
500
|
+
value: item,
|
|
501
|
+
enumerable: true,
|
|
502
|
+
configurable: true,
|
|
503
|
+
writable: true
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
const tasks = [{
|
|
507
|
+
kind: "visit",
|
|
508
|
+
value,
|
|
509
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
510
|
+
}];
|
|
511
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
512
|
+
if (task.kind === "leave") {
|
|
513
|
+
ancestors.delete(task.source);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (task.kind === "array-item") {
|
|
517
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
518
|
+
tasks.push({
|
|
519
|
+
kind: "visit",
|
|
520
|
+
value: task.source[task.index],
|
|
521
|
+
...task.target === void 0 ? {} : { destination: {
|
|
522
|
+
kind: "array",
|
|
523
|
+
target: task.target,
|
|
524
|
+
index: task.index
|
|
525
|
+
} }
|
|
526
|
+
});
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (task.kind === "object-property") {
|
|
530
|
+
tasks.push({
|
|
531
|
+
kind: "visit",
|
|
532
|
+
value: task.source[task.key],
|
|
533
|
+
...task.target === void 0 ? {} : { destination: {
|
|
534
|
+
kind: "object",
|
|
535
|
+
target: task.target,
|
|
536
|
+
key: task.key
|
|
537
|
+
} }
|
|
538
|
+
});
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
const current = task.value;
|
|
542
|
+
if (current === null) {
|
|
543
|
+
assign(task.destination, null);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
547
|
+
assign(task.destination, current);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (typeof current === "number") {
|
|
551
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
552
|
+
assign(task.destination, current);
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (typeof current !== "object") return void 0;
|
|
556
|
+
if (ancestors.has(current)) return void 0;
|
|
557
|
+
if (Array.isArray(current)) {
|
|
558
|
+
if (!hasPlainArrayPrototype$1(current)) return void 0;
|
|
559
|
+
const length = current.length;
|
|
560
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
561
|
+
const target = detach ? [] : void 0;
|
|
562
|
+
if (target !== void 0) assign(task.destination, target);
|
|
563
|
+
ancestors.add(current);
|
|
564
|
+
tasks.push({
|
|
565
|
+
kind: "leave",
|
|
566
|
+
source: current
|
|
567
|
+
});
|
|
568
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
569
|
+
kind: "array-item",
|
|
570
|
+
source: current,
|
|
571
|
+
index,
|
|
572
|
+
...target === void 0 ? {} : { target }
|
|
573
|
+
});
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
577
|
+
const keys = enumerableStringKeys(current);
|
|
578
|
+
if (keys === void 0) return void 0;
|
|
579
|
+
const target = detach ? {} : void 0;
|
|
580
|
+
if (target !== void 0) assign(task.destination, target);
|
|
581
|
+
ancestors.add(current);
|
|
582
|
+
tasks.push({
|
|
583
|
+
kind: "leave",
|
|
584
|
+
source: current
|
|
585
|
+
});
|
|
586
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
587
|
+
const key = keys[index];
|
|
588
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
589
|
+
if (key === void 0) return void 0;
|
|
590
|
+
tasks.push({
|
|
591
|
+
kind: "object-property",
|
|
592
|
+
source: current,
|
|
593
|
+
key,
|
|
594
|
+
...target === void 0 ? {} : { target }
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return detach ? root : true;
|
|
1235
599
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
const methods = Reflect.get(descriptor, "methods");
|
|
1244
|
-
if (!Array.isArray(methods)) throw new TypeError("typert-protocol: Remote method descriptor methods must be an array");
|
|
1245
|
-
return descriptor;
|
|
600
|
+
/**
|
|
601
|
+
* Validate and detach lossless JSON in one read per property.
|
|
602
|
+
* @param value - candidate value to validate and detach.
|
|
603
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
604
|
+
*/
|
|
605
|
+
function snapshotJsonValue(value) {
|
|
606
|
+
return walkJsonValue(value, true);
|
|
1246
607
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
});
|
|
608
|
+
/**
|
|
609
|
+
* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
|
|
610
|
+
* @param value - candidate value to test.
|
|
611
|
+
* @returns whether the value survives a JSON round trip without loss.
|
|
612
|
+
*/
|
|
613
|
+
function isJsonValue(value) {
|
|
614
|
+
return walkJsonValue(value, false) === true;
|
|
1255
615
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
616
|
+
/**
|
|
617
|
+
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
618
|
+
* @param value - value to freeze.
|
|
619
|
+
* @returns the same value after every reachable enumerable child is frozen.
|
|
620
|
+
*/
|
|
621
|
+
function deepFreeze(value) {
|
|
622
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
623
|
+
const pending = [{
|
|
624
|
+
kind: "visit",
|
|
625
|
+
node: value
|
|
626
|
+
}];
|
|
627
|
+
while (pending.length > 0) {
|
|
628
|
+
const task = pending.pop();
|
|
629
|
+
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
630
|
+
if (task === void 0) continue;
|
|
631
|
+
if (task.kind === "property") {
|
|
632
|
+
pending.push({
|
|
633
|
+
kind: "visit",
|
|
634
|
+
node: task.source[task.key]
|
|
635
|
+
});
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const node = task.node;
|
|
639
|
+
if (node === null || typeof node !== "object") continue;
|
|
640
|
+
if (node instanceof AbortSignal) continue;
|
|
641
|
+
if (seen.has(node)) continue;
|
|
642
|
+
seen.add(node);
|
|
643
|
+
Object.freeze(node);
|
|
644
|
+
const keys = Object.keys(node);
|
|
645
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
646
|
+
const key = keys[index];
|
|
647
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
648
|
+
if (key === void 0) continue;
|
|
649
|
+
pending.push({
|
|
650
|
+
kind: "property",
|
|
651
|
+
source: node,
|
|
652
|
+
key
|
|
653
|
+
});
|
|
654
|
+
}
|
|
1268
655
|
}
|
|
1269
|
-
|
|
1270
|
-
configurable: true,
|
|
1271
|
-
value: Object.freeze({
|
|
1272
|
-
version: 1,
|
|
1273
|
-
methods: Object.freeze([...descriptor?.methods ?? [], marker])
|
|
1274
|
-
})
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
function sameInvocation(left, right) {
|
|
1278
|
-
if (left.kind === "direct") return right.kind === "direct";
|
|
1279
|
-
if (right.kind === "direct") return false;
|
|
1280
|
-
return left.context === right.context;
|
|
1281
|
-
}
|
|
1282
|
-
function validateName(subject, value) {
|
|
1283
|
-
if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
|
|
656
|
+
return value;
|
|
1284
657
|
}
|
|
1285
658
|
//#endregion
|
|
1286
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-
|
|
659
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypt_fa1ca4579d1c8036d3a90fef6a53e902/node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
|
|
1287
660
|
/**
|
|
1288
661
|
* Random v4 UUID, minted from `crypto.getRandomValues`.
|
|
1289
662
|
* @returns the UUID string.
|
|
@@ -1296,7 +669,7 @@ function randomUUID$1() {
|
|
|
1296
669
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1297
670
|
}
|
|
1298
671
|
//#endregion
|
|
1299
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.
|
|
672
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.7-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-brand/lib/index.js
|
|
1300
673
|
/**
|
|
1301
674
|
* Duplicate-install-safe nominal primitive helpers.
|
|
1302
675
|
*
|
|
@@ -1319,11 +692,11 @@ function brandString(value) {
|
|
|
1319
692
|
return value;
|
|
1320
693
|
}
|
|
1321
694
|
//#endregion
|
|
1322
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0.
|
|
695
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0._c2aac02a97d68b3ee31a2a9dae07960b/node_modules/@deepseek-ai/dsh-timeout/lib/index.js
|
|
1323
696
|
/** Largest delay Node schedules without clamping it to one millisecond. */
|
|
1324
697
|
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
1325
698
|
//#endregion
|
|
1326
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.
|
|
699
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.7-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-llm/lib/index.js
|
|
1327
700
|
/**
|
|
1328
701
|
* Detach and deep-freeze a message whose identity already exists.
|
|
1329
702
|
* @param message - complete message, including its stable identity.
|
|
@@ -1338,10 +711,10 @@ function freezeMessage(message) {
|
|
|
1338
711
|
* @returns an immutable message with a fresh stable identity.
|
|
1339
712
|
*/
|
|
1340
713
|
function createMessage(input) {
|
|
1341
|
-
return
|
|
714
|
+
return deepFreeze(structuredClone({
|
|
1342
715
|
...input,
|
|
1343
716
|
id: brandString(randomUUID$1())
|
|
1344
|
-
});
|
|
717
|
+
}));
|
|
1345
718
|
}
|
|
1346
719
|
/**
|
|
1347
720
|
* Create one identified user-role message and freeze it before publication.
|
|
@@ -1565,13 +938,15 @@ function failureSnapshot(value) {
|
|
|
1565
938
|
const status = candidate.status;
|
|
1566
939
|
const providerRetryAfterMs = candidate.providerRetryAfterMs;
|
|
1567
940
|
const requestId = candidate.requestId;
|
|
1568
|
-
|
|
941
|
+
const offloadImages = candidate.offloadImages;
|
|
942
|
+
if (typeof message !== "string" || message.length === 0 || typeof code !== "string" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== "string" || requestId.length === 0) || offloadImages !== void 0 && (!Number.isSafeInteger(offloadImages) || offloadImages <= 0)) return void 0;
|
|
1569
943
|
return Object.freeze({
|
|
1570
944
|
message,
|
|
1571
945
|
code,
|
|
1572
946
|
...status === void 0 ? {} : { status },
|
|
1573
947
|
...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },
|
|
1574
|
-
...requestId === void 0 ? {} : { requestId }
|
|
948
|
+
...requestId === void 0 ? {} : { requestId },
|
|
949
|
+
...offloadImages === void 0 ? {} : { offloadImages }
|
|
1575
950
|
});
|
|
1576
951
|
} catch (_sdkFailureGetter) {
|
|
1577
952
|
return;
|
|
@@ -1601,25 +976,23 @@ function textOnlyImageText(ref) {
|
|
|
1601
976
|
return `[image omitted because this model accepts text only; attachment sha256:${String(ref.attachmentId).slice(7, 15)}]`;
|
|
1602
977
|
}
|
|
1603
978
|
/**
|
|
1604
|
-
* True when typed model content contains an image block
|
|
1605
|
-
*
|
|
1606
|
-
*
|
|
1607
|
-
* survey), so a consumer cannot silently diverge on nesting depth.
|
|
979
|
+
* True when typed model content contains an image block. This is the one image
|
|
980
|
+
* walk shared by every image policy (capability gating, text-only
|
|
981
|
+
* serialization, compaction survey), so a consumer cannot silently diverge.
|
|
1608
982
|
* @param content - typed model content blocks.
|
|
1609
|
-
* @returns whether any
|
|
983
|
+
* @returns whether any block is an image.
|
|
1610
984
|
*/
|
|
1611
985
|
function contentHasImage(content) {
|
|
1612
|
-
return content.some((block) => block.type === "image"
|
|
986
|
+
return content.some((block) => block.type === "image");
|
|
1613
987
|
}
|
|
1614
988
|
/**
|
|
1615
|
-
* True when typed model content contains a file block
|
|
1616
|
-
* tool-result content on the same recursion every file policy shares.
|
|
989
|
+
* True when typed model content contains a file block.
|
|
1617
990
|
* Reads current content on every call without retaining scan results.
|
|
1618
991
|
* @param content - typed model content blocks.
|
|
1619
|
-
* @returns whether any
|
|
992
|
+
* @returns whether any block is a file.
|
|
1620
993
|
*/
|
|
1621
994
|
function contentHasFile(content) {
|
|
1622
|
-
for (const block of content) if (block.type === "file"
|
|
995
|
+
for (const block of content) if (block.type === "file") return true;
|
|
1623
996
|
return false;
|
|
1624
997
|
}
|
|
1625
998
|
/**
|
|
@@ -1636,7 +1009,7 @@ function fileHandleText(ref, readonlyPath) {
|
|
|
1636
1009
|
if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
|
|
1637
1010
|
return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
|
|
1638
1011
|
}
|
|
1639
|
-
/** Replace every file occurrence
|
|
1012
|
+
/** Replace every file occurrence with handle text. */
|
|
1640
1013
|
function replaceFilesWithHandles(blocks, resolvePath) {
|
|
1641
1014
|
let next;
|
|
1642
1015
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -1648,29 +1021,10 @@ function replaceFilesWithHandles(blocks, resolvePath) {
|
|
|
1648
1021
|
});
|
|
1649
1022
|
continue;
|
|
1650
1023
|
}
|
|
1651
|
-
if (block.type === "tool-result") {
|
|
1652
|
-
const content = replaceFilesWithHandles(block.content, resolvePath);
|
|
1653
|
-
if (content !== block.content) {
|
|
1654
|
-
next ??= blocks.slice(0, index);
|
|
1655
|
-
next.push({
|
|
1656
|
-
...block,
|
|
1657
|
-
content
|
|
1658
|
-
});
|
|
1659
|
-
continue;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
1024
|
next?.push(block);
|
|
1663
1025
|
}
|
|
1664
1026
|
return next ?? blocks;
|
|
1665
1027
|
}
|
|
1666
|
-
/**
|
|
1667
|
-
* Project durable file history into deterministic handle text for every model
|
|
1668
|
-
* route. Unlike images, no provider receives file blocks natively, so this
|
|
1669
|
-
* projection is unconditional in request assembly.
|
|
1670
|
-
* @param messages - complete request history.
|
|
1671
|
-
* @param resolvePath - resolve one reference's current execution-world read path.
|
|
1672
|
-
* @returns the original list without files, otherwise shallow message copies with handle text.
|
|
1673
|
-
*/
|
|
1674
1028
|
function projectFilesToText(messages, resolvePath) {
|
|
1675
1029
|
if (!messages.some((message) => contentHasFile(message.content))) return messages;
|
|
1676
1030
|
return messages.map((message) => {
|
|
@@ -1681,7 +1035,7 @@ function projectFilesToText(messages, resolvePath) {
|
|
|
1681
1035
|
};
|
|
1682
1036
|
});
|
|
1683
1037
|
}
|
|
1684
|
-
/** Replace every image occurrence
|
|
1038
|
+
/** Replace every image occurrence for a text-only model. */
|
|
1685
1039
|
function replaceImagesForTextModel(blocks) {
|
|
1686
1040
|
let next;
|
|
1687
1041
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -1693,26 +1047,10 @@ function replaceImagesForTextModel(blocks) {
|
|
|
1693
1047
|
});
|
|
1694
1048
|
continue;
|
|
1695
1049
|
}
|
|
1696
|
-
if (block.type === "tool-result") {
|
|
1697
|
-
const content = replaceImagesForTextModel(block.content);
|
|
1698
|
-
if (content !== block.content) {
|
|
1699
|
-
next ??= blocks.slice(0, index);
|
|
1700
|
-
next.push({
|
|
1701
|
-
...block,
|
|
1702
|
-
content
|
|
1703
|
-
});
|
|
1704
|
-
continue;
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
1050
|
next?.push(block);
|
|
1708
1051
|
}
|
|
1709
1052
|
return next ?? blocks;
|
|
1710
1053
|
}
|
|
1711
|
-
/**
|
|
1712
|
-
* Project durable image history into deterministic text for an exact text-only model.
|
|
1713
|
-
* @param messages - complete request history.
|
|
1714
|
-
* @returns the original list without images, otherwise shallow message copies with stable placeholders.
|
|
1715
|
-
*/
|
|
1716
1054
|
function projectImagesForTextModel(messages) {
|
|
1717
1055
|
if (!messages.some((message) => contentHasImage(message.content))) return messages;
|
|
1718
1056
|
return messages.map((message) => {
|
|
@@ -1723,6 +1061,89 @@ function projectImagesForTextModel(messages) {
|
|
|
1723
1061
|
};
|
|
1724
1062
|
});
|
|
1725
1063
|
}
|
|
1064
|
+
function withoutDeveloperMessages(messages) {
|
|
1065
|
+
const retained = messages.filter((message) => message.role !== "developer");
|
|
1066
|
+
return retained.length === messages.length ? messages : retained;
|
|
1067
|
+
}
|
|
1068
|
+
function toolDeclarations(tools, mode, history) {
|
|
1069
|
+
const declarations = new Map(history.tools.map((tool) => [tool.name, tool]));
|
|
1070
|
+
for (const update of history.updates) for (const tool of update.additions) if (!declarations.has(tool.name)) declarations.set(tool.name, {
|
|
1071
|
+
...tool,
|
|
1072
|
+
deferLoading: true
|
|
1073
|
+
});
|
|
1074
|
+
switch (mode) {
|
|
1075
|
+
case "in-history": return declarations;
|
|
1076
|
+
case "addition-only": {
|
|
1077
|
+
const activeNames = new Set(tools?.map((tool) => tool.name));
|
|
1078
|
+
for (const name of declarations.keys()) if (!activeNames.has(name)) declarations.delete(name);
|
|
1079
|
+
return declarations;
|
|
1080
|
+
}
|
|
1081
|
+
/* v8 ignore next 2 -- closed-union exhaustiveness guard */
|
|
1082
|
+
default: return assertNever(mode);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Construct provider declarations from session-folded history without changing logged active tools.
|
|
1087
|
+
* Unsupported routes and incomplete history use current declarations without developer updates.
|
|
1088
|
+
* Explicitly deferred baseline tools become available only after their first retained addition.
|
|
1089
|
+
* @param messages - complete request inputs, or the prefix selected for an auxiliary call.
|
|
1090
|
+
* @param tools - currently active tool schemas.
|
|
1091
|
+
* @param toolUpdate - the resolved route's update mode.
|
|
1092
|
+
* @param history - immutable state folded from committed headers and developer messages.
|
|
1093
|
+
* @returns provider declarations and the corresponding filtered history.
|
|
1094
|
+
*/
|
|
1095
|
+
function projectToolUpdates(messages, tools, toolUpdate, history) {
|
|
1096
|
+
if (toolUpdate === void 0) {
|
|
1097
|
+
let immediateTools = tools;
|
|
1098
|
+
if (tools?.some((tool) => tool.deferLoading === true)) immediateTools = tools.map(({ deferLoading: _loading, ...tool }) => tool);
|
|
1099
|
+
return {
|
|
1100
|
+
messages: withoutDeveloperMessages(messages),
|
|
1101
|
+
tools: immediateTools
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
if (history === void 0) return {
|
|
1105
|
+
messages: withoutDeveloperMessages(messages),
|
|
1106
|
+
tools
|
|
1107
|
+
};
|
|
1108
|
+
const messageIds = new Set(messages.flatMap((message) => message.role === "developer" ? [message.id] : []));
|
|
1109
|
+
if (history.updates.some((update) => !messageIds.has(update.messageId))) return {
|
|
1110
|
+
messages: withoutDeveloperMessages(messages),
|
|
1111
|
+
tools
|
|
1112
|
+
};
|
|
1113
|
+
const declarations = toolDeclarations(tools, toolUpdate, history);
|
|
1114
|
+
const updateIds = new Set(history.updates.map((update) => update.messageId));
|
|
1115
|
+
const offered = new Set(history.tools.filter((tool) => !tool.deferLoading).map((tool) => tool.name));
|
|
1116
|
+
const projectedMessages = [];
|
|
1117
|
+
for (const message of messages) {
|
|
1118
|
+
if (message.role !== "developer") {
|
|
1119
|
+
projectedMessages.push(message);
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
if (!updateIds.has(message.id)) continue;
|
|
1123
|
+
const content = message.content.filter((block) => {
|
|
1124
|
+
switch (block.type) {
|
|
1125
|
+
case "tool-addition":
|
|
1126
|
+
if (!declarations.has(block.toolName) || offered.has(block.toolName)) return false;
|
|
1127
|
+
offered.add(block.toolName);
|
|
1128
|
+
return true;
|
|
1129
|
+
case "tool-removal":
|
|
1130
|
+
if (toolUpdate !== "in-history") return false;
|
|
1131
|
+
return offered.delete(block.toolName);
|
|
1132
|
+
default: return true;
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
if (content.length === 0) continue;
|
|
1136
|
+
if (content.length === message.content.length) projectedMessages.push(message);
|
|
1137
|
+
else projectedMessages.push({
|
|
1138
|
+
...message,
|
|
1139
|
+
content
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
messages: projectedMessages.length === messages.length && projectedMessages.every((message, index) => message === messages[index]) ? messages : projectedMessages,
|
|
1144
|
+
tools: [...declarations.values()]
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1726
1147
|
/**
|
|
1727
1148
|
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
|
|
1728
1149
|
* adapters from drifting. See
|
|
@@ -1802,7 +1223,8 @@ var LlmError = class extends HarnessError {
|
|
|
1802
1223
|
code,
|
|
1803
1224
|
...options?.status === void 0 ? {} : { status: options.status },
|
|
1804
1225
|
...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },
|
|
1805
|
-
...options?.requestId === void 0 ? {} : { requestId: options.requestId }
|
|
1226
|
+
...options?.requestId === void 0 ? {} : { requestId: options.requestId },
|
|
1227
|
+
...options?.offloadImages === void 0 ? {} : { offloadImages: options.offloadImages }
|
|
1806
1228
|
});
|
|
1807
1229
|
}
|
|
1808
1230
|
};
|
|
@@ -2070,7 +1492,8 @@ var LlmError = class extends HarnessError {
|
|
|
2070
1492
|
id: model.id,
|
|
2071
1493
|
...model.name === void 0 ? {} : { name: model.name },
|
|
2072
1494
|
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
2073
|
-
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
1495
|
+
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
|
|
1496
|
+
...model.inputModalities === void 0 ? {} : { inputModalities: [...model.inputModalities] }
|
|
2074
1497
|
});
|
|
2075
1498
|
}
|
|
2076
1499
|
return models;
|
|
@@ -2128,7 +1551,8 @@ var LlmError = class extends HarnessError {
|
|
|
2128
1551
|
}
|
|
2129
1552
|
/**
|
|
2130
1553
|
* Discover models advertised by one registered provider. Catalog membership
|
|
2131
|
-
*
|
|
1554
|
+
* does not constrain core routing. Catalog-driven entry points may restrict
|
|
1555
|
+
* selection and submission to the advertised models.
|
|
2132
1556
|
* @param provider - registered provider route to inspect.
|
|
2133
1557
|
* @returns detached model metadata in adapter-preferred order.
|
|
2134
1558
|
*/
|
|
@@ -2173,6 +1597,8 @@ var LlmError = class extends HarnessError {
|
|
|
2173
1597
|
const inputModalities = this.detachedModalities(resolved.inputModalities);
|
|
2174
1598
|
const systemPromptUpdate = resolved.systemPromptUpdate;
|
|
2175
1599
|
if (systemPromptUpdate !== void 0 && systemPromptUpdate !== "in-history") throw new LlmError(`adapter returned invalid system prompt update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
|
|
1600
|
+
const toolUpdate = resolved.toolUpdate;
|
|
1601
|
+
if (toolUpdate !== void 0 && toolUpdate !== "in-history" && toolUpdate !== "addition-only") throw new LlmError(`adapter returned invalid tool update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
|
|
2176
1602
|
const defaultMaxTokens = resolved.defaultMaxTokens;
|
|
2177
1603
|
if (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider "${provider}" model "${model}"`, "INVALID_MODEL_MAX_TOKENS");
|
|
2178
1604
|
const info = {
|
|
@@ -2183,7 +1609,8 @@ var LlmError = class extends HarnessError {
|
|
|
2183
1609
|
...inputModalities === void 0 ? {} : { inputModalities },
|
|
2184
1610
|
...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
|
|
2185
1611
|
...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens },
|
|
2186
|
-
...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate }
|
|
1612
|
+
...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate },
|
|
1613
|
+
...resolved.toolUpdate === void 0 ? {} : { toolUpdate: resolved.toolUpdate }
|
|
2187
1614
|
};
|
|
2188
1615
|
const reasoning = resolved.reasoning;
|
|
2189
1616
|
if (reasoning === void 0) return info;
|
|
@@ -2278,6 +1705,7 @@ var LlmError = class extends HarnessError {
|
|
|
2278
1705
|
...context === void 0 ? {} : { context },
|
|
2279
1706
|
...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
|
|
2280
1707
|
...modelInfo.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: modelInfo.systemPromptUpdate },
|
|
1708
|
+
...modelInfo.toolUpdate === void 0 ? {} : { toolUpdate: modelInfo.toolUpdate },
|
|
2281
1709
|
stream: (options) => {
|
|
2282
1710
|
if (dispatched) throw new LlmError("a prepared LLM call can only be dispatched once", "INVALID_PREPARED_CALL");
|
|
2283
1711
|
if (!callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
@@ -2299,8 +1727,9 @@ var LlmError = class extends HarnessError {
|
|
|
2299
1727
|
/** Remove replay state whose historical route is owned by another adapter. */
|
|
2300
1728
|
forAdapter(options, adapter) {
|
|
2301
1729
|
const messages = options.messages.map((message) => {
|
|
1730
|
+
if (message.role !== "assistant") return message;
|
|
2302
1731
|
const source = message.source;
|
|
2303
|
-
if (
|
|
1732
|
+
if (source.replayState === void 0) return message;
|
|
2304
1733
|
if (this.adapters.get(source.provider)?.adapter === adapter) return message;
|
|
2305
1734
|
return freezeMessage({
|
|
2306
1735
|
...message,
|
|
@@ -2366,13 +1795,17 @@ var LlmError = class extends HarnessError {
|
|
|
2366
1795
|
let projectedMessages = resolvedOptions.messages;
|
|
2367
1796
|
if (projectedMessages.some((message) => contentHasFile(message.content))) projectedMessages = projectFilesToText(projectedMessages, (ref) => this.fileReadPath(ref));
|
|
2368
1797
|
if (modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && projectedMessages.some((message) => contentHasImage(message.content))) projectedMessages = projectImagesForTextModel(projectedMessages);
|
|
2369
|
-
const
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
1798
|
+
const projectedTools = projectToolUpdates(projectedMessages, resolvedOptions.tools, modelInfo.toolUpdate, resolvedOptions.toolHistory);
|
|
1799
|
+
projectedMessages = projectedTools.messages;
|
|
1800
|
+
let projectedOptions = resolvedOptions;
|
|
1801
|
+
if (projectedMessages !== resolvedOptions.messages || projectedTools.tools !== resolvedOptions.tools) {
|
|
1802
|
+
projectedOptions = {
|
|
1803
|
+
...resolvedOptions,
|
|
1804
|
+
messages: projectedMessages,
|
|
1805
|
+
...projectedTools.tools === void 0 ? {} : { tools: projectedTools.tools }
|
|
1806
|
+
};
|
|
1807
|
+
if (Object.isFrozen(resolvedOptions)) deepFreeze(projectedOptions);
|
|
1808
|
+
}
|
|
2376
1809
|
iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
|
|
2377
1810
|
} catch (error) {
|
|
2378
1811
|
yield adapterFailureChunk(error, options.signal);
|
|
@@ -2440,7 +1873,93 @@ function adapterFailureChunk(error, signal) {
|
|
|
2440
1873
|
};
|
|
2441
1874
|
}
|
|
2442
1875
|
//#endregion
|
|
2443
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-
|
|
1876
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-sandbox@0._a8c9906be8c926809212c5fbb3bc7c61/node_modules/@deepseek-ai/dsh-sandbox/lib/index.js
|
|
1877
|
+
/**
|
|
1878
|
+
* The escalation vocabulary and choreography shared by every sandbox-enforcing
|
|
1879
|
+
* tool family (`@deepseek-ai/dsh-tool-bash`, `@deepseek-ai/dsh-tool-fs`): the
|
|
1880
|
+
* strictly-wider ladder, the argument-pairing validation, the model-facing
|
|
1881
|
+
* denial/hint markers, and {@link approveEscalation} — the ordered fail-closed
|
|
1882
|
+
* sequence that resolves a `sandbox_permissions` request through a
|
|
1883
|
+
* user-approval channel BEFORE anything executes. One home keeps the two
|
|
1884
|
+
* families' approval ordering and verbatim error texts from drifting apart.
|
|
1885
|
+
*
|
|
1886
|
+
* The channel is a minimal STRUCTURAL function shape ({@link EscalationAsk}),
|
|
1887
|
+
* not the approval service type: the tool layer — which owns the agent, the
|
|
1888
|
+
* call id, and the tool name — closes over `ctx.approval.request(...)` and
|
|
1889
|
+
* hands the closure down, so this package never depends on the approval or
|
|
1890
|
+
* agent packages.
|
|
1891
|
+
*
|
|
1892
|
+
* @module dsh-sandbox/escalation
|
|
1893
|
+
*/
|
|
1894
|
+
/**
|
|
1895
|
+
* The strictly-wider table: what a call whose effective mode is the key may
|
|
1896
|
+
* escalate TO. Checked at EXECUTION, never baked into a tool schema — the
|
|
1897
|
+
* schema's enum is {@link ESCALATION_TARGETS}, because schemas are
|
|
1898
|
+
* registry-global while the effective mode is per-call truth.
|
|
1899
|
+
*/
|
|
1900
|
+
const WIDER_MODES = {
|
|
1901
|
+
"read-only": ["workspace-write", "danger-full-access"],
|
|
1902
|
+
"workspace-write": ["danger-full-access"]
|
|
1903
|
+
};
|
|
1904
|
+
/**
|
|
1905
|
+
* The closed escalation-target vocabulary — every mode a call could ever
|
|
1906
|
+
* escalate TO (`read-only` is the floor; nothing escalates to it). Advertised
|
|
1907
|
+
* whenever the mounted capability confines: cutting the enum down to the modes
|
|
1908
|
+
* wider than the composition's DEFAULT would strand a session whose effective
|
|
1909
|
+
* mode sits below it (a `danger-full-access` default would advertise nothing
|
|
1910
|
+
* while a narrower-switched session stays confined with no lever).
|
|
1911
|
+
*/
|
|
1912
|
+
const ESCALATION_TARGETS = ["workspace-write", "danger-full-access"];
|
|
1913
|
+
/**
|
|
1914
|
+
* Validate the escalation argument pairing a tool schema cannot express:
|
|
1915
|
+
* `sandbox_permissions` and `justification` travel together — an approval
|
|
1916
|
+
* prompt without a reason, or a reason driving nothing, is a malformed ask —
|
|
1917
|
+
* and the justification must be a non-empty sentence.
|
|
1918
|
+
* @param sandboxPermissions - the raw `sandbox_permissions` argument, if given.
|
|
1919
|
+
* @param justification - the raw `justification` argument, if given.
|
|
1920
|
+
*/
|
|
1921
|
+
function validateEscalationArgs(sandboxPermissions, justification) {
|
|
1922
|
+
if (sandboxPermissions !== void 0 && justification === void 0) throw new Error("invalid escalation: sandbox_permissions requires a justification");
|
|
1923
|
+
if (justification !== void 0 && sandboxPermissions === void 0) throw new Error("invalid escalation: justification is only valid together with sandbox_permissions");
|
|
1924
|
+
if (justification !== void 0 && justification.trim().length === 0) throw new Error("invalid justification: expected a non-empty sentence");
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Resolve a sandbox permission request before execution. Repeating the call's
|
|
1928
|
+
* effective mode returns it without approval. A strictly wider mode requires
|
|
1929
|
+
* approval and applies only to this call. Narrower or unsupported targets,
|
|
1930
|
+
* missing approval services or agents for widening, and non-grant outcomes
|
|
1931
|
+
* throw before execution.
|
|
1932
|
+
* @param request - the escalation to judge (see {@link EscalationRequest}).
|
|
1933
|
+
* @param approval - the approval ingredients the tool holds (see {@link EscalationApproval}).
|
|
1934
|
+
* @returns the granted mode, consumed by the one call that asked.
|
|
1935
|
+
*/
|
|
1936
|
+
async function approveEscalation(request, approval) {
|
|
1937
|
+
const { requestedMode: mode, effectiveMode, justification, subject } = request;
|
|
1938
|
+
if (mode === effectiveMode) return effectiveMode;
|
|
1939
|
+
if (!(WIDER_MODES[effectiveMode] ?? []).includes(mode)) throw new Error(`sandbox escalation to "${mode}" is not strictly wider than this call's current "${effectiveMode}" mode`);
|
|
1940
|
+
if (approval.approver === void 0) throw new Error(`sandbox escalation to "${mode}" requires approval, but no approval service is composed`);
|
|
1941
|
+
if (approval.agent === void 0) throw new Error(`sandbox escalation to "${mode}" requires approval, but the call has no agent to route it through`);
|
|
1942
|
+
const outcome = await approval.approver.request({
|
|
1943
|
+
agent: approval.agent,
|
|
1944
|
+
toolName: approval.toolName,
|
|
1945
|
+
callId: approval.callId,
|
|
1946
|
+
reason: `escalate sandbox to ${mode}: ${justification}`,
|
|
1947
|
+
displayReason: {
|
|
1948
|
+
en: `Allow this operation with ${mode} permissions: ${justification}`,
|
|
1949
|
+
zh: `允许本次操作使用 ${mode} 权限:${justification}`
|
|
1950
|
+
},
|
|
1951
|
+
...approval.signal ? { signal: approval.signal } : {}
|
|
1952
|
+
});
|
|
1953
|
+
switch (outcome) {
|
|
1954
|
+
case "allowed-once": return mode;
|
|
1955
|
+
case "rejected": throw new Error(`the user rejected escalating this ${subject} to "${mode}"; it stays denied, so stop and explain instead of working around it`);
|
|
1956
|
+
case "cancelled": throw new Error(`approval for escalating to "${mode}" was cancelled`);
|
|
1957
|
+
case "unavailable": throw new Error(`sandbox escalation to "${mode}" requires approval, but no approval channel is available`);
|
|
1958
|
+
default: return assertNever(outcome, "EscalationOutcome");
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
//#endregion
|
|
1962
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1._97968258e5effb0cd09174fd979b65a8/node_modules/@deepseek-ai/dsh-tools/lib/index.js
|
|
2444
1963
|
/**
|
|
2445
1964
|
* Enforced JSON Schema subset shared by tool outputs, generated PTC mode
|
|
2446
1965
|
* types, subagents, and workflows. The subset accepts any JSON root, an
|
|
@@ -3248,6 +2767,7 @@ var ToolArgsError = class extends HarnessError {
|
|
|
3248
2767
|
function defineTool(options) {
|
|
3249
2768
|
const userExecute = options.execute;
|
|
3250
2769
|
const userFinalizeContent = options.finalizeContent;
|
|
2770
|
+
const userProjectContent = options.projectContent;
|
|
3251
2771
|
const userRender = options.output.render;
|
|
3252
2772
|
const userPresentationMeta = options.output.presentationMeta;
|
|
3253
2773
|
const userPresentCall = options.presentCall;
|
|
@@ -3270,6 +2790,7 @@ function defineTool(options) {
|
|
|
3270
2790
|
return userPresentationMeta(args, value);
|
|
3271
2791
|
} } : {}
|
|
3272
2792
|
},
|
|
2793
|
+
...options.deferLoading === true ? { deferLoading: options.deferLoading } : {},
|
|
3273
2794
|
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
3274
2795
|
async execute(args, exec) {
|
|
3275
2796
|
const violations = validate(args);
|
|
@@ -3277,6 +2798,7 @@ function defineTool(options) {
|
|
|
3277
2798
|
return userExecute(args, exec);
|
|
3278
2799
|
}
|
|
3279
2800
|
};
|
|
2801
|
+
if (userProjectContent) tool.projectContent = (exec, result) => userProjectContent(exec, result);
|
|
3280
2802
|
if (userFinalizeContent) tool.finalizeContent = (exec, result) => userFinalizeContent(exec, result);
|
|
3281
2803
|
if (userPresentCall) tool.presentCall = (args) => {
|
|
3282
2804
|
if (validate(args).length > 0) return void 0;
|
|
@@ -3311,7 +2833,7 @@ const TYPESCRIPT_FLAVOR = {
|
|
|
3311
2833
|
description: "Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run.",
|
|
3312
2834
|
codeDescription: "The program: the body of an async TypeScript function."
|
|
3313
2835
|
};
|
|
3314
|
-
/** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link
|
|
2836
|
+
/** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link PtcSdkLanguage}. */
|
|
3315
2837
|
const RUN_CODE_FLAVORS = {
|
|
3316
2838
|
typescript: TYPESCRIPT_FLAVOR,
|
|
3317
2839
|
python: {
|
|
@@ -3326,17 +2848,48 @@ const RUN_CODE_FLAVORS = {
|
|
|
3326
2848
|
* can never drift.
|
|
3327
2849
|
*/
|
|
3328
2850
|
const RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION = "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\".";
|
|
2851
|
+
const RUN_CODE_CONTROLS = {
|
|
2852
|
+
timeoutMs: {
|
|
2853
|
+
type: "number",
|
|
2854
|
+
description: "Positive elapsed-time budget in milliseconds, capped by the deployment maximum."
|
|
2855
|
+
},
|
|
2856
|
+
sandbox_permissions: {
|
|
2857
|
+
type: "string",
|
|
2858
|
+
enum: [...ESCALATION_TARGETS],
|
|
2859
|
+
description: "Wider sandbox mode for this complete program execution; requires justification and approval."
|
|
2860
|
+
},
|
|
2861
|
+
justification: {
|
|
2862
|
+
type: "string",
|
|
2863
|
+
description: "Reason this complete program needs wider access, shown to the user for approval. Use the language of the user’s current request."
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2866
|
+
function controlParameters(runtime) {
|
|
2867
|
+
if (runtime === void 0) return RUN_CODE_CONTROLS;
|
|
2868
|
+
return {
|
|
2869
|
+
...runtime.timeout === void 0 ? {} : { timeoutMs: {
|
|
2870
|
+
...RUN_CODE_CONTROLS.timeoutMs,
|
|
2871
|
+
description: `Positive elapsed-time budget in milliseconds, including nested tool and approval waits. Default ${runtime.timeout.defaultMs}; capped at ${runtime.timeout.maxMs}. Zero does not disable the deadline.`
|
|
2872
|
+
} },
|
|
2873
|
+
...runtime.sandboxMode === void 0 ? {} : {
|
|
2874
|
+
sandbox_permissions: RUN_CODE_CONTROLS.sandbox_permissions,
|
|
2875
|
+
justification: RUN_CODE_CONTROLS.justification
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
function escalationGuidance(runtime) {
|
|
2880
|
+
return runtime?.sandboxMode === void 0 ? "" : " A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.";
|
|
2881
|
+
}
|
|
3329
2882
|
/**
|
|
3330
2883
|
* Resolve the {@link RunCodeFlavor} for the loaded runtime's language, read at
|
|
3331
2884
|
* schema-emission time so the model-visible `run_code` schema always matches
|
|
3332
2885
|
* the SDK section's language. `peekRuntime` returns `undefined` only when no
|
|
3333
2886
|
* runtime is mounted, which reaches this function through definition readers
|
|
3334
2887
|
* and `schemas()` — the doc-catalog harvest is the only shipped one, and none
|
|
3335
|
-
* of them feeds a model, because `wireSchemas` calls `
|
|
2888
|
+
* of them feeds a model, because `wireSchemas` calls `requirePtcRuntime`
|
|
3336
2889
|
* before projecting — so that path degrades to {@link TYPESCRIPT_FLAVOR}. A
|
|
3337
2890
|
* mounted runtime whose language has no flavor entry fails loud, exactly as
|
|
3338
|
-
* `
|
|
3339
|
-
* `SDK_RENDERERS` is the compiler's job ({@link
|
|
2891
|
+
* `requirePtcRuntime` rejects it at assembly. Keeping this table in step with
|
|
2892
|
+
* `SDK_RENDERERS` is the compiler's job ({@link PtcSdkLanguage}); what this
|
|
3340
2893
|
* guard owns is the runtime-supplied language neither table knows, which never
|
|
3341
2894
|
* yields a wrong-language schema for a real runtime.
|
|
3342
2895
|
*/
|
|
@@ -3505,7 +3058,8 @@ function createRunCodeTool(registry, options) {
|
|
|
3505
3058
|
type: "string",
|
|
3506
3059
|
required: true,
|
|
3507
3060
|
description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION
|
|
3508
|
-
}
|
|
3061
|
+
},
|
|
3062
|
+
...RUN_CODE_CONTROLS
|
|
3509
3063
|
},
|
|
3510
3064
|
output: {
|
|
3511
3065
|
schema: {
|
|
@@ -3517,12 +3071,37 @@ function createRunCodeTool(registry, options) {
|
|
|
3517
3071
|
required: true,
|
|
3518
3072
|
items: { type: "string" }
|
|
3519
3073
|
},
|
|
3520
|
-
result: { type: "json" }
|
|
3074
|
+
result: { type: "json" },
|
|
3075
|
+
sandbox: {
|
|
3076
|
+
type: "object",
|
|
3077
|
+
additionalProperties: false,
|
|
3078
|
+
properties: {
|
|
3079
|
+
mode: {
|
|
3080
|
+
type: "string",
|
|
3081
|
+
required: true,
|
|
3082
|
+
enum: [
|
|
3083
|
+
"read-only",
|
|
3084
|
+
"workspace-write",
|
|
3085
|
+
"danger-full-access"
|
|
3086
|
+
]
|
|
3087
|
+
},
|
|
3088
|
+
denied: {
|
|
3089
|
+
type: "boolean",
|
|
3090
|
+
required: true
|
|
3091
|
+
},
|
|
3092
|
+
enforcement: {
|
|
3093
|
+
type: "string",
|
|
3094
|
+
enum: ["full", "partial"]
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3521
3098
|
}
|
|
3522
3099
|
},
|
|
3523
3100
|
render: (_args, value) => {
|
|
3524
3101
|
const rendered = value.result === void 0 ? "" : renderValue(value.result);
|
|
3525
3102
|
const parts = [value.logs.join("\n"), rendered].filter((part) => part.length > 0);
|
|
3103
|
+
if (value.sandbox?.enforcement === "partial") parts.push("File sandbox enforcement is partial on this host.");
|
|
3104
|
+
if (value.sandbox?.denied) parts.push(`The ${value.sandbox.mode} file sandbox denied an operation.${escalationGuidance(peekRuntime())}`);
|
|
3526
3105
|
return [{
|
|
3527
3106
|
type: "text",
|
|
3528
3107
|
text: parts.length > 0 ? parts.join("\n") : "(run_code completed with no output)"
|
|
@@ -3532,6 +3111,31 @@ function createRunCodeTool(registry, options) {
|
|
|
3532
3111
|
async execute(args, exec) {
|
|
3533
3112
|
if (args.description.trim().length === 0) throw new Error("invalid description: expected a non-empty string");
|
|
3534
3113
|
const runtime = requireRuntime();
|
|
3114
|
+
validateEscalationArgs(args.sandbox_permissions, args.justification);
|
|
3115
|
+
if (args.timeoutMs !== void 0 && runtime.timeout === void 0) throw new Error("timeoutMs is not available for this PTC runtime");
|
|
3116
|
+
if (args.timeoutMs !== void 0 && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) throw new Error("invalid timeoutMs: expected a positive finite number");
|
|
3117
|
+
const standingPolicy = runtime.sandboxMode === void 0 ? void 0 : options.resolveSandboxPolicy(exec);
|
|
3118
|
+
let policy = standingPolicy;
|
|
3119
|
+
if (args.sandbox_permissions !== void 0 && args.justification !== void 0) {
|
|
3120
|
+
if (standingPolicy === void 0) throw new Error("sandbox_permissions is not available for this PTC runtime");
|
|
3121
|
+
const approvedMode = await approveEscalation({
|
|
3122
|
+
requestedMode: args.sandbox_permissions,
|
|
3123
|
+
justification: args.justification,
|
|
3124
|
+
effectiveMode: standingPolicy.mode,
|
|
3125
|
+
subject: "program"
|
|
3126
|
+
}, {
|
|
3127
|
+
approver: options.peekApprover(),
|
|
3128
|
+
agent: exec.agent,
|
|
3129
|
+
callId: exec.callId,
|
|
3130
|
+
toolName: RUN_CODE_NAME,
|
|
3131
|
+
signal: exec.signal
|
|
3132
|
+
});
|
|
3133
|
+
policy = {
|
|
3134
|
+
...standingPolicy,
|
|
3135
|
+
mode: approvedMode
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
exec.signal.throwIfAborted();
|
|
3535
3139
|
const runController = new AbortController();
|
|
3536
3140
|
const onOuterAbort = () => {
|
|
3537
3141
|
runController.abort(exec.signal.reason);
|
|
@@ -3613,7 +3217,8 @@ function createRunCodeTool(registry, options) {
|
|
|
3613
3217
|
while (logWork.size > 0) await Promise.allSettled([...logWork]);
|
|
3614
3218
|
};
|
|
3615
3219
|
const runOver = () => runController.signal.aborted;
|
|
3616
|
-
const binding = (
|
|
3220
|
+
const binding = (schema) => async (rawArgs) => {
|
|
3221
|
+
const { name } = schema;
|
|
3617
3222
|
if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
|
|
3618
3223
|
const normalized = jsonNormalizeArgs(rawArgs);
|
|
3619
3224
|
const n = ++dispatches;
|
|
@@ -3622,6 +3227,7 @@ function createRunCodeTool(registry, options) {
|
|
|
3622
3227
|
callId: subCallId,
|
|
3623
3228
|
rootCallId: exec.rootCallId,
|
|
3624
3229
|
name,
|
|
3230
|
+
schema,
|
|
3625
3231
|
arguments: normalized.dispatched,
|
|
3626
3232
|
...exec.agent ? { agent: exec.agent } : {},
|
|
3627
3233
|
parent: exec.token,
|
|
@@ -3656,6 +3262,7 @@ function createRunCodeTool(registry, options) {
|
|
|
3656
3262
|
name,
|
|
3657
3263
|
arguments: normalized.logged,
|
|
3658
3264
|
isError: result.isError,
|
|
3265
|
+
...result.error?.info === void 0 ? {} : { error: result.error.info },
|
|
3659
3266
|
content: logged
|
|
3660
3267
|
});
|
|
3661
3268
|
})().finally(() => {
|
|
@@ -3703,10 +3310,7 @@ function createRunCodeTool(registry, options) {
|
|
|
3703
3310
|
const result = parked.kind === "post-result" ? await scheduler.finalize(parked.exec, parked.result) : scheduler.finish(parked.exec, parked.result);
|
|
3704
3311
|
if (!result.isError && result.content.some((block) => block.type === "image")) exec.deferContext(createUserMessage({
|
|
3705
3312
|
content: result.content,
|
|
3706
|
-
source: {
|
|
3707
|
-
kind: "plugin",
|
|
3708
|
-
plugin: "tools-ptc"
|
|
3709
|
-
}
|
|
3313
|
+
source: { kind: "ptc-mode" }
|
|
3710
3314
|
}));
|
|
3711
3315
|
for (const context of result.additionalContexts ?? []) exec.deferContext(context);
|
|
3712
3316
|
if (result.concludesTurn) exec.concludeTurn();
|
|
@@ -3726,13 +3330,13 @@ function createRunCodeTool(registry, options) {
|
|
|
3726
3330
|
if (schema.name === "run_code") continue;
|
|
3727
3331
|
Object.defineProperty(functions, schema.name, {
|
|
3728
3332
|
enumerable: true,
|
|
3729
|
-
value: binding(schema
|
|
3333
|
+
value: binding(deepFreeze(schema))
|
|
3730
3334
|
});
|
|
3731
3335
|
}
|
|
3732
3336
|
try {
|
|
3733
3337
|
let result;
|
|
3734
3338
|
try {
|
|
3735
|
-
result = await runtime.run({
|
|
3339
|
+
result = await runtime.run(runtime.resolve({
|
|
3736
3340
|
program: args.code,
|
|
3737
3341
|
bindings: [{
|
|
3738
3342
|
global: "tools",
|
|
@@ -3742,18 +3346,23 @@ function createRunCodeTool(registry, options) {
|
|
|
3742
3346
|
memberNameProperty: "toolName"
|
|
3743
3347
|
}
|
|
3744
3348
|
}],
|
|
3745
|
-
signal: runController.signal
|
|
3746
|
-
|
|
3349
|
+
signal: runController.signal,
|
|
3350
|
+
...exec.agent?.session.header.cwd !== void 0 ? { cwd: exec.agent.session.header.cwd } : {},
|
|
3351
|
+
...policy !== void 0 ? { sandboxPolicy: policy } : {},
|
|
3352
|
+
...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {}
|
|
3353
|
+
}));
|
|
3747
3354
|
} finally {
|
|
3748
3355
|
runController.abort("run_code settled");
|
|
3749
3356
|
await drainDispatches();
|
|
3750
3357
|
}
|
|
3751
3358
|
if (result.error) {
|
|
3752
3359
|
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join("\n")}` : "";
|
|
3753
|
-
|
|
3360
|
+
const sandboxText = result.sandbox === void 0 ? "" : `\nFile sandbox: ${result.sandbox.mode}${result.sandbox.enforcement === void 0 ? "" : `; enforcement: ${result.sandbox.enforcement}`}${result.sandbox.denied ? "; operation denied" : ""}.`;
|
|
3361
|
+
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime) : ""}`);
|
|
3754
3362
|
}
|
|
3755
3363
|
return {
|
|
3756
3364
|
logs: result.logs,
|
|
3365
|
+
...result.sandbox === void 0 ? {} : { sandbox: result.sandbox },
|
|
3757
3366
|
...result.value !== void 0 ? { result: result.value } : {}
|
|
3758
3367
|
};
|
|
3759
3368
|
} finally {
|
|
@@ -3769,7 +3378,11 @@ function createRunCodeTool(registry, options) {
|
|
|
3769
3378
|
});
|
|
3770
3379
|
Object.defineProperty(definition, "description", {
|
|
3771
3380
|
enumerable: true,
|
|
3772
|
-
get: () =>
|
|
3381
|
+
get: () => {
|
|
3382
|
+
const runtime = peekRuntime();
|
|
3383
|
+
const instructions = runtime?.executionInstructions;
|
|
3384
|
+
return resolveFlavor(peekRuntime).description + (instructions ? ` ${instructions}` : "") + (runtime === void 0 ? "" : " The working directory is the Session's current directory.") + escalationGuidance(runtime);
|
|
3385
|
+
}
|
|
3773
3386
|
});
|
|
3774
3387
|
Object.defineProperty(definition, "parameters", {
|
|
3775
3388
|
enumerable: true,
|
|
@@ -3783,7 +3396,8 @@ function createRunCodeTool(registry, options) {
|
|
|
3783
3396
|
type: "string",
|
|
3784
3397
|
required: true,
|
|
3785
3398
|
description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION
|
|
3786
|
-
}
|
|
3399
|
+
},
|
|
3400
|
+
...controlParameters(peekRuntime())
|
|
3787
3401
|
})
|
|
3788
3402
|
});
|
|
3789
3403
|
return definition;
|
|
@@ -4068,7 +3682,7 @@ function renderToolsSdk(schemas) {
|
|
|
4068
3682
|
* PTC mode codegen — Python flavor. The pure projection from registered tool schemas to the
|
|
4069
3683
|
* Python SDK text the model programs against under `runtime.language === 'python'`. Sibling of
|
|
4070
3684
|
* {@link ./ts-types.ts | ts-types.ts}; the two files are two projections of the same registry
|
|
4071
|
-
* store, keyed by the loaded {@link @deepseek-ai/dsh-
|
|
3685
|
+
* store, keyed by the loaded {@link @deepseek-ai/dsh-ptc-runtime#PtcRuntime.language | PTC
|
|
4072
3686
|
* runtime's language}.
|
|
4073
3687
|
*
|
|
4074
3688
|
* Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated
|
|
@@ -4755,17 +4369,17 @@ function renderToolsSdkPy(schemas) {
|
|
|
4755
4369
|
*/
|
|
4756
4370
|
/**
|
|
4757
4371
|
* Language → SDK-section renderer. The registry looks up the loaded
|
|
4758
|
-
* `ctx.
|
|
4372
|
+
* `ctx.ptcRuntime.language` in this table when assembling the `tools:sdk`
|
|
4759
4373
|
* section under a non-native mode; a runtime whose language is not a key
|
|
4760
4374
|
* fails the assembly loudly (same idiom as `toolOrder` violations). Adding a
|
|
4761
|
-
* new backend language is three parallel edits — a {@link
|
|
4375
|
+
* new backend language is three parallel edits — a {@link PtcSdkLanguage}
|
|
4762
4376
|
* member, an entry here, and a `RUN_CODE_FLAVORS` entry in `ptc.ts` for
|
|
4763
4377
|
* its `run_code` schema strings — plus the renderer function this table points
|
|
4764
4378
|
* at. The `satisfies` clause pins this table's key set to that union, which
|
|
4765
4379
|
* the flavor table is checked against too, so any of the three left out is a
|
|
4766
4380
|
* typecheck failure. What no check reaches is the prose that names the values
|
|
4767
|
-
* instead of deriving them: the seam's `dsh-
|
|
4768
|
-
* `
|
|
4381
|
+
* instead of deriving them: the seam's `dsh-ptc-runtime` README pair, its
|
|
4382
|
+
* `PtcRuntime.language` JSDoc, and `docs/subsystems/ptc-runtime.md`
|
|
4769
4383
|
* with its zh pair, plus this package's own README pair and the
|
|
4770
4384
|
* {@link Config.mode} JSDoc.
|
|
4771
4385
|
*/
|
|
@@ -4941,6 +4555,8 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4941
4555
|
cancellationStates = /* @__PURE__ */ new WeakMap();
|
|
4942
4556
|
/** Definition-owned final content transform snapshotted before policy begins. */
|
|
4943
4557
|
contentFinalizers = /* @__PURE__ */ new WeakMap();
|
|
4558
|
+
/** Execution-prepared content installed before post-execute policy. */
|
|
4559
|
+
contentProjectors = /* @__PURE__ */ new WeakMap();
|
|
4944
4560
|
layers = new ScopedLayers((scope) => new ToolLayer(scope), () => {
|
|
4945
4561
|
this.ctx.emit("tools/change");
|
|
4946
4562
|
});
|
|
@@ -5000,12 +4616,13 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5000
4616
|
return {
|
|
5001
4617
|
name: "tools:sdk",
|
|
5002
4618
|
order: this.ctx.systemPrompt.getSectionOrder("TOOLS_SDK"),
|
|
4619
|
+
interpolate: false,
|
|
5003
4620
|
text: (context) => {
|
|
5004
4621
|
const mode = this.modeFor(context.scope);
|
|
5005
4622
|
if (mode === "native") return "";
|
|
5006
|
-
const runtime = this.
|
|
4623
|
+
const runtime = this.requirePtcRuntime(mode);
|
|
5007
4624
|
const render = SDK_RENDERERS[runtime.language];
|
|
5008
|
-
/* v8 ignore next --
|
|
4625
|
+
/* v8 ignore next -- requirePtcRuntime rejects an unknown language before this runs. */
|
|
5009
4626
|
if (render === void 0) throw new Error(`dsh-tools: no SDK renderer for ${runtime.language}`);
|
|
5010
4627
|
return render(this.sdkSchemas(context.scope));
|
|
5011
4628
|
}
|
|
@@ -5034,10 +4651,16 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5034
4651
|
* and only for scopes whose mode actually presents it.
|
|
5035
4652
|
* @returns the shared transport definition.
|
|
5036
4653
|
*/
|
|
5037
|
-
|
|
4654
|
+
requirePtcTransport() {
|
|
5038
4655
|
this.ptcTransport ??= createRunCodeTool(this, {
|
|
5039
|
-
requireRuntime: () => this.
|
|
5040
|
-
|
|
4656
|
+
requireRuntime: () => this.requirePtcRuntime(this.defaultMode),
|
|
4657
|
+
peekApprover: () => this.ctx.get("approval"),
|
|
4658
|
+
resolveSandboxPolicy: (exec) => {
|
|
4659
|
+
const policy = this.ctx.get("sandboxPolicy");
|
|
4660
|
+
if (policy === void 0) throw new Error("dsh-tools: confined PTC runtime requires sandboxPolicy");
|
|
4661
|
+
return policy.resolve(exec.agent === void 0 ? {} : { session: exec.agent.session });
|
|
4662
|
+
},
|
|
4663
|
+
peekRuntime: () => this.ctx.get("ptcRuntime"),
|
|
5041
4664
|
maxParallel: this.maxParallelSubCalls,
|
|
5042
4665
|
shapeDispatchLog: (dispatch) => this.shapeDispatchLog(dispatch)
|
|
5043
4666
|
});
|
|
@@ -5082,7 +4705,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5082
4705
|
schemas: [...view.visible.values()].map((definition) => this.schemaOf(definition, false)),
|
|
5083
4706
|
knownNames: [...view.knownNames]
|
|
5084
4707
|
};
|
|
5085
|
-
this.
|
|
4708
|
+
this.requirePtcRuntime(mode);
|
|
5086
4709
|
const schemas = [...view.visible.values()].map((definition) => this.schemaOf(definition, false));
|
|
5087
4710
|
if (mode === "ptc") return {
|
|
5088
4711
|
schemas: schemas.filter((schema) => schema.name === RUN_CODE_NAME),
|
|
@@ -5094,10 +4717,10 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5094
4717
|
};
|
|
5095
4718
|
}
|
|
5096
4719
|
/**
|
|
5097
|
-
* Resolve the
|
|
4720
|
+
* Resolve the PTC runtime or throw the actionable misconfiguration error.
|
|
5098
4721
|
* Read at use time (assembly / run_code execution), NOT via static
|
|
5099
4722
|
* `inject`: an inject entry would hold `ctx.tools` — and every tool plugin
|
|
5100
|
-
* behind it — hostage to a
|
|
4723
|
+
* behind it — hostage to a PTC runtime existing even under `mode:
|
|
5101
4724
|
* 'native'`.
|
|
5102
4725
|
*
|
|
5103
4726
|
* Assembly and `run_code` execution read separately, so the language is not
|
|
@@ -5107,9 +4730,9 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5107
4730
|
* other. Binding it is deferred until a second backend ships (the first
|
|
5108
4731
|
* point it is testable).
|
|
5109
4732
|
*/
|
|
5110
|
-
|
|
5111
|
-
const runtime = this.ctx.get("
|
|
5112
|
-
if (!runtime) throw new Error(`dsh-tools: mode "${mode}" requires a
|
|
4733
|
+
requirePtcRuntime(mode) {
|
|
4734
|
+
const runtime = this.ctx.get("ptcRuntime");
|
|
4735
|
+
if (!runtime) throw new Error(`dsh-tools: mode "${mode}" requires a PTC runtime — load a ctx.ptcRuntime implementation (e.g. @deepseek-ai/dsh-ptc-runtime-node) or set tools mode to "native"`);
|
|
5113
4736
|
if (!Object.hasOwn(SDK_RENDERERS, runtime.language)) {
|
|
5114
4737
|
const known = Object.keys(SDK_RENDERERS).map((name) => JSON.stringify(name)).join(", ");
|
|
5115
4738
|
throw new Error(`dsh-tools: no SDK renderer registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`);
|
|
@@ -5223,7 +4846,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5223
4846
|
knownNames.add(name);
|
|
5224
4847
|
visible.set(name, definition);
|
|
5225
4848
|
}
|
|
5226
|
-
if (this.modeFor(scope) !== "native") visible.set(RUN_CODE_NAME, this.
|
|
4849
|
+
if (this.modeFor(scope) !== "native") visible.set(RUN_CODE_NAME, this.requirePtcTransport());
|
|
5227
4850
|
return {
|
|
5228
4851
|
visible,
|
|
5229
4852
|
knownNames,
|
|
@@ -5284,13 +4907,14 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5284
4907
|
}
|
|
5285
4908
|
/** Project one definition onto the model-facing schema fields. */
|
|
5286
4909
|
schemaOf(definition, detachParameters) {
|
|
5287
|
-
const { name, description, parameters } = definition;
|
|
4910
|
+
const { name, description, parameters, deferLoading } = definition;
|
|
5288
4911
|
const detached = detachParameters ? snapshotJsonValue(parameters) : parameters;
|
|
5289
4912
|
if (detached === void 0) throw new Error(`tool "${name}" parameters must be lossless JSON before schema projection`);
|
|
5290
4913
|
return {
|
|
5291
4914
|
name,
|
|
5292
4915
|
description,
|
|
5293
|
-
parameters: detached
|
|
4916
|
+
parameters: detached,
|
|
4917
|
+
...deferLoading === true ? { deferLoading } : {}
|
|
5294
4918
|
};
|
|
5295
4919
|
}
|
|
5296
4920
|
/**
|
|
@@ -5394,6 +5018,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5394
5018
|
signal,
|
|
5395
5019
|
...agent !== void 0 ? { agent } : {},
|
|
5396
5020
|
...parent !== void 0 ? { parent } : {},
|
|
5021
|
+
...exec.schema !== void 0 ? { schema: exec.schema } : {},
|
|
5397
5022
|
deferContext(context) {
|
|
5398
5023
|
deferredContexts.push(context);
|
|
5399
5024
|
},
|
|
@@ -5402,6 +5027,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5402
5027
|
}
|
|
5403
5028
|
};
|
|
5404
5029
|
const capturedFinalizer = visible?.finalizeContent?.bind(visible);
|
|
5030
|
+
const capturedProjector = visible?.projectContent?.bind(visible);
|
|
5405
5031
|
const finalizerFor = () => collapsed && !signal.aborted ? void 0 : capturedFinalizer;
|
|
5406
5032
|
try {
|
|
5407
5033
|
const detached = snapshotJsonValue(exec.arguments);
|
|
@@ -5412,6 +5038,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5412
5038
|
};
|
|
5413
5039
|
this.deferredContexts.set(execution, deferredContexts);
|
|
5414
5040
|
this.contentFinalizers.set(execution, finalizerFor());
|
|
5041
|
+
if (!collapsed) this.contentProjectors.set(execution, capturedProjector);
|
|
5415
5042
|
this.cancellationStates.set(execution, {
|
|
5416
5043
|
callerSignal: signal,
|
|
5417
5044
|
bodyInvoked: false
|
|
@@ -5476,7 +5103,13 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5476
5103
|
exec,
|
|
5477
5104
|
result: toolAbortedBeforeDispatchResult()
|
|
5478
5105
|
});
|
|
5106
|
+
if (decision.kind === "cancel") return await next({
|
|
5107
|
+
kind: "post-result",
|
|
5108
|
+
exec,
|
|
5109
|
+
result: toolAbortedBeforeDispatchResult()
|
|
5110
|
+
});
|
|
5479
5111
|
const denialReason = decision.kind === "allow" ? this.guardReason(exec) : decision.reason;
|
|
5112
|
+
const denialInfo = decision.kind === "deny" ? decision.info : void 0;
|
|
5480
5113
|
if (denialReason !== void 0) return await next({
|
|
5481
5114
|
kind: "post-result",
|
|
5482
5115
|
exec,
|
|
@@ -5486,7 +5119,10 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5486
5119
|
text: `Error: ${denialReason}`
|
|
5487
5120
|
}],
|
|
5488
5121
|
isError: true,
|
|
5489
|
-
error: {
|
|
5122
|
+
error: {
|
|
5123
|
+
message: denialReason,
|
|
5124
|
+
...denialInfo === void 0 ? {} : { info: denialInfo }
|
|
5125
|
+
}
|
|
5490
5126
|
})
|
|
5491
5127
|
});
|
|
5492
5128
|
if (this.callerCancelled(exec)) return await next({
|
|
@@ -5592,7 +5228,14 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5592
5228
|
*/
|
|
5593
5229
|
async finalizeScheduledExecution(exec, result) {
|
|
5594
5230
|
try {
|
|
5595
|
-
const
|
|
5231
|
+
const project = this.contentProjectors.get(exec);
|
|
5232
|
+
this.contentProjectors.delete(exec);
|
|
5233
|
+
const content = project?.(exec, result);
|
|
5234
|
+
const projected = content === void 0 ? result : this.markCanonical(exec, this.materializeFinalResult({
|
|
5235
|
+
...result,
|
|
5236
|
+
content
|
|
5237
|
+
}));
|
|
5238
|
+
const postResult = await this.postExecute(exec, projected);
|
|
5596
5239
|
return this.finishScheduledExecution(exec, this.callerCancelled(exec) && !postResult.isError ? this.cancellationResult(exec, postResult) : postResult);
|
|
5597
5240
|
} catch (error) {
|
|
5598
5241
|
return this.finishScheduledExecution(exec, toolErrorResult(error));
|
|
@@ -5684,6 +5327,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
5684
5327
|
toolName: exec.name,
|
|
5685
5328
|
callId: exec.callId,
|
|
5686
5329
|
...ask.reason !== void 0 ? { reason: ask.reason } : {},
|
|
5330
|
+
...ask.displayReason !== void 0 ? { displayReason: ask.displayReason } : {},
|
|
5687
5331
|
signal: exec.signal
|
|
5688
5332
|
});
|
|
5689
5333
|
switch (outcome) {
|
|
@@ -6186,40 +5830,56 @@ const IMAGE_SIZES = [
|
|
|
6186
5830
|
"2K",
|
|
6187
5831
|
"4K"
|
|
6188
5832
|
];
|
|
6189
|
-
/**
|
|
5833
|
+
/**
|
|
5834
|
+
* Mark a schema field live for DSH 0.1.7+: the host serves a plugin's settings
|
|
5835
|
+
* form only for `.volatile()` fields and hands `apply` a `Volatile` box per
|
|
5836
|
+
* field (unwrapped in `src/index.ts`). Hosts ≤0.1.6 ship a schemastery without
|
|
5837
|
+
* the method, so marking is conditional — an unmarked schema stays plain there
|
|
5838
|
+
* and the old settings relay keeps working.
|
|
5839
|
+
*/
|
|
5840
|
+
function volatile(schema) {
|
|
5841
|
+
const mark = schema.volatile;
|
|
5842
|
+
return typeof mark === "function" ? mark.call(schema) : schema;
|
|
5843
|
+
}
|
|
5844
|
+
/**
|
|
5845
|
+
* Cordis configuration schema. Volatile marking happens per field (see
|
|
5846
|
+
* `volatile`); the `z<Config>` annotation keeps the plain output type for the
|
|
5847
|
+
* rest of the codebase because the wrapping is unwrapped at the entry.
|
|
5848
|
+
*/
|
|
6190
5849
|
const Config = z.object({
|
|
6191
|
-
provider: z.union(IMAGE_PROVIDERS).default("google"),
|
|
6192
|
-
googleModel: z.string().default(DEFAULT_GOOGLE_MODEL),
|
|
6193
|
-
googleEndpoint: z.string().default(DEFAULT_GOOGLE_ENDPOINT),
|
|
6194
|
-
openaiBaseURL: z.string().default(DEFAULT_OPENAI_BASE_URL),
|
|
6195
|
-
openaiModel: z.string().default(DEFAULT_OPENAI_MODEL),
|
|
6196
|
-
openaiCompatBaseURL: z.string().default(""),
|
|
6197
|
-
openaiCompatModel: z.string().default(""),
|
|
6198
|
-
openaiCompatEditFormat: z.union([z.const("multipart"), z.const("jsonImageUrlArray")]).default("multipart"),
|
|
6199
|
-
openaiCompatEditExtra: z.dict(z.any()).default({}),
|
|
6200
|
-
seedreamBaseURL: z.string().default(DEFAULT_SEEDREAM_BASE_URL),
|
|
6201
|
-
seedreamModel: z.string().default(DEFAULT_SEEDREAM_MODEL),
|
|
6202
|
-
seedreamOutputFormat: z.union(ARK_OUTPUT_FORMATS).default("jpeg"),
|
|
6203
|
-
seedreamWatermark: z.boolean().default(true),
|
|
6204
|
-
seedreamBackground: z.union(ARK_BACKGROUND_MODES).default("opaque"),
|
|
6205
|
-
dashscopeEndpoint: z.string().default(DEFAULT_DASHSCOPE_ENDPOINT),
|
|
6206
|
-
dashscopeModel: z.string().default(DEFAULT_DASHSCOPE_MODEL),
|
|
6207
|
-
xaiBaseURL: z.string().default(DEFAULT_XAI_BASE_URL),
|
|
6208
|
-
xaiModel: z.string().default(DEFAULT_XAI_MODEL),
|
|
6209
|
-
zhipuBaseURL: z.string().default(DEFAULT_ZHIPU_BASE_URL),
|
|
6210
|
-
zhipuModel: z.string().default(DEFAULT_ZHIPU_MODEL),
|
|
6211
|
-
comfyuiBaseURL: z.string().default(DEFAULT_COMFYUI_BASE_URL),
|
|
6212
|
-
comfyuiWorkflows: z.array(z.object({
|
|
5850
|
+
provider: volatile(z.union(IMAGE_PROVIDERS).default("google")),
|
|
5851
|
+
googleModel: volatile(z.string().default(DEFAULT_GOOGLE_MODEL)),
|
|
5852
|
+
googleEndpoint: volatile(z.string().default(DEFAULT_GOOGLE_ENDPOINT)),
|
|
5853
|
+
openaiBaseURL: volatile(z.string().default(DEFAULT_OPENAI_BASE_URL)),
|
|
5854
|
+
openaiModel: volatile(z.string().default(DEFAULT_OPENAI_MODEL)),
|
|
5855
|
+
openaiCompatBaseURL: volatile(z.string().default("")),
|
|
5856
|
+
openaiCompatModel: volatile(z.string().default("")),
|
|
5857
|
+
openaiCompatEditFormat: volatile(z.union([z.const("multipart"), z.const("jsonImageUrlArray")]).default("multipart")),
|
|
5858
|
+
openaiCompatEditExtra: volatile(z.dict(z.any()).default({})),
|
|
5859
|
+
seedreamBaseURL: volatile(z.string().default(DEFAULT_SEEDREAM_BASE_URL)),
|
|
5860
|
+
seedreamModel: volatile(z.string().default(DEFAULT_SEEDREAM_MODEL)),
|
|
5861
|
+
seedreamOutputFormat: volatile(z.union(ARK_OUTPUT_FORMATS).default("jpeg")),
|
|
5862
|
+
seedreamWatermark: volatile(z.boolean().default(true)),
|
|
5863
|
+
seedreamBackground: volatile(z.union(ARK_BACKGROUND_MODES).default("opaque")),
|
|
5864
|
+
dashscopeEndpoint: volatile(z.string().default(DEFAULT_DASHSCOPE_ENDPOINT)),
|
|
5865
|
+
dashscopeModel: volatile(z.string().default(DEFAULT_DASHSCOPE_MODEL)),
|
|
5866
|
+
xaiBaseURL: volatile(z.string().default(DEFAULT_XAI_BASE_URL)),
|
|
5867
|
+
xaiModel: volatile(z.string().default(DEFAULT_XAI_MODEL)),
|
|
5868
|
+
zhipuBaseURL: volatile(z.string().default(DEFAULT_ZHIPU_BASE_URL)),
|
|
5869
|
+
zhipuModel: volatile(z.string().default(DEFAULT_ZHIPU_MODEL)),
|
|
5870
|
+
comfyuiBaseURL: volatile(z.string().default(DEFAULT_COMFYUI_BASE_URL)),
|
|
5871
|
+
comfyuiWorkflows: volatile(z.array(z.object({
|
|
6213
5872
|
name: z.string(),
|
|
6214
5873
|
json: z.string(),
|
|
6215
5874
|
presetPrompt: z.string().default("")
|
|
6216
|
-
})).default([]),
|
|
6217
|
-
comfyuiActiveWorkflow: z.string().default(""),
|
|
6218
|
-
comfyuiWorkflowJson: z.string().default(""),
|
|
6219
|
-
comfyuiWorkflowName: z.string().default(""),
|
|
6220
|
-
comfyuiTimeoutMs: z.number().min(1e3).max(36e5).default(DEFAULT_COMFYUI_TIMEOUT_MS),
|
|
6221
|
-
saveToWorkspace: z.boolean().default(true),
|
|
6222
|
-
workspaceFolder: z.string().default(DEFAULT_WORKSPACE_FOLDER)
|
|
5875
|
+
})).default([])),
|
|
5876
|
+
comfyuiActiveWorkflow: volatile(z.string().default("")),
|
|
5877
|
+
comfyuiWorkflowJson: volatile(z.string().default("")),
|
|
5878
|
+
comfyuiWorkflowName: volatile(z.string().default("")),
|
|
5879
|
+
comfyuiTimeoutMs: volatile(z.number().min(1e3).max(36e5).default(DEFAULT_COMFYUI_TIMEOUT_MS)),
|
|
5880
|
+
saveToWorkspace: volatile(z.boolean().default(true)),
|
|
5881
|
+
workspaceFolder: volatile(z.string().default(DEFAULT_WORKSPACE_FOLDER)),
|
|
5882
|
+
showProviderPill: volatile(z.boolean().default(false))
|
|
6223
5883
|
});
|
|
6224
5884
|
/** Resolve exactly one provider profile for a tool call. */
|
|
6225
5885
|
function resolveProvider(config) {
|
|
@@ -6400,7 +6060,7 @@ function selectComfyUIWorkflow(active, requested) {
|
|
|
6400
6060
|
return workflow;
|
|
6401
6061
|
}
|
|
6402
6062
|
//#endregion
|
|
6403
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-
|
|
6063
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-credential_98fa173416f048996704a605a96a2b0e/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
|
|
6404
6064
|
/**
|
|
6405
6065
|
* Service Definition for the credential-reference capability seam (`ctx.credentials`). Settings and composition files carry
|
|
6406
6066
|
* *references* to secrets — environment-variable names — while providers own
|
|
@@ -6722,6 +6382,13 @@ function parseImageAttachmentRef(value) {
|
|
|
6722
6382
|
...originalDimensions === void 0 ? {} : { originalDimensions }
|
|
6723
6383
|
};
|
|
6724
6384
|
}
|
|
6385
|
+
/**
|
|
6386
|
+
* Newest matching image in one message's blocks. Tool results arrive in two
|
|
6387
|
+
* shapes: 0.1.7 promotes them to first-class `role: 'tool'` messages (walked
|
|
6388
|
+
* by the message-level callers below), while <=0.1.6 nests them as blocks with
|
|
6389
|
+
* a `content` array inside a message. The 0.1.7 `ContentBlock` union declares
|
|
6390
|
+
* no nested `content`, so recurse only when a block actually carries one.
|
|
6391
|
+
*/
|
|
6725
6392
|
function findInBlocks(blocks, sourceAttachmentId) {
|
|
6726
6393
|
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
6727
6394
|
const block = blocks[index];
|
|
@@ -6730,9 +6397,10 @@ function findInBlocks(blocks, sourceAttachmentId) {
|
|
|
6730
6397
|
if (sourceAttachmentId === void 0 || attachmentIdsEqual(String(block.attachment.attachmentId), sourceAttachmentId)) return block.attachment;
|
|
6731
6398
|
continue;
|
|
6732
6399
|
}
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6400
|
+
const nested = block.content;
|
|
6401
|
+
if (Array.isArray(nested)) {
|
|
6402
|
+
const found = findInBlocks(nested, sourceAttachmentId);
|
|
6403
|
+
if (found !== void 0) return found;
|
|
6736
6404
|
}
|
|
6737
6405
|
}
|
|
6738
6406
|
}
|
|
@@ -6751,7 +6419,8 @@ function collectInBlocks(blocks) {
|
|
|
6751
6419
|
const refs = [];
|
|
6752
6420
|
for (const block of blocks) {
|
|
6753
6421
|
if (block.type === "image") refs.push(block.attachment);
|
|
6754
|
-
|
|
6422
|
+
const nested = block.content;
|
|
6423
|
+
if (Array.isArray(nested)) refs.push(...collectInBlocks(nested));
|
|
6755
6424
|
}
|
|
6756
6425
|
return refs;
|
|
6757
6426
|
}
|
|
@@ -8609,6 +8278,16 @@ async function formTokenRequest(url, params, fetchImpl) {
|
|
|
8609
8278
|
const OK_HTML = "<!doctype html><meta charset=\"utf-8\"><title>dsh-image-gen</title><p>登录成功,可以关闭此页返回设置。</p>";
|
|
8610
8279
|
const ERR_HTML = "<!doctype html><meta charset=\"utf-8\"><title>dsh-image-gen</title><p>登录失败,请返回设置重试。</p>";
|
|
8611
8280
|
/**
|
|
8281
|
+
* Loopback addresses accepted by the request handler. On dual-stack systems
|
|
8282
|
+
* Node reports IPv4-mapped IPv6 addresses as `::ffff:127.0.0.1`, so we must
|
|
8283
|
+
* accept that form in addition to the canonical IPv4 and IPv6 loopback.
|
|
8284
|
+
*/
|
|
8285
|
+
const LOOPBACK_ADDRESSES = /* @__PURE__ */ new Set([
|
|
8286
|
+
"127.0.0.1",
|
|
8287
|
+
"::1",
|
|
8288
|
+
"::ffff:127.0.0.1"
|
|
8289
|
+
]);
|
|
8290
|
+
/**
|
|
8612
8291
|
* Listen on the redirect_uri's port and resolve when the provider calls back.
|
|
8613
8292
|
* Rejects on port conflicts, network errors, or when nothing arrives in time.
|
|
8614
8293
|
*/
|
|
@@ -8621,6 +8300,12 @@ function startLoopback(options) {
|
|
|
8621
8300
|
const state = { done: false };
|
|
8622
8301
|
return new Promise((resolve, reject) => {
|
|
8623
8302
|
const server = http.createServer((req, res) => {
|
|
8303
|
+
const remote = req.socket.remoteAddress ?? "";
|
|
8304
|
+
if (!LOOPBACK_ADDRESSES.has(remote)) {
|
|
8305
|
+
res.writeHead(403);
|
|
8306
|
+
res.end();
|
|
8307
|
+
return;
|
|
8308
|
+
}
|
|
8624
8309
|
const url = new URL(req.url ?? "/", `http://127.0.0.1:${String(port)}`);
|
|
8625
8310
|
if (!url.pathname.startsWith(path)) {
|
|
8626
8311
|
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
@@ -8652,7 +8337,7 @@ function startLoopback(options) {
|
|
|
8652
8337
|
server.close();
|
|
8653
8338
|
reject(/* @__PURE__ */ new Error("loopback timeout: no callback received"));
|
|
8654
8339
|
}, timeoutMs);
|
|
8655
|
-
server.listen(port
|
|
8340
|
+
server.listen(port);
|
|
8656
8341
|
});
|
|
8657
8342
|
}
|
|
8658
8343
|
//#endregion
|
|
@@ -21554,14 +21239,19 @@ function providerOverrideOf(value) {
|
|
|
21554
21239
|
return value;
|
|
21555
21240
|
}
|
|
21556
21241
|
function apply(ctx, config = {}) {
|
|
21557
|
-
|
|
21242
|
+
const plainConfig = (source) => {
|
|
21243
|
+
const out = {};
|
|
21244
|
+
for (const [key, value] of Object.entries(source)) out[key] = value !== null && typeof value === "object" && typeof value.get === "function" ? value.get() : value;
|
|
21245
|
+
return out;
|
|
21246
|
+
};
|
|
21247
|
+
let current = () => migrateOpenAICompatConfig(plainConfig(config));
|
|
21558
21248
|
const knownWorkspaceRoots = /* @__PURE__ */ new Set();
|
|
21559
21249
|
const canvasMirror = new CanvasMirror();
|
|
21560
21250
|
const subscriptionManager = new SubscriptionManager(ctx);
|
|
21561
21251
|
registerSubscriptionRoutes(ctx, subscriptionManager);
|
|
21562
|
-
installImageSettings(ctx,
|
|
21252
|
+
installImageSettings(ctx, current(), {
|
|
21563
21253
|
setSource: (source) => {
|
|
21564
|
-
current = () => migrateOpenAICompatConfig(source());
|
|
21254
|
+
current = () => migrateOpenAICompatConfig(plainConfig(source()));
|
|
21565
21255
|
},
|
|
21566
21256
|
onChange: () => {}
|
|
21567
21257
|
});
|
|
@@ -22145,26 +21835,21 @@ function imagePresentation(result) {
|
|
|
22145
21835
|
}
|
|
22146
21836
|
/**
|
|
22147
21837
|
* Wire the settings namespace across both dsh-settings API generations.
|
|
22148
|
-
*
|
|
22149
|
-
*
|
|
22150
|
-
*
|
|
22151
|
-
*
|
|
21838
|
+
* The probe reads the HOST's `settings` service, never the bundled
|
|
21839
|
+
* dsh-settings module: the build inlines that module, so its exports always
|
|
21840
|
+
* look like the compile-time generation while only the service handed to this
|
|
21841
|
+
* fiber reflects the running host. dsh-settings 0.1.2–0.1.6 put `installSection`
|
|
21842
|
+
* on the service, so the namespace registers through it; DSH 0.1.7 replaced it
|
|
21843
|
+
* with schema-derived forms served by the loader itself (keyed by entry id),
|
|
21844
|
+
* so its service carries no installer and a host without any settings service
|
|
21845
|
+
* simply never runs the callback — both stay silent instead of warning.
|
|
22152
21846
|
*/
|
|
22153
21847
|
function installImageSettings(ctx, config, hooks) {
|
|
22154
|
-
|
|
22155
|
-
|
|
22156
|
-
|
|
22157
|
-
|
|
22158
|
-
|
|
22159
|
-
return;
|
|
22160
|
-
}
|
|
22161
|
-
const legacyInstall = namespace.installSettingsSection;
|
|
22162
|
-
const legacyNamespace = namespace.settingsNamespace;
|
|
22163
|
-
if (typeof legacyInstall === "function" && typeof legacyNamespace === "function") {
|
|
22164
|
-
legacyInstall(ctx, legacyNamespace(IMAGE_GENERATION_NAMESPACE), Config, config, hooks);
|
|
22165
|
-
return;
|
|
22166
|
-
}
|
|
22167
|
-
ctx.logger.warn("dsh-image-gen: this DSH exposes neither settings API generation; settings UI stays on the composition entry");
|
|
21848
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
21849
|
+
const settings = settingsCtx.settings;
|
|
21850
|
+
if (typeof settings.installSection !== "function") return;
|
|
21851
|
+
settings.installSection(ctx, IMAGE_GENERATION_NAMESPACE, Config, config, hooks);
|
|
21852
|
+
});
|
|
22168
21853
|
}
|
|
22169
21854
|
//#endregion
|
|
22170
21855
|
export { CANVAS_STATE_ROUTE, Config, DELETE_ROUTE, IMAGE_ROUTE, INSPIRATION_ROUTE, SAVE_WORKSPACE_ROUTE, STUDIO_ROUTE, TEST_CONNECTION_ROUTE, apply, imageAttachmentFromMeta, inject, name };
|