dsh-image-gen 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +345 -344
- package/README.md +345 -344
- package/README.zh-CN.md +3 -3
- package/lib/client.js +115006 -228
- package/lib/client.js.map +1 -1
- package/lib/index.js +2270 -601
- package/lib/types/canvas-state-route.d.ts +37 -0
- package/lib/types/canvas-state.d.ts +77 -0
- package/lib/types/canvas-tools.d.ts +44 -0
- package/lib/types/client/browser-image-utils.d.ts +4 -0
- package/lib/types/client/canvas/canvas-bridge.d.ts +10 -0
- package/lib/types/client/canvas/canvas-model.d.ts +129 -0
- package/lib/types/client/canvas/canvas-nodes.d.ts +23 -0
- package/lib/types/client/canvas/canvas-store.d.ts +29 -0
- package/lib/types/client/canvas/canvas-style.d.ts +1 -0
- package/lib/types/client/canvas/canvas-view.d.ts +8 -0
- package/lib/types/client/canvas/rf-base-css.d.ts +2 -0
- package/lib/types/client/gallery-store.d.ts +2 -0
- package/lib/types/client/gallery-view.d.ts +13 -2
- package/lib/types/client/image-result-node.d.ts +2 -0
- package/lib/types/client/index.d.ts +4 -2
- package/lib/types/client/studio-chat-panel.d.ts +23 -0
- package/lib/types/client/studio-style.d.ts +1 -1
- package/lib/types/client/studio-view.d.ts +8 -6
- package/lib/types/client/tl/canvas-sync.d.ts +22 -0
- package/lib/types/client/tl/studio-tl-canvas.d.ts +2 -0
- package/lib/types/client/tl/tl-canvas-bridge.d.ts +53 -0
- package/lib/types/client/tl/tl-css.d.ts +2 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/reference-image.d.ts +1 -0
- package/lib/types/shared.d.ts +86 -0
- package/lib/types/studio-chat.d.ts +48 -0
- package/package.json +22 -15
- package/skills/install-dsh-image-gen/SKILL.md +22 -22
- package/lib/types/client/inspiration-api.d.ts +0 -10
- package/lib/types/client/inspiration-detail.d.ts +0 -14
- package/lib/types/inspiration/cache.d.ts +0 -20
- package/lib/types/inspiration/downloader.d.ts +0 -27
- package/lib/types/inspiration/routes.d.ts +0 -13
- package/lib/types/inspiration/sources.d.ts +0 -33
- package/lib/types/inspiration/types.d.ts +0 -65
- package/lib/types/inspiration/updater.d.ts +0 -7
package/lib/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
4
|
import { lstat, mkdir, readFile, readdir, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
//#region \0rolldown/runtime.js
|
|
8
8
|
var __defProp = Object.defineProperty;
|
|
@@ -16,7 +16,175 @@ var __exportAll = (all, no_symbols) => {
|
|
|
16
16
|
return target;
|
|
17
17
|
};
|
|
18
18
|
//#endregion
|
|
19
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-
|
|
19
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-value_005f00f2ec004257b269f27fd5e936b4/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
|
|
20
|
+
/** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
|
|
21
|
+
/**
|
|
22
|
+
* Mark an unreachable closed-union branch.
|
|
23
|
+
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
24
|
+
* @param context - optional switch-site label included in the failure message.
|
|
25
|
+
* @returns never; a runtime value that escaped its type always throws.
|
|
26
|
+
*/
|
|
27
|
+
function assertNever(value, context) {
|
|
28
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
29
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
30
|
+
}
|
|
31
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
32
|
+
function hasIntrinsicConstructor$1(prototype, name) {
|
|
33
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
34
|
+
if (typeof constructor !== "function") return false;
|
|
35
|
+
try {
|
|
36
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
42
|
+
function isIntrinsicObjectPrototype$1(value) {
|
|
43
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor$1(value, "Object");
|
|
44
|
+
}
|
|
45
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
46
|
+
function hasPlainArrayPrototype$1(value) {
|
|
47
|
+
const prototype = Object.getPrototypeOf(value);
|
|
48
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor$1(prototype, "Array")) return false;
|
|
49
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
50
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype$1(objectPrototype);
|
|
51
|
+
}
|
|
52
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
53
|
+
function hasPlainObjectPrototype(value) {
|
|
54
|
+
const prototype = Object.getPrototypeOf(value);
|
|
55
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype$1(prototype);
|
|
56
|
+
}
|
|
57
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
58
|
+
function enumerableStringKeys(value) {
|
|
59
|
+
const keys = Reflect.ownKeys(value);
|
|
60
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
61
|
+
return keys;
|
|
62
|
+
}
|
|
63
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
64
|
+
function walkJsonValue(value, detach) {
|
|
65
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
66
|
+
let root;
|
|
67
|
+
const assign = (destination, item) => {
|
|
68
|
+
if (destination === void 0) return;
|
|
69
|
+
if (destination.kind === "root") root = item;
|
|
70
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
71
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
72
|
+
value: item,
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const tasks = [{
|
|
79
|
+
kind: "visit",
|
|
80
|
+
value,
|
|
81
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
82
|
+
}];
|
|
83
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
84
|
+
if (task.kind === "leave") {
|
|
85
|
+
ancestors.delete(task.source);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (task.kind === "array-item") {
|
|
89
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
90
|
+
tasks.push({
|
|
91
|
+
kind: "visit",
|
|
92
|
+
value: task.source[task.index],
|
|
93
|
+
...task.target === void 0 ? {} : { destination: {
|
|
94
|
+
kind: "array",
|
|
95
|
+
target: task.target,
|
|
96
|
+
index: task.index
|
|
97
|
+
} }
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (task.kind === "object-property") {
|
|
102
|
+
tasks.push({
|
|
103
|
+
kind: "visit",
|
|
104
|
+
value: task.source[task.key],
|
|
105
|
+
...task.target === void 0 ? {} : { destination: {
|
|
106
|
+
kind: "object",
|
|
107
|
+
target: task.target,
|
|
108
|
+
key: task.key
|
|
109
|
+
} }
|
|
110
|
+
});
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const current = task.value;
|
|
114
|
+
if (current === null) {
|
|
115
|
+
assign(task.destination, null);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
119
|
+
assign(task.destination, current);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (typeof current === "number") {
|
|
123
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
124
|
+
assign(task.destination, current);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (typeof current !== "object") return void 0;
|
|
128
|
+
if (ancestors.has(current)) return void 0;
|
|
129
|
+
if (Array.isArray(current)) {
|
|
130
|
+
if (!hasPlainArrayPrototype$1(current)) return void 0;
|
|
131
|
+
const length = current.length;
|
|
132
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
133
|
+
const target = detach ? [] : void 0;
|
|
134
|
+
if (target !== void 0) assign(task.destination, target);
|
|
135
|
+
ancestors.add(current);
|
|
136
|
+
tasks.push({
|
|
137
|
+
kind: "leave",
|
|
138
|
+
source: current
|
|
139
|
+
});
|
|
140
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
141
|
+
kind: "array-item",
|
|
142
|
+
source: current,
|
|
143
|
+
index,
|
|
144
|
+
...target === void 0 ? {} : { target }
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
149
|
+
const keys = enumerableStringKeys(current);
|
|
150
|
+
if (keys === void 0) return void 0;
|
|
151
|
+
const target = detach ? {} : void 0;
|
|
152
|
+
if (target !== void 0) assign(task.destination, target);
|
|
153
|
+
ancestors.add(current);
|
|
154
|
+
tasks.push({
|
|
155
|
+
kind: "leave",
|
|
156
|
+
source: current
|
|
157
|
+
});
|
|
158
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
159
|
+
const key = keys[index];
|
|
160
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
161
|
+
if (key === void 0) return void 0;
|
|
162
|
+
tasks.push({
|
|
163
|
+
kind: "object-property",
|
|
164
|
+
source: current,
|
|
165
|
+
key,
|
|
166
|
+
...target === void 0 ? {} : { target }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return detach ? root : true;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Validate and detach lossless JSON in one read per property.
|
|
174
|
+
* @param value - candidate value to validate and detach.
|
|
175
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
176
|
+
*/
|
|
177
|
+
function snapshotJsonValue(value) {
|
|
178
|
+
return walkJsonValue(value, true);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
|
|
182
|
+
* @param value - candidate value to test.
|
|
183
|
+
* @returns whether the value survives a JSON round trip without loss.
|
|
184
|
+
*/
|
|
185
|
+
function isJsonValue(value) {
|
|
186
|
+
return walkJsonValue(value, false) === true;
|
|
187
|
+
}
|
|
20
188
|
/**
|
|
21
189
|
* Compare JSON-compatible values structurally.
|
|
22
190
|
* @param a - one JSON-compatible value.
|
|
@@ -41,7 +209,7 @@ function deepEqualJson(a, b) {
|
|
|
41
209
|
* @param value - value to freeze.
|
|
42
210
|
* @returns the same value after every reachable enumerable child is frozen.
|
|
43
211
|
*/
|
|
44
|
-
function deepFreeze
|
|
212
|
+
function deepFreeze(value) {
|
|
45
213
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
46
214
|
const pending = [{
|
|
47
215
|
kind: "visit",
|
|
@@ -79,7 +247,7 @@ function deepFreeze$1(value) {
|
|
|
79
247
|
return value;
|
|
80
248
|
}
|
|
81
249
|
//#endregion
|
|
82
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-settings@
|
|
250
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-settings@0_935f3a2ac52a1f8e83ba61ff4169507a/node_modules/@deepseek-ai/dsh-settings/lib/index.js
|
|
83
251
|
var lib_exports = /* @__PURE__ */ __exportAll({
|
|
84
252
|
SettingsConflictError: () => SettingsConflictError,
|
|
85
253
|
SettingsProvider: () => SettingsProvider,
|
|
@@ -370,7 +538,7 @@ var SettingsProvider = class extends Service {
|
|
|
370
538
|
base: options?.base,
|
|
371
539
|
applies: options?.applies ?? "live",
|
|
372
540
|
...options?.validate === void 0 ? {} : { validate: options.validate },
|
|
373
|
-
resolved: deepFreeze
|
|
541
|
+
resolved: deepFreeze(this.resolve(schema, options?.base, this.section(parsedNs), options?.validate)),
|
|
374
542
|
revision: 0,
|
|
375
543
|
watchers: /* @__PURE__ */ new Set()
|
|
376
544
|
};
|
|
@@ -542,7 +710,7 @@ var SettingsProvider = class extends Service {
|
|
|
542
710
|
const current = this.section(ns) ?? {};
|
|
543
711
|
if (expectedRevision !== void 0 && expectedRevision !== registration.revision) throw new SettingsConflictError(ns, expectedRevision, registration.revision);
|
|
544
712
|
const section = mode === "merge" ? mergeLayers(current, snapshot) : mode === "replace" ? snapshot : snapshot["ops"].reduce(applyPathOp, current);
|
|
545
|
-
const next = deepFreeze
|
|
713
|
+
const next = deepFreeze(this.resolve(registration.schema, registration.base, section, registration.validate));
|
|
546
714
|
await this.persist(ns, section);
|
|
547
715
|
this.document[ns] = section;
|
|
548
716
|
if (this.registrations.get(ns) === registration && !this.isStopped()) {
|
|
@@ -571,7 +739,7 @@ var SettingsProvider = class extends Service {
|
|
|
571
739
|
for (const registration of this.registrations.values()) {
|
|
572
740
|
let next;
|
|
573
741
|
try {
|
|
574
|
-
next = deepFreeze
|
|
742
|
+
next = deepFreeze(this.resolve(registration.schema, registration.base, this.section(registration.ns), registration.validate));
|
|
575
743
|
} catch (error) {
|
|
576
744
|
this.ctx.logger.warn("settings: keeping last good \"%s\" after invalid stored section", registration.ns);
|
|
577
745
|
this.ctx.logger.warn(error);
|
|
@@ -969,83 +1137,192 @@ function scopeTarget(base, key) {
|
|
|
969
1137
|
return carrier;
|
|
970
1138
|
}
|
|
971
1139
|
//#endregion
|
|
972
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-
|
|
973
|
-
/**
|
|
974
|
-
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
975
|
-
//#endregion
|
|
976
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.1-_de8559ed89b7370843bac1bad71a6196/node_modules/@deepseek-ai/dsh-llm/lib/index.js
|
|
1140
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-typert-pro_32eafa0ce53608e9afb5dafcec019560/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
|
|
1141
|
+
/** The one Remote failure class shared by owners, the Gateway, and consumers. */
|
|
977
1142
|
/**
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
*
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
* brand it without depending on dsh-llm; see that package's README for the
|
|
984
|
-
* nominal-typing policy.
|
|
985
|
-
*
|
|
986
|
-
* @module @deepseek-ai/dsh-llm/brand
|
|
1143
|
+
* One Remote call failure: a real Error carrying its stable code and typed
|
|
1144
|
+
* details. Owners throw it at the failure point; the Host Gateway encodes it
|
|
1145
|
+
* onto the wire unchanged; the Client face rebuilds an instance for the
|
|
1146
|
+
* `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
|
|
1147
|
+
* Discrimination is always by `code`, never by instanceof.
|
|
987
1148
|
*/
|
|
1149
|
+
var RemoteError = class extends Error {
|
|
1150
|
+
code;
|
|
1151
|
+
details;
|
|
1152
|
+
/** Structural marker: cross-realm/bundle identification never uses instanceof. */
|
|
1153
|
+
isDSHRemoteError = true;
|
|
1154
|
+
/**
|
|
1155
|
+
* @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
|
|
1156
|
+
* @param message - human diagnostic carried across the wire.
|
|
1157
|
+
* @param details - structured payload typed by the code.
|
|
1158
|
+
* @param options - standard Error options (`cause` survives in-process only).
|
|
1159
|
+
*/
|
|
1160
|
+
constructor(code, message, details, options) {
|
|
1161
|
+
super(message, options);
|
|
1162
|
+
this.code = code;
|
|
1163
|
+
this.details = details;
|
|
1164
|
+
this.name = "RemoteError";
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
988
1167
|
/**
|
|
989
|
-
*
|
|
990
|
-
*
|
|
991
|
-
*
|
|
1168
|
+
* Remote decorators and explicit Gateway bindings backed by versioned
|
|
1169
|
+
* descriptors carried on decorated class prototypes. Strict reflection
|
|
1170
|
+
* remains a Typert compiler responsibility.
|
|
1171
|
+
* @module @deepseek-ai/dsh-typert-protocol
|
|
992
1172
|
*/
|
|
993
|
-
|
|
994
|
-
return id;
|
|
995
|
-
}
|
|
1173
|
+
const TYPERT_REMOTE_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
|
|
996
1174
|
/**
|
|
997
|
-
*
|
|
998
|
-
* @param
|
|
999
|
-
* @returns the
|
|
1175
|
+
* Test one generated Remote name against the Connection endpoint grammar.
|
|
1176
|
+
* @param value - namespace, method, lookup, or Context segment.
|
|
1177
|
+
* @returns whether the value can cross the shared RPC carrier unchanged.
|
|
1000
1178
|
*/
|
|
1001
|
-
function
|
|
1002
|
-
return
|
|
1179
|
+
function isTypertRemoteSegment(value) {
|
|
1180
|
+
return value !== "." && value !== ".." && TYPERT_REMOTE_SEGMENT_PATTERN.test(value);
|
|
1003
1181
|
}
|
|
1182
|
+
const REMOTE_METHOD_DESCRIPTOR = "@deepseek-ai/dsh-typert-protocol/remote-methods";
|
|
1004
1183
|
/**
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
* @
|
|
1010
|
-
* @returns the same value, frozen.
|
|
1184
|
+
* Bind one visible Service field to a Cordis key and Remote namespace.
|
|
1185
|
+
* @param service - owning Service instance, normally `this`.
|
|
1186
|
+
* @param serviceKey - exact Cordis service key.
|
|
1187
|
+
* @param options - optional distinct wire namespace.
|
|
1188
|
+
* @returns a frozen, inspectable binding with no compiler-injected metadata.
|
|
1011
1189
|
*/
|
|
1012
|
-
function
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1190
|
+
function bindTypertRemote(service, serviceKey, options = {}) {
|
|
1191
|
+
validateName("service key", serviceKey);
|
|
1192
|
+
const namespace = options.namespace ?? serviceKey;
|
|
1193
|
+
validateName("namespace", namespace);
|
|
1194
|
+
return Object.freeze({
|
|
1195
|
+
service,
|
|
1196
|
+
serviceKey,
|
|
1197
|
+
namespace
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
/** Cordis Service base that exposes its registered name through Typert Gateway. */
|
|
1201
|
+
var TypertRemoteService = class extends Service {
|
|
1202
|
+
/** Visible binding consumed by the Gateway's source-mode discovery. */
|
|
1203
|
+
typertRemote;
|
|
1204
|
+
/**
|
|
1205
|
+
* Register the Service and bind the same key to Typert Gateway.
|
|
1206
|
+
* @param ctx - owning Cordis Context.
|
|
1207
|
+
* @param serviceKey - exact Cordis service key and default wire namespace.
|
|
1208
|
+
* @param options - optional distinct wire namespace.
|
|
1209
|
+
*/
|
|
1210
|
+
constructor(ctx, serviceKey, options = {}) {
|
|
1211
|
+
super(ctx, serviceKey);
|
|
1212
|
+
this.typertRemote = bindTypertRemote(this, this.name, options);
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
function Remote(methodExportOrOptions, context) {
|
|
1216
|
+
if (typeof methodExportOrOptions === "string") {
|
|
1217
|
+
validateName("Remote export name", methodExportOrOptions);
|
|
1218
|
+
return remoteDecorator({ kind: "direct" }, void 0, methodExportOrOptions);
|
|
1219
|
+
}
|
|
1220
|
+
if (typeof methodExportOrOptions === "object") {
|
|
1221
|
+
if (remoteOptionMode(methodExportOrOptions) !== "stream" || Reflect.ownKeys(methodExportOrOptions).length !== 1) throw new TypeError("typert-protocol: Remote options must contain exactly mode: \"stream\"");
|
|
1222
|
+
return remoteDecorator({ kind: "direct" }, "stream");
|
|
1223
|
+
}
|
|
1224
|
+
if (context === void 0) throw new TypeError("typert-protocol: Remote decorator context is missing");
|
|
1225
|
+
addMarkerInitializer(context, { kind: "direct" });
|
|
1226
|
+
}
|
|
1227
|
+
function remoteOptionMode(options) {
|
|
1228
|
+
return Reflect.get(options, "mode");
|
|
1229
|
+
}
|
|
1230
|
+
function remoteDecorator(invocation, mode, exportName) {
|
|
1231
|
+
return function(_method, context) {
|
|
1232
|
+
addMarkerInitializer(context, invocation, mode, exportName);
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
function readRemoteMethodDescriptor(prototype) {
|
|
1236
|
+
const property = Object.getOwnPropertyDescriptor(prototype, REMOTE_METHOD_DESCRIPTOR);
|
|
1237
|
+
if (property === void 0) return void 0;
|
|
1238
|
+
const descriptor = property.value;
|
|
1239
|
+
if (descriptor === null || typeof descriptor !== "object") throw new TypeError("typert-protocol: Remote method descriptor must be an object");
|
|
1240
|
+
const version = Reflect.get(descriptor, "version");
|
|
1241
|
+
if (version !== 1) throw new TypeError(`typert-protocol: unsupported Remote method descriptor version ${String(version)}`);
|
|
1242
|
+
const methods = Reflect.get(descriptor, "methods");
|
|
1243
|
+
if (!Array.isArray(methods)) throw new TypeError("typert-protocol: Remote method descriptor methods must be an array");
|
|
1244
|
+
return descriptor;
|
|
1245
|
+
}
|
|
1246
|
+
function addMarkerInitializer(context, invocation, mode, exportName) {
|
|
1247
|
+
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");
|
|
1248
|
+
const method = context.name;
|
|
1249
|
+
context.addInitializer(function() {
|
|
1250
|
+
const prototype = Object.getPrototypeOf(this);
|
|
1251
|
+
if (prototype === null) throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`);
|
|
1252
|
+
mark(prototype, method, invocation, mode, exportName);
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
function mark(prototype, method, invocation, mode, exportName) {
|
|
1256
|
+
const descriptor = readRemoteMethodDescriptor(prototype);
|
|
1257
|
+
const marker = Object.freeze({
|
|
1258
|
+
method,
|
|
1259
|
+
...exportName === void 0 || exportName === method ? {} : { exportName },
|
|
1260
|
+
...mode === void 0 ? {} : { mode },
|
|
1261
|
+
invocation: Object.freeze(invocation)
|
|
1262
|
+
});
|
|
1263
|
+
const current = descriptor?.methods.find((candidate) => candidate.method === method);
|
|
1264
|
+
if (current !== void 0) {
|
|
1265
|
+
if (current.exportName === marker.exportName && current.mode === marker.mode && sameInvocation(current.invocation, invocation)) return;
|
|
1266
|
+
throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`);
|
|
1046
1267
|
}
|
|
1268
|
+
Object.defineProperty(prototype, REMOTE_METHOD_DESCRIPTOR, {
|
|
1269
|
+
configurable: true,
|
|
1270
|
+
value: Object.freeze({
|
|
1271
|
+
version: 1,
|
|
1272
|
+
methods: Object.freeze([...descriptor?.methods ?? [], marker])
|
|
1273
|
+
})
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
function sameInvocation(left, right) {
|
|
1277
|
+
if (left.kind === "direct") return right.kind === "direct";
|
|
1278
|
+
if (right.kind === "direct") return false;
|
|
1279
|
+
return left.context === right.context;
|
|
1280
|
+
}
|
|
1281
|
+
function validateName(subject, value) {
|
|
1282
|
+
if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
|
|
1283
|
+
}
|
|
1284
|
+
//#endregion
|
|
1285
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypt_66ec5f64228d8b6b846c0e0318a734f3/node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
|
|
1286
|
+
/**
|
|
1287
|
+
* Random v4 UUID, minted from `crypto.getRandomValues`.
|
|
1288
|
+
* @returns the UUID string.
|
|
1289
|
+
*/
|
|
1290
|
+
function randomUUID$1() {
|
|
1291
|
+
const bytes = globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
|
|
1292
|
+
const hex = Array.from(bytes, (byte, index) => {
|
|
1293
|
+
return (index === 6 ? byte & 15 | 64 : index === 8 ? byte & 63 | 128 : byte).toString(16).padStart(2, "0");
|
|
1294
|
+
}).join("");
|
|
1295
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1296
|
+
}
|
|
1297
|
+
//#endregion
|
|
1298
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.5-rc.1_@deepseek-ai+cordis@4.0.1/node_modules/@deepseek-ai/dsh-brand/lib/index.js
|
|
1299
|
+
/**
|
|
1300
|
+
* Duplicate-install-safe nominal primitive helpers.
|
|
1301
|
+
*
|
|
1302
|
+
* A brand makes structurally identical strings or numbers non-interchangeable
|
|
1303
|
+
* at the type level: a `SessionId` cannot be passed where a `ToolCallId` is
|
|
1304
|
+
* expected, and an event sequence cannot be passed as a log offset. Comparison,
|
|
1305
|
+
* logging, and serialization retain the underlying primitive behavior.
|
|
1306
|
+
*
|
|
1307
|
+
* This package owns no concrete domain value and keeps no runtime identity or mutable
|
|
1308
|
+
* state, so independently installed copies produce interchangeable values.
|
|
1309
|
+
*
|
|
1310
|
+
* @module @deepseek-ai/dsh-brand
|
|
1311
|
+
*/
|
|
1312
|
+
/**
|
|
1313
|
+
* Apply a compile-time string brand without changing the value.
|
|
1314
|
+
* @param value - string admitted by the domain that owns the target brand.
|
|
1315
|
+
* @returns the same string with the requested compile-time brand.
|
|
1316
|
+
*/
|
|
1317
|
+
function brandString(value) {
|
|
1047
1318
|
return value;
|
|
1048
1319
|
}
|
|
1320
|
+
//#endregion
|
|
1321
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0._5b95bb48758e6032066aa142c5fbb450/node_modules/@deepseek-ai/dsh-timeout/lib/index.js
|
|
1322
|
+
/** Largest delay Node schedules without clamping it to one millisecond. */
|
|
1323
|
+
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
1324
|
+
//#endregion
|
|
1325
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.5-rc.1_@deepseek-ai+cordis@4.0.1/node_modules/@deepseek-ai/dsh-llm/lib/index.js
|
|
1049
1326
|
/**
|
|
1050
1327
|
* Detach and deep-freeze a message whose identity already exists.
|
|
1051
1328
|
* @param message - complete message, including its stable identity.
|
|
@@ -1062,7 +1339,7 @@ function freezeMessage(message) {
|
|
|
1062
1339
|
function createMessage(input) {
|
|
1063
1340
|
return freezeMessage({
|
|
1064
1341
|
...input,
|
|
1065
|
-
id:
|
|
1342
|
+
id: brandString(randomUUID$1())
|
|
1066
1343
|
});
|
|
1067
1344
|
}
|
|
1068
1345
|
/**
|
|
@@ -1144,255 +1421,1076 @@ const alwaysPolicySchema = z.object({
|
|
|
1144
1421
|
backoff: backoffSchema
|
|
1145
1422
|
});
|
|
1146
1423
|
z.union([normalPolicySchema, alwaysPolicySchema]);
|
|
1424
|
+
const NORMAL_POLICY_KEYS = /* @__PURE__ */ new Set([
|
|
1425
|
+
"mode",
|
|
1426
|
+
"maxRetries",
|
|
1427
|
+
"retryableCodes",
|
|
1428
|
+
"backoff"
|
|
1429
|
+
]);
|
|
1430
|
+
const ALWAYS_POLICY_KEYS = /* @__PURE__ */ new Set([
|
|
1431
|
+
"mode",
|
|
1432
|
+
"maxRetries",
|
|
1433
|
+
"retryableCodes",
|
|
1434
|
+
"backoff"
|
|
1435
|
+
]);
|
|
1436
|
+
const BACKOFF_KEYS = /* @__PURE__ */ new Set([
|
|
1437
|
+
"initialDelayMs",
|
|
1438
|
+
"maxDelayMs",
|
|
1439
|
+
"jitterRatio"
|
|
1440
|
+
]);
|
|
1441
|
+
function validateKeys(value, allowed, path) {
|
|
1442
|
+
for (const key of Object.keys(value)) if (!allowed.has(key)) throw new Error(`${path}: unknown key "${key}"`);
|
|
1443
|
+
}
|
|
1444
|
+
function resolveBackoff(config, path) {
|
|
1445
|
+
if (config !== void 0) validateKeys(config, BACKOFF_KEYS, path);
|
|
1446
|
+
const initialDelayMs = config?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
1447
|
+
const maxDelayMs = config?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
1448
|
+
const jitterRatio = config?.jitterRatio ?? DEFAULT_JITTER_RATIO;
|
|
1449
|
+
if (!Number.isFinite(initialDelayMs) || initialDelayMs <= 0 || initialDelayMs > 2147483647) throw new Error(`${path}.initialDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
1450
|
+
if (!Number.isFinite(maxDelayMs) || maxDelayMs <= 0 || maxDelayMs > 2147483647) throw new Error(`${path}.maxDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
1451
|
+
if (initialDelayMs > maxDelayMs) throw new Error(`${path}.initialDelayMs must be less than or equal to maxDelayMs`);
|
|
1452
|
+
if (!Number.isFinite(jitterRatio) || jitterRatio < 0 || jitterRatio > 1) throw new Error(`${path}.jitterRatio must be between 0 and 1`);
|
|
1453
|
+
return Object.freeze({
|
|
1454
|
+
initialDelayMs,
|
|
1455
|
+
maxDelayMs,
|
|
1456
|
+
jitterRatio
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1147
1459
|
/**
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
-
* App-attribution vocabulary for provider requests.
|
|
1153
|
-
* @module @deepseek-ai/dsh-llm/attribution
|
|
1460
|
+
* Validate, default, and detach one provider-owned retry policy.
|
|
1461
|
+
* @param config - optional provider configuration; omission selects normal defaults.
|
|
1462
|
+
* @param path - diagnostic path naming the provider config that owns the value.
|
|
1463
|
+
* @returns an immutable policy safe to capture in provider registration state.
|
|
1154
1464
|
*/
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1465
|
+
function resolveRetryPolicy(config, path) {
|
|
1466
|
+
if (config === void 0) return Object.freeze({
|
|
1467
|
+
mode: "normal",
|
|
1468
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
1469
|
+
retryableCodes: DEFAULT_RETRYABLE_CODES,
|
|
1470
|
+
...resolveBackoff(void 0, `${path}.backoff`)
|
|
1471
|
+
});
|
|
1472
|
+
switch (config.mode) {
|
|
1473
|
+
case "normal": {
|
|
1474
|
+
validateKeys(config, NORMAL_POLICY_KEYS, path);
|
|
1475
|
+
const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1476
|
+
const retryableCodes = config.retryableCodes ?? [...DEFAULT_RETRYABLE_CODES];
|
|
1477
|
+
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) throw new Error(`${path}.maxRetries must be a non-negative safe integer`);
|
|
1478
|
+
if (retryableCodes.length === 0) throw new Error(`${path}.retryableCodes must not be empty`);
|
|
1479
|
+
if (retryableCodes.some((code) => typeof code !== "string" || code.length === 0)) throw new Error(`${path}.retryableCodes must contain only non-empty strings`);
|
|
1480
|
+
if (new Set(retryableCodes).size !== retryableCodes.length) throw new Error(`${path}.retryableCodes must not contain duplicates`);
|
|
1481
|
+
return Object.freeze({
|
|
1482
|
+
mode: "normal",
|
|
1483
|
+
maxRetries,
|
|
1484
|
+
retryableCodes: Object.freeze([...retryableCodes]),
|
|
1485
|
+
...resolveBackoff(config.backoff, `${path}.backoff`)
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
case "always":
|
|
1489
|
+
validateKeys(config, ALWAYS_POLICY_KEYS, path);
|
|
1490
|
+
return Object.freeze({
|
|
1491
|
+
mode: "always",
|
|
1492
|
+
...resolveBackoff(config.backoff, `${path}.backoff`)
|
|
1493
|
+
});
|
|
1494
|
+
default: throw new Error(`${path}.mode must be "normal" or "always"`);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Field-wise equality over {@link LlmCallConfig} — the comparison a caller
|
|
1499
|
+
* runs to decide whether a proposed configuration is a real change (worth a
|
|
1500
|
+
* logged header snapshot) or the held one restated.
|
|
1501
|
+
* @param a - one configuration.
|
|
1502
|
+
* @param b - the other.
|
|
1503
|
+
* @returns whether every field (including the `stop` list, element-wise) matches.
|
|
1504
|
+
*/
|
|
1505
|
+
function callConfigEquals(a, b) {
|
|
1506
|
+
if (a.provider !== b.provider || a.model !== b.model || a.reasoningEffort !== b.reasoningEffort || a.temperature !== b.temperature || a.maxTokens !== b.maxTokens) return false;
|
|
1507
|
+
if (a.stop === void 0 || b.stop === void 0) return a.stop === b.stop;
|
|
1508
|
+
return a.stop.length === b.stop.length && a.stop.every((s, i) => s === b.stop?.[i]);
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Normalization for values thrown by a final LLM adapter boundary.
|
|
1512
|
+
*
|
|
1513
|
+
* @module @deepseek-ai/dsh-llm/adapter-failure
|
|
1162
1514
|
*/
|
|
1163
1515
|
/**
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
1166
|
-
* @
|
|
1167
|
-
* @
|
|
1168
|
-
* @returns never — it always throws, with the offending value JSON-rendered in the message.
|
|
1516
|
+
* Detach serializable provider facts from a value thrown by an adapter.
|
|
1517
|
+
* @param value - arbitrary value thrown during adapter dispatch or iteration.
|
|
1518
|
+
* @returns immutable provider-neutral facts suitable for a terminal finish chunk.
|
|
1519
|
+
* @internal
|
|
1169
1520
|
*/
|
|
1170
|
-
function
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1521
|
+
function normalizeLlmFailure(value) {
|
|
1522
|
+
const error = value instanceof Error ? value : new HarnessError(thrownMessage(value), "UNKNOWN", { cause: value });
|
|
1523
|
+
const carried = ownFailureSnapshot(error);
|
|
1524
|
+
if (carried !== void 0 && carried.code === ownErrorCode(error)) return carried;
|
|
1525
|
+
return Object.freeze({
|
|
1526
|
+
message: errorMessage$2(error),
|
|
1527
|
+
code: harnessErrorCode(error)
|
|
1528
|
+
});
|
|
1173
1529
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
/** Lossless-JSON validation and detached snapshots for durable session data. @module @deepseek-ai/dsh-session/json */
|
|
1177
|
-
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
1178
|
-
function hasIntrinsicConstructor$1(prototype, name) {
|
|
1179
|
-
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
1180
|
-
if (typeof constructor !== "function") return false;
|
|
1530
|
+
/** Render a non-Error throw without letting hostile coercion escape normalization. */
|
|
1531
|
+
function thrownMessage(value) {
|
|
1181
1532
|
try {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1533
|
+
const message = String(value);
|
|
1534
|
+
return message.length > 0 ? message : "LLM adapter failed";
|
|
1535
|
+
} catch (_hostileThrownValue) {
|
|
1536
|
+
return "LLM adapter failed";
|
|
1185
1537
|
}
|
|
1186
1538
|
}
|
|
1187
|
-
/**
|
|
1188
|
-
function
|
|
1189
|
-
|
|
1539
|
+
/** Read a foreign error's own data-backed `code` without invoking accessors. */
|
|
1540
|
+
function ownErrorCode(error) {
|
|
1541
|
+
try {
|
|
1542
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
1543
|
+
return descriptor !== void 0 && "value" in descriptor ? descriptor.value : void 0;
|
|
1544
|
+
} catch (_sdkPropertyTrap) {
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1190
1547
|
}
|
|
1191
|
-
/**
|
|
1192
|
-
function
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1548
|
+
/** Snapshot an own data property without invoking an SDK-defined accessor. */
|
|
1549
|
+
function ownFailureSnapshot(error) {
|
|
1550
|
+
try {
|
|
1551
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "failure");
|
|
1552
|
+
return descriptor !== void 0 && "value" in descriptor ? failureSnapshot(descriptor.value) : void 0;
|
|
1553
|
+
} catch (_sdkPropertyTrap) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1197
1556
|
}
|
|
1198
|
-
/**
|
|
1199
|
-
function
|
|
1200
|
-
|
|
1201
|
-
|
|
1557
|
+
/** Validate and detach an arbitrary serializable failure payload. */
|
|
1558
|
+
function failureSnapshot(value) {
|
|
1559
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1560
|
+
try {
|
|
1561
|
+
const candidate = value;
|
|
1562
|
+
const message = candidate.message;
|
|
1563
|
+
const code = candidate.code;
|
|
1564
|
+
const status = candidate.status;
|
|
1565
|
+
const providerRetryAfterMs = candidate.providerRetryAfterMs;
|
|
1566
|
+
const requestId = candidate.requestId;
|
|
1567
|
+
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)) return void 0;
|
|
1568
|
+
return Object.freeze({
|
|
1569
|
+
message,
|
|
1570
|
+
code,
|
|
1571
|
+
...status === void 0 ? {} : { status },
|
|
1572
|
+
...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },
|
|
1573
|
+
...requestId === void 0 ? {} : { requestId }
|
|
1574
|
+
});
|
|
1575
|
+
} catch (_sdkFailureGetter) {
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1202
1578
|
}
|
|
1203
|
-
/**
|
|
1204
|
-
function
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1579
|
+
/** Read an SDK error message without letting an accessor replace the primary failure. */
|
|
1580
|
+
function errorMessage$2(error) {
|
|
1581
|
+
try {
|
|
1582
|
+
const message = error.message;
|
|
1583
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
1584
|
+
} catch (_sdkMessageGetter) {}
|
|
1585
|
+
return "LLM adapter failed";
|
|
1208
1586
|
}
|
|
1209
|
-
/**
|
|
1210
|
-
function
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1587
|
+
/** Trust only Harness-owned codes; third-party SDK codes are not our taxonomy. */
|
|
1588
|
+
function harnessErrorCode(error) {
|
|
1589
|
+
return error instanceof HarnessError ? error.code : "UNKNOWN";
|
|
1590
|
+
}
|
|
1591
|
+
function quoted(value) {
|
|
1592
|
+
return JSON.stringify(value);
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Stable text shown to a model that cannot accept one durable image reference.
|
|
1596
|
+
* @param ref - durable normalized attachment omitted from the request.
|
|
1597
|
+
* @returns deterministic text-only placeholder.
|
|
1598
|
+
*/
|
|
1599
|
+
function textOnlyImageText(ref) {
|
|
1600
|
+
return `[image omitted because this model accepts text only; attachment sha256:${String(ref.attachmentId).slice(7, 15)}]`;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* True when typed model content contains an image block, walking nested
|
|
1604
|
+
* tool-result content. This is the one recursive image walk shared by every
|
|
1605
|
+
* image policy (capability gating, text-only serialization, compaction
|
|
1606
|
+
* survey), so a consumer cannot silently diverge on nesting depth.
|
|
1607
|
+
* @param content - typed model content blocks.
|
|
1608
|
+
* @returns whether any nested block is an image.
|
|
1609
|
+
*/
|
|
1610
|
+
function contentHasImage(content) {
|
|
1611
|
+
return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* True when typed model content contains a file block, walking nested
|
|
1615
|
+
* tool-result content on the same recursion every file policy shares.
|
|
1616
|
+
* Reads current content on every call without retaining scan results.
|
|
1617
|
+
* @param content - typed model content blocks.
|
|
1618
|
+
* @returns whether any nested block is a file.
|
|
1619
|
+
*/
|
|
1620
|
+
function contentHasFile(content) {
|
|
1621
|
+
for (const block of content) if (block.type === "file" || block.type === "tool-result" && contentHasFile(block.content)) return true;
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Stable model-facing handle for one durable file reference: the address of
|
|
1626
|
+
* the verbatim stored copy and the instruction to read it on demand. This is
|
|
1627
|
+
* the only representation a provider ever receives for a file.
|
|
1628
|
+
* @param ref - durable verbatim file reference.
|
|
1629
|
+
* @param readonlyPath - execution-world path of the stored copy, when resolvable.
|
|
1630
|
+
* @returns deterministic handle text naming the file, its size, and its address.
|
|
1631
|
+
*/
|
|
1632
|
+
function fileHandleText(ref, readonlyPath) {
|
|
1633
|
+
const digest = String(ref.attachmentId).slice(7, 15);
|
|
1634
|
+
const identity = `File ${quoted(ref.name)} (${ref.bytes} bytes, sha256:${digest})`;
|
|
1635
|
+
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.]`;
|
|
1636
|
+
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.]`;
|
|
1637
|
+
}
|
|
1638
|
+
/** Replace every file occurrence, including nested tool results, with handle text. */
|
|
1639
|
+
function replaceFilesWithHandles(blocks, resolvePath) {
|
|
1640
|
+
let next;
|
|
1641
|
+
for (const [index, block] of blocks.entries()) {
|
|
1642
|
+
if (block.type === "file") {
|
|
1643
|
+
next ??= blocks.slice(0, index);
|
|
1644
|
+
next.push({
|
|
1645
|
+
type: "text",
|
|
1646
|
+
text: fileHandleText(block.attachment, resolvePath(block.attachment))
|
|
1256
1647
|
});
|
|
1257
1648
|
continue;
|
|
1258
1649
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
1270
|
-
assign(task.destination, current);
|
|
1271
|
-
continue;
|
|
1650
|
+
if (block.type === "tool-result") {
|
|
1651
|
+
const content = replaceFilesWithHandles(block.content, resolvePath);
|
|
1652
|
+
if (content !== block.content) {
|
|
1653
|
+
next ??= blocks.slice(0, index);
|
|
1654
|
+
next.push({
|
|
1655
|
+
...block,
|
|
1656
|
+
content
|
|
1657
|
+
});
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1272
1660
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1661
|
+
next?.push(block);
|
|
1662
|
+
}
|
|
1663
|
+
return next ?? blocks;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Project durable file history into deterministic handle text for every model
|
|
1667
|
+
* route. Unlike images, no provider receives file blocks natively, so this
|
|
1668
|
+
* projection is unconditional in request assembly.
|
|
1669
|
+
* @param messages - complete request history.
|
|
1670
|
+
* @param resolvePath - resolve one reference's current execution-world read path.
|
|
1671
|
+
* @returns the original list without files, otherwise shallow message copies with handle text.
|
|
1672
|
+
*/
|
|
1673
|
+
function projectFilesToText(messages, resolvePath) {
|
|
1674
|
+
if (!messages.some((message) => contentHasFile(message.content))) return messages;
|
|
1675
|
+
return messages.map((message) => {
|
|
1676
|
+
const content = replaceFilesWithHandles(message.content, resolvePath);
|
|
1677
|
+
return content === message.content ? message : {
|
|
1678
|
+
...message,
|
|
1679
|
+
content
|
|
1680
|
+
};
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
/** Replace every image occurrence, including nested tool results, for a text-only model. */
|
|
1684
|
+
function replaceImagesForTextModel(blocks) {
|
|
1685
|
+
let next;
|
|
1686
|
+
for (const [index, block] of blocks.entries()) {
|
|
1687
|
+
if (block.type === "image") {
|
|
1688
|
+
next ??= blocks.slice(0, index);
|
|
1689
|
+
next.push({
|
|
1690
|
+
type: "text",
|
|
1691
|
+
text: textOnlyImageText(block.attachment)
|
|
1291
1692
|
});
|
|
1292
1693
|
continue;
|
|
1293
1694
|
}
|
|
1294
|
-
if (
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
1305
|
-
const key = keys[index];
|
|
1306
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
1307
|
-
if (key === void 0) return void 0;
|
|
1308
|
-
tasks.push({
|
|
1309
|
-
kind: "object-property",
|
|
1310
|
-
source: current,
|
|
1311
|
-
key,
|
|
1312
|
-
...target === void 0 ? {} : { target }
|
|
1313
|
-
});
|
|
1695
|
+
if (block.type === "tool-result") {
|
|
1696
|
+
const content = replaceImagesForTextModel(block.content);
|
|
1697
|
+
if (content !== block.content) {
|
|
1698
|
+
next ??= blocks.slice(0, index);
|
|
1699
|
+
next.push({
|
|
1700
|
+
...block,
|
|
1701
|
+
content
|
|
1702
|
+
});
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1314
1705
|
}
|
|
1706
|
+
next?.push(block);
|
|
1315
1707
|
}
|
|
1316
|
-
return
|
|
1708
|
+
return next ?? blocks;
|
|
1317
1709
|
}
|
|
1318
1710
|
/**
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1322
|
-
* call stack. Accepts ordinary arrays, plain or null-prototype objects, and JSON
|
|
1323
|
-
* scalars; rejects sparse, cyclic, exotic, negative-zero, and non-finite values.
|
|
1324
|
-
* Getter throws propagate.
|
|
1325
|
-
*
|
|
1326
|
-
* @param value - the candidate value to validate and detach.
|
|
1327
|
-
* @returns the detached snapshot, or `undefined` when the value is not
|
|
1328
|
-
* losslessly JSON-serializable.
|
|
1711
|
+
* Project durable image history into deterministic text for an exact text-only model.
|
|
1712
|
+
* @param messages - complete request history.
|
|
1713
|
+
* @returns the original list without images, otherwise shallow message copies with stable placeholders.
|
|
1329
1714
|
*/
|
|
1330
|
-
function
|
|
1331
|
-
|
|
1715
|
+
function projectImagesForTextModel(messages) {
|
|
1716
|
+
if (!messages.some((message) => contentHasImage(message.content))) return messages;
|
|
1717
|
+
return messages.map((message) => {
|
|
1718
|
+
const content = replaceImagesForTextModel(message.content);
|
|
1719
|
+
return content === message.content ? message : {
|
|
1720
|
+
...message,
|
|
1721
|
+
content
|
|
1722
|
+
};
|
|
1723
|
+
});
|
|
1332
1724
|
}
|
|
1333
1725
|
/**
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1726
|
+
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
|
|
1727
|
+
* adapters from drifting. See
|
|
1728
|
+
* `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
|
|
1729
|
+
*
|
|
1730
|
+
* App-attribution vocabulary for provider requests.
|
|
1731
|
+
* @module @deepseek-ai/dsh-llm/attribution
|
|
1339
1732
|
*/
|
|
1340
|
-
|
|
1341
|
-
return walkJsonValue(value, false) === true;
|
|
1342
|
-
}
|
|
1343
|
-
//#endregion
|
|
1344
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1._7117760508de0db46d4e3da5e4a645e6/node_modules/@deepseek-ai/dsh-tools/lib/index.js
|
|
1733
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
1345
1734
|
/**
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
* `properties`/`required`/boolean `additionalProperties`, array `items`,
|
|
1350
|
-
* type-correct scalar `enum`/`const`, and exact-one `oneOf`.
|
|
1735
|
+
* LLM service: adapter registry with a waterfall-interceptable streaming call
|
|
1736
|
+
* API. Exports the `LlmRuntime` default, the abstract `LlmAdapter` for
|
|
1737
|
+
* provider backends, and `BlockAssembler` for chunk assembly.
|
|
1351
1738
|
*
|
|
1352
|
-
*
|
|
1353
|
-
* enforcement. Consumers that require an object root apply
|
|
1354
|
-
* {@link assertObjectJsonSchema} before accepting input.
|
|
1355
|
-
* @module dsh-tools/json-schema
|
|
1739
|
+
* @module @deepseek-ai/dsh-llm
|
|
1356
1740
|
*/
|
|
1741
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
1742
|
+
var useValue = arguments.length > 2;
|
|
1743
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
1744
|
+
return useValue ? value : void 0;
|
|
1745
|
+
};
|
|
1746
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
1747
|
+
function accept(f) {
|
|
1748
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
1749
|
+
return f;
|
|
1750
|
+
}
|
|
1751
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
1752
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
1753
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
1754
|
+
var _, done = false;
|
|
1755
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
1756
|
+
var context = {};
|
|
1757
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
1758
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
1759
|
+
context.addInitializer = function(f) {
|
|
1760
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
1761
|
+
extraInitializers.push(accept(f || null));
|
|
1762
|
+
};
|
|
1763
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
1764
|
+
get: descriptor.get,
|
|
1765
|
+
set: descriptor.set
|
|
1766
|
+
} : descriptor[key], context);
|
|
1767
|
+
if (kind === "accessor") {
|
|
1768
|
+
if (result === void 0) continue;
|
|
1769
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
1770
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
1771
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
1772
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
1773
|
+
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
1774
|
+
else descriptor[key] = _;
|
|
1775
|
+
}
|
|
1776
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
1777
|
+
done = true;
|
|
1778
|
+
};
|
|
1357
1779
|
/**
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1780
|
+
* Typed error for LLM-related failures. Extends {@link HarnessError}, so the
|
|
1781
|
+
* `code` string (e.g. `AUTH`, `RATE_LIMIT`, `NO_ADAPTER`) is shared taxonomy.
|
|
1360
1782
|
*/
|
|
1361
|
-
var
|
|
1362
|
-
/**
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1783
|
+
var LlmError = class extends HarnessError {
|
|
1784
|
+
/** Serializable facts retained beside this live Error. */
|
|
1785
|
+
failure;
|
|
1786
|
+
/**
|
|
1787
|
+
* @param message - non-empty human-readable failure summary.
|
|
1788
|
+
* @param code - non-empty stable provider-neutral machine code.
|
|
1789
|
+
* @param options - optional cause and validated serializable provider facts.
|
|
1790
|
+
*/
|
|
1791
|
+
constructor(message, code, options) {
|
|
1792
|
+
if (typeof message !== "string" || message.length === 0) throw new Error("LlmError message must be a non-empty string");
|
|
1793
|
+
if (typeof code !== "string" || code.length === 0) throw new Error("LlmError code must be a non-empty string");
|
|
1794
|
+
if (options?.status !== void 0 && (!Number.isInteger(options.status) || options.status < 100 || options.status > 599)) throw new Error("LlmError status must be an integer from 100 through 599");
|
|
1795
|
+
if (options?.providerRetryAfterMs !== void 0 && (!Number.isFinite(options.providerRetryAfterMs) || options.providerRetryAfterMs <= 0)) throw new Error("LlmError providerRetryAfterMs must be a positive finite number");
|
|
1796
|
+
if (options?.requestId !== void 0 && (typeof options.requestId !== "string" || options.requestId.length === 0)) throw new Error("LlmError requestId must be a non-empty string");
|
|
1797
|
+
super(message, code, options);
|
|
1798
|
+
this.name = "LlmError";
|
|
1799
|
+
this.failure = Object.freeze({
|
|
1800
|
+
message,
|
|
1801
|
+
code,
|
|
1802
|
+
...options?.status === void 0 ? {} : { status: options.status },
|
|
1803
|
+
...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },
|
|
1804
|
+
...options?.requestId === void 0 ? {} : { requestId: options.requestId }
|
|
1805
|
+
});
|
|
1368
1806
|
}
|
|
1369
1807
|
};
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
]
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1808
|
+
(() => {
|
|
1809
|
+
let _classSuper = TypertRemoteService;
|
|
1810
|
+
let _instanceExtraInitializers = [];
|
|
1811
|
+
let _listProviders_decorators;
|
|
1812
|
+
let _listConfigurableProviders_decorators;
|
|
1813
|
+
let _remoteDiscoverModels_decorators;
|
|
1814
|
+
return class LlmRuntime extends _classSuper {
|
|
1815
|
+
static {
|
|
1816
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
1817
|
+
_listProviders_decorators = [Remote];
|
|
1818
|
+
_listConfigurableProviders_decorators = [Remote];
|
|
1819
|
+
_remoteDiscoverModels_decorators = [Remote("discoverModels")];
|
|
1820
|
+
__esDecorate(this, null, _listProviders_decorators, {
|
|
1821
|
+
kind: "method",
|
|
1822
|
+
name: "listProviders",
|
|
1823
|
+
static: false,
|
|
1824
|
+
private: false,
|
|
1825
|
+
access: {
|
|
1826
|
+
has: (obj) => "listProviders" in obj,
|
|
1827
|
+
get: (obj) => obj.listProviders
|
|
1828
|
+
},
|
|
1829
|
+
metadata: _metadata
|
|
1830
|
+
}, null, _instanceExtraInitializers);
|
|
1831
|
+
__esDecorate(this, null, _listConfigurableProviders_decorators, {
|
|
1832
|
+
kind: "method",
|
|
1833
|
+
name: "listConfigurableProviders",
|
|
1834
|
+
static: false,
|
|
1835
|
+
private: false,
|
|
1836
|
+
access: {
|
|
1837
|
+
has: (obj) => "listConfigurableProviders" in obj,
|
|
1838
|
+
get: (obj) => obj.listConfigurableProviders
|
|
1839
|
+
},
|
|
1840
|
+
metadata: _metadata
|
|
1841
|
+
}, null, _instanceExtraInitializers);
|
|
1842
|
+
__esDecorate(this, null, _remoteDiscoverModels_decorators, {
|
|
1843
|
+
kind: "method",
|
|
1844
|
+
name: "remoteDiscoverModels",
|
|
1845
|
+
static: false,
|
|
1846
|
+
private: false,
|
|
1847
|
+
access: {
|
|
1848
|
+
has: (obj) => "remoteDiscoverModels" in obj,
|
|
1849
|
+
get: (obj) => obj.remoteDiscoverModels
|
|
1850
|
+
},
|
|
1851
|
+
metadata: _metadata
|
|
1852
|
+
}, null, _instanceExtraInitializers);
|
|
1853
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
1854
|
+
enumerable: true,
|
|
1855
|
+
configurable: true,
|
|
1856
|
+
writable: true,
|
|
1857
|
+
value: _metadata
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
adapters = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Map());
|
|
1861
|
+
directory = /* @__PURE__ */ new Map();
|
|
1862
|
+
discoveries = /* @__PURE__ */ new Map();
|
|
1863
|
+
constructor(ctx) {
|
|
1864
|
+
super(ctx, "llm");
|
|
1865
|
+
}
|
|
1866
|
+
/** Notify topology observers without letting one broken listener veto the commit. */
|
|
1867
|
+
emitAdaptersUpdated() {
|
|
1868
|
+
let invariantFailure;
|
|
1869
|
+
for (const listener of this.ctx.events.dispatch("emit", ["llm/adapters-updated"])) try {
|
|
1870
|
+
const returned = listener();
|
|
1871
|
+
if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
|
|
1872
|
+
this.warnAdaptersListenerFailure(error);
|
|
1873
|
+
});
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
if (error?.code === "INVARIANT") {
|
|
1876
|
+
invariantFailure ??= error;
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
this.warnAdaptersListenerFailure(error);
|
|
1880
|
+
}
|
|
1881
|
+
if (invariantFailure !== void 0) throw invariantFailure;
|
|
1882
|
+
}
|
|
1883
|
+
/** Contained-listener diagnostic shared by the sync and async failure paths. */
|
|
1884
|
+
warnAdaptersListenerFailure(error) {
|
|
1885
|
+
this.ctx.logger.warn("llm: an llm/adapters-updated listener failed");
|
|
1886
|
+
this.ctx.logger.warn(error);
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Register an adapter for the given provider routes. Throws `LlmError` with code
|
|
1890
|
+
* `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing).
|
|
1891
|
+
* Disposed with the fiber.
|
|
1892
|
+
* @param providers - every provider route this adapter should serve.
|
|
1893
|
+
* @param adapter - the adapter that streams calls for those providers.
|
|
1894
|
+
* @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}.
|
|
1895
|
+
*/
|
|
1896
|
+
registerAdapter(providers, adapter) {
|
|
1897
|
+
const owned = /* @__PURE__ */ new Set();
|
|
1898
|
+
let released = false;
|
|
1899
|
+
const dispose = this.ctx.effect(function* () {
|
|
1900
|
+
if (providers.length === 0) throw new LlmError("an adapter must register at least one provider", "INVALID_ADAPTER");
|
|
1901
|
+
this.commitRoutes(owned, this.prepareRoutes(providers, adapter, owned));
|
|
1902
|
+
yield () => {
|
|
1903
|
+
released = true;
|
|
1904
|
+
for (const provider of owned) this.adapters.delete(provider);
|
|
1905
|
+
owned.clear();
|
|
1906
|
+
this.emitAdaptersUpdated();
|
|
1907
|
+
};
|
|
1908
|
+
}.bind(this), "llm.registerAdapter()");
|
|
1909
|
+
const handle = (() => void dispose());
|
|
1910
|
+
handle.replace = (next) => {
|
|
1911
|
+
if (released) throw new LlmError("a disposed adapter registration cannot replace its routes", "REGISTRATION_DISPOSED");
|
|
1912
|
+
this.commitRoutes(owned, this.prepareRoutes(next, adapter, owned));
|
|
1913
|
+
};
|
|
1914
|
+
return handle;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Validate one candidate route set for `adapter`, treating routes this
|
|
1918
|
+
* registration already holds as available. Nothing is mutated: a rejected
|
|
1919
|
+
* candidate leaves the registry exactly as it was.
|
|
1920
|
+
*/
|
|
1921
|
+
prepareRoutes(providers, adapter, owned) {
|
|
1922
|
+
const unique = /* @__PURE__ */ new Set();
|
|
1923
|
+
const registrations = [];
|
|
1924
|
+
for (const provider of providers) {
|
|
1925
|
+
if (provider.length === 0) throw new LlmError("adapter provider names must be non-empty", "INVALID_ADAPTER");
|
|
1926
|
+
if (unique.has(provider) || this.adapters.has(provider) && !owned.has(provider)) throw new LlmError(`an adapter for provider "${provider}" is already registered`, "DUPLICATE_ADAPTER");
|
|
1927
|
+
const info = adapter.providerInfo(provider);
|
|
1928
|
+
if (typeof info.id !== "string" || info.id !== provider || typeof info.name !== "string" || info.name.length === 0) throw new LlmError(`adapter metadata for provider "${provider}" must preserve its id and have a non-empty name`, "INVALID_ADAPTER");
|
|
1929
|
+
unique.add(provider);
|
|
1930
|
+
const retryPolicy = adapter.providerRetryPolicy(provider) ?? resolveRetryPolicy(void 0, `llm: provider "${provider}" retryPolicy`);
|
|
1931
|
+
registrations.push({
|
|
1932
|
+
adapter,
|
|
1933
|
+
provider: {
|
|
1934
|
+
id: info.id,
|
|
1935
|
+
name: info.name
|
|
1936
|
+
},
|
|
1937
|
+
retryPolicy
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
return registrations;
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Swap this registration's routes for the prepared ones in one synchronous
|
|
1944
|
+
* section, so no observer can see the registry between the release and the
|
|
1945
|
+
* re-registration. The route set's one mutation point is also where
|
|
1946
|
+
* `llm/adapters-updated` is published, so a `replace` announces itself
|
|
1947
|
+
* exactly like a first registration.
|
|
1948
|
+
*/
|
|
1949
|
+
commitRoutes(owned, registrations) {
|
|
1950
|
+
for (const provider of owned) this.adapters.delete(provider);
|
|
1951
|
+
owned.clear();
|
|
1952
|
+
for (const registration of registrations) {
|
|
1953
|
+
this.adapters.set(registration.provider.id, registration);
|
|
1954
|
+
owned.add(registration.provider.id);
|
|
1955
|
+
}
|
|
1956
|
+
this.emitAdaptersUpdated();
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Describe provider routes with a registered adapter.
|
|
1960
|
+
* @returns detached provider metadata in registration order.
|
|
1961
|
+
*/
|
|
1962
|
+
listProviders() {
|
|
1963
|
+
return [...this.adapters.values()].map(({ provider }) => ({ ...provider }));
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Declare provider routes an adapter plugin can activate through
|
|
1967
|
+
* configuration. Registration is all-or-nothing: an empty list, invalid
|
|
1968
|
+
* entry, or a provider already declared by any registration throws
|
|
1969
|
+
* `LlmError` without registering the rest. Disposed with the fiber.
|
|
1970
|
+
* @param entries - every configurable provider this plugin owns.
|
|
1971
|
+
* @returns a handle that withdraws all of them, and can atomically replace them.
|
|
1972
|
+
*/
|
|
1973
|
+
registerConfigurableProviders(entries) {
|
|
1974
|
+
let held = [];
|
|
1975
|
+
let disposed = false;
|
|
1976
|
+
/**
|
|
1977
|
+
* Validate a candidate set in full against everything this registration
|
|
1978
|
+
* does not already hold, then publish it. Nothing is written until the
|
|
1979
|
+
* whole set passes, so a refused candidate leaves the current entries in
|
|
1980
|
+
* place — the property that makes `replace` a swap rather than a
|
|
1981
|
+
* delete-then-add that can strand the directory empty.
|
|
1982
|
+
*/
|
|
1983
|
+
const commit = (candidates) => {
|
|
1984
|
+
const detached = [];
|
|
1985
|
+
const own = new Set(held.map((entry) => entry.provider));
|
|
1986
|
+
for (const entry of candidates) {
|
|
1987
|
+
if (entry.provider.length === 0 || entry.displayName.length === 0 || entry.settingsNs.length === 0) throw new LlmError("configurable providers need a non-empty provider, displayName, and settingsNs", "INVALID_DIRECTORY");
|
|
1988
|
+
if (entry.settingsPath.some((segment) => segment.length === 0)) throw new LlmError(`configurable provider "${entry.provider}" has an empty settingsPath segment`, "INVALID_DIRECTORY");
|
|
1989
|
+
if (this.directory.has(entry.provider) && !own.has(entry.provider) || detached.some((seen) => seen.provider === entry.provider)) throw new LlmError(`configurable provider "${entry.provider}" is already declared`, "DUPLICATE_DIRECTORY");
|
|
1990
|
+
detached.push({
|
|
1991
|
+
...entry,
|
|
1992
|
+
settingsPath: [...entry.settingsPath]
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
for (const entry of held) this.directory.delete(entry.provider);
|
|
1996
|
+
for (const entry of detached) this.directory.set(entry.provider, entry);
|
|
1997
|
+
held = detached;
|
|
1998
|
+
this.emitAdaptersUpdated();
|
|
1999
|
+
};
|
|
2000
|
+
const dispose = this.ctx.effect(function* () {
|
|
2001
|
+
if (entries.length === 0) throw new LlmError("a configurable-provider registration must declare at least one provider", "INVALID_DIRECTORY");
|
|
2002
|
+
commit(entries);
|
|
2003
|
+
yield () => {
|
|
2004
|
+
disposed = true;
|
|
2005
|
+
for (const entry of held) this.directory.delete(entry.provider);
|
|
2006
|
+
held = [];
|
|
2007
|
+
this.emitAdaptersUpdated();
|
|
2008
|
+
};
|
|
2009
|
+
}.bind(this), "llm.registerConfigurableProviders()");
|
|
2010
|
+
const handle = (() => void dispose());
|
|
2011
|
+
handle.replace = (next) => {
|
|
2012
|
+
if (disposed) throw new LlmError("this configurable-provider registration was disposed", "REGISTRATION_DISPOSED");
|
|
2013
|
+
commit(next);
|
|
2014
|
+
};
|
|
2015
|
+
return handle;
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* List every declared configurable provider, registered or dormant.
|
|
2019
|
+
* @returns detached directory entries in declaration order.
|
|
2020
|
+
*/
|
|
2021
|
+
listConfigurableProviders() {
|
|
2022
|
+
return [...this.directory.values()].map((entry) => ({
|
|
2023
|
+
...entry,
|
|
2024
|
+
settingsPath: [...entry.settingsPath]
|
|
2025
|
+
}));
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Offer to interrogate provider endpoints on behalf of the settings
|
|
2029
|
+
* namespace this plugin owns. The namespace is the key because that is what
|
|
2030
|
+
* a configuration surface already holds from the configurable-provider
|
|
2031
|
+
* directory, and because a provider being *added* has no route to name yet.
|
|
2032
|
+
* Disposed with the fiber.
|
|
2033
|
+
* @param settingsNs - the namespace whose profiles this discovery serves.
|
|
2034
|
+
* @param discover - interrogates one endpoint and must honor the supplied signal.
|
|
2035
|
+
* @returns the disposer that withdraws the offer.
|
|
2036
|
+
*/
|
|
2037
|
+
registerModelDiscovery(settingsNs, discover) {
|
|
2038
|
+
const dispose = this.ctx.effect(function* () {
|
|
2039
|
+
if (settingsNs.length === 0) throw new LlmError("model discovery needs a non-empty settings namespace", "INVALID_DISCOVERY");
|
|
2040
|
+
if (this.discoveries.has(settingsNs)) throw new LlmError(`model discovery for "${settingsNs}" is already registered`, "DUPLICATE_DISCOVERY");
|
|
2041
|
+
this.discoveries.set(settingsNs, discover);
|
|
2042
|
+
yield () => {
|
|
2043
|
+
this.discoveries.delete(settingsNs);
|
|
2044
|
+
};
|
|
2045
|
+
}.bind(this), "llm.registerModelDiscovery()");
|
|
2046
|
+
return () => void dispose();
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Interrogate one provider endpoint for the models it advertises. The
|
|
2050
|
+
* request describes a draft, not a stored route, so nothing here reads or
|
|
2051
|
+
* writes settings or credentials — the caller owns both, and the reply is
|
|
2052
|
+
* candidate metadata a surface may offer for adoption.
|
|
2053
|
+
* @param settingsNs - namespace whose registered discovery serves this draft.
|
|
2054
|
+
* @param request - the endpoint, protocol, and one-shot credential to use.
|
|
2055
|
+
* @param signal - caller cancellation.
|
|
2056
|
+
* @returns the advertised models, deduplicated in endpoint order.
|
|
2057
|
+
*/
|
|
2058
|
+
async discoverModels(settingsNs, request, signal) {
|
|
2059
|
+
const discover = this.discoveries.get(settingsNs);
|
|
2060
|
+
if (discover === void 0) throw new LlmError(`no model discovery is registered for "${settingsNs}"`, "NO_DISCOVERY");
|
|
2061
|
+
if ((request.provider ?? "").length === 0 && (request.baseURL ?? "").length === 0) throw new LlmError("model discovery needs a provider route or a baseURL", "INVALID_DISCOVERY");
|
|
2062
|
+
const discovered = signal === void 0 ? await discover(request) : await discover(request, signal);
|
|
2063
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2064
|
+
const models = [];
|
|
2065
|
+
for (const model of discovered) {
|
|
2066
|
+
if (typeof model.id !== "string" || model.id.length === 0 || seen.has(model.id)) continue;
|
|
2067
|
+
seen.add(model.id);
|
|
2068
|
+
models.push({
|
|
2069
|
+
id: model.id,
|
|
2070
|
+
...model.name === void 0 ? {} : { name: model.name },
|
|
2071
|
+
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
2072
|
+
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
return models;
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Remote adapter for one draft provider interrogation.
|
|
2079
|
+
* @param settingsNs - namespace whose registered discovery serves this draft.
|
|
2080
|
+
* @param request - endpoint, protocol, and one-shot credential to use.
|
|
2081
|
+
* @param signal - caller cancellation supplied by the Remote carrier.
|
|
2082
|
+
* @returns advertised models in endpoint order.
|
|
2083
|
+
* @throws RemoteError with `llm/model-discovery-rejected` when discovery refuses or fails.
|
|
2084
|
+
*/
|
|
2085
|
+
async remoteDiscoverModels(settingsNs, request, signal) {
|
|
2086
|
+
try {
|
|
2087
|
+
return await this.discoverModels(settingsNs, request, signal);
|
|
2088
|
+
} catch (error) {
|
|
2089
|
+
throw new RemoteError("llm/model-discovery-rejected", error instanceof Error ? error.message : String(error), {
|
|
2090
|
+
settingsNs,
|
|
2091
|
+
...request.baseURL === void 0 ? {} : { baseURL: request.baseURL }
|
|
2092
|
+
}, { cause: error });
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Resolve the retry policy captured when one provider route was registered.
|
|
2097
|
+
* @param provider - registered provider route to inspect.
|
|
2098
|
+
* @returns the provider-owned policy, with normal defaults already resolved.
|
|
2099
|
+
*/
|
|
2100
|
+
providerRetryPolicy(provider) {
|
|
2101
|
+
return this.registration(provider).retryPolicy;
|
|
2102
|
+
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Resolve provider-side request-image pricing for one exact route, or
|
|
2105
|
+
* `undefined` when the provider is unregistered or declares none. Unknown
|
|
2106
|
+
* providers degrade to `undefined` rather than throwing because callers
|
|
2107
|
+
* price durable history whose route may no longer be mounted.
|
|
2108
|
+
* @param provider - provider route named by a request header.
|
|
2109
|
+
* @param model - exact model id named by the same header.
|
|
2110
|
+
* @returns the owning adapter's image pricing for the route, when declared.
|
|
2111
|
+
*/
|
|
2112
|
+
imageRequestPricing(provider, model) {
|
|
2113
|
+
return this.adapters.get(provider)?.adapter.imageRequestPricing(provider, model);
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
2116
|
+
* Resolve the exact text one durable file occurrence contributes to every
|
|
2117
|
+
* provider request in the current execution environment.
|
|
2118
|
+
* @param ref - durable verbatim file reference from model history.
|
|
2119
|
+
* @returns the same deterministic handle text used at adapter dispatch.
|
|
2120
|
+
*/
|
|
2121
|
+
fileRequestText(ref) {
|
|
2122
|
+
return fileHandleText(ref, this.fileReadPath(ref));
|
|
2123
|
+
}
|
|
2124
|
+
/** Detach typed adapter-owned modality metadata. */
|
|
2125
|
+
detachedModalities(modalities) {
|
|
2126
|
+
return modalities === void 0 ? void 0 : [...modalities];
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Discover models advertised by one registered provider. Catalog membership
|
|
2130
|
+
* is advisory and never changes routing or request validation.
|
|
2131
|
+
* @param provider - registered provider route to inspect.
|
|
2132
|
+
* @returns detached model metadata in adapter-preferred order.
|
|
2133
|
+
*/
|
|
2134
|
+
async listModels(provider) {
|
|
2135
|
+
const models = await this.registration(provider).adapter.listModels(provider);
|
|
2136
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2137
|
+
return models.map((model) => {
|
|
2138
|
+
if (typeof model.provider !== "string" || model.provider !== provider || typeof model.id !== "string" || model.id.length === 0 || typeof model.name !== "string" || model.name.length === 0 || model.description !== void 0 && typeof model.description !== "string" || seen.has(model.id)) throw new LlmError(`adapter returned invalid or duplicate model metadata for provider "${provider}"`, "INVALID_CATALOG");
|
|
2139
|
+
seen.add(model.id);
|
|
2140
|
+
const inputModalities = this.detachedModalities(model.inputModalities);
|
|
2141
|
+
return {
|
|
2142
|
+
provider: model.provider,
|
|
2143
|
+
id: model.id,
|
|
2144
|
+
name: model.name,
|
|
2145
|
+
...model.description === void 0 ? {} : { description: model.description },
|
|
2146
|
+
...inputModalities === void 0 ? {} : { inputModalities }
|
|
2147
|
+
};
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Resolve and validate all metadata from the adapter that owns one exact
|
|
2152
|
+
* route. The result is detached from adapter-owned objects; catalog
|
|
2153
|
+
* membership remains advisory and does not control request routing.
|
|
2154
|
+
* @param provider - registered provider route to inspect.
|
|
2155
|
+
* @param model - exact model id passed to the adapter.
|
|
2156
|
+
* @param signal - optional cancellation for adapter-owned asynchronous lookup.
|
|
2157
|
+
* @returns exact model identity plus available context and reasoning metadata.
|
|
2158
|
+
*/
|
|
2159
|
+
async resolveModelInfo(provider, model, signal) {
|
|
2160
|
+
return this.resolveModelInfoFor(this.registration(provider), model, signal);
|
|
2161
|
+
}
|
|
2162
|
+
async resolveModelInfoFor(registration, model, signal) {
|
|
2163
|
+
const resolved = await registration.adapter.resolveModel(registration.provider.id, model, signal);
|
|
2164
|
+
return this.normalizeModelInfo(registration, model, resolved);
|
|
2165
|
+
}
|
|
2166
|
+
/** Validate and detach one adapter-returned exact model result. */
|
|
2167
|
+
normalizeModelInfo(registration, model, resolved) {
|
|
2168
|
+
const provider = registration.provider.id;
|
|
2169
|
+
if (typeof resolved.provider !== "string" || resolved.provider !== provider || typeof resolved.id !== "string" || resolved.id !== model || typeof resolved.name !== "string" || resolved.name.length === 0 || resolved.description !== void 0 && typeof resolved.description !== "string") throw new LlmError(`adapter returned invalid exact model metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
|
|
2170
|
+
const context = resolved.context;
|
|
2171
|
+
if (context !== void 0 && (!Number.isInteger(context.contextWindow) || context.contextWindow <= 0)) throw new LlmError(`adapter returned invalid context metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_CONTEXT");
|
|
2172
|
+
const inputModalities = this.detachedModalities(resolved.inputModalities);
|
|
2173
|
+
const systemPromptUpdate = resolved.systemPromptUpdate;
|
|
2174
|
+
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");
|
|
2175
|
+
const defaultMaxTokens = resolved.defaultMaxTokens;
|
|
2176
|
+
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");
|
|
2177
|
+
const info = {
|
|
2178
|
+
provider,
|
|
2179
|
+
id: model,
|
|
2180
|
+
name: resolved.name,
|
|
2181
|
+
...resolved.description === void 0 ? {} : { description: resolved.description },
|
|
2182
|
+
...inputModalities === void 0 ? {} : { inputModalities },
|
|
2183
|
+
...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
|
|
2184
|
+
...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens },
|
|
2185
|
+
...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate }
|
|
2186
|
+
};
|
|
2187
|
+
const reasoning = resolved.reasoning;
|
|
2188
|
+
if (reasoning === void 0) return info;
|
|
2189
|
+
if (reasoning.efforts.length === 0) throw new LlmError(`adapter returned invalid reasoning metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
2190
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2191
|
+
const efforts = reasoning.efforts.map((effort) => {
|
|
2192
|
+
if (typeof effort.id !== "string" || effort.id.length === 0 || typeof effort.name !== "string" || effort.name.length === 0 || effort.description !== void 0 && typeof effort.description !== "string" || seen.has(effort.id)) throw new LlmError(`adapter returned invalid or duplicate reasoning effort metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
2193
|
+
seen.add(effort.id);
|
|
2194
|
+
return {
|
|
2195
|
+
id: effort.id,
|
|
2196
|
+
name: effort.name,
|
|
2197
|
+
...effort.description === void 0 ? {} : { description: effort.description }
|
|
2198
|
+
};
|
|
2199
|
+
});
|
|
2200
|
+
if (reasoning.defaultEffort !== void 0 && !seen.has(reasoning.defaultEffort)) throw new LlmError(`adapter returned an unknown default reasoning effort for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
2201
|
+
return {
|
|
2202
|
+
...info,
|
|
2203
|
+
reasoning: {
|
|
2204
|
+
efforts,
|
|
2205
|
+
...reasoning.defaultEffort === void 0 ? {} : { defaultEffort: reasoning.defaultEffort }
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Validate a conversation call config against its exact model capability and
|
|
2211
|
+
* materialize adapter-configured defaults. Unsupported explicit efforts
|
|
2212
|
+
* reject before provider I/O; no clamping or aliasing is performed. This
|
|
2213
|
+
* standalone query does not bind a later dispatch; use {@link prepareCall}
|
|
2214
|
+
* when logging and streaming must share one adapter registration.
|
|
2215
|
+
* @param config - provider/model route and optional request controls.
|
|
2216
|
+
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
2217
|
+
* @returns a detached config only when a default must be materialized.
|
|
2218
|
+
*/
|
|
2219
|
+
async resolveCallConfig(config, signal) {
|
|
2220
|
+
return (await this.resolveCallFor(this.registration(config.provider), config, signal)).config;
|
|
2221
|
+
}
|
|
2222
|
+
async resolveCallFor(registration, config, signal) {
|
|
2223
|
+
const info = await this.resolveModelInfoFor(registration, config.model, signal);
|
|
2224
|
+
return this.resolveCallWithInfo(config, info);
|
|
2225
|
+
}
|
|
2226
|
+
/** Validate request controls against one already-bound exact model result. */
|
|
2227
|
+
resolveCallWithInfo(config, info) {
|
|
2228
|
+
const defaulted = config.maxTokens === void 0 && info.defaultMaxTokens !== void 0 ? {
|
|
2229
|
+
...config,
|
|
2230
|
+
maxTokens: info.defaultMaxTokens
|
|
2231
|
+
} : config;
|
|
2232
|
+
const reasoning = info.reasoning;
|
|
2233
|
+
const requested = defaulted.reasoningEffort;
|
|
2234
|
+
let resolvedConfig = defaulted;
|
|
2235
|
+
if (reasoning === void 0) {
|
|
2236
|
+
if (requested !== void 0) throw new LlmError(`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${requested}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
2237
|
+
} else {
|
|
2238
|
+
const effective = requested ?? reasoning.defaultEffort;
|
|
2239
|
+
if (effective !== void 0) {
|
|
2240
|
+
if (!reasoning.efforts.some((effort) => effort.id === effective)) throw new LlmError(`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${effective}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
2241
|
+
if (requested !== effective) resolvedConfig = {
|
|
2242
|
+
...defaulted,
|
|
2243
|
+
reasoningEffort: effective
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
return {
|
|
2248
|
+
config: resolvedConfig,
|
|
2249
|
+
...info.context === void 0 ? {} : { context: info.context },
|
|
2250
|
+
modelInfo: info
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Resolve one call under its current adapter registration. The returned
|
|
2255
|
+
* one-shot handle keeps that registration across header logging and dispatch,
|
|
2256
|
+
* so HMR cannot combine one adapter's capability result with another adapter.
|
|
2257
|
+
* @param config - provider/model route and optional request controls.
|
|
2258
|
+
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
2259
|
+
* @returns a prepared config and its registration-bound stream entry point.
|
|
2260
|
+
*/
|
|
2261
|
+
async prepareCall(config, signal) {
|
|
2262
|
+
const registration = this.registration(config.provider);
|
|
2263
|
+
const adapterCall = await registration.adapter.prepareCall(config.provider, config.model, signal);
|
|
2264
|
+
const modelInfo = this.normalizeModelInfo(registration, config.model, adapterCall.model);
|
|
2265
|
+
const resolved = this.resolveCallWithInfo(config, modelInfo);
|
|
2266
|
+
const resolvedConfig = deepFreeze(structuredClone(resolved.config));
|
|
2267
|
+
const context = resolved.context === void 0 ? void 0 : deepFreeze(structuredClone(resolved.context));
|
|
2268
|
+
const adapterDefaults = deepFreeze({
|
|
2269
|
+
...config.reasoningEffort === void 0 && resolvedConfig.reasoningEffort !== void 0 ? { reasoningEffort: true } : {},
|
|
2270
|
+
...config.maxTokens === void 0 && resolvedConfig.maxTokens !== void 0 ? { maxTokens: true } : {}
|
|
2271
|
+
});
|
|
2272
|
+
let dispatched = false;
|
|
2273
|
+
return Object.freeze({
|
|
2274
|
+
config: resolvedConfig,
|
|
2275
|
+
retryPolicy: registration.retryPolicy,
|
|
2276
|
+
adapterDefaults,
|
|
2277
|
+
...context === void 0 ? {} : { context },
|
|
2278
|
+
...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
|
|
2279
|
+
...modelInfo.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: modelInfo.systemPromptUpdate },
|
|
2280
|
+
stream: (options) => {
|
|
2281
|
+
if (dispatched) throw new LlmError("a prepared LLM call can only be dispatched once", "INVALID_PREPARED_CALL");
|
|
2282
|
+
if (!callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
2283
|
+
dispatched = true;
|
|
2284
|
+
return this.streamWithRegistration(options, {
|
|
2285
|
+
registration,
|
|
2286
|
+
config: resolvedConfig,
|
|
2287
|
+
modelInfo,
|
|
2288
|
+
dispatch: (options) => adapterCall.stream(options)
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
registration(provider) {
|
|
2294
|
+
const registration = this.adapters.get(provider);
|
|
2295
|
+
if (!registration) throw new LlmError(`no adapter registered for provider "${provider}"`, "NO_ADAPTER");
|
|
2296
|
+
return registration;
|
|
2297
|
+
}
|
|
2298
|
+
/** Remove replay state whose historical route is owned by another adapter. */
|
|
2299
|
+
forAdapter(options, adapter) {
|
|
2300
|
+
const messages = options.messages.map((message) => {
|
|
2301
|
+
const source = message.source;
|
|
2302
|
+
if (message.role !== "assistant" || source.kind !== "model" || source.replayState === void 0) return message;
|
|
2303
|
+
if (this.adapters.get(source.provider)?.adapter === adapter) return message;
|
|
2304
|
+
return freezeMessage({
|
|
2305
|
+
...message,
|
|
2306
|
+
source: {
|
|
2307
|
+
kind: "model",
|
|
2308
|
+
provider: source.provider,
|
|
2309
|
+
model: source.model
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
});
|
|
2313
|
+
if (messages.every((message, index) => message === options.messages[index])) return options;
|
|
2314
|
+
const filtered = {
|
|
2315
|
+
...options,
|
|
2316
|
+
messages
|
|
2317
|
+
};
|
|
2318
|
+
return Object.isFrozen(options) ? deepFreeze(filtered) : filtered;
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Resolve the current execution-world read path of one durable file
|
|
2322
|
+
* reference through the mounted attachment and filesystem providers.
|
|
2323
|
+
*/
|
|
2324
|
+
fileReadPath(ref) {
|
|
2325
|
+
let hostPath;
|
|
2326
|
+
try {
|
|
2327
|
+
hostPath = this.ctx.get("attachments")?.fileHostPath(ref);
|
|
2328
|
+
} catch {
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
if (hostPath === void 0) return void 0;
|
|
2332
|
+
return this.ctx.get("fs")?.processPathFromHostPath(hostPath);
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Final adapter boundary. Adapter selection, dispatch, iterator construction,
|
|
2336
|
+
* and iteration failures become one terminal failure chunk. Middleware and
|
|
2337
|
+
* downstream consumer failures remain thrown plugin or consumer errors.
|
|
2338
|
+
*/
|
|
2339
|
+
async *adapterStream(options, prepared) {
|
|
2340
|
+
let iterator;
|
|
2341
|
+
try {
|
|
2342
|
+
const registration = prepared?.registration ?? this.registration(options.provider);
|
|
2343
|
+
const adapter = registration.adapter;
|
|
2344
|
+
let modelInfo;
|
|
2345
|
+
let resolvedConfig;
|
|
2346
|
+
let dispatch;
|
|
2347
|
+
if (prepared === void 0) {
|
|
2348
|
+
const adapterCall = await adapter.prepareCall(options.provider, options.model, options.signal);
|
|
2349
|
+
modelInfo = this.normalizeModelInfo(registration, options.model, adapterCall.model);
|
|
2350
|
+
resolvedConfig = this.resolveCallWithInfo(options, modelInfo).config;
|
|
2351
|
+
dispatch = (options) => adapterCall.stream(options);
|
|
2352
|
+
} else {
|
|
2353
|
+
modelInfo = prepared.modelInfo;
|
|
2354
|
+
resolvedConfig = prepared.config;
|
|
2355
|
+
dispatch = prepared.dispatch;
|
|
2356
|
+
}
|
|
2357
|
+
if (prepared !== void 0 && !callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
2358
|
+
const resolvedOptions = callConfigEquals(options, resolvedConfig) ? options : Object.isFrozen(options) ? deepFreeze({
|
|
2359
|
+
...options,
|
|
2360
|
+
...resolvedConfig
|
|
2361
|
+
}) : {
|
|
2362
|
+
...options,
|
|
2363
|
+
...resolvedConfig
|
|
2364
|
+
};
|
|
2365
|
+
let projectedMessages = resolvedOptions.messages;
|
|
2366
|
+
if (projectedMessages.some((message) => contentHasFile(message.content))) projectedMessages = projectFilesToText(projectedMessages, (ref) => this.fileReadPath(ref));
|
|
2367
|
+
if (modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && projectedMessages.some((message) => contentHasImage(message.content))) projectedMessages = projectImagesForTextModel(projectedMessages);
|
|
2368
|
+
const projectedOptions = projectedMessages === resolvedOptions.messages ? resolvedOptions : Object.isFrozen(resolvedOptions) ? deepFreeze({
|
|
2369
|
+
...resolvedOptions,
|
|
2370
|
+
messages: projectedMessages
|
|
2371
|
+
}) : {
|
|
2372
|
+
...resolvedOptions,
|
|
2373
|
+
messages: projectedMessages
|
|
2374
|
+
};
|
|
2375
|
+
iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
|
|
2376
|
+
} catch (error) {
|
|
2377
|
+
yield adapterFailureChunk(error, options.signal);
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
let completed = false;
|
|
2381
|
+
try {
|
|
2382
|
+
while (true) {
|
|
2383
|
+
let item;
|
|
2384
|
+
try {
|
|
2385
|
+
const next = await iterator.next();
|
|
2386
|
+
item = next.done ? { done: true } : {
|
|
2387
|
+
done: false,
|
|
2388
|
+
value: next.value
|
|
2389
|
+
};
|
|
2390
|
+
} catch (error) {
|
|
2391
|
+
completed = true;
|
|
2392
|
+
yield adapterFailureChunk(error, options.signal);
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
if (item.done) {
|
|
2396
|
+
completed = true;
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
yield item.value;
|
|
2400
|
+
}
|
|
2401
|
+
} finally {
|
|
2402
|
+
if (!completed) {
|
|
2403
|
+
const close = iterator.return?.bind(iterator);
|
|
2404
|
+
if (close) await close();
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Stream one model call as raw chunks (token-level deltas). Replay state is
|
|
2410
|
+
* retained only when the same adapter instance owns its historical provider
|
|
2411
|
+
* and the target provider. Final adapter selection remains fixed through
|
|
2412
|
+
* asynchronous exact-model resolution and dispatch. Adapter selection,
|
|
2413
|
+
* dispatch, and iteration failures become terminal `error` or `aborted`
|
|
2414
|
+
* finish chunks; middleware, nested-call, cleanup, and consumer failures
|
|
2415
|
+
* remain thrown.
|
|
2416
|
+
* @param options - the full request; `options.provider` selects the adapter.
|
|
2417
|
+
* @returns the chunk stream, possibly wrapped by `llm/stream` listeners.
|
|
2418
|
+
*/
|
|
2419
|
+
stream(options) {
|
|
2420
|
+
return this.streamWithRegistration(options);
|
|
2421
|
+
}
|
|
2422
|
+
streamWithRegistration(options, prepared) {
|
|
2423
|
+
return this.ctx.waterfall(this, "llm/stream", options, () => this.adapterStream(options, prepared));
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
})();
|
|
2427
|
+
/** Convert one adapter throw into the stream protocol's terminal outcome. */
|
|
2428
|
+
function adapterFailureChunk(error, signal) {
|
|
2429
|
+
const failure = normalizeLlmFailure(error);
|
|
2430
|
+
return {
|
|
2431
|
+
type: "finish",
|
|
2432
|
+
reason: signal?.aborted || failure.code === "ABORTED" ? {
|
|
2433
|
+
kind: "aborted",
|
|
2434
|
+
failure
|
|
2435
|
+
} : {
|
|
2436
|
+
kind: "error",
|
|
2437
|
+
failure
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
//#endregion
|
|
2442
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1._aaa8ad560a1b4ea9b6e9490b78b8a32d/node_modules/@deepseek-ai/dsh-tools/lib/index.js
|
|
2443
|
+
/**
|
|
2444
|
+
* Enforced JSON Schema subset shared by tool outputs, generated PTC mode
|
|
2445
|
+
* types, subagents, and workflows. The subset accepts any JSON root, an
|
|
2446
|
+
* annotation-only schema for unconstrained JSON, one scalar `type`, object
|
|
2447
|
+
* `properties`/`required`/boolean `additionalProperties`, array `items`,
|
|
2448
|
+
* type-correct scalar `enum`/`const`, and exact-one `oneOf`.
|
|
2449
|
+
*
|
|
2450
|
+
* Unsupported or misplaced keywords reject rather than being accepted without
|
|
2451
|
+
* enforcement. Consumers that require an object root apply
|
|
2452
|
+
* {@link assertObjectJsonSchema} before accepting input.
|
|
2453
|
+
* @module dsh-tools/json-schema
|
|
2454
|
+
*/
|
|
2455
|
+
/**
|
|
2456
|
+
* Thrown when a raw schema falls outside the enforced subset. `violations`
|
|
2457
|
+
* lists every offending path instead of stopping at the first author error.
|
|
2458
|
+
*/
|
|
2459
|
+
var JsonSchemaError = class extends HarnessError {
|
|
2460
|
+
/** Individual schema violations in walk order. */
|
|
2461
|
+
violations;
|
|
2462
|
+
constructor(violations) {
|
|
2463
|
+
super(`unsupported JSON schema: ${violations.join("; ")}`, "UNSUPPORTED_SCHEMA");
|
|
2464
|
+
this.name = "JsonSchemaError";
|
|
2465
|
+
this.violations = violations;
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2468
|
+
const CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2469
|
+
"type",
|
|
2470
|
+
"oneOf",
|
|
2471
|
+
"properties",
|
|
2472
|
+
"required",
|
|
2473
|
+
"additionalProperties",
|
|
2474
|
+
"items",
|
|
2475
|
+
"enum",
|
|
2476
|
+
"const"
|
|
2477
|
+
]);
|
|
2478
|
+
const ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2479
|
+
"description",
|
|
2480
|
+
"title",
|
|
2481
|
+
"default",
|
|
2482
|
+
"examples"
|
|
2483
|
+
]);
|
|
2484
|
+
const SCHEMA_TYPES = [
|
|
2485
|
+
"object",
|
|
2486
|
+
"array",
|
|
2487
|
+
"string",
|
|
2488
|
+
"number",
|
|
2489
|
+
"integer",
|
|
2490
|
+
"boolean",
|
|
2491
|
+
"null"
|
|
2492
|
+
];
|
|
2493
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
1396
2494
|
function hasIntrinsicConstructor(prototype, name) {
|
|
1397
2495
|
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
1398
2496
|
if (typeof constructor !== "function") return false;
|
|
@@ -2194,13 +3292,13 @@ function defineTool(options) {
|
|
|
2194
3292
|
return tool;
|
|
2195
3293
|
}
|
|
2196
3294
|
/**
|
|
2197
|
-
*
|
|
3295
|
+
* PTC mode `run_code` transport. Programs call the registry's agent-visible
|
|
2198
3296
|
* tools through nested executions scheduled under the native concurrency
|
|
2199
3297
|
* contract; each sub-dispatch is logged for reconstruction, while only the
|
|
2200
3298
|
* outer curated result enters model history.
|
|
2201
|
-
* @module @deepseek-ai/dsh-tools/src/
|
|
3299
|
+
* @module @deepseek-ai/dsh-tools/src/ptc
|
|
2202
3300
|
*/
|
|
2203
|
-
/** The model-facing name of the
|
|
3301
|
+
/** The model-facing name of the PTC mode tool. */
|
|
2204
3302
|
const RUN_CODE_NAME = "run_code";
|
|
2205
3303
|
/**
|
|
2206
3304
|
* The TypeScript flavor: the fallback for a schema read with no runtime
|
|
@@ -2518,7 +3616,7 @@ function createRunCodeTool(registry, options) {
|
|
|
2518
3616
|
if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
|
|
2519
3617
|
const normalized = jsonNormalizeArgs(rawArgs);
|
|
2520
3618
|
const n = ++dispatches;
|
|
2521
|
-
const subCallId =
|
|
3619
|
+
const subCallId = brandString(`${String(exec.callId)}:ptc:${n}`);
|
|
2522
3620
|
const input = {
|
|
2523
3621
|
callId: subCallId,
|
|
2524
3622
|
rootCallId: exec.rootCallId,
|
|
@@ -2550,7 +3648,7 @@ function createRunCodeTool(registry, options) {
|
|
|
2550
3648
|
isError: result.isError,
|
|
2551
3649
|
content: result.content
|
|
2552
3650
|
});
|
|
2553
|
-
agent.session.append("tool/
|
|
3651
|
+
agent.session.append("tool/ptc-dispatch", {
|
|
2554
3652
|
rootCallId: exec.rootCallId,
|
|
2555
3653
|
parentCallId: exec.callId,
|
|
2556
3654
|
subCallId,
|
|
@@ -2572,7 +3670,7 @@ function createRunCodeTool(registry, options) {
|
|
|
2572
3670
|
reject(/* @__PURE__ */ new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`));
|
|
2573
3671
|
},
|
|
2574
3672
|
async start() {
|
|
2575
|
-
exec.agent?.session.append("tool/
|
|
3673
|
+
exec.agent?.session.append("tool/ptc-dispatch-start", {
|
|
2576
3674
|
rootCallId: exec.rootCallId,
|
|
2577
3675
|
parentCallId: exec.callId,
|
|
2578
3676
|
subCallId,
|
|
@@ -2606,7 +3704,7 @@ function createRunCodeTool(registry, options) {
|
|
|
2606
3704
|
content: result.content,
|
|
2607
3705
|
source: {
|
|
2608
3706
|
kind: "plugin",
|
|
2609
|
-
plugin: "tools-
|
|
3707
|
+
plugin: "tools-ptc"
|
|
2610
3708
|
}
|
|
2611
3709
|
}));
|
|
2612
3710
|
for (const context of result.additionalContexts ?? []) exec.deferContext(context);
|
|
@@ -2690,7 +3788,7 @@ function createRunCodeTool(registry, options) {
|
|
|
2690
3788
|
return definition;
|
|
2691
3789
|
}
|
|
2692
3790
|
/**
|
|
2693
|
-
*
|
|
3791
|
+
* PTC mode codegen: the pure projection from registered tool schemas to the TypeScript SDK
|
|
2694
3792
|
* text the model programs against (the `tools:sdk` prompt section). Sibling of
|
|
2695
3793
|
* `json-schema.ts` — `schemas()` (native function calling) and this module (the generated
|
|
2696
3794
|
* `declare const tools` API) are two projections of the same store.
|
|
@@ -2897,17 +3995,35 @@ function jsonSchemaToTs(schema, indent = 0) {
|
|
|
2897
3995
|
return "unknown";
|
|
2898
3996
|
}
|
|
2899
3997
|
}
|
|
2900
|
-
/** The fixed model-facing usage contract rendered above the declarations (see the
|
|
3998
|
+
/** The fixed model-facing usage contract rendered above the declarations (see the PTC mode Agent Note's "What the model sees"). */
|
|
2901
3999
|
const SDK_INSTRUCTIONS$1 = `## Writing code for run_code
|
|
2902
4000
|
|
|
2903
|
-
\`run_code\` takes two required arguments: \`code\` — the body of an async TypeScript function (erasable syntax only — no \`enum\` or namespaces; type annotations are advisory, the code runs type-stripped) — and \`description\`, a short summary of what the program does.
|
|
4001
|
+
\`run_code\` takes two required arguments: \`code\` — the body of an async TypeScript function (erasable syntax only — no \`enum\` or namespaces; type annotations are advisory, the code runs type-stripped) — and \`description\`, a short summary of what the program does. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.`;
|
|
4002
|
+
const SDK_PROGRAM_INSTRUCTIONS = `Inside the program:
|
|
2904
4003
|
|
|
2905
4004
|
- Call tools as \`await tools.name(args)\` — quoted access for exotic names: \`tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
|
|
2906
4005
|
- A FAILED tool call rejects with \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose \`message\` is human-readable — \`try/catch\` it to handle and continue.
|
|
2907
4006
|
- Independent read-only calls MAY overlap under \`Promise.all\` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with \`await\`.
|
|
2908
4007
|
- Emit results with \`return\` and/or \`console.log(...)\`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
|
|
2909
4008
|
|
|
2910
|
-
|
|
4009
|
+
Program-only SDK bindings:`;
|
|
4010
|
+
/** Whether one string schema accepts the literal used by the bash example. */
|
|
4011
|
+
function acceptsExampleString(schema, value) {
|
|
4012
|
+
return schema?.type === "string" && (schema.const === void 0 || schema.const === value) && (schema.enum === void 0 || schema.enum.includes(value));
|
|
4013
|
+
}
|
|
4014
|
+
/** Render the bash example only when its literal arguments satisfy the current parameter schema. */
|
|
4015
|
+
function renderBashExample(schemas) {
|
|
4016
|
+
const bash = schemas.find((schema) => schema.name === "bash");
|
|
4017
|
+
if (bash === void 0) return "";
|
|
4018
|
+
const parameters = bash.parameters;
|
|
4019
|
+
if (parameters.type !== "object") return "";
|
|
4020
|
+
const required = parameters.required ?? [];
|
|
4021
|
+
if (required.some((name) => name !== "command" && name !== "description")) return "";
|
|
4022
|
+
if (!acceptsExampleString(parameters.properties?.command, "pwd")) return "";
|
|
4023
|
+
const needsDescription = required.includes("description");
|
|
4024
|
+
if (needsDescription && !acceptsExampleString(parameters.properties?.description, "Show current directory")) return "";
|
|
4025
|
+
return ` When no separate \`bash\` schema is supplied, invoke a declared \`bash\` binding inside \`run_code\`:\n\n\`run_code({ code: "return await tools.bash({ command: 'pwd'${needsDescription ? ", description: 'Show current directory'" : ""} })", description: "Show current directory" })\``;
|
|
4026
|
+
}
|
|
2911
4027
|
/**
|
|
2912
4028
|
* Render the full `tools:sdk` prompt section: the fixed usage instructions
|
|
2913
4029
|
* plus one `declare const tools` interface covering every given tool.
|
|
@@ -2929,7 +4045,7 @@ function renderToolsSdk(schemas) {
|
|
|
2929
4045
|
argsMembers.push(`${pad$1(1)}${renderKey(schema.name)}: ${jsonSchemaToTs(schema.parameters, 1)};`);
|
|
2930
4046
|
outputMembers.push(`${pad$1(1)}${renderKey(schema.name)}: ${jsonSchemaToTs(schema.output, 1)};`);
|
|
2931
4047
|
}
|
|
2932
|
-
|
|
4048
|
+
const declaration = [
|
|
2933
4049
|
`interface ToolArgsMap {${argsMembers.length > 0 ? `\n${argsMembers.join("\n")}\n` : ""}}`,
|
|
2934
4050
|
`interface ToolOutputMap {${outputMembers.length > 0 ? `\n${outputMembers.join("\n")}\n` : ""}}`,
|
|
2935
4051
|
"type ToolName = keyof ToolOutputMap",
|
|
@@ -2944,16 +4060,17 @@ function renderToolsSdk(schemas) {
|
|
|
2944
4060
|
" [K in ToolName]: (args: ToolArgsMap[K]) => Promise<ToolOutputMap[K]>;",
|
|
2945
4061
|
"}"
|
|
2946
4062
|
].join("\n")
|
|
2947
|
-
].join("\n\n")
|
|
4063
|
+
].join("\n\n");
|
|
4064
|
+
return `${SDK_INSTRUCTIONS$1}${renderBashExample(sorted)}\n\n${SDK_PROGRAM_INSTRUCTIONS}\n\n\`\`\`ts\ntype JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }\n\n${declaration}\n\`\`\``;
|
|
2948
4065
|
}
|
|
2949
4066
|
/**
|
|
2950
|
-
*
|
|
4067
|
+
* PTC mode codegen — Python flavor. The pure projection from registered tool schemas to the
|
|
2951
4068
|
* Python SDK text the model programs against under `runtime.language === 'python'`. Sibling of
|
|
2952
4069
|
* {@link ./ts-types.ts | ts-types.ts}; the two files are two projections of the same registry
|
|
2953
4070
|
* store, keyed by the loaded {@link @deepseek-ai/dsh-code-runtime#CodeRuntime.language | code
|
|
2954
4071
|
* runtime's language}.
|
|
2955
4072
|
*
|
|
2956
|
-
* Under `mode: '
|
|
4073
|
+
* Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated
|
|
2957
4074
|
* SDK is the model's ONLY source for each tool's argument names, required fields, types,
|
|
2958
4075
|
* descriptions, and canonical output shapes; under `mode: 'both'` the native schemas ship
|
|
2959
4076
|
* alongside it and it is one of two. Object-shaped arguments and outputs therefore render as one
|
|
@@ -2974,7 +4091,7 @@ const IDENTIFIER = /^[\p{XID_Start}_]\p{XID_Continue}*$/u;
|
|
|
2974
4091
|
* Python identifiers are not ASCII: `路径` is as legal a field name as `path`,
|
|
2975
4092
|
* and rejecting it would degrade the whole enclosing object, dropping every
|
|
2976
4093
|
* field's name, requiredness, and type — information whose only source under
|
|
2977
|
-
* `mode: '
|
|
4094
|
+
* `mode: 'ptc'` is this generated text.
|
|
2978
4095
|
*
|
|
2979
4096
|
* NFKC stability is a second and separate condition, because CPython
|
|
2980
4097
|
* normalizes identifiers at compile time while JSON keys are compared as
|
|
@@ -3121,7 +4238,7 @@ function pad(indent) {
|
|
|
3121
4238
|
* (`SyntaxError: source code string cannot contain null bytes`), whether it
|
|
3122
4239
|
* sits in a docstring or in a comment, so one such byte anywhere in a schema
|
|
3123
4240
|
* description would make the whole generated SDK unparseable — under
|
|
3124
|
-
* `mode: '
|
|
4241
|
+
* `mode: 'ptc'`, the model's only declaration of the tools. The rest are
|
|
3125
4242
|
* legal but invisible; escaping them with the same rule keeps the emitted text
|
|
3126
4243
|
* readable and the treatment uniform.
|
|
3127
4244
|
*
|
|
@@ -3187,7 +4304,7 @@ function describe(schema) {
|
|
|
3187
4304
|
* Backslashes are doubled first, every quote is escaped, and a trailing
|
|
3188
4305
|
* backslash cannot survive: a description ending in `"` or an odd backslash
|
|
3189
4306
|
* would otherwise merge with (or escape) the closing triple quote and make
|
|
3190
|
-
* the generated block —
|
|
4307
|
+
* the generated block — PTC mode's only SDK — syntactically invalid Python.
|
|
3191
4308
|
*/
|
|
3192
4309
|
function docLines(description, indent) {
|
|
3193
4310
|
const collapsed = describe({ description });
|
|
@@ -3641,7 +4758,7 @@ function renderToolsSdkPy(schemas) {
|
|
|
3641
4758
|
* section under a non-native mode; a runtime whose language is not a key
|
|
3642
4759
|
* fails the assembly loudly (same idiom as `toolOrder` violations). Adding a
|
|
3643
4760
|
* new backend language is three parallel edits — a {@link CodeSdkLanguage}
|
|
3644
|
-
* member, an entry here, and a `RUN_CODE_FLAVORS` entry in `
|
|
4761
|
+
* member, an entry here, and a `RUN_CODE_FLAVORS` entry in `ptc.ts` for
|
|
3645
4762
|
* its `run_code` schema strings — plus the renderer function this table points
|
|
3646
4763
|
* at. The `satisfies` clause pins this table's key set to that union, which
|
|
3647
4764
|
* the flavor table is checked against too, so any of the three left out is a
|
|
@@ -3652,17 +4769,11 @@ function renderToolsSdkPy(schemas) {
|
|
|
3652
4769
|
* {@link Config.mode} JSDoc.
|
|
3653
4770
|
*/
|
|
3654
4771
|
/**
|
|
3655
|
-
*
|
|
3656
|
-
* the 100-199 per-tool guidance band, so the model reads which tools it may
|
|
3657
|
-
* call before it reads what each one is for.
|
|
3658
|
-
*/
|
|
3659
|
-
const COLLAPSE_SECTION_ORDER = 99;
|
|
3660
|
-
/**
|
|
3661
|
-
* The model-facing statement of the `code` collapse. Names the consequence
|
|
4772
|
+
* The model-facing statement of the `ptc` collapse. Names the consequence
|
|
3662
4773
|
* (the call fails) and the route (inside the program), because a rule the
|
|
3663
4774
|
* model can only discover by being denied is one it corrects too late.
|
|
3664
4775
|
*/
|
|
3665
|
-
const
|
|
4776
|
+
const PTC_ONLY_INSTRUCTION = `\`${RUN_CODE_NAME}\` is the only tool you can call directly — a tool call naming any other tool fails. Reach every tool the SDK declares below from inside the program.`;
|
|
3666
4777
|
const SDK_RENDERERS = {
|
|
3667
4778
|
typescript: renderToolsSdk,
|
|
3668
4779
|
python: renderToolsSdkPy
|
|
@@ -3809,7 +4920,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
3809
4920
|
static Config = z.object({
|
|
3810
4921
|
mode: z.union([
|
|
3811
4922
|
"native",
|
|
3812
|
-
"
|
|
4923
|
+
"ptc",
|
|
3813
4924
|
"both"
|
|
3814
4925
|
]).default("native"),
|
|
3815
4926
|
maxParallelSubCalls: z.natural().min(1).default(10)
|
|
@@ -3838,10 +4949,10 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
3838
4949
|
/**
|
|
3839
4950
|
* Reserved presentation transport, kept outside the filterable registration
|
|
3840
4951
|
* layers. Built on first need rather than at construction: which agents run
|
|
3841
|
-
* a
|
|
4952
|
+
* a PTC mode is no longer known when the service is constructed, and the
|
|
3842
4953
|
* transport is stateless beyond its closures over `this`.
|
|
3843
4954
|
*/
|
|
3844
|
-
|
|
4955
|
+
ptcTransport;
|
|
3845
4956
|
constructor(ctx, config = {}) {
|
|
3846
4957
|
super(ctx, "tools");
|
|
3847
4958
|
this.defaultMode = config.mode ?? "native";
|
|
@@ -3853,34 +4964,33 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
3853
4964
|
}
|
|
3854
4965
|
}
|
|
3855
4966
|
/**
|
|
3856
|
-
* The prompt statement of the `
|
|
3857
|
-
* {@link sdkSection} is and rendering empty outside an effective `
|
|
4967
|
+
* The prompt statement of the `ptc` executor collapse, registered wherever
|
|
4968
|
+
* {@link sdkSection} is and rendering empty outside an effective `ptc`.
|
|
3858
4969
|
*
|
|
3859
4970
|
* Every tool contributes its own guidance section naming its tool, none of
|
|
3860
|
-
* them qualify how that tool is reached, and they all render before the SDK
|
|
3861
|
-
*
|
|
3862
|
-
*
|
|
3863
|
-
* `
|
|
3864
|
-
*
|
|
3865
|
-
*
|
|
3866
|
-
* before that guidance rather than after it.
|
|
4971
|
+
* them qualify how that tool is reached, and they all render before the SDK.
|
|
4972
|
+
* Without this the model reads a catalog of tools it is told to use and no
|
|
4973
|
+
* statement that only `run_code` may be called, so it emits a native call,
|
|
4974
|
+
* receives `UNKNOWN_TOOL` for a tool the prompt just declared, and concludes
|
|
4975
|
+
* the deployment is inconsistent. Its order places the rule before that
|
|
4976
|
+
* guidance rather than after it.
|
|
3867
4977
|
*
|
|
3868
4978
|
* `both` renders empty: native calls do execute there, so the rule is false.
|
|
3869
4979
|
* @returns the section registration.
|
|
3870
4980
|
*/
|
|
3871
4981
|
collapseSection() {
|
|
3872
4982
|
return {
|
|
3873
|
-
name: "tools:
|
|
3874
|
-
order:
|
|
3875
|
-
text: (context) => this.modeFor(context.scope) === "
|
|
4983
|
+
name: "tools:ptc-only",
|
|
4984
|
+
order: this.ctx.systemPrompt.getSectionOrder("PTC_ONLY"),
|
|
4985
|
+
text: (context) => this.modeFor(context.scope) === "ptc" ? PTC_ONLY_INSTRUCTION : ""
|
|
3876
4986
|
};
|
|
3877
4987
|
}
|
|
3878
4988
|
/**
|
|
3879
|
-
* The generated-SDK prompt section, registered globally by a
|
|
4989
|
+
* The generated-SDK prompt section, registered globally by a PTC mode
|
|
3880
4990
|
* deployment and per scope by {@link presentAs}.
|
|
3881
4991
|
*
|
|
3882
4992
|
* The body regenerates from the CALLING scope, and renders empty for an
|
|
3883
|
-
* agent presenting natively — an agent that opted out under a
|
|
4993
|
+
* agent presenting natively — an agent that opted out under a PTC mode
|
|
3884
4994
|
* deployment still sees the global registration, and an empty section is
|
|
3885
4995
|
* dropped from the rendered prompt.
|
|
3886
4996
|
* @returns the section registration.
|
|
@@ -3888,7 +4998,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
3888
4998
|
sdkSection() {
|
|
3889
4999
|
return {
|
|
3890
5000
|
name: "tools:sdk",
|
|
3891
|
-
order:
|
|
5001
|
+
order: this.ctx.systemPrompt.getSectionOrder("TOOLS_SDK"),
|
|
3892
5002
|
text: (context) => {
|
|
3893
5003
|
const mode = this.modeFor(context.scope);
|
|
3894
5004
|
if (mode === "native") return "";
|
|
@@ -3924,13 +5034,13 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
3924
5034
|
* @returns the shared transport definition.
|
|
3925
5035
|
*/
|
|
3926
5036
|
requireCodeTransport() {
|
|
3927
|
-
this.
|
|
5037
|
+
this.ptcTransport ??= createRunCodeTool(this, {
|
|
3928
5038
|
requireRuntime: () => this.requireCodeRuntime(this.defaultMode),
|
|
3929
5039
|
peekRuntime: () => this.ctx.get("codeRuntime"),
|
|
3930
5040
|
maxParallel: this.maxParallelSubCalls,
|
|
3931
5041
|
shapeDispatchLog: (dispatch) => this.shapeDispatchLog(dispatch)
|
|
3932
5042
|
});
|
|
3933
|
-
return this.
|
|
5043
|
+
return this.ptcTransport;
|
|
3934
5044
|
}
|
|
3935
5045
|
/**
|
|
3936
5046
|
* Present the calling scope's tools in `mode` instead of the deployment
|
|
@@ -3938,7 +5048,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
3938
5048
|
* declaration covers every agent joined under it.
|
|
3939
5049
|
*
|
|
3940
5050
|
* Scoped only, and one declaration per scope: this is how an agent preset
|
|
3941
|
-
* composes
|
|
5051
|
+
* composes PTC mode agents beside native ones in the same process, and a
|
|
3942
5052
|
* process-global override would be the `mode` config field instead.
|
|
3943
5053
|
* @param mode - the presentation the covered agents' models see.
|
|
3944
5054
|
* @returns the exact disposer that restores the deployment default.
|
|
@@ -3973,7 +5083,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
3973
5083
|
};
|
|
3974
5084
|
this.requireCodeRuntime(mode);
|
|
3975
5085
|
const schemas = [...view.visible.values()].map((definition) => this.schemaOf(definition, false));
|
|
3976
|
-
if (mode === "
|
|
5086
|
+
if (mode === "ptc") return {
|
|
3977
5087
|
schemas: schemas.filter((schema) => schema.name === RUN_CODE_NAME),
|
|
3978
5088
|
knownNames: [RUN_CODE_NAME]
|
|
3979
5089
|
};
|
|
@@ -3987,16 +5097,14 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
3987
5097
|
* Read at use time (assembly / run_code execution), NOT via static
|
|
3988
5098
|
* `inject`: an inject entry would hold `ctx.tools` — and every tool plugin
|
|
3989
5099
|
* behind it — hostage to a code runtime existing even under `mode:
|
|
3990
|
-
* 'native'
|
|
3991
|
-
* `sessionPersistence`).
|
|
5100
|
+
* 'native'`.
|
|
3992
5101
|
*
|
|
3993
5102
|
* Assembly and `run_code` execution read separately, so the language is not
|
|
3994
5103
|
* bound to a request. Harmless while one published backend exists — both
|
|
3995
5104
|
* reads return the same flavor — but a reload that swapped in a second
|
|
3996
5105
|
* language between them would hand a program written against one SDK to the
|
|
3997
5106
|
* other. Binding it is deferred until a second backend ships (the first
|
|
3998
|
-
* point it is testable)
|
|
3999
|
-
* [language-dispatch note](../../../../.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md).
|
|
5107
|
+
* point it is testable).
|
|
4000
5108
|
*/
|
|
4001
5109
|
requireCodeRuntime(mode) {
|
|
4002
5110
|
const runtime = this.ctx.get("codeRuntime");
|
|
@@ -4020,7 +5128,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4020
5128
|
assertSupportedJsonSchema(output.schema);
|
|
4021
5129
|
const timeoutMs = definition.timeoutMs;
|
|
4022
5130
|
if (timeoutMs !== void 0 && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) throw new TypeError(`tool "${name}" timeoutMs must be a positive finite number`);
|
|
4023
|
-
if (name === "run_code") throw new Error(`tool name "${RUN_CODE_NAME}" is reserved for the
|
|
5131
|
+
if (name === "run_code") throw new Error(`tool name "${RUN_CODE_NAME}" is reserved for the PTC mode presentation transport and cannot be registered or shadowed`);
|
|
4024
5132
|
return this.layers.effect(this.ctx, (layer) => layer.tools.insert(name, definition), { label: "tools.register()" });
|
|
4025
5133
|
}
|
|
4026
5134
|
/**
|
|
@@ -4040,7 +5148,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4040
5148
|
...allow !== void 0 ? { allow: new Set(allow) } : {},
|
|
4041
5149
|
...deny !== void 0 ? { deny: new Set(deny) } : {}
|
|
4042
5150
|
};
|
|
4043
|
-
if ([...allow ?? [], ...deny ?? []].includes("run_code")) throw new Error(`tools.restrict() cannot name reserved
|
|
5151
|
+
if ([...allow ?? [], ...deny ?? []].includes("run_code")) throw new Error(`tools.restrict() cannot name reserved PTC mode presentation transport "${RUN_CODE_NAME}"; restrict end-capability tools instead`);
|
|
4044
5152
|
const known = this.view(scope).restrictableNames;
|
|
4045
5153
|
const unknown = [...allow ?? [], ...deny ?? []].filter((name) => !known.has(name));
|
|
4046
5154
|
if (unknown.length > 0) throw new Error(`tools.restrict() names unknown global tool${unknown.length > 1 ? "s" : ""} ${unknown.map((n) => `"${n}"`).join(", ")}; known global tools: ${[...known].sort().join(", ") || "(none)"}`);
|
|
@@ -4082,9 +5190,9 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4082
5190
|
* A restriction filters what a scope inherits — the global layer and every
|
|
4083
5191
|
* ancestor layer on its chain — and never what its OWN layer registers.
|
|
4084
5192
|
* That exemption is what a per-child capability filter has to keep intact:
|
|
4085
|
-
* the delegation runtime registers a child's
|
|
4086
|
-
*
|
|
4087
|
-
*
|
|
5193
|
+
* the delegation runtime registers a child's structured-output tool into the
|
|
5194
|
+
* child's own layer, and a filter naming the capabilities the child may use
|
|
5195
|
+
* must not strip the machinery it answers through.
|
|
4088
5196
|
*
|
|
4089
5197
|
* Reading the exempt set as "the global layer" instead of "not mine" held
|
|
4090
5198
|
* only while every model-facing tool sat in the host composition. Once
|
|
@@ -4136,7 +5244,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4136
5244
|
/**
|
|
4137
5245
|
* Resolve the definition that MAY EXECUTE for a call, applying the mode
|
|
4138
5246
|
* collapse at the operation boundary that owns it. The registry view
|
|
4139
|
-
* (`get`) is presentation-agnostic; here a MODEL-DIRECT call under `
|
|
5247
|
+
* (`get`) is presentation-agnostic; here a MODEL-DIRECT call under `ptc`
|
|
4140
5248
|
* may only name the reserved `run_code` transport, while a nested
|
|
4141
5249
|
* sub-dispatch (a `parent` token set — the `run_code` SDK calling a tool
|
|
4142
5250
|
* it bound) may call any visible tool. Denial surfaces as `UNKNOWN_TOOL`
|
|
@@ -4161,7 +5269,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4161
5269
|
schemas(scope) {
|
|
4162
5270
|
return [...this.view(scope).visible.values()].map((definition) => this.schemaOf(definition, true));
|
|
4163
5271
|
}
|
|
4164
|
-
/** Project visible callable tools onto the generated
|
|
5272
|
+
/** Project visible callable tools onto the generated PTC mode SDK contract. */
|
|
4165
5273
|
sdkSchemas(scope) {
|
|
4166
5274
|
return [...this.view(scope).visible.values()].filter((definition) => definition.name !== RUN_CODE_NAME).map((definition) => {
|
|
4167
5275
|
const output = snapshotJsonValue(definition.output.schema);
|
|
@@ -4201,8 +5309,8 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4201
5309
|
}
|
|
4202
5310
|
}
|
|
4203
5311
|
/**
|
|
4204
|
-
* Run the `tools/
|
|
4205
|
-
* and return the content the bridge should log on `tool/
|
|
5312
|
+
* Run the `tools/ptc-dispatch-log` waterfall over one settled sub-dispatch
|
|
5313
|
+
* and return the content the bridge should log on `tool/ptc-dispatch`.
|
|
4206
5314
|
* Contained: when a listener throws, the method logs the original settled
|
|
4207
5315
|
* content; that failure must not fail the dispatch or omit the settle event. Private:
|
|
4208
5316
|
* the ONE consumer is the `run_code` bridge this registry constructs, which
|
|
@@ -4211,20 +5319,20 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4211
5319
|
*/
|
|
4212
5320
|
async shapeDispatchLog(dispatch) {
|
|
4213
5321
|
try {
|
|
4214
|
-
return await this.ctx.waterfall(scopeTarget(this, dispatch.agent), "tools/
|
|
5322
|
+
return await this.ctx.waterfall(scopeTarget(this, dispatch.agent), "tools/ptc-dispatch-log", dispatch, () => Promise.resolve(dispatch.content));
|
|
4215
5323
|
} catch (error) {
|
|
4216
|
-
this.ctx.logger.warn(`tools:
|
|
5324
|
+
this.ctx.logger.warn(`tools: ptc-dispatch-log listener failed for ${dispatch.name}: ${errorMessage$1(error)}; logging the original settled content`);
|
|
4217
5325
|
return dispatch.content;
|
|
4218
5326
|
}
|
|
4219
5327
|
}
|
|
4220
5328
|
/**
|
|
4221
|
-
* Whether the `
|
|
5329
|
+
* Whether the `ptc` mode collapse denies a model-direct call: only the
|
|
4222
5330
|
* reserved `run_code` transport may be named. Nested sub-dispatches (a
|
|
4223
5331
|
* `parent` token set) bypass the collapse. One home for the
|
|
4224
5332
|
* security-relevant predicate, shared by {@link resolveExecution} and
|
|
4225
5333
|
* {@link createExecution} so the two can never drift apart.
|
|
4226
5334
|
*
|
|
4227
|
-
* Resolved through {@link modeFor}, NOT `defaultMode`: an agent given `
|
|
5335
|
+
* Resolved through {@link modeFor}, NOT `defaultMode`: an agent given `ptc`
|
|
4228
5336
|
* by an agent preset under a native deployment is the composition
|
|
4229
5337
|
* `dsh-agent-tool-presentation` exists for, and reading the deployment default would
|
|
4230
5338
|
* leave exactly that agent uncollapsed — announcing one surface while
|
|
@@ -4234,7 +5342,7 @@ function resolveMaxParallelSubCalls(value) {
|
|
|
4234
5342
|
* @param nested - whether the call is a transport sub-dispatch, not a model-direct call.
|
|
4235
5343
|
*/
|
|
4236
5344
|
collapses(name, scope, nested) {
|
|
4237
|
-
return !nested && this.modeFor(scope) === "
|
|
5345
|
+
return !nested && this.modeFor(scope) === "ptc" && name !== "run_code";
|
|
4238
5346
|
}
|
|
4239
5347
|
/**
|
|
4240
5348
|
* Execute through pre-policy, guards, around-dispatch, post-policy,
|
|
@@ -4841,6 +5949,19 @@ const INSPIRATION_ROUTE = "/plugins/dsh-image-gen/inspiration";
|
|
|
4841
5949
|
const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
|
|
4842
5950
|
/** Browser route the settings card probes provider connectivity through. */
|
|
4843
5951
|
const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
|
|
5952
|
+
/** Browser route the workbench infinite canvas pushes live state through. */
|
|
5953
|
+
const CANVAS_STATE_ROUTE = "/plugins/dsh-image-gen/canvas-state";
|
|
5954
|
+
/** Every accepted canvas node kind, for validating untrusted pushes. */
|
|
5955
|
+
const CANVAS_NODE_KINDS = [
|
|
5956
|
+
"image",
|
|
5957
|
+
"draw",
|
|
5958
|
+
"text",
|
|
5959
|
+
"note",
|
|
5960
|
+
"geo",
|
|
5961
|
+
"arrow",
|
|
5962
|
+
"frame",
|
|
5963
|
+
"other"
|
|
5964
|
+
];
|
|
4844
5965
|
/** Namespace persisted through DSH Settings. */
|
|
4845
5966
|
const IMAGE_GENERATION_NAMESPACE = "image-generation";
|
|
4846
5967
|
/** Supported providers. */
|
|
@@ -5175,7 +6296,7 @@ function selectComfyUIWorkflow(active, requested) {
|
|
|
5175
6296
|
return workflow;
|
|
5176
6297
|
}
|
|
5177
6298
|
//#endregion
|
|
5178
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-
|
|
6299
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-credential_bc2d3e5c9a179df7ac53b40c354d894e/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
|
|
5179
6300
|
/**
|
|
5180
6301
|
* Service Definition for the credential-reference capability seam (`ctx.credentials`). Settings and composition files carry
|
|
5181
6302
|
* *references* to secrets — environment-variable names — while providers own
|
|
@@ -5193,7 +6314,7 @@ const REF_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
5193
6314
|
*/
|
|
5194
6315
|
function credentialRef(value) {
|
|
5195
6316
|
if (!isCredentialRefName(value)) throw new TypeError(`credential ref "${value}" must match ${String(REF_PATTERN)}`);
|
|
5196
|
-
return value;
|
|
6317
|
+
return brandString(value);
|
|
5197
6318
|
}
|
|
5198
6319
|
/**
|
|
5199
6320
|
* Whether a raw string could name a reference at all. Consumers that receive
|
|
@@ -5220,15 +6341,738 @@ async function resolveApiKey(ctx, provider) {
|
|
|
5220
6341
|
return value.length > 0 ? value : void 0;
|
|
5221
6342
|
}
|
|
5222
6343
|
/**
|
|
5223
|
-
* Resolve one provider's API key or fail with a user-facing message that says
|
|
5224
|
-
* where to configure it. Passing `tool` selects the Agent-facing English
|
|
5225
|
-
* wording; without it the message is the browser-facing Chinese one used by
|
|
5226
|
-
* the Studio route.
|
|
6344
|
+
* Resolve one provider's API key or fail with a user-facing message that says
|
|
6345
|
+
* where to configure it. Passing `tool` selects the Agent-facing English
|
|
6346
|
+
* wording; without it the message is the browser-facing Chinese one used by
|
|
6347
|
+
* the Studio route.
|
|
6348
|
+
*/
|
|
6349
|
+
async function requireApiKey(ctx, provider, tool) {
|
|
6350
|
+
const value = await resolveApiKey(ctx, provider);
|
|
6351
|
+
if (value !== void 0) return value;
|
|
6352
|
+
throw new Error(tool === void 0 ? `${PROVIDER_DISPLAY_NAMES[provider]} 尚未配置 API Key,请先到 设置 > 插件 > 图像生成 配置` : `${tool} requires the ${PROVIDER_DISPLAY_NAMES[provider]} API key; configure it in Settings > Plugins > Image generation.`);
|
|
6353
|
+
}
|
|
6354
|
+
//#endregion
|
|
6355
|
+
//#region lib/types/canvas-state.js
|
|
6356
|
+
/** How many node lines the digest lists before collapsing to "+N more". */
|
|
6357
|
+
const DIGEST_NODE_LIMIT = 24;
|
|
6358
|
+
/** How many selected shapes the digest itemizes before collapsing. */
|
|
6359
|
+
const DIGEST_SELECTION_LIMIT = 12;
|
|
6360
|
+
/**
|
|
6361
|
+
* Identity-bearing selection signature. Count and kinds alone would make
|
|
6362
|
+
* "1 selected image" of the lion and of the monster indistinguishable; the
|
|
6363
|
+
* item summaries (name, attachment id, dimensions, text) keep them apart.
|
|
6364
|
+
*/
|
|
6365
|
+
function signatureOf(count, kinds, items) {
|
|
6366
|
+
return JSON.stringify([
|
|
6367
|
+
count,
|
|
6368
|
+
kinds,
|
|
6369
|
+
items
|
|
6370
|
+
]);
|
|
6371
|
+
}
|
|
6372
|
+
function describeNode(node) {
|
|
6373
|
+
switch (node.kind) {
|
|
6374
|
+
case "image": return [
|
|
6375
|
+
"image",
|
|
6376
|
+
typeof node.width === "number" && typeof node.height === "number" ? `${node.width}x${node.height}` : void 0,
|
|
6377
|
+
typeof node.name === "string" && node.name.length > 0 ? `"${node.name}"` : void 0,
|
|
6378
|
+
typeof node.attachmentId === "string" ? `conversation attachment ${node.attachmentId}` : void 0,
|
|
6379
|
+
typeof node.provider === "string" && node.provider.length > 0 ? `generated by ${node.provider}${typeof node.model === "string" && node.model.length > 0 ? `/${node.model}` : ""}` : void 0,
|
|
6380
|
+
typeof node.prompt === "string" && node.prompt.length > 0 ? `prompt "${node.prompt}"` : void 0
|
|
6381
|
+
].filter((part) => part !== void 0).join(" ");
|
|
6382
|
+
case "draw": return "draw (freehand stroke)";
|
|
6383
|
+
case "text":
|
|
6384
|
+
case "note": {
|
|
6385
|
+
const preview = typeof node.text === "string" && node.text.length > 0 ? `"${node.text}"` : "(empty)";
|
|
6386
|
+
return `${node.kind} ${preview}`;
|
|
6387
|
+
}
|
|
6388
|
+
case "geo": return typeof node.name === "string" && node.name.length > 0 ? `shape ${node.name}` : "shape";
|
|
6389
|
+
default: return node.kind;
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
/** The most recently applied connected entry, or undefined when none is connected. */
|
|
6393
|
+
function freshestEntry(entries) {
|
|
6394
|
+
let latest;
|
|
6395
|
+
for (const entry of entries) if (latest === void 0 || entry.seq > latest.seq) latest = entry;
|
|
6396
|
+
return latest;
|
|
6397
|
+
}
|
|
6398
|
+
/**
|
|
6399
|
+
* Host-side mirror of every currently connected workbench canvas.
|
|
6400
|
+
*
|
|
6401
|
+
* One instance is shared by the canvas-state route, the canvas tools, and the
|
|
6402
|
+
* system-prompt context registration. More than one canvas can be mounted at
|
|
6403
|
+
* once (right-sidebar studio tab, conversation gallery tab); each instance
|
|
6404
|
+
* pushes under its own `clientInstance` id and `latest()` reads the freshest.
|
|
6405
|
+
*/
|
|
6406
|
+
var CanvasMirror = class {
|
|
6407
|
+
entries = /* @__PURE__ */ new Map();
|
|
6408
|
+
nextSeq = 0;
|
|
6409
|
+
/**
|
|
6410
|
+
* Apply one validated push. A disconnect removes the instance; an unchanged
|
|
6411
|
+
* selection keeps the previously received screenshot bytes so the model can
|
|
6412
|
+
* still view them while unrelated canvas edits keep flowing in.
|
|
6413
|
+
*/
|
|
6414
|
+
apply(push, selectionImage) {
|
|
6415
|
+
if (!push.connected) {
|
|
6416
|
+
this.entries.delete(push.clientInstance);
|
|
6417
|
+
return;
|
|
6418
|
+
}
|
|
6419
|
+
const previous = this.entries.get(push.clientInstance);
|
|
6420
|
+
const selectionCount = push.selection?.count ?? 0;
|
|
6421
|
+
const selectionKinds = [...push.selection?.kinds ?? []];
|
|
6422
|
+
const selectionItems = [...push.selection?.items ?? []];
|
|
6423
|
+
const selectionSignature = signatureOf(selectionCount, selectionKinds, selectionItems);
|
|
6424
|
+
const keepImage = previous !== void 0 && previous.selectionSignature === selectionSignature ? previous.selectionImage : void 0;
|
|
6425
|
+
this.entries.set(push.clientInstance, {
|
|
6426
|
+
clientInstance: push.clientInstance,
|
|
6427
|
+
connected: true,
|
|
6428
|
+
nodeCount: push.nodeCount,
|
|
6429
|
+
nodes: [...push.nodes ?? []],
|
|
6430
|
+
selectionCount,
|
|
6431
|
+
selectionKinds,
|
|
6432
|
+
selectionItems,
|
|
6433
|
+
selectionSignature,
|
|
6434
|
+
...selectionImage !== void 0 ? { selectionImage } : keepImage !== void 0 ? { selectionImage: keepImage } : {},
|
|
6435
|
+
receivedAt: Date.now(),
|
|
6436
|
+
seq: ++this.nextSeq,
|
|
6437
|
+
updatedAt: push.updatedAt
|
|
6438
|
+
});
|
|
6439
|
+
}
|
|
6440
|
+
/** The freshest connected canvas entry, or undefined. */
|
|
6441
|
+
latest() {
|
|
6442
|
+
return freshestEntry(this.entries.values());
|
|
6443
|
+
}
|
|
6444
|
+
/** The in-memory selection screenshot of the freshest canvas, if present. */
|
|
6445
|
+
latestSelectionImage() {
|
|
6446
|
+
return this.latest()?.selectionImage;
|
|
6447
|
+
}
|
|
6448
|
+
/** Whether any canvas instance is currently connected. */
|
|
6449
|
+
connected() {
|
|
6450
|
+
return this.latest() !== void 0;
|
|
6451
|
+
}
|
|
6452
|
+
/**
|
|
6453
|
+
* Compact one-screen digest of the canvas for the system prompt and tools.
|
|
6454
|
+
* Evaluated per call so the "updated N ago" line stays current between pushes.
|
|
6455
|
+
*/
|
|
6456
|
+
digest(now = Date.now()) {
|
|
6457
|
+
const entry = this.latest();
|
|
6458
|
+
if (entry === void 0) return ["Image-gen workbench canvas: not connected.", "No image-gen workbench infinite canvas is open right now. If the user refers to a canvas, drawings, or a sketch, ask them to open the image-gen workbench (gallery tab or right-sidebar studio tab) and switch it to the infinite canvas view."].join(" ");
|
|
6459
|
+
const lines = [];
|
|
6460
|
+
const kindCounts = /* @__PURE__ */ new Map();
|
|
6461
|
+
for (const node of entry.nodes) kindCounts.set(node.kind, (kindCounts.get(node.kind) ?? 0) + 1);
|
|
6462
|
+
const unlisted = Math.max(0, entry.nodeCount - entry.nodes.length);
|
|
6463
|
+
const shapeTotal = `${entry.nodeCount} shape${entry.nodeCount === 1 ? "" : "s"}`;
|
|
6464
|
+
const breakdown = [...kindCounts.entries()].map(([kind, count]) => `${count} ${kind}${count === 1 ? "" : "s"}`).join(", ");
|
|
6465
|
+
const age = Math.max(0, Math.round((now - entry.receivedAt) / 1e3));
|
|
6466
|
+
const ageText = age < 5 ? "just now" : age < 90 ? `${age}s ago` : `${Math.round(age / 60)}min ago`;
|
|
6467
|
+
lines.push(`Image-gen workbench canvas: ${shapeTotal}${breakdown.length > 0 ? ` (${breakdown})` : ""}, updated ${ageText}.`);
|
|
6468
|
+
for (const node of entry.nodes.slice(0, DIGEST_NODE_LIMIT)) lines.push(`- ${describeNode(node)}`);
|
|
6469
|
+
if (entry.nodes.length > DIGEST_NODE_LIMIT) lines.push(`- ... ${entry.nodes.length - DIGEST_NODE_LIMIT} more shapes omitted`);
|
|
6470
|
+
if (unlisted > 0) lines.push(`- (${unlisted} further shapes not itemized)`);
|
|
6471
|
+
if (entry.selectionCount === 0) lines.push("Selection: none. Ask the user to select shapes on the canvas when they want you to work on something specific.");
|
|
6472
|
+
else {
|
|
6473
|
+
const kinds = entry.selectionKinds.length > 0 ? ` (${entry.selectionKinds.join(", ")})` : "";
|
|
6474
|
+
lines.push(`Selection: ${entry.selectionCount} shape${entry.selectionCount === 1 ? "" : "s"}${kinds}.`);
|
|
6475
|
+
for (const item of entry.selectionItems.slice(0, DIGEST_SELECTION_LIMIT)) lines.push(`- selected: ${describeNode(item)}`);
|
|
6476
|
+
if (entry.selectionItems.length > DIGEST_SELECTION_LIMIT) lines.push(`- ... ${entry.selectionItems.length - DIGEST_SELECTION_LIMIT} more selected shapes omitted`);
|
|
6477
|
+
const unlistedSelection = Math.max(0, entry.selectionCount - entry.selectionItems.length);
|
|
6478
|
+
if (unlistedSelection > 0) lines.push(`- (${unlistedSelection} further selected shapes not itemized)`);
|
|
6479
|
+
const screenshot = entry.selectionImage !== void 0 ? "A screenshot of the selection is available; call view_canvas to see it. edit_image with source=canvas_selection sends the full-resolution originals of selected conversation images, plus the screenshot when the selection also contains other content (hand-drawn strokes, pasted images)." : "No selection screenshot yet — the canvas pushes one about a second after the selection settles. edit_image with source=canvas_selection can still use the full-resolution originals of selected conversation images; for anything else ask the user to re-select the shapes.";
|
|
6480
|
+
lines.push(screenshot);
|
|
6481
|
+
}
|
|
6482
|
+
return lines.join("\n");
|
|
6483
|
+
}
|
|
6484
|
+
};
|
|
6485
|
+
//#endregion
|
|
6486
|
+
//#region lib/types/canvas-state-route.js
|
|
6487
|
+
const MAX_CLIENT_INSTANCE_CHARS = 64;
|
|
6488
|
+
const MAX_NODE_TEXT_CHARS = 200;
|
|
6489
|
+
const MAX_ID_CHARS = 256;
|
|
6490
|
+
/** Provider ids come from the shared enum (longest is "openai-compat"). */
|
|
6491
|
+
const MAX_PROVIDER_CHARS = 32;
|
|
6492
|
+
/** Model ids and ComfyUI workflow labels are user-facing strings. */
|
|
6493
|
+
const MAX_MODEL_CHARS = 128;
|
|
6494
|
+
const MAX_NODE_COUNT = 1e5;
|
|
6495
|
+
const MAX_SELECTION_COUNT = 1e3;
|
|
6496
|
+
/** Serve one canvas-state push. */
|
|
6497
|
+
async function serveCanvasState(req, res, deps) {
|
|
6498
|
+
if (req.method !== "POST") return jsonError$4(res, 405, "method-not-allowed");
|
|
6499
|
+
if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$4(res, 415, "json-required");
|
|
6500
|
+
const origin = req.headers.origin;
|
|
6501
|
+
const host = req.headers.host;
|
|
6502
|
+
if (origin !== void 0 && host !== void 0 && origin !== `http://${host}` && origin !== `https://${host}`) return jsonError$4(res, 403, "origin-rejected");
|
|
6503
|
+
let body;
|
|
6504
|
+
try {
|
|
6505
|
+
body = JSON.parse(await readBody$3(req, deps.maxBodyBytes));
|
|
6506
|
+
} catch (error) {
|
|
6507
|
+
return jsonError$4(res, error instanceof Error && error.message === "request-too-large" ? 413 : 400, "invalid-request");
|
|
6508
|
+
}
|
|
6509
|
+
const push = parseCanvasStatePush(body);
|
|
6510
|
+
if (push === void 0) return jsonError$4(res, 400, "invalid-canvas-state");
|
|
6511
|
+
let selectionImage;
|
|
6512
|
+
if (push.selectionImage !== void 0) {
|
|
6513
|
+
const decoded = decodeSelectionImage(push.selectionImage);
|
|
6514
|
+
if (decoded === void 0) return jsonError$4(res, 400, "invalid-selection-image");
|
|
6515
|
+
if (decoded.data.byteLength > deps.maxImageBytes) return jsonError$4(res, 413, "selection-image-too-large");
|
|
6516
|
+
selectionImage = decoded;
|
|
6517
|
+
}
|
|
6518
|
+
deps.mirror.apply(push, selectionImage);
|
|
6519
|
+
res.writeHead(200, {
|
|
6520
|
+
"content-type": "application/json",
|
|
6521
|
+
"cache-control": "no-store"
|
|
6522
|
+
});
|
|
6523
|
+
res.end(JSON.stringify({ ok: true }));
|
|
6524
|
+
}
|
|
6525
|
+
/**
|
|
6526
|
+
* Validate one untrusted canvas-state push at the HTTP boundary.
|
|
6527
|
+
* Returns a normalized copy, or undefined when any field is malformed.
|
|
6528
|
+
* Present-but-malformed known fields reject the whole push: they signal a
|
|
6529
|
+
* client/server version skew rather than a legitimately absent value.
|
|
6530
|
+
*/
|
|
6531
|
+
function parseCanvasStatePush(value) {
|
|
6532
|
+
try {
|
|
6533
|
+
return parsePush(value);
|
|
6534
|
+
} catch (error) {
|
|
6535
|
+
if (error instanceof InvalidPush) return void 0;
|
|
6536
|
+
throw error;
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
/** Sentinel thrown by field validators; caught at the public parse boundary. */
|
|
6540
|
+
var InvalidPush = class extends Error {};
|
|
6541
|
+
function parsePush(value) {
|
|
6542
|
+
const record = plainRecord(value);
|
|
6543
|
+
if (record === void 0) throw new InvalidPush();
|
|
6544
|
+
const clientInstance = boundedString(record.clientInstance, 1, MAX_CLIENT_INSTANCE_CHARS);
|
|
6545
|
+
if (clientInstance === void 0) throw new InvalidPush();
|
|
6546
|
+
if (typeof record.connected !== "boolean") throw new InvalidPush();
|
|
6547
|
+
if (!nonNegativeInteger$1(record.nodeCount) || record.nodeCount > MAX_NODE_COUNT) throw new InvalidPush();
|
|
6548
|
+
if (!nonNegativeInteger$1(record.updatedAt)) throw new InvalidPush();
|
|
6549
|
+
if (Math.abs(record.updatedAt - Date.now()) > 3e5) throw new InvalidPush();
|
|
6550
|
+
let nodes;
|
|
6551
|
+
if (record.nodes !== void 0) {
|
|
6552
|
+
if (!Array.isArray(record.nodes) || record.nodes.length > 48) throw new InvalidPush();
|
|
6553
|
+
const parsed = [];
|
|
6554
|
+
for (const raw of record.nodes) {
|
|
6555
|
+
const node = parseNodeSummary(raw);
|
|
6556
|
+
if (node === void 0) throw new InvalidPush();
|
|
6557
|
+
parsed.push(node);
|
|
6558
|
+
}
|
|
6559
|
+
nodes = parsed;
|
|
6560
|
+
}
|
|
6561
|
+
let selection;
|
|
6562
|
+
if (record.selection !== void 0) {
|
|
6563
|
+
const raw = plainRecord(record.selection);
|
|
6564
|
+
if (raw === void 0) throw new InvalidPush();
|
|
6565
|
+
if (!nonNegativeInteger$1(raw.count) || raw.count > MAX_SELECTION_COUNT) throw new InvalidPush();
|
|
6566
|
+
if (!Array.isArray(raw.kinds) || raw.kinds.length > 8) throw new InvalidPush();
|
|
6567
|
+
const kinds = [];
|
|
6568
|
+
for (const kind of raw.kinds) {
|
|
6569
|
+
if (typeof kind !== "string" || !CANVAS_NODE_KINDS.includes(kind)) throw new InvalidPush();
|
|
6570
|
+
kinds.push(kind);
|
|
6571
|
+
}
|
|
6572
|
+
let items;
|
|
6573
|
+
if (raw.items !== void 0) {
|
|
6574
|
+
if (!Array.isArray(raw.items) || raw.items.length > 16) throw new InvalidPush();
|
|
6575
|
+
if (raw.items.length > raw.count) throw new InvalidPush();
|
|
6576
|
+
const parsed = [];
|
|
6577
|
+
for (const item of raw.items) {
|
|
6578
|
+
const node = parseNodeSummary(item);
|
|
6579
|
+
if (node === void 0) throw new InvalidPush();
|
|
6580
|
+
parsed.push(node);
|
|
6581
|
+
}
|
|
6582
|
+
items = parsed;
|
|
6583
|
+
}
|
|
6584
|
+
selection = items === void 0 ? {
|
|
6585
|
+
count: raw.count,
|
|
6586
|
+
kinds
|
|
6587
|
+
} : {
|
|
6588
|
+
count: raw.count,
|
|
6589
|
+
kinds,
|
|
6590
|
+
items
|
|
6591
|
+
};
|
|
6592
|
+
}
|
|
6593
|
+
if (record.selectionImage !== void 0 && typeof record.selectionImage !== "string") throw new InvalidPush();
|
|
6594
|
+
return {
|
|
6595
|
+
clientInstance,
|
|
6596
|
+
connected: record.connected,
|
|
6597
|
+
nodeCount: record.nodeCount,
|
|
6598
|
+
...nodes === void 0 ? {} : { nodes },
|
|
6599
|
+
...selection === void 0 ? {} : { selection },
|
|
6600
|
+
...typeof record.selectionImage === "string" ? { selectionImage: record.selectionImage } : {},
|
|
6601
|
+
updatedAt: record.updatedAt
|
|
6602
|
+
};
|
|
6603
|
+
}
|
|
6604
|
+
/** Decode and sanity-check an embedded selection screenshot data URL. */
|
|
6605
|
+
function decodeSelectionImage(dataUrl) {
|
|
6606
|
+
const match = /^data:(image\/(?:png|jpeg|webp));base64,([A-Za-z0-9+/]*={0,2})$/.exec(dataUrl);
|
|
6607
|
+
if (match === null) return void 0;
|
|
6608
|
+
const mediaType = match[1];
|
|
6609
|
+
const base64 = match[2];
|
|
6610
|
+
if (mediaType === void 0 || base64 === void 0) return void 0;
|
|
6611
|
+
try {
|
|
6612
|
+
const buffer = Buffer.from(base64, "base64");
|
|
6613
|
+
if (buffer.byteLength === 0) return void 0;
|
|
6614
|
+
return {
|
|
6615
|
+
data: new Uint8Array(buffer),
|
|
6616
|
+
mediaType
|
|
6617
|
+
};
|
|
6618
|
+
} catch {
|
|
6619
|
+
return;
|
|
6620
|
+
}
|
|
6621
|
+
}
|
|
6622
|
+
function parseNodeSummary(value) {
|
|
6623
|
+
const record = plainRecord(value);
|
|
6624
|
+
if (record === void 0) return void 0;
|
|
6625
|
+
if (typeof record.kind !== "string" || !CANVAS_NODE_KINDS.includes(record.kind)) return void 0;
|
|
6626
|
+
const galleryId = optionalBoundedString(record.galleryId, MAX_ID_CHARS);
|
|
6627
|
+
const attachmentId = optionalBoundedString(record.attachmentId, MAX_ID_CHARS);
|
|
6628
|
+
const name = optionalBoundedString(record.name, MAX_ID_CHARS);
|
|
6629
|
+
const text = optionalBoundedString(record.text, MAX_NODE_TEXT_CHARS);
|
|
6630
|
+
const prompt = optionalBoundedString(record.prompt, 120);
|
|
6631
|
+
const provider = optionalBoundedString(record.provider, MAX_PROVIDER_CHARS);
|
|
6632
|
+
const model = optionalBoundedString(record.model, MAX_MODEL_CHARS);
|
|
6633
|
+
const width = optionalDimension(record.width);
|
|
6634
|
+
const height = optionalDimension(record.height);
|
|
6635
|
+
return {
|
|
6636
|
+
kind: record.kind,
|
|
6637
|
+
...galleryId === void 0 ? {} : { galleryId },
|
|
6638
|
+
...attachmentId === void 0 ? {} : { attachmentId },
|
|
6639
|
+
...name === void 0 ? {} : { name },
|
|
6640
|
+
...width === void 0 ? {} : { width },
|
|
6641
|
+
...height === void 0 ? {} : { height },
|
|
6642
|
+
...text === void 0 ? {} : { text },
|
|
6643
|
+
...prompt === void 0 ? {} : { prompt },
|
|
6644
|
+
...provider === void 0 ? {} : { provider },
|
|
6645
|
+
...model === void 0 ? {} : { model }
|
|
6646
|
+
};
|
|
6647
|
+
}
|
|
6648
|
+
function plainRecord(value) {
|
|
6649
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6650
|
+
}
|
|
6651
|
+
function boundedString(value, min, max) {
|
|
6652
|
+
if (typeof value !== "string" || value.length < min || value.length > max) return void 0;
|
|
6653
|
+
return value;
|
|
6654
|
+
}
|
|
6655
|
+
/** Absent is fine; present-but-not-a-bounded-string rejects the whole push. */
|
|
6656
|
+
function optionalBoundedString(value, max) {
|
|
6657
|
+
if (value === void 0) return void 0;
|
|
6658
|
+
const parsed = boundedString(value, 0, max);
|
|
6659
|
+
if (parsed === void 0) throw new InvalidPush();
|
|
6660
|
+
return parsed;
|
|
6661
|
+
}
|
|
6662
|
+
/** Absent is fine; present-but-not-a-finite-dimension rejects the whole push. */
|
|
6663
|
+
function optionalDimension(value) {
|
|
6664
|
+
if (value === void 0) return void 0;
|
|
6665
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1e6) throw new InvalidPush();
|
|
6666
|
+
return Math.round(value);
|
|
6667
|
+
}
|
|
6668
|
+
function nonNegativeInteger$1(value) {
|
|
6669
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
6670
|
+
}
|
|
6671
|
+
async function readBody$3(req, maxBytes) {
|
|
6672
|
+
const chunks = [];
|
|
6673
|
+
let bytes = 0;
|
|
6674
|
+
for await (const chunk of req) {
|
|
6675
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
6676
|
+
bytes += buffer.byteLength;
|
|
6677
|
+
if (bytes > maxBytes) throw new Error("request-too-large");
|
|
6678
|
+
chunks.push(buffer);
|
|
6679
|
+
}
|
|
6680
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
6681
|
+
}
|
|
6682
|
+
function jsonError$4(res, status, code) {
|
|
6683
|
+
res.writeHead(status, {
|
|
6684
|
+
"content-type": "application/json",
|
|
6685
|
+
"cache-control": "no-store"
|
|
6686
|
+
});
|
|
6687
|
+
res.end(JSON.stringify({ error: code }));
|
|
6688
|
+
}
|
|
6689
|
+
//#endregion
|
|
6690
|
+
//#region lib/types/reference-image.js
|
|
6691
|
+
/** Latest-DSH compatibility boundary for resolving conversation image references. */
|
|
6692
|
+
/**
|
|
6693
|
+
* Resolve one or more edit references while keeping every DSH-specific detail
|
|
6694
|
+
* behind this compatibility boundary. Explicit selectors preserve caller order;
|
|
6695
|
+
* without selectors, all images in the newest image-bearing message are used.
|
|
6696
|
+
*/
|
|
6697
|
+
async function resolveReferenceImages(input) {
|
|
6698
|
+
const sourceAttachmentIds = mergeSelectors({
|
|
6699
|
+
single: input.sourceAttachmentId,
|
|
6700
|
+
multiple: input.sourceAttachmentIds,
|
|
6701
|
+
singleName: "source_attachment_id",
|
|
6702
|
+
multipleName: "source_attachment_ids",
|
|
6703
|
+
equal: attachmentIdsEqual
|
|
6704
|
+
});
|
|
6705
|
+
const sourcePaths = mergeSelectors({
|
|
6706
|
+
single: input.sourcePath,
|
|
6707
|
+
multiple: input.sourcePaths,
|
|
6708
|
+
singleName: "source_path",
|
|
6709
|
+
multipleName: "source_paths",
|
|
6710
|
+
equal: (left, right) => left.trim() === right.trim()
|
|
6711
|
+
});
|
|
6712
|
+
if (sourceAttachmentIds !== void 0 && sourcePaths !== void 0) throw new Error("edit_image accepts only one of source_attachment_id, source_attachment_ids, source_path, or source_paths");
|
|
6713
|
+
if (sourcePaths !== void 0) return Promise.all(sourcePaths.map((sourcePath) => readWorkspaceReferenceImage({
|
|
6714
|
+
sourcePath,
|
|
6715
|
+
...input.agent?.session.header?.cwd === void 0 ? {} : { workspaceRoot: input.agent.session.header.cwd },
|
|
6716
|
+
...input.maxBytes === void 0 ? {} : { maxBytes: input.maxBytes },
|
|
6717
|
+
signal: input.signal
|
|
6718
|
+
})));
|
|
6719
|
+
if (input.agent === void 0) throw new Error("edit_image requires an active DSH agent session to resolve a reference image");
|
|
6720
|
+
const refs = findReferenceImages(input.agent.session.deriveMessages(), sourceAttachmentIds);
|
|
6721
|
+
if (refs.length === 0) {
|
|
6722
|
+
if (sourceAttachmentIds !== void 0) throw new Error(`edit_image could not find image attachment ${sourceAttachmentIds[0]} in the current conversation`);
|
|
6723
|
+
throw new Error("edit_image requires an image in the current conversation; upload or generate an image first");
|
|
6724
|
+
}
|
|
6725
|
+
if (sourceAttachmentIds !== void 0 && refs.length !== sourceAttachmentIds.length) {
|
|
6726
|
+
const missing = sourceAttachmentIds.find((id) => !refs.some((ref) => attachmentIdsEqual(String(ref.attachmentId), id)));
|
|
6727
|
+
throw new Error(`edit_image could not find image attachment ${missing ?? "unknown"} in the current conversation`);
|
|
6728
|
+
}
|
|
6729
|
+
return Promise.all(refs.map(async (ref) => {
|
|
6730
|
+
const stored = await input.attachments.readImage(ref, input.signal);
|
|
6731
|
+
if (input.maxBytes !== void 0 && stored.data.byteLength > input.maxBytes) throw new Error(`edit_image source image is too large (${stored.data.byteLength} bytes; maximum ${input.maxBytes})`);
|
|
6732
|
+
return {
|
|
6733
|
+
data: stored.data,
|
|
6734
|
+
mediaType: stored.ref.mediaType
|
|
6735
|
+
};
|
|
6736
|
+
}));
|
|
6737
|
+
}
|
|
6738
|
+
/** Find images in caller order, or every image in the newest image-bearing message. */
|
|
6739
|
+
function findReferenceImages(messages, sourceAttachmentIds) {
|
|
6740
|
+
if (sourceAttachmentIds !== void 0) return sourceAttachmentIds.flatMap((id) => {
|
|
6741
|
+
const ref = findReferenceImage(messages, id);
|
|
6742
|
+
return ref === void 0 ? [] : [ref];
|
|
6743
|
+
});
|
|
6744
|
+
const latestHumanMessage = [...messages].reverse().find((message) => message.source?.kind === "user");
|
|
6745
|
+
if (latestHumanMessage !== void 0) {
|
|
6746
|
+
const refs = collectInBlocks(latestHumanMessage.content);
|
|
6747
|
+
if (refs.length > 0) return refs;
|
|
6748
|
+
}
|
|
6749
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
6750
|
+
const refs = collectInBlocks(messages[index]?.content ?? []);
|
|
6751
|
+
if (refs.length > 0) return refs;
|
|
6752
|
+
}
|
|
6753
|
+
return [];
|
|
6754
|
+
}
|
|
6755
|
+
/**
|
|
6756
|
+
* Read an explicitly named workspace image without exposing filesystem or DSH
|
|
6757
|
+
* details to provider adapters. Both lexical and real-path containment are
|
|
6758
|
+
* enforced so absolute paths, parent traversal, and symlink escapes fail.
|
|
6759
|
+
*/
|
|
6760
|
+
async function readWorkspaceReferenceImage(input) {
|
|
6761
|
+
const requested = input.sourcePath.trim();
|
|
6762
|
+
if (requested.length === 0) throw new Error("edit_image source_path must not be empty");
|
|
6763
|
+
if (input.workspaceRoot === void 0) throw new Error("edit_image source_path requires an active DSH session workspace");
|
|
6764
|
+
const root = resolve(input.workspaceRoot);
|
|
6765
|
+
const candidate = isAbsolute(requested) ? resolve(requested) : resolve(root, requested);
|
|
6766
|
+
if (!containsPath$1(root, candidate)) throw new Error("edit_image source_path must stay inside the session workspace: " + requested);
|
|
6767
|
+
let realRoot;
|
|
6768
|
+
let realCandidate;
|
|
6769
|
+
try {
|
|
6770
|
+
[realRoot, realCandidate] = await Promise.all([realpath(root), realpath(candidate)]);
|
|
6771
|
+
} catch (error) {
|
|
6772
|
+
if (error.code === "ENOENT") throw new Error("edit_image could not find workspace image: " + requested);
|
|
6773
|
+
throw error;
|
|
6774
|
+
}
|
|
6775
|
+
if (!containsPath$1(realRoot, realCandidate)) throw new Error("edit_image source_path resolves outside the session workspace: " + requested);
|
|
6776
|
+
const file = await stat(realCandidate);
|
|
6777
|
+
if (!file.isFile()) throw new Error("edit_image source_path is not a file: " + requested);
|
|
6778
|
+
if (input.maxBytes !== void 0 && file.size > input.maxBytes) throw new Error("edit_image source image is too large (" + file.size + " bytes; maximum " + input.maxBytes + ")");
|
|
6779
|
+
const data = await readFile(realCandidate, { signal: input.signal });
|
|
6780
|
+
const mediaType = detectImageMediaType(data);
|
|
6781
|
+
if (mediaType === void 0) throw new Error("edit_image source_path is not a supported PNG, JPEG, WebP, or GIF image: " + requested);
|
|
6782
|
+
return {
|
|
6783
|
+
data: new Uint8Array(data),
|
|
6784
|
+
mediaType
|
|
6785
|
+
};
|
|
6786
|
+
}
|
|
6787
|
+
/** Find the newest matching image in the effective, replacement-aware history. */
|
|
6788
|
+
function findReferenceImage(messages, sourceAttachmentId) {
|
|
6789
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
6790
|
+
const found = findInBlocks(messages[index]?.content ?? [], sourceAttachmentId);
|
|
6791
|
+
if (found !== void 0) return found;
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
/** Validate an untrusted serialized image reference at an HTTP/UI boundary. */
|
|
6795
|
+
function parseImageAttachmentRef(value) {
|
|
6796
|
+
const ref = record$7(value);
|
|
6797
|
+
if (ref === void 0) return void 0;
|
|
6798
|
+
if (typeof ref.attachmentId !== "string" || !imageMediaType$4(ref.mediaType)) return void 0;
|
|
6799
|
+
if (!nonNegativeInteger(ref.bytes) || !positiveInteger(ref.width) || !positiveInteger(ref.height)) return void 0;
|
|
6800
|
+
if (ref.name !== void 0 && typeof ref.name !== "string") return void 0;
|
|
6801
|
+
const originalDimensions = ref.originalDimensions === void 0 ? void 0 : parseDimensions(ref.originalDimensions);
|
|
6802
|
+
if (ref.originalDimensions !== void 0 && originalDimensions === void 0) return void 0;
|
|
6803
|
+
return {
|
|
6804
|
+
attachmentId: ref.attachmentId,
|
|
6805
|
+
mediaType: ref.mediaType,
|
|
6806
|
+
bytes: ref.bytes,
|
|
6807
|
+
width: ref.width,
|
|
6808
|
+
height: ref.height,
|
|
6809
|
+
...typeof ref.name === "string" ? { name: ref.name } : {},
|
|
6810
|
+
...originalDimensions === void 0 ? {} : { originalDimensions }
|
|
6811
|
+
};
|
|
6812
|
+
}
|
|
6813
|
+
function findInBlocks(blocks, sourceAttachmentId) {
|
|
6814
|
+
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
6815
|
+
const block = blocks[index];
|
|
6816
|
+
if (block === void 0) continue;
|
|
6817
|
+
if (block.type === "image") {
|
|
6818
|
+
if (sourceAttachmentId === void 0 || attachmentIdsEqual(String(block.attachment.attachmentId), sourceAttachmentId)) return block.attachment;
|
|
6819
|
+
continue;
|
|
6820
|
+
}
|
|
6821
|
+
if (block.type === "tool-result") {
|
|
6822
|
+
const nested = findInBlocks(block.content, sourceAttachmentId);
|
|
6823
|
+
if (nested !== void 0) return nested;
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
function parseDimensions(value) {
|
|
6828
|
+
const dimensions = record$7(value);
|
|
6829
|
+
if (dimensions === void 0 || !positiveInteger(dimensions.width) || !positiveInteger(dimensions.height)) return void 0;
|
|
6830
|
+
return {
|
|
6831
|
+
width: dimensions.width,
|
|
6832
|
+
height: dimensions.height
|
|
6833
|
+
};
|
|
6834
|
+
}
|
|
6835
|
+
function imageMediaType$4(value) {
|
|
6836
|
+
return value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
|
|
6837
|
+
}
|
|
6838
|
+
function collectInBlocks(blocks) {
|
|
6839
|
+
const refs = [];
|
|
6840
|
+
for (const block of blocks) {
|
|
6841
|
+
if (block.type === "image") refs.push(block.attachment);
|
|
6842
|
+
if (block.type === "tool-result") refs.push(...collectInBlocks(block.content));
|
|
6843
|
+
}
|
|
6844
|
+
return refs;
|
|
6845
|
+
}
|
|
6846
|
+
function attachmentIdsEqual(actual, requested) {
|
|
6847
|
+
if (actual === requested) return true;
|
|
6848
|
+
const actualDigest = sha256Digest(actual);
|
|
6849
|
+
const requestedDigest = sha256Digest(requested);
|
|
6850
|
+
return actualDigest !== void 0 && actualDigest === requestedDigest;
|
|
6851
|
+
}
|
|
6852
|
+
function sha256Digest(value) {
|
|
6853
|
+
return /^(?:sha256:)?([0-9a-f]{64})$/i.exec(value.trim())?.[1]?.toLowerCase();
|
|
6854
|
+
}
|
|
6855
|
+
function mergeSelectors(input) {
|
|
6856
|
+
if (input.multiple === void 0) return input.single === void 0 ? void 0 : [input.single];
|
|
6857
|
+
if (input.multiple.length === 0) {
|
|
6858
|
+
if (input.single !== void 0) return [input.single];
|
|
6859
|
+
throw new Error(`edit_image ${input.multipleName} must not be empty`);
|
|
6860
|
+
}
|
|
6861
|
+
const single = input.single;
|
|
6862
|
+
if (single !== void 0 && !input.multiple.some((value) => input.equal(single, value))) throw new Error(`edit_image ${input.singleName} must also appear in ${input.multipleName} when both are provided`);
|
|
6863
|
+
return input.multiple;
|
|
6864
|
+
}
|
|
6865
|
+
function detectImageMediaType(data) {
|
|
6866
|
+
if (startsWith(data, [
|
|
6867
|
+
137,
|
|
6868
|
+
80,
|
|
6869
|
+
78,
|
|
6870
|
+
71,
|
|
6871
|
+
13,
|
|
6872
|
+
10,
|
|
6873
|
+
26,
|
|
6874
|
+
10
|
|
6875
|
+
])) return "image/png";
|
|
6876
|
+
if (startsWith(data, [
|
|
6877
|
+
255,
|
|
6878
|
+
216,
|
|
6879
|
+
255
|
|
6880
|
+
])) return "image/jpeg";
|
|
6881
|
+
if (ascii(data, 0, 6) === "GIF87a" || ascii(data, 0, 6) === "GIF89a") return "image/gif";
|
|
6882
|
+
if (ascii(data, 0, 4) === "RIFF" && ascii(data, 8, 4) === "WEBP") return "image/webp";
|
|
6883
|
+
}
|
|
6884
|
+
function startsWith(data, signature) {
|
|
6885
|
+
return signature.every((byte, index) => data[index] === byte);
|
|
6886
|
+
}
|
|
6887
|
+
function ascii(data, offset, length) {
|
|
6888
|
+
return String.fromCharCode(...data.subarray(offset, offset + length));
|
|
6889
|
+
}
|
|
6890
|
+
function containsPath$1(parent, child) {
|
|
6891
|
+
const rel = relative(parent, child);
|
|
6892
|
+
return rel === "" || rel !== ".." && !rel.startsWith(".." + sep) && !isAbsolute(rel);
|
|
6893
|
+
}
|
|
6894
|
+
function positiveInteger(value) {
|
|
6895
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
6896
|
+
}
|
|
6897
|
+
function nonNegativeInteger(value) {
|
|
6898
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
6899
|
+
}
|
|
6900
|
+
function record$7(value) {
|
|
6901
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6902
|
+
}
|
|
6903
|
+
//#endregion
|
|
6904
|
+
//#region lib/types/canvas-tools.js
|
|
6905
|
+
/** Register the canvas tools on a context that owns a canvas mirror. */
|
|
6906
|
+
function registerCanvasTools(ctx, mirror, deps) {
|
|
6907
|
+
ctx.tools.register(defineTool({
|
|
6908
|
+
name: "canvas_state",
|
|
6909
|
+
description: "Inspect the current state of the image-gen workbench infinite canvas: what shapes are on it (generated images, hand-drawn strokes, text notes) and what is currently selected. Use whenever the user refers to \"the canvas\", something they drew or placed there, or a selection, and before view_canvas or edit_image with source=canvas_selection.",
|
|
6910
|
+
parameters: {},
|
|
6911
|
+
output: {
|
|
6912
|
+
schema: {
|
|
6913
|
+
type: "object",
|
|
6914
|
+
additionalProperties: false,
|
|
6915
|
+
properties: {
|
|
6916
|
+
digest: {
|
|
6917
|
+
type: "string",
|
|
6918
|
+
required: true
|
|
6919
|
+
},
|
|
6920
|
+
connected: {
|
|
6921
|
+
type: "boolean",
|
|
6922
|
+
required: true
|
|
6923
|
+
},
|
|
6924
|
+
nodeCount: {
|
|
6925
|
+
type: "integer",
|
|
6926
|
+
required: true
|
|
6927
|
+
},
|
|
6928
|
+
selectionCount: {
|
|
6929
|
+
type: "integer",
|
|
6930
|
+
required: true
|
|
6931
|
+
},
|
|
6932
|
+
hasSelectionImage: {
|
|
6933
|
+
type: "boolean",
|
|
6934
|
+
required: true
|
|
6935
|
+
}
|
|
6936
|
+
}
|
|
6937
|
+
},
|
|
6938
|
+
render: (_args, value) => [{
|
|
6939
|
+
type: "text",
|
|
6940
|
+
text: value.digest
|
|
6941
|
+
}]
|
|
6942
|
+
},
|
|
6943
|
+
async execute() {
|
|
6944
|
+
const entry = mirror.latest();
|
|
6945
|
+
return {
|
|
6946
|
+
digest: mirror.digest(),
|
|
6947
|
+
connected: entry !== void 0,
|
|
6948
|
+
nodeCount: entry?.nodeCount ?? 0,
|
|
6949
|
+
selectionCount: entry?.selectionCount ?? 0,
|
|
6950
|
+
hasSelectionImage: mirror.latestSelectionImage() !== void 0
|
|
6951
|
+
};
|
|
6952
|
+
}
|
|
6953
|
+
}));
|
|
6954
|
+
ctx.tools.register(defineTool({
|
|
6955
|
+
name: "view_canvas",
|
|
6956
|
+
description: "View the current selection on the image-gen workbench infinite canvas as an image (a screenshot the canvas pushed for its selection: hand-drawn sketches, annotated images, or any selected shapes). Call it after canvas_state confirms a selection exists. When nothing is selected, ask the user to select the shapes first. The image becomes part of the conversation, so you can reason about it directly.",
|
|
6957
|
+
parameters: {},
|
|
6958
|
+
output: {
|
|
6959
|
+
schema: {
|
|
6960
|
+
type: "object",
|
|
6961
|
+
additionalProperties: false,
|
|
6962
|
+
properties: {
|
|
6963
|
+
digest: {
|
|
6964
|
+
type: "string",
|
|
6965
|
+
required: true
|
|
6966
|
+
},
|
|
6967
|
+
attachment: {
|
|
6968
|
+
type: "object",
|
|
6969
|
+
additionalProperties: false,
|
|
6970
|
+
properties: {
|
|
6971
|
+
attachmentId: {
|
|
6972
|
+
type: "string",
|
|
6973
|
+
required: true
|
|
6974
|
+
},
|
|
6975
|
+
mediaType: {
|
|
6976
|
+
type: "string",
|
|
6977
|
+
required: true
|
|
6978
|
+
},
|
|
6979
|
+
bytes: {
|
|
6980
|
+
type: "integer",
|
|
6981
|
+
required: true
|
|
6982
|
+
},
|
|
6983
|
+
width: {
|
|
6984
|
+
type: "integer",
|
|
6985
|
+
required: true
|
|
6986
|
+
},
|
|
6987
|
+
height: {
|
|
6988
|
+
type: "integer",
|
|
6989
|
+
required: true
|
|
6990
|
+
},
|
|
6991
|
+
name: { type: "string" }
|
|
6992
|
+
}
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6995
|
+
},
|
|
6996
|
+
render: (_args, value) => {
|
|
6997
|
+
if (value.attachment === void 0) return [{
|
|
6998
|
+
type: "text",
|
|
6999
|
+
text: `${value.digest}\nNo selection screenshot is available. Ask the user to open the image-gen workbench infinite canvas and select the shapes, then try again.`
|
|
7000
|
+
}];
|
|
7001
|
+
return [{
|
|
7002
|
+
type: "text",
|
|
7003
|
+
text: `${value.digest}\nThe screenshot of the current canvas selection is attached below. Respond to the user without calling read or other tools to locate it.`
|
|
7004
|
+
}, {
|
|
7005
|
+
type: "image",
|
|
7006
|
+
attachment: value.attachment
|
|
7007
|
+
}];
|
|
7008
|
+
}
|
|
7009
|
+
},
|
|
7010
|
+
async execute() {
|
|
7011
|
+
const image = mirror.latestSelectionImage();
|
|
7012
|
+
const attachment = image === void 0 ? void 0 : await deps.persistSelectionImage(image);
|
|
7013
|
+
return {
|
|
7014
|
+
digest: mirror.digest(),
|
|
7015
|
+
...attachment === void 0 ? {} : { attachment: attachmentMeta$1(attachment) }
|
|
7016
|
+
};
|
|
7017
|
+
}
|
|
7018
|
+
}));
|
|
7019
|
+
}
|
|
7020
|
+
function attachmentMeta$1(ref) {
|
|
7021
|
+
return {
|
|
7022
|
+
attachmentId: ref.attachmentId,
|
|
7023
|
+
mediaType: ref.mediaType,
|
|
7024
|
+
bytes: ref.bytes,
|
|
7025
|
+
width: ref.width,
|
|
7026
|
+
height: ref.height,
|
|
7027
|
+
...ref.name === void 0 ? {} : { name: ref.name }
|
|
7028
|
+
};
|
|
7029
|
+
}
|
|
7030
|
+
/**
|
|
7031
|
+
* Resolve the mirrored canvas selection as an edit reference image for
|
|
7032
|
+
* edit_image's `canvas_selection` source. The screenshot comes straight from
|
|
7033
|
+
* the mirror's in-memory bytes — identical to what view_canvas shows the
|
|
7034
|
+
* model — and is never persisted here: reference images go to the image
|
|
7035
|
+
* provider, not the conversation.
|
|
5227
7036
|
*/
|
|
5228
|
-
async function
|
|
5229
|
-
const
|
|
5230
|
-
if (
|
|
5231
|
-
throw new Error(
|
|
7037
|
+
async function resolveCanvasSelectionReferences(input) {
|
|
7038
|
+
const entry = input.mirror.latest();
|
|
7039
|
+
if (entry === void 0) throw new Error("The image-gen workbench canvas is not connected. Ask the user to open the image-gen workbench (gallery tab or right-sidebar studio tab), switch to the infinite canvas view, and select the shapes to edit.");
|
|
7040
|
+
if (entry.selectionCount === 0) throw new Error("The image-gen workbench canvas has no selection to use as a reference image. Ask the user to select the shapes (for example their sketch) on the infinite canvas, then retry.");
|
|
7041
|
+
const attachmentIds = [];
|
|
7042
|
+
let attachmentBacked = 0;
|
|
7043
|
+
for (const item of entry.selectionItems) {
|
|
7044
|
+
if (item.kind !== "image" || typeof item.attachmentId !== "string" || item.attachmentId.length === 0) continue;
|
|
7045
|
+
attachmentIds.push(item.attachmentId);
|
|
7046
|
+
attachmentBacked += 1;
|
|
7047
|
+
}
|
|
7048
|
+
const fullCoverage = entry.selectionItems.length === entry.selectionCount;
|
|
7049
|
+
const originals = [];
|
|
7050
|
+
if (attachmentIds.length > 0 && input.agent !== void 0) {
|
|
7051
|
+
const refs = findReferenceImages(input.agent.session.deriveMessages(), attachmentIds);
|
|
7052
|
+
for (const ref of refs) {
|
|
7053
|
+
const stored = await input.attachments.readImage(ref, input.signal);
|
|
7054
|
+
if (input.maxBytes !== void 0 && stored.data.byteLength > input.maxBytes) throw new Error(`edit_image canvas selection image is too large (${stored.data.byteLength} bytes; maximum ${input.maxBytes})`);
|
|
7055
|
+
originals.push({
|
|
7056
|
+
data: stored.data,
|
|
7057
|
+
mediaType: stored.ref.mediaType
|
|
7058
|
+
});
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
if (originals.length > 0 && fullCoverage && attachmentBacked === entry.selectionItems.length && originals.length === attachmentIds.length) return originals;
|
|
7062
|
+
const screenshot = readSelectionScreenshot(entry, input);
|
|
7063
|
+
if (screenshot !== void 0) return [...originals, screenshot];
|
|
7064
|
+
if (originals.length > 0) return originals;
|
|
7065
|
+
throw new Error("The image-gen workbench canvas selection has no usable reference image yet. Ask the user to keep the shapes selected for a moment longer (the canvas pushes a screenshot about a second after the selection settles), then retry.");
|
|
7066
|
+
}
|
|
7067
|
+
/** The mirrored selection screenshot bytes, or undefined when none was pushed. */
|
|
7068
|
+
function readSelectionScreenshot(entry, input) {
|
|
7069
|
+
const image = entry.selectionImage;
|
|
7070
|
+
if (image === void 0) return void 0;
|
|
7071
|
+
if (input.maxBytes !== void 0 && image.data.byteLength > input.maxBytes) throw new Error(`edit_image canvas selection screenshot is too large (${image.data.byteLength} bytes; maximum ${input.maxBytes})`);
|
|
7072
|
+
return {
|
|
7073
|
+
data: image.data,
|
|
7074
|
+
mediaType: image.mediaType
|
|
7075
|
+
};
|
|
5232
7076
|
}
|
|
5233
7077
|
//#endregion
|
|
5234
7078
|
//#region lib/types/comfyui-workflow.js
|
|
@@ -5271,8 +7115,8 @@ function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed(), image
|
|
|
5271
7115
|
};
|
|
5272
7116
|
let imageInputs = 0;
|
|
5273
7117
|
const prepared = Object.fromEntries(Object.entries(workflow).map(([nodeId, value]) => {
|
|
5274
|
-
const node = record$
|
|
5275
|
-
const inputs = node === void 0 ? void 0 : record$
|
|
7118
|
+
const node = record$6(value);
|
|
7119
|
+
const inputs = node === void 0 ? void 0 : record$6(node.inputs);
|
|
5276
7120
|
if (inputs === void 0) return [nodeId, value];
|
|
5277
7121
|
const nextInputs = inject(inputs);
|
|
5278
7122
|
if (isImagePlaceholder(nextInputs[IMAGE_INPUT_KEY])) {
|
|
@@ -5308,7 +7152,7 @@ function parseWorkflow(workflowJson) {
|
|
|
5308
7152
|
}
|
|
5309
7153
|
if (!isRecord(value) || Object.keys(value).length === 0) throw new Error("ComfyUI workflow must be a non-empty API-format JSON object");
|
|
5310
7154
|
if (!Object.values(value).some((node) => {
|
|
5311
|
-
const inputs = record$
|
|
7155
|
+
const inputs = record$6(node)?.inputs;
|
|
5312
7156
|
return inputs !== void 0 && containsPromptPlaceholder(inputs);
|
|
5313
7157
|
})) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} (or ${LEGACY_PROMPT_PLACEHOLDER}) in a text input`);
|
|
5314
7158
|
return value;
|
|
@@ -5324,7 +7168,7 @@ function containsPromptPlaceholder(value) {
|
|
|
5324
7168
|
function isRecord(value) {
|
|
5325
7169
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5326
7170
|
}
|
|
5327
|
-
function record$
|
|
7171
|
+
function record$6(value) {
|
|
5328
7172
|
return isRecord(value) ? value : void 0;
|
|
5329
7173
|
}
|
|
5330
7174
|
//#endregion
|
|
@@ -5458,9 +7302,9 @@ async function waitForOutput(baseURL, promptId, signal) {
|
|
|
5458
7302
|
});
|
|
5459
7303
|
const text = await readBoundedText$3(response, MAX_HISTORY_BYTES);
|
|
5460
7304
|
if (!response.ok) throw new Error(`ComfyUI history request failed (${response.status}): ${redactSecrets(text).slice(0, ERROR_LIMIT$3)}`);
|
|
5461
|
-
const entry = record$
|
|
7305
|
+
const entry = record$5(parseJsonRecord(text, "ComfyUI history returned invalid JSON")[promptId]);
|
|
5462
7306
|
if (entry !== void 0) {
|
|
5463
|
-
const status = record$
|
|
7307
|
+
const status = record$5(entry.status);
|
|
5464
7308
|
if (status?.status_str === "error") throw new Error(`ComfyUI workflow failed: ${redactSecrets(JSON.stringify(status.messages ?? status)).slice(0, ERROR_LIMIT$3)}`);
|
|
5465
7309
|
const output = firstOutputImage(entry.outputs);
|
|
5466
7310
|
if (output !== void 0) return output;
|
|
@@ -5470,13 +7314,13 @@ async function waitForOutput(baseURL, promptId, signal) {
|
|
|
5470
7314
|
}
|
|
5471
7315
|
}
|
|
5472
7316
|
function firstOutputImage(value) {
|
|
5473
|
-
const outputs = record$
|
|
7317
|
+
const outputs = record$5(value);
|
|
5474
7318
|
if (outputs === void 0) return void 0;
|
|
5475
7319
|
for (const nodeOutput of Object.values(outputs)) {
|
|
5476
|
-
const images = record$
|
|
7320
|
+
const images = record$5(nodeOutput)?.images;
|
|
5477
7321
|
if (!Array.isArray(images)) continue;
|
|
5478
7322
|
for (const image of images) {
|
|
5479
|
-
const item = record$
|
|
7323
|
+
const item = record$5(image);
|
|
5480
7324
|
if (typeof item?.filename !== "string" || item.filename.length === 0) continue;
|
|
5481
7325
|
const output = {
|
|
5482
7326
|
filename: item.filename,
|
|
@@ -5497,7 +7341,7 @@ async function downloadOutput(baseURL, output, maxBytes, signal) {
|
|
|
5497
7341
|
signal
|
|
5498
7342
|
});
|
|
5499
7343
|
if (!response.ok) throw new Error(`ComfyUI image download failed (${response.status})`);
|
|
5500
|
-
const mediaType = imageMediaType$
|
|
7344
|
+
const mediaType = imageMediaType$3(response.headers.get("content-type")) ?? imageMediaTypeFromName(output.filename);
|
|
5501
7345
|
if (mediaType === void 0) throw new Error("ComfyUI image download returned an unsupported content type");
|
|
5502
7346
|
return {
|
|
5503
7347
|
data: await readBoundedBytes$2(response, maxBytes),
|
|
@@ -5517,7 +7361,7 @@ function comfyUIBaseURL(value) {
|
|
|
5517
7361
|
function endpoint(baseURL, path) {
|
|
5518
7362
|
return new URL(path, baseURL);
|
|
5519
7363
|
}
|
|
5520
|
-
function record$
|
|
7364
|
+
function record$5(value) {
|
|
5521
7365
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
5522
7366
|
}
|
|
5523
7367
|
function parseJsonRecord(text, message) {
|
|
@@ -5527,11 +7371,11 @@ function parseJsonRecord(text, message) {
|
|
|
5527
7371
|
} catch {
|
|
5528
7372
|
throw new Error(message);
|
|
5529
7373
|
}
|
|
5530
|
-
const parsed = record$
|
|
7374
|
+
const parsed = record$5(value);
|
|
5531
7375
|
if (parsed === void 0) throw new Error(message);
|
|
5532
7376
|
return parsed;
|
|
5533
7377
|
}
|
|
5534
|
-
function imageMediaType$
|
|
7378
|
+
function imageMediaType$3(value) {
|
|
5535
7379
|
const mediaType = value?.split(";", 1)[0]?.trim().toLowerCase();
|
|
5536
7380
|
return mediaType === "image/png" || mediaType === "image/jpeg" || mediaType === "image/webp" || mediaType === "image/gif" ? mediaType : void 0;
|
|
5537
7381
|
}
|
|
@@ -5752,13 +7596,13 @@ async function requestGoogleImage(input) {
|
|
|
5752
7596
|
};
|
|
5753
7597
|
}
|
|
5754
7598
|
function outputImage(value) {
|
|
5755
|
-
const interaction = record$
|
|
7599
|
+
const interaction = record$4(value);
|
|
5756
7600
|
if (interaction === void 0) return void 0;
|
|
5757
7601
|
const direct = imageContent(interaction.output_image, false);
|
|
5758
7602
|
if (direct !== void 0) return direct;
|
|
5759
7603
|
if (!Array.isArray(interaction.steps)) return void 0;
|
|
5760
7604
|
for (const step of interaction.steps) {
|
|
5761
|
-
const modelOutput = record$
|
|
7605
|
+
const modelOutput = record$4(step);
|
|
5762
7606
|
if (modelOutput?.type !== "model_output" || !Array.isArray(modelOutput.content)) continue;
|
|
5763
7607
|
for (const content of modelOutput.content) {
|
|
5764
7608
|
const image = imageContent(content, true);
|
|
@@ -5767,14 +7611,14 @@ function outputImage(value) {
|
|
|
5767
7611
|
}
|
|
5768
7612
|
}
|
|
5769
7613
|
function imageContent(value, requiresImageType) {
|
|
5770
|
-
const image = record$
|
|
7614
|
+
const image = record$4(value);
|
|
5771
7615
|
if (image === void 0 || requiresImageType && image.type !== "image" || typeof image.data !== "string") return void 0;
|
|
5772
7616
|
return {
|
|
5773
7617
|
data: image.data,
|
|
5774
7618
|
mime_type: image.mime_type
|
|
5775
7619
|
};
|
|
5776
7620
|
}
|
|
5777
|
-
function record$
|
|
7621
|
+
function record$4(value) {
|
|
5778
7622
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
5779
7623
|
}
|
|
5780
7624
|
function mediaTypeOf(value) {
|
|
@@ -5812,220 +7656,6 @@ async function readBoundedText$2(response, maxBytes, label) {
|
|
|
5812
7656
|
return new TextDecoder().decode(joined);
|
|
5813
7657
|
}
|
|
5814
7658
|
//#endregion
|
|
5815
|
-
//#region lib/types/reference-image.js
|
|
5816
|
-
/** Latest-DSH compatibility boundary for resolving conversation image references. */
|
|
5817
|
-
/**
|
|
5818
|
-
* Resolve one or more edit references while keeping every DSH-specific detail
|
|
5819
|
-
* behind this compatibility boundary. Explicit selectors preserve caller order;
|
|
5820
|
-
* without selectors, all images in the newest image-bearing message are used.
|
|
5821
|
-
*/
|
|
5822
|
-
async function resolveReferenceImages(input) {
|
|
5823
|
-
const sourceAttachmentIds = mergeSelectors({
|
|
5824
|
-
single: input.sourceAttachmentId,
|
|
5825
|
-
multiple: input.sourceAttachmentIds,
|
|
5826
|
-
singleName: "source_attachment_id",
|
|
5827
|
-
multipleName: "source_attachment_ids",
|
|
5828
|
-
equal: attachmentIdsEqual
|
|
5829
|
-
});
|
|
5830
|
-
const sourcePaths = mergeSelectors({
|
|
5831
|
-
single: input.sourcePath,
|
|
5832
|
-
multiple: input.sourcePaths,
|
|
5833
|
-
singleName: "source_path",
|
|
5834
|
-
multipleName: "source_paths",
|
|
5835
|
-
equal: (left, right) => left.trim() === right.trim()
|
|
5836
|
-
});
|
|
5837
|
-
if (sourceAttachmentIds !== void 0 && sourcePaths !== void 0) throw new Error("edit_image accepts only one of source_attachment_id, source_attachment_ids, source_path, or source_paths");
|
|
5838
|
-
if (sourcePaths !== void 0) return Promise.all(sourcePaths.map((sourcePath) => readWorkspaceReferenceImage({
|
|
5839
|
-
sourcePath,
|
|
5840
|
-
...input.agent?.session.header?.cwd === void 0 ? {} : { workspaceRoot: input.agent.session.header.cwd },
|
|
5841
|
-
...input.maxBytes === void 0 ? {} : { maxBytes: input.maxBytes },
|
|
5842
|
-
signal: input.signal
|
|
5843
|
-
})));
|
|
5844
|
-
if (input.agent === void 0) throw new Error("edit_image requires an active DSH agent session to resolve a reference image");
|
|
5845
|
-
const refs = findReferenceImages(input.agent.session.deriveMessages(), sourceAttachmentIds);
|
|
5846
|
-
if (refs.length === 0) {
|
|
5847
|
-
if (sourceAttachmentIds !== void 0) throw new Error(`edit_image could not find image attachment ${sourceAttachmentIds[0]} in the current conversation`);
|
|
5848
|
-
throw new Error("edit_image requires an image in the current conversation; upload or generate an image first");
|
|
5849
|
-
}
|
|
5850
|
-
if (sourceAttachmentIds !== void 0 && refs.length !== sourceAttachmentIds.length) {
|
|
5851
|
-
const missing = sourceAttachmentIds.find((id) => !refs.some((ref) => attachmentIdsEqual(String(ref.attachmentId), id)));
|
|
5852
|
-
throw new Error(`edit_image could not find image attachment ${missing ?? "unknown"} in the current conversation`);
|
|
5853
|
-
}
|
|
5854
|
-
return Promise.all(refs.map(async (ref) => {
|
|
5855
|
-
const stored = await input.attachments.readImage(ref, input.signal);
|
|
5856
|
-
if (input.maxBytes !== void 0 && stored.data.byteLength > input.maxBytes) throw new Error(`edit_image source image is too large (${stored.data.byteLength} bytes; maximum ${input.maxBytes})`);
|
|
5857
|
-
return {
|
|
5858
|
-
data: stored.data,
|
|
5859
|
-
mediaType: stored.ref.mediaType
|
|
5860
|
-
};
|
|
5861
|
-
}));
|
|
5862
|
-
}
|
|
5863
|
-
/** Find images in caller order, or every image in the newest image-bearing message. */
|
|
5864
|
-
function findReferenceImages(messages, sourceAttachmentIds) {
|
|
5865
|
-
if (sourceAttachmentIds !== void 0) return sourceAttachmentIds.flatMap((id) => {
|
|
5866
|
-
const ref = findReferenceImage(messages, id);
|
|
5867
|
-
return ref === void 0 ? [] : [ref];
|
|
5868
|
-
});
|
|
5869
|
-
const latestHumanMessage = [...messages].reverse().find((message) => message.source?.kind === "user");
|
|
5870
|
-
if (latestHumanMessage !== void 0) {
|
|
5871
|
-
const refs = collectInBlocks(latestHumanMessage.content);
|
|
5872
|
-
if (refs.length > 0) return refs;
|
|
5873
|
-
}
|
|
5874
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
5875
|
-
const refs = collectInBlocks(messages[index]?.content ?? []);
|
|
5876
|
-
if (refs.length > 0) return refs;
|
|
5877
|
-
}
|
|
5878
|
-
return [];
|
|
5879
|
-
}
|
|
5880
|
-
/**
|
|
5881
|
-
* Read an explicitly named workspace image without exposing filesystem or DSH
|
|
5882
|
-
* details to provider adapters. Both lexical and real-path containment are
|
|
5883
|
-
* enforced so absolute paths, parent traversal, and symlink escapes fail.
|
|
5884
|
-
*/
|
|
5885
|
-
async function readWorkspaceReferenceImage(input) {
|
|
5886
|
-
const requested = input.sourcePath.trim();
|
|
5887
|
-
if (requested.length === 0) throw new Error("edit_image source_path must not be empty");
|
|
5888
|
-
if (input.workspaceRoot === void 0) throw new Error("edit_image source_path requires an active DSH session workspace");
|
|
5889
|
-
const root = resolve(input.workspaceRoot);
|
|
5890
|
-
const candidate = isAbsolute(requested) ? resolve(requested) : resolve(root, requested);
|
|
5891
|
-
if (!containsPath$1(root, candidate)) throw new Error("edit_image source_path must stay inside the session workspace: " + requested);
|
|
5892
|
-
let realRoot;
|
|
5893
|
-
let realCandidate;
|
|
5894
|
-
try {
|
|
5895
|
-
[realRoot, realCandidate] = await Promise.all([realpath(root), realpath(candidate)]);
|
|
5896
|
-
} catch (error) {
|
|
5897
|
-
if (error.code === "ENOENT") throw new Error("edit_image could not find workspace image: " + requested);
|
|
5898
|
-
throw error;
|
|
5899
|
-
}
|
|
5900
|
-
if (!containsPath$1(realRoot, realCandidate)) throw new Error("edit_image source_path resolves outside the session workspace: " + requested);
|
|
5901
|
-
const file = await stat(realCandidate);
|
|
5902
|
-
if (!file.isFile()) throw new Error("edit_image source_path is not a file: " + requested);
|
|
5903
|
-
if (input.maxBytes !== void 0 && file.size > input.maxBytes) throw new Error("edit_image source image is too large (" + file.size + " bytes; maximum " + input.maxBytes + ")");
|
|
5904
|
-
const data = await readFile(realCandidate, { signal: input.signal });
|
|
5905
|
-
const mediaType = detectImageMediaType(data);
|
|
5906
|
-
if (mediaType === void 0) throw new Error("edit_image source_path is not a supported PNG, JPEG, WebP, or GIF image: " + requested);
|
|
5907
|
-
return {
|
|
5908
|
-
data: new Uint8Array(data),
|
|
5909
|
-
mediaType
|
|
5910
|
-
};
|
|
5911
|
-
}
|
|
5912
|
-
/** Find the newest matching image in the effective, replacement-aware history. */
|
|
5913
|
-
function findReferenceImage(messages, sourceAttachmentId) {
|
|
5914
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
5915
|
-
const found = findInBlocks(messages[index]?.content ?? [], sourceAttachmentId);
|
|
5916
|
-
if (found !== void 0) return found;
|
|
5917
|
-
}
|
|
5918
|
-
}
|
|
5919
|
-
/** Validate an untrusted serialized image reference at an HTTP/UI boundary. */
|
|
5920
|
-
function parseImageAttachmentRef(value) {
|
|
5921
|
-
const ref = record$4(value);
|
|
5922
|
-
if (ref === void 0) return void 0;
|
|
5923
|
-
if (typeof ref.attachmentId !== "string" || !imageMediaType$3(ref.mediaType)) return void 0;
|
|
5924
|
-
if (!nonNegativeInteger(ref.bytes) || !positiveInteger(ref.width) || !positiveInteger(ref.height)) return void 0;
|
|
5925
|
-
if (ref.name !== void 0 && typeof ref.name !== "string") return void 0;
|
|
5926
|
-
const originalDimensions = ref.originalDimensions === void 0 ? void 0 : parseDimensions(ref.originalDimensions);
|
|
5927
|
-
if (ref.originalDimensions !== void 0 && originalDimensions === void 0) return void 0;
|
|
5928
|
-
return {
|
|
5929
|
-
attachmentId: ref.attachmentId,
|
|
5930
|
-
mediaType: ref.mediaType,
|
|
5931
|
-
bytes: ref.bytes,
|
|
5932
|
-
width: ref.width,
|
|
5933
|
-
height: ref.height,
|
|
5934
|
-
...typeof ref.name === "string" ? { name: ref.name } : {},
|
|
5935
|
-
...originalDimensions === void 0 ? {} : { originalDimensions }
|
|
5936
|
-
};
|
|
5937
|
-
}
|
|
5938
|
-
function findInBlocks(blocks, sourceAttachmentId) {
|
|
5939
|
-
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
5940
|
-
const block = blocks[index];
|
|
5941
|
-
if (block === void 0) continue;
|
|
5942
|
-
if (block.type === "image") {
|
|
5943
|
-
if (sourceAttachmentId === void 0 || attachmentIdsEqual(String(block.attachment.attachmentId), sourceAttachmentId)) return block.attachment;
|
|
5944
|
-
continue;
|
|
5945
|
-
}
|
|
5946
|
-
if (block.type === "tool-result") {
|
|
5947
|
-
const nested = findInBlocks(block.content, sourceAttachmentId);
|
|
5948
|
-
if (nested !== void 0) return nested;
|
|
5949
|
-
}
|
|
5950
|
-
}
|
|
5951
|
-
}
|
|
5952
|
-
function parseDimensions(value) {
|
|
5953
|
-
const dimensions = record$4(value);
|
|
5954
|
-
if (dimensions === void 0 || !positiveInteger(dimensions.width) || !positiveInteger(dimensions.height)) return void 0;
|
|
5955
|
-
return {
|
|
5956
|
-
width: dimensions.width,
|
|
5957
|
-
height: dimensions.height
|
|
5958
|
-
};
|
|
5959
|
-
}
|
|
5960
|
-
function imageMediaType$3(value) {
|
|
5961
|
-
return value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
|
|
5962
|
-
}
|
|
5963
|
-
function collectInBlocks(blocks) {
|
|
5964
|
-
const refs = [];
|
|
5965
|
-
for (const block of blocks) {
|
|
5966
|
-
if (block.type === "image") refs.push(block.attachment);
|
|
5967
|
-
if (block.type === "tool-result") refs.push(...collectInBlocks(block.content));
|
|
5968
|
-
}
|
|
5969
|
-
return refs;
|
|
5970
|
-
}
|
|
5971
|
-
function attachmentIdsEqual(actual, requested) {
|
|
5972
|
-
if (actual === requested) return true;
|
|
5973
|
-
const actualDigest = sha256Digest(actual);
|
|
5974
|
-
const requestedDigest = sha256Digest(requested);
|
|
5975
|
-
return actualDigest !== void 0 && actualDigest === requestedDigest;
|
|
5976
|
-
}
|
|
5977
|
-
function sha256Digest(value) {
|
|
5978
|
-
return /^(?:sha256:)?([0-9a-f]{64})$/i.exec(value.trim())?.[1]?.toLowerCase();
|
|
5979
|
-
}
|
|
5980
|
-
function mergeSelectors(input) {
|
|
5981
|
-
if (input.multiple === void 0) return input.single === void 0 ? void 0 : [input.single];
|
|
5982
|
-
if (input.multiple.length === 0) {
|
|
5983
|
-
if (input.single !== void 0) return [input.single];
|
|
5984
|
-
throw new Error(`edit_image ${input.multipleName} must not be empty`);
|
|
5985
|
-
}
|
|
5986
|
-
const single = input.single;
|
|
5987
|
-
if (single !== void 0 && !input.multiple.some((value) => input.equal(single, value))) throw new Error(`edit_image ${input.singleName} must also appear in ${input.multipleName} when both are provided`);
|
|
5988
|
-
return input.multiple;
|
|
5989
|
-
}
|
|
5990
|
-
function detectImageMediaType(data) {
|
|
5991
|
-
if (startsWith(data, [
|
|
5992
|
-
137,
|
|
5993
|
-
80,
|
|
5994
|
-
78,
|
|
5995
|
-
71,
|
|
5996
|
-
13,
|
|
5997
|
-
10,
|
|
5998
|
-
26,
|
|
5999
|
-
10
|
|
6000
|
-
])) return "image/png";
|
|
6001
|
-
if (startsWith(data, [
|
|
6002
|
-
255,
|
|
6003
|
-
216,
|
|
6004
|
-
255
|
|
6005
|
-
])) return "image/jpeg";
|
|
6006
|
-
if (ascii(data, 0, 6) === "GIF87a" || ascii(data, 0, 6) === "GIF89a") return "image/gif";
|
|
6007
|
-
if (ascii(data, 0, 4) === "RIFF" && ascii(data, 8, 4) === "WEBP") return "image/webp";
|
|
6008
|
-
}
|
|
6009
|
-
function startsWith(data, signature) {
|
|
6010
|
-
return signature.every((byte, index) => data[index] === byte);
|
|
6011
|
-
}
|
|
6012
|
-
function ascii(data, offset, length) {
|
|
6013
|
-
return String.fromCharCode(...data.subarray(offset, offset + length));
|
|
6014
|
-
}
|
|
6015
|
-
function containsPath$1(parent, child) {
|
|
6016
|
-
const rel = relative(parent, child);
|
|
6017
|
-
return rel === "" || rel !== ".." && !rel.startsWith(".." + sep) && !isAbsolute(rel);
|
|
6018
|
-
}
|
|
6019
|
-
function positiveInteger(value) {
|
|
6020
|
-
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
6021
|
-
}
|
|
6022
|
-
function nonNegativeInteger(value) {
|
|
6023
|
-
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
6024
|
-
}
|
|
6025
|
-
function record$4(value) {
|
|
6026
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6027
|
-
}
|
|
6028
|
-
//#endregion
|
|
6029
7659
|
//#region lib/types/workspace-save.js
|
|
6030
7660
|
/** Persist one generated image as a file under the session workspace. */
|
|
6031
7661
|
/** File extension for each supported image media type. */
|
|
@@ -6505,10 +8135,11 @@ async function downloadImage$1(url, provider, input) {
|
|
|
6505
8135
|
...input.apiKey === void 0 ? {} : { headers: { authorization: `Bearer ${input.apiKey}` } }
|
|
6506
8136
|
});
|
|
6507
8137
|
if (!response.ok) throw new Error(`${provider} image download failed (${response.status})`);
|
|
6508
|
-
const
|
|
8138
|
+
const data = await readBoundedBytes$1(response, input.maxBytes);
|
|
8139
|
+
const mediaType = detectImageMediaType(data) ?? imageMediaType$2(response.headers.get("content-type"));
|
|
6509
8140
|
if (mediaType === void 0) throw new Error(`${provider} image download returned unsupported content type`);
|
|
6510
8141
|
return {
|
|
6511
|
-
data
|
|
8142
|
+
data,
|
|
6512
8143
|
mediaType
|
|
6513
8144
|
};
|
|
6514
8145
|
}
|
|
@@ -17897,6 +19528,8 @@ async function serveStudio(req, res, deps) {
|
|
|
17897
19528
|
if (req.method !== "POST") return jsonError$1(res, 405, "method-not-allowed");
|
|
17898
19529
|
if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$1(res, 415, "json-required");
|
|
17899
19530
|
const controller = new AbortController();
|
|
19531
|
+
const upstreamTimeout = AbortSignal.timeout(15e4);
|
|
19532
|
+
const signal = AbortSignal.any([controller.signal, upstreamTimeout]);
|
|
17900
19533
|
const onConnectionClose = () => {
|
|
17901
19534
|
if (!res.writableEnded) controller.abort(/* @__PURE__ */ new Error("The browser closed the image generation request."));
|
|
17902
19535
|
};
|
|
@@ -17912,10 +19545,11 @@ async function serveStudio(req, res, deps) {
|
|
|
17912
19545
|
} catch (error) {
|
|
17913
19546
|
return jsonError$1(res, 400, errorMessage(error, "invalid-request"));
|
|
17914
19547
|
}
|
|
17915
|
-
const output = await deps.generate(input,
|
|
19548
|
+
const output = await deps.generate(input, signal);
|
|
17916
19549
|
if (!res.headersSent && !res.writableEnded && !res.destroyed) json$1(res, 200, output);
|
|
17917
19550
|
} catch (error) {
|
|
17918
19551
|
if (res.headersSent || res.writableEnded || res.destroyed) return;
|
|
19552
|
+
if (upstreamTimeout.aborted) return jsonError$1(res, 504, "上游生成超时(150 秒未返回),请检查网络或代理后重试");
|
|
17919
19553
|
jsonError$1(res, controller.signal.aborted ? 499 : 502, errorMessage(error, "generation-failed"));
|
|
17920
19554
|
} finally {
|
|
17921
19555
|
res.off("close", onConnectionClose);
|
|
@@ -18364,6 +19998,7 @@ function providerOverrideOf(value) {
|
|
|
18364
19998
|
function apply(ctx, config = {}) {
|
|
18365
19999
|
let current = () => migrateOpenAICompatConfig(config);
|
|
18366
20000
|
const knownWorkspaceRoots = /* @__PURE__ */ new Set();
|
|
20001
|
+
const canvasMirror = new CanvasMirror();
|
|
18367
20002
|
installImageSettings(ctx, config, {
|
|
18368
20003
|
setSource: (source) => {
|
|
18369
20004
|
current = () => migrateOpenAICompatConfig(source());
|
|
@@ -18422,6 +20057,27 @@ function apply(ctx, config = {}) {
|
|
|
18422
20057
|
config: () => current()
|
|
18423
20058
|
})
|
|
18424
20059
|
}), "dsh-image-gen: test connection route");
|
|
20060
|
+
ctx.effect(() => ctx.webServer.register({
|
|
20061
|
+
kind: "exact",
|
|
20062
|
+
path: CANVAS_STATE_ROUTE,
|
|
20063
|
+
handler: (req, res) => serveCanvasState(req, res, {
|
|
20064
|
+
mirror: canvasMirror,
|
|
20065
|
+
maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4) + 262144,
|
|
20066
|
+
maxImageBytes: ctx.attachments.imageLimits.maxImageBytes
|
|
20067
|
+
})
|
|
20068
|
+
}), "dsh-image-gen: canvas state route");
|
|
20069
|
+
ctx.inject(["systemPrompt"], (promptCtx) => {
|
|
20070
|
+
promptCtx.systemPrompt.context({
|
|
20071
|
+
name: "dsh-image-gen:canvas",
|
|
20072
|
+
order: 60,
|
|
20073
|
+
text: () => canvasMirror.digest()
|
|
20074
|
+
});
|
|
20075
|
+
});
|
|
20076
|
+
registerCanvasTools(ctx, canvasMirror, { persistSelectionImage: (image) => ctx.attachments.saveImage({
|
|
20077
|
+
data: image.data,
|
|
20078
|
+
mediaType: image.mediaType,
|
|
20079
|
+
name: "canvas-selection"
|
|
20080
|
+
}) });
|
|
18425
20081
|
ctx.effect(() => ctx.webServer.register({
|
|
18426
20082
|
kind: "exact",
|
|
18427
20083
|
path: STUDIO_ROUTE,
|
|
@@ -18568,7 +20224,7 @@ function apply(ctx, config = {}) {
|
|
|
18568
20224
|
}));
|
|
18569
20225
|
ctx.tools.register(defineTool({
|
|
18570
20226
|
name: "edit_image",
|
|
18571
|
-
description: "Edit, combine, or restyle existing images with the configured provider. Images attached inline to the latest human message are already readable DSH attachments even when no workspace file exists. In that case, call edit_image immediately with prompt only; NEVER call read_image, glob, or shell to locate them, and NEVER invent @ paths. All inline images will be used in upload order. For specific older conversation images use source_attachment_id or source_attachment_ids; both canonical sha256: IDs and full bare SHA-256 digests are accepted. For files the user explicitly names in the workspace use source_path or source_paths. Provide exactly one selector field. Without a selector, images from the latest human message take priority; only when that message has no images does editing fall back to the newest conversation image.",
|
|
20227
|
+
description: "Edit, combine, or restyle existing images with the configured provider. Images attached inline to the latest human message are already readable DSH attachments even when no workspace file exists. In that case, call edit_image immediately with prompt only; NEVER call read_image, glob, or shell to locate them, and NEVER invent @ paths. All inline images will be used in upload order. For specific older conversation images use source_attachment_id or source_attachment_ids; both canonical sha256: IDs and full bare SHA-256 digests are accepted. For files the user explicitly names in the workspace use source_path or source_paths. For what the user selected or drew on the image-gen workbench canvas (for example a hand-drawn sketch) use source=canvas_selection; canvas_state can verify a selection exists first. Provide exactly one selector field. Without a selector, images from the latest human message take priority; only when that message has no images does editing fall back to the newest conversation image.",
|
|
18572
20228
|
parameters: {
|
|
18573
20229
|
prompt: {
|
|
18574
20230
|
type: "string",
|
|
@@ -18593,6 +20249,11 @@ function apply(ctx, config = {}) {
|
|
|
18593
20249
|
type: "string",
|
|
18594
20250
|
description: "Optional model name for this call only, overriding the configured model. Not used by ComfyUI (use workflow instead)."
|
|
18595
20251
|
},
|
|
20252
|
+
source: {
|
|
20253
|
+
type: "string",
|
|
20254
|
+
enum: ["canvas_selection"],
|
|
20255
|
+
description: "Use the current selection on the image-gen workbench infinite canvas as the reference image(s): full-resolution originals when the selection is conversation-generated images, plus a screenshot of the whole selection when it also contains other content (hand-drawn strokes, pasted images). Choose this when the user refers to what they selected or drew on the canvas; combine with no other selector field."
|
|
20256
|
+
},
|
|
18596
20257
|
source_attachment_id: {
|
|
18597
20258
|
type: "string",
|
|
18598
20259
|
description: "Optional attachment id of a specific image already present in the current conversation."
|
|
@@ -18645,7 +20306,15 @@ function apply(ctx, config = {}) {
|
|
|
18645
20306
|
output: imageOutput("Edited"),
|
|
18646
20307
|
async execute(args, exec) {
|
|
18647
20308
|
const active = resolveProvider(withProviderOverrides(current(), providerOverrideOf(args.provider), args.model));
|
|
18648
|
-
const
|
|
20309
|
+
const canvasSelection = args.source === "canvas_selection";
|
|
20310
|
+
if (canvasSelection && (args.source_attachment_id !== void 0 || Array.isArray(args.source_attachment_ids) || args.source_path !== void 0 || Array.isArray(args.source_paths))) throw new Error("edit_image source=canvas_selection cannot be combined with source_attachment_id, source_attachment_ids, source_path, or source_paths; provide exactly one selector");
|
|
20311
|
+
const sourceImages = canvasSelection ? await resolveCanvasSelectionReferences({
|
|
20312
|
+
mirror: canvasMirror,
|
|
20313
|
+
attachments: ctx.attachments,
|
|
20314
|
+
...exec.agent === void 0 ? {} : { agent: exec.agent },
|
|
20315
|
+
maxBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
20316
|
+
signal: exec.signal
|
|
20317
|
+
}) : await resolveReferenceImages({
|
|
18649
20318
|
...exec.agent === void 0 ? {} : { agent: exec.agent },
|
|
18650
20319
|
attachments: ctx.attachments,
|
|
18651
20320
|
...typeof args.source_attachment_id === "string" ? { sourceAttachmentId: args.source_attachment_id } : {},
|
|
@@ -18896,4 +20565,4 @@ function installImageSettings(ctx, config, hooks) {
|
|
|
18896
20565
|
ctx.logger.warn("dsh-image-gen: this DSH exposes neither settings API generation; settings UI stays on the composition entry");
|
|
18897
20566
|
}
|
|
18898
20567
|
//#endregion
|
|
18899
|
-
export { Config, DELETE_ROUTE, IMAGE_ROUTE, INSPIRATION_ROUTE, SAVE_WORKSPACE_ROUTE, STUDIO_ROUTE, TEST_CONNECTION_ROUTE, apply, imageAttachmentFromMeta, inject, name };
|
|
20568
|
+
export { CANVAS_STATE_ROUTE, Config, DELETE_ROUTE, IMAGE_ROUTE, INSPIRATION_ROUTE, SAVE_WORKSPACE_ROUTE, STUDIO_ROUTE, TEST_CONNECTION_ROUTE, apply, imageAttachmentFromMeta, inject, name };
|