返回 CodeWhale
dsh-tools-compat.js
根目录 / crates / tui / extension-host / src / dsh / dsh-tools-compat.js
1 // @ts-nocheck
2 /**
3 * `@deepseek-ai/dsh-tools` compatibility module for the Codewhale extension host.
4 *
5 * DSH plugins import `defineTool` and its error types from
6 * `@deepseek-ai/dsh-tools` as a peer. The real package also ships the DSH
7 * ToolRuntime service, which would be a second tool authority; Codewhale's
8 * Rust core is the only one. So the host resolves that specifier here, to the
9 * *definition-side* subset only.
10 *
11 * Lines between the VERBATIM markers are copied unchanged from
12 * `@deepseek-ai/dsh-tools@0.1.7-alpha.2` `lib/index.js` (lines 8-888 and
13 * 2528-2558), MIT License, Copyright (c) 2026 DeepSeek. See
14 * ../../LICENSES.txt. Everything outside the markers is Codewhale's.
15 *
16 * `ToolRuntime` is deliberately NOT exported: a plugin that tries to provide
17 * or construct the DSH tool runtime fails to link, loudly.
18 */
19 import { assertNever, isJsonValue } from '@deepseek-ai/dsh-util-values'
20
21 /** Local stand-in for `@deepseek-ai/dsh-llm` `HarnessError` (same shape). */
22 export class HarnessError extends Error {
23 code
24 constructor(message, code, options) {
25 super(message, options)
26 this.code = code
27 this.name = new.target.name
28 }
29 }
30
31 // ---- VERBATIM @deepseek-ai/dsh-tools@0.1.7-alpha.2 lib/index.js:8-888 ----
32 //#region lib/types/json-schema.js
33 /**
34 * Enforced JSON Schema subset shared by tool outputs, generated PTC mode
35 * types, subagents, and workflows. The subset accepts any JSON root, an
36 * annotation-only schema for unconstrained JSON, one scalar `type`, object
37 * `properties`/`required`/boolean `additionalProperties`, array `items`,
38 * type-correct scalar `enum`/`const`, and exact-one `oneOf`.
39 *
40 * Unsupported or misplaced keywords reject rather than being accepted without
41 * enforcement. Consumers that require an object root apply
42 * {@link assertObjectJsonSchema} before accepting input.
43 * @module dsh-tools/json-schema
44 */
45 /**
46 * Thrown when a raw schema falls outside the enforced subset. `violations`
47 * lists every offending path instead of stopping at the first author error.
48 */
49 var JsonSchemaError = class extends HarnessError {
50 /** Individual schema violations in walk order. */
51 violations;
52 constructor(violations) {
53 super(`unsupported JSON schema: ${violations.join("; ")}`, "UNSUPPORTED_SCHEMA");
54 this.name = "JsonSchemaError";
55 this.violations = violations;
56 }
57 };
58 const CONSTRAINT_KEYWORDS = new Set([
59 "type",
60 "oneOf",
61 "properties",
62 "required",
63 "additionalProperties",
64 "items",
65 "enum",
66 "const"
67 ]);
68 const ANNOTATION_KEYWORDS = new Set([
69 "description",
70 "title",
71 "default",
72 "examples"
73 ]);
74 const SCHEMA_TYPES = [
75 "object",
76 "array",
77 "string",
78 "number",
79 "integer",
80 "boolean",
81 "null"
82 ];
83 /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
84 function hasIntrinsicConstructor(prototype, name) {
85 const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
86 if (typeof constructor !== "function") return false;
87 try {
88 return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
89 } catch {
90 return false;
91 }
92 }
93 /** Whether a candidate is one realm's intrinsic `Object.prototype`. */
94 function isIntrinsicObjectPrototype(value) {
95 return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
96 }
97 /**
98 * Test for a realm-agnostic plain JSON record without accepting arrays or
99 * exotic objects.
100 * @param value - candidate record from any JavaScript realm.
101 * @returns Whether the value has a plain-object prototype chain.
102 */
103 function isPlainJsonRecord(value) {
104 if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
105 try {
106 const prototype = Object.getPrototypeOf(value);
107 return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
108 } catch {
109 return false;
110 }
111 }
112 /** Whether an array uses one realm's intrinsic `Array.prototype`. */
113 function hasPlainArrayPrototype(value) {
114 const prototype = Object.getPrototypeOf(value);
115 if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
116 const objectPrototype = Object.getPrototypeOf(prototype);
117 return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
118 }
119 /** Return whether a record contains only own enumerable string keys. */
120 function hasOnlyEnumerableStringKeys(value) {
121 try {
122 return Reflect.ownKeys(value).every((key) => typeof key === "string" && Object.prototype.propertyIsEnumerable.call(value, key));
123 } catch {
124 return false;
125 }
126 }
127 /**
128 * Test for an ordinary schema record whose keys survive JSON projection.
129 * @param value - candidate record from any JavaScript realm.
130 * @returns Whether the record has an intrinsic prototype and only own enumerable string keys.
131 */
132 function isJsonSchemaRecord(value) {
133 return isPlainJsonRecord(value) && hasOnlyEnumerableStringKeys(value);
134 }
135 /**
136 * Test for a dense ordinary array with no JSON-invisible decorations.
137 * @param value - candidate array from any JavaScript realm.
138 * @returns Whether the array is intrinsic, dense, and undecorated.
139 */
140 function isPlainJsonArray(value) {
141 if (!Array.isArray(value)) return false;
142 try {
143 if (!hasPlainArrayPrototype(value) || Reflect.ownKeys(value).length !== value.length + 1) return false;
144 for (let index = 0; index < value.length; index++) if (!Object.hasOwn(value, index)) return false;
145 return true;
146 } catch {
147 return false;
148 }
149 }
150 /** Lossless finite JSON number, excluding negative zero. */
151 function isJsonNumber(value) {
152 return typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0);
153 }
154 /** Whether a scalar is valid for one declared schema type. */
155 function scalarMatches(type, value) {
156 switch (type) {
157 case "string": return typeof value === "string";
158 case "number": return isJsonNumber(value);
159 case "integer": return isJsonNumber(value) && Number.isInteger(value);
160 case "boolean": return typeof value === "boolean";
161 case "null": return value === null;
162 /* v8 ignore next -- JsonSchemaScalarType is closed; this retains compile-time exhaustiveness. */
163 default: return assertNever(type, "JsonSchemaType");
164 }
165 }
166 /** Keywords that are invalid beside `oneOf`. */
167 const ONE_OF_SIBLING_KEYWORDS = [
168 "properties",
169 "required",
170 "additionalProperties",
171 "items",
172 "enum",
173 "const"
174 ];
175 /** Validate object-only fields after its property schemas have been visited. */
176 function checkObjectSchemaTail(node, path, properties, violations) {
177 const hasRequired = Object.hasOwn(node, "required");
178 const required = hasRequired ? node.required : void 0;
179 if (hasRequired) if (!isPlainJsonArray(required) || required.some((entry) => typeof entry !== "string")) violations.push(`${path}.required must be an array of strings`);
180 else {
181 const declared = isJsonSchemaRecord(properties) ? properties : {};
182 for (const key of required) if (!Object.hasOwn(declared, key)) violations.push(`${path}.required names "${key}" which is not in properties`);
183 }
184 if (Object.hasOwn(node, "additionalProperties") && typeof node.additionalProperties !== "boolean") violations.push(`${path}.additionalProperties must be a boolean`);
185 }
186 /** Collect every violation for one raw schema tree without using the JavaScript call stack. */
187 function checkSchemaNode(root, rootPath, violations, seen) {
188 const tasks = [{
189 kind: "enter",
190 node: root,
191 path: rootPath
192 }];
193 for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
194 if (task.kind === "leave") {
195 seen.delete(task.node);
196 continue;
197 }
198 if (task.kind === "one-of-tail") {
199 for (const key of ONE_OF_SIBLING_KEYWORDS) if (Object.hasOwn(task.node, key)) violations.push(`${task.path}.${key} is not supported beside oneOf`);
200 continue;
201 }
202 if (task.kind === "object-tail") {
203 checkObjectSchemaTail(task.node, task.path, task.properties, violations);
204 continue;
205 }
206 const { node, path } = task;
207 if (!isJsonSchemaRecord(node)) {
208 violations.push(`${path} must be a schema object`);
209 continue;
210 }
211 if (seen.has(node)) {
212 violations.push(`${path} is circular`);
213 continue;
214 }
215 seen.add(node);
216 tasks.push({
217 kind: "leave",
218 node
219 });
220 for (const key of Object.keys(node)) {
221 if (CONSTRAINT_KEYWORDS.has(key)) continue;
222 if (ANNOTATION_KEYWORDS.has(key)) {
223 try {
224 if (!isJsonValue(node[key])) violations.push(`${path}.${key} annotation must be lossless JSON data`);
225 } catch {
226 violations.push(`${path}.${key} annotation must be lossless JSON data`);
227 }
228 continue;
229 }
230 violations.push(`${path}.${key} is not a supported keyword (subset: type/oneOf/properties/required/additionalProperties/items/enum/const + annotations)`);
231 }
232 if (Object.hasOwn(node, "description") && typeof node.description !== "string") violations.push(`${path}.description must be a string`);
233 if (Object.hasOwn(node, "title") && typeof node.title !== "string") violations.push(`${path}.title must be a string`);
234 const hasType = Object.hasOwn(node, "type");
235 const hasOneOf = Object.hasOwn(node, "oneOf");
236 if (hasType && hasOneOf) {
237 violations.push(`${path} cannot declare both type and oneOf`);
238 continue;
239 }
240 if (!hasType && !hasOneOf) {
241 for (const key of ONE_OF_SIBLING_KEYWORDS) if (Object.hasOwn(node, key)) violations.push(`${path}.${key} requires type or oneOf`);
242 continue;
243 }
244 if (hasOneOf) {
245 const oneOf = node.oneOf;
246 tasks.push({
247 kind: "one-of-tail",
248 node,
249 path
250 });
251 if (!isPlainJsonArray(oneOf) || oneOf.length < 2) violations.push(`${path}.oneOf must be an array of at least two schemas`);
252 else for (let index = oneOf.length - 1; index >= 0; index--) tasks.push({
253 kind: "enter",
254 node: oneOf[index],
255 path: `${path}.oneOf[${index}]`
256 });
257 continue;
258 }
259 const type = node.type;
260 if (typeof type !== "string" || !SCHEMA_TYPES.includes(type)) {
261 violations.push(Array.isArray(type) ? `${path}.type must be a single type string (type arrays are not supported)` : `${path}.type must be one of ${SCHEMA_TYPES.join("/")}`);
262 continue;
263 }
264 const schemaType = type;
265 for (const [key, types] of Object.entries({
266 properties: ["object"],
267 required: ["object"],
268 additionalProperties: ["object"],
269 items: ["array"],
270 enum: [
271 "string",
272 "number",
273 "integer",
274 "boolean",
275 "null"
276 ],
277 const: [
278 "string",
279 "number",
280 "integer",
281 "boolean",
282 "null"
283 ]
284 })) if (Object.hasOwn(node, key) && !types.includes(schemaType)) violations.push(`${path}.${key} is not supported on type "${schemaType}"`);
285 switch (schemaType) {
286 case "object": {
287 const properties = Object.hasOwn(node, "properties") ? node.properties : void 0;
288 tasks.push({
289 kind: "object-tail",
290 node,
291 path,
292 properties
293 });
294 if (Object.hasOwn(node, "properties")) if (!isJsonSchemaRecord(properties)) violations.push(`${path}.properties must be an object of schemas`);
295 else {
296 const entries = Object.entries(properties);
297 for (let index = entries.length - 1; index >= 0; index--) {
298 const entry = entries[index];
299 /* v8 ignore next -- the loop is bounded by the captured entry count. */
300 if (entry === void 0) continue;
301 tasks.push({
302 kind: "enter",
303 node: entry[1],
304 path: `${path}.properties.${entry[0]}`
305 });
306 }
307 }
308 break;
309 }
310 case "array":
311 if (Object.hasOwn(node, "items")) tasks.push({
312 kind: "enter",
313 node: node.items,
314 path: `${path}.items`
315 });
316 break;
317 case "string":
318 case "number":
319 case "integer":
320 case "boolean":
321 case "null": {
322 const hasEnum = Object.hasOwn(node, "enum");
323 const allowed = hasEnum ? node.enum : void 0;
324 const enumValid = isPlainJsonArray(allowed) && allowed.length > 0 && allowed.every((entry) => scalarMatches(schemaType, entry));
325 if (hasEnum && !enumValid) violations.push(`${path}.enum must be a non-empty array of ${schemaType} values`);
326 const hasConst = Object.hasOwn(node, "const");
327 const declaredConst = hasConst ? node.const : void 0;
328 const constValid = scalarMatches(schemaType, declaredConst);
329 if (hasConst) {
330 if (!constValid) violations.push(`${path}.const must be a ${schemaType} value`);
331 else if (enumValid && !allowed.includes(declaredConst)) violations.push(`${path}.const must be one of ${path}.enum when both are declared`);
332 }
333 break;
334 }
335 /* v8 ignore next -- schemaType was narrowed from the closed SCHEMA_TYPES table above. */
336 default: assertNever(schemaType, "JsonSchemaType");
337 }
338 }
339 }
340 /**
341 * Assert that an arbitrary raw schema uses only the enforced subset.
342 * Annotation-only schemas are accepted as the standard unconstrained-JSON
343 * form; callers that require an object root use {@link assertObjectJsonSchema}.
344 * @param schema - untrusted raw JSON Schema.
345 * @returns Assertion that the schema belongs to the supported subset.
346 */
347 function assertSupportedJsonSchema(schema) {
348 const violations = [];
349 checkSchemaNode(schema, "schema", violations, /* @__PURE__ */ new Set());
350 if (violations.length > 0) throw new JsonSchemaError(violations);
351 }
352 /**
353 * Assert the enforced subset plus the object-root constraint retained by
354 * subagent and workflow structured outputs.
355 * @param schema - untrusted caller-supplied schema.
356 * @returns Assertion that the schema belongs to the supported subset and has an object root.
357 */
358 function assertObjectJsonSchema(schema) {
359 const violations = [];
360 checkSchemaNode(schema, "schema", violations, /* @__PURE__ */ new Set());
361 if (violations.length === 0 && (!isJsonSchemaRecord(schema) || !Object.hasOwn(schema, "type") || schema.type !== "object")) violations.push("schema.type must be \"object\" (structured output is object-rooted)");
362 if (violations.length > 0) throw new JsonSchemaError(violations);
363 }
364 /** Safely test the lossless JSON boundary when a getter may throw. */
365 function safelyIsJsonValue(value) {
366 try {
367 return isJsonValue(value);
368 } catch {
369 return false;
370 }
371 }
372 /** Root-aware diagnostic path for the parameter validator's empty sentinel. */
373 function diagnosticPath(path) {
374 return path === "" ? "arguments" : path;
375 }
376 /** Append one object property without a leading dot at an implicit root. */
377 function propertyPath(path, key) {
378 return path === "" ? key : `${path}.${key}`;
379 }
380 /** The generic exception-containment diagnostic owned by one valid schema node. */
381 function losslessValueViolation(path) {
382 return [`"${diagnosticPath(path)}" must be a lossless JSON value`];
383 }
384 /** Append diagnostics without spreading a potentially wide child result as call arguments. */
385 function appendViolations(target, source) {
386 for (const violation of source) target.push(violation);
387 }
388 /** Initialize one validation frame with empty aggregation state. */
389 function valueFrame(node, value, path) {
390 return {
391 node,
392 value,
393 path,
394 catches: false,
395 phase: "start",
396 children: [],
397 childIndex: 0,
398 violations: [],
399 tailViolations: [],
400 matches: 0
401 };
402 }
403 /** Validate one scalar node after its primitive type check. */
404 function checkScalarValue(node, value, path) {
405 const allowed = Object.hasOwn(node, "enum") ? node.enum : void 0;
406 if (allowed !== void 0 && !allowed.includes(value)) return [`"${diagnosticPath(path)}" must be one of ${JSON.stringify(allowed)}`];
407 if (Object.hasOwn(node, "const") && value !== node.const) return [`"${diagnosticPath(path)}" must be ${JSON.stringify(node.const)}`];
408 return [];
409 }
410 /** Validate one trusted schema/value pair with explicit frames rather than recursive calls. */
411 function checkValue(schema, value, path) {
412 const frames = [valueFrame(schema, value, path)];
413 let rootResult;
414 const receive = (result) => {
415 const parent = frames.at(-1);
416 if (parent === void 0) {
417 rootResult = result;
418 return;
419 }
420 if (parent.kind === "oneOf") {
421 if (result.length === 0) parent.matches++;
422 } else appendViolations(parent.violations, result);
423 };
424 const finish = (result) => {
425 frames.pop();
426 receive(result);
427 };
428 while (frames.length > 0) {
429 const frame = frames.at(-1);
430 /* v8 ignore next -- the loop condition guarantees a current frame. */
431 if (frame === void 0) break;
432 try {
433 if (frame.phase === "children") {
434 if (frame.childIndex < frame.children.length) {
435 const child = frame.children[frame.childIndex];
436 /* v8 ignore next -- childIndex is bounded by children.length. */
437 if (child === void 0) throw new Error("missing schema-value child frame");
438 frame.childIndex++;
439 frames.push(valueFrame(child.node, child.value, child.path));
440 continue;
441 }
442 if (frame.kind === "oneOf") {
443 finish(frame.matches === 1 ? [] : [`"${diagnosticPath(frame.path)}" must match exactly one oneOf branch (matched ${frame.matches})`]);
444 continue;
445 }
446 appendViolations(frame.violations, frame.tailViolations);
447 if (frame.violations.length > 0) finish(frame.violations);
448 else if (frame.kind === "object") finish(safelyIsJsonValue(frame.value) ? [] : [`"${diagnosticPath(frame.path)}" must be a lossless JSON object`]);
449 else finish(safelyIsJsonValue(frame.value) ? [] : [`"${diagnosticPath(frame.path)}" must be a dense lossless JSON array`]);
450 continue;
451 }
452 const nodeType = Object.hasOwn(frame.node, "type") ? frame.node.type : void 0;
453 frame.catches = !(nodeType !== void 0 && !SCHEMA_TYPES.includes(nodeType));
454 const oneOf = Object.hasOwn(frame.node, "oneOf") ? frame.node.oneOf : void 0;
455 if (oneOf !== void 0) {
456 frame.kind = "oneOf";
457 frame.children = Array.from(oneOf, (branch) => ({
458 node: branch,
459 value: frame.value,
460 path: frame.path
461 }));
462 frame.childIndex = 0;
463 frame.matches = 0;
464 frame.phase = "children";
465 continue;
466 }
467 if (nodeType === void 0) {
468 finish(safelyIsJsonValue(frame.value) ? [] : losslessValueViolation(frame.path));
469 continue;
470 }
471 switch (nodeType) {
472 case "object": {
473 if (!isPlainJsonRecord(frame.value)) {
474 finish([`"${diagnosticPath(frame.path)}" must be an object`]);
475 break;
476 }
477 const properties = Object.hasOwn(frame.node, "properties") ? frame.node.properties ?? {} : {};
478 const violations = [];
479 const required = Object.hasOwn(frame.node, "required") ? frame.node.required ?? [] : [];
480 for (const key of required) if (!Object.hasOwn(frame.value, key) || frame.value[key] === void 0) violations.push(`missing required property "${propertyPath(frame.path, key)}"`);
481 const children = [];
482 for (const [key, child] of Object.entries(properties)) {
483 if (!Object.hasOwn(frame.value, key) || frame.value[key] === void 0) continue;
484 children.push({
485 node: child,
486 value: frame.value[key],
487 path: propertyPath(frame.path, key)
488 });
489 }
490 const tailViolations = [];
491 if (Object.hasOwn(frame.node, "additionalProperties") && frame.node.additionalProperties === false) {
492 for (const key of Object.keys(frame.value)) if (!Object.hasOwn(properties, key)) tailViolations.push(`"${propertyPath(frame.path, key)}" is not a declared property (additionalProperties: false)`);
493 }
494 frame.kind = "object";
495 frame.children = children;
496 frame.childIndex = 0;
497 frame.violations = violations;
498 frame.tailViolations = tailViolations;
499 frame.phase = "children";
500 break;
501 }
502 case "array": {
503 if (!Array.isArray(frame.value)) {
504 finish([`"${diagnosticPath(frame.path)}" must be an array`]);
505 break;
506 }
507 const items = Object.hasOwn(frame.node, "items") ? frame.node.items : void 0;
508 const children = items === void 0 ? [] : frame.value.flatMap((entry, index) => [{
509 node: items,
510 value: entry,
511 path: `${frame.path}[${index}]`
512 }]);
513 frame.kind = "array";
514 frame.children = children;
515 frame.childIndex = 0;
516 frame.violations = [];
517 frame.phase = "children";
518 break;
519 }
520 case "string":
521 finish(typeof frame.value === "string" ? checkScalarValue(frame.node, frame.value, frame.path) : [`"${diagnosticPath(frame.path)}" must be a string`]);
522 break;
523 case "number":
524 finish(typeof frame.value !== "number" ? [`"${diagnosticPath(frame.path)}" must be a number`] : !isJsonNumber(frame.value) ? [`"${diagnosticPath(frame.path)}" must be a finite JSON number`] : checkScalarValue(frame.node, frame.value, frame.path));
525 break;
526 case "integer":
527 finish(!isJsonNumber(frame.value) || !Number.isInteger(frame.value) ? [`"${diagnosticPath(frame.path)}" must be an integer`] : checkScalarValue(frame.node, frame.value, frame.path));
528 break;
529 case "boolean":
530 finish(typeof frame.value === "boolean" ? checkScalarValue(frame.node, frame.value, frame.path) : [`"${diagnosticPath(frame.path)}" must be a boolean`]);
531 break;
532 case "null":
533 finish(frame.value === null ? checkScalarValue(frame.node, frame.value, frame.path) : [`"${diagnosticPath(frame.path)}" must be null`]);
534 break;
535 default: finish(assertNever(nodeType, "JsonSchemaType"));
536 }
537 } catch (error) {
538 let failed = frames.pop();
539 while (failed !== void 0 && !failed.catches) failed = frames.pop();
540 if (failed === void 0) throw error;
541 receive(losslessValueViolation(failed.path));
542 }
543 }
544 /* v8 ignore next -- every root frame finishes or throws. */
545 return rootResult ?? losslessValueViolation(path);
546 }
547 /**
548 * Validate a candidate value against an asserted raw schema. The function is
549 * total for arbitrary values and returns path-qualified violations.
550 * @param schema - a schema accepted by {@link assertSupportedJsonSchema}.
551 * @param value - the candidate JSON value.
552 * @param path - root label used in diagnostics.
553 * @returns All violations in walk order; empty means valid.
554 */
555 function validateJsonSchemaValue(schema, value, path = "value") {
556 return checkValue(schema, value, path);
557 }
558 //#endregion
559 //#region lib/types/schema.js
560 /** Unified JSON-value schema DSL, inference, compilation, and typed tool helper. @module dsh-tools/schema */
561 const ANNOTATION_KEYS = [
562 "description",
563 "title",
564 "default",
565 "examples"
566 ];
567 /** Throw one author-schema violation through the shared schema error type. */
568 function authorError(message) {
569 throw new JsonSchemaError([message]);
570 }
571 /** Copy own annotation fields for validation by the raw-schema boundary. */
572 function copyAnnotations(source, target) {
573 if (Object.hasOwn(source, "description")) target.description = source.description;
574 if (Object.hasOwn(source, "title")) target.title = source.title;
575 if (Object.hasOwn(source, "default")) target.default = source.default;
576 if (Object.hasOwn(source, "examples")) target.examples = source.examples;
577 }
578 /** Reject author-only keys outside one node's declared vocabulary. */
579 function assertAuthorKeys(source, path, allowed) {
580 for (const key of Object.keys(source)) if (!allowed.includes(key)) authorError(`${path}.${key} is not supported by the value schema DSL`);
581 }
582 /** Install a compiled node without giving `__proto__` assignment semantics. */
583 function assignCompiledNode(destination, node) {
584 switch (destination.kind) {
585 case "root":
586 destination.holder.value = node;
587 break;
588 case "property":
589 Object.defineProperty(destination.target, destination.key, {
590 value: node,
591 enumerable: true,
592 configurable: true,
593 writable: true
594 });
595 break;
596 case "item":
597 destination.target.items = node;
598 break;
599 case "one-of":
600 destination.target[destination.index] = node;
601 break;
602 }
603 }
604 /** Install a compiled property map at its root or containing object node. */
605 function assignCompiledPropertyMap(destination, compiled) {
606 if (destination.kind === "root") destination.holder.value = compiled;
607 else destination.target.properties = compiled.properties;
608 }
609 /** Execute an author-schema compilation task graph without recursive descent. */
610 function runSchemaCompiler(initial) {
611 const seen = /* @__PURE__ */ new Set();
612 const tasks = [initial];
613 for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
614 if (task.kind === "leave") {
615 seen.delete(task.input);
616 continue;
617 }
618 if (task.kind === "property-map-tail") {
619 if (task.required.length > 0) {
620 task.compiled.required = task.required;
621 if (task.destination.kind === "object") task.destination.target.required = task.required;
622 }
623 continue;
624 }
625 if (task.kind === "property") {
626 if (!isJsonSchemaRecord(task.property)) authorError(`${task.path} must be a value schema object`);
627 if (Object.hasOwn(task.property, "required") && task.property.required !== true) authorError(`${task.path}.required must be true when present`);
628 if (Object.hasOwn(task.property, "required") && task.property.required === true) task.required.push(task.key);
629 tasks.push({
630 kind: "value",
631 input: task.property,
632 path: task.path,
633 allowRequired: true,
634 destination: {
635 kind: "property",
636 target: task.properties,
637 key: task.key
638 }
639 });
640 continue;
641 }
642 if (task.kind === "property-map") {
643 if (!isJsonSchemaRecord(task.input)) authorError(`${task.path} must be an object of value schemas`);
644 if (seen.has(task.input)) authorError(`${task.path} is circular`);
645 seen.add(task.input);
646 const compiled = { properties: {} };
647 const required = [];
648 assignCompiledPropertyMap(task.destination, compiled);
649 tasks.push({
650 kind: "leave",
651 input: task.input
652 });
653 tasks.push({
654 kind: "property-map-tail",
655 compiled,
656 required,
657 destination: task.destination
658 });
659 const entries = Object.entries(task.input);
660 for (let index = entries.length - 1; index >= 0; index--) {
661 const entry = entries[index];
662 /* v8 ignore next -- the loop is bounded by the captured entry count. */
663 if (entry === void 0) continue;
664 tasks.push({
665 kind: "property",
666 property: entry[1],
667 path: `${task.path}.${entry[0]}`,
668 key: entry[0],
669 properties: compiled.properties,
670 required
671 });
672 }
673 continue;
674 }
675 const { input, path } = task;
676 if (!isJsonSchemaRecord(input)) authorError(`${path} must be a value schema object`);
677 if (seen.has(input)) authorError(`${path} is circular`);
678 seen.add(input);
679 const authorKeys = [...ANNOTATION_KEYS, ...task.allowRequired ? ["required"] : []];
680 const node = {};
681 assignCompiledNode(task.destination, node);
682 tasks.push({
683 kind: "leave",
684 input
685 });
686 if (Object.hasOwn(input, "oneOf")) {
687 assertAuthorKeys(input, path, [
688 ...authorKeys,
689 "oneOf",
690 "type"
691 ]);
692 if (Object.hasOwn(input, "type")) authorError(`${path} cannot declare both type and oneOf`);
693 if (!isPlainJsonArray(input.oneOf)) authorError(`${path}.oneOf must be an array of at least two value schemas`);
694 const branches = [];
695 node.oneOf = branches;
696 copyAnnotations(input, node);
697 for (let index = input.oneOf.length - 1; index >= 0; index--) tasks.push({
698 kind: "value",
699 input: input.oneOf[index],
700 path: `${path}.oneOf[${index}]`,
701 allowRequired: false,
702 destination: {
703 kind: "one-of",
704 target: branches,
705 index
706 }
707 });
708 continue;
709 }
710 const inputType = Object.hasOwn(input, "type") ? input.type : void 0;
711 switch (inputType) {
712 case "json":
713 assertAuthorKeys(input, path, [...authorKeys, "type"]);
714 copyAnnotations(input, node);
715 break;
716 case "object":
717 assertAuthorKeys(input, path, [
718 ...authorKeys,
719 "type",
720 "properties",
721 "additionalProperties"
722 ]);
723 if (!Object.hasOwn(input, "additionalProperties") || typeof input.additionalProperties !== "boolean") authorError(`${path}.additionalProperties must be explicitly true or false`);
724 node.type = "object";
725 copyAnnotations(input, node);
726 node.additionalProperties = input.additionalProperties;
727 if (Object.hasOwn(input, "properties")) tasks.push({
728 kind: "property-map",
729 input: input.properties,
730 path: `${path}.properties`,
731 destination: {
732 kind: "object",
733 target: node
734 }
735 });
736 break;
737 case "array":
738 assertAuthorKeys(input, path, [
739 ...authorKeys,
740 "type",
741 "items"
742 ]);
743 node.type = "array";
744 copyAnnotations(input, node);
745 if (Object.hasOwn(input, "items")) tasks.push({
746 kind: "value",
747 input: input.items,
748 path: `${path}.items`,
749 allowRequired: false,
750 destination: {
751 kind: "item",
752 target: node
753 }
754 });
755 break;
756 case "string":
757 case "number":
758 case "integer":
759 case "boolean":
760 case "null":
761 assertAuthorKeys(input, path, [
762 ...authorKeys,
763 "type",
764 "enum",
765 "const"
766 ]);
767 node.type = inputType;
768 copyAnnotations(input, node);
769 if (Object.hasOwn(input, "enum")) {
770 if (!isPlainJsonArray(input.enum)) authorError(`${path}.enum must be a non-empty array of scalar values`);
771 node.enum = Array.from(input.enum, (entry) => entry);
772 }
773 if (Object.hasOwn(input, "const")) node.const = input.const;
774 break;
775 default: authorError(`${path}.type must be string/number/integer/boolean/null/array/object/json, or use oneOf`);
776 }
777 }
778 }
779 /** Compile one implicit property map, collecting per-property requiredness. */
780 function compilePropertyMap(input, path) {
781 const holder = {};
782 runSchemaCompiler({
783 kind: "property-map",
784 input,
785 path,
786 destination: {
787 kind: "root",
788 holder
789 }
790 });
791 /* v8 ignore next -- the root task assigns before scheduling any descendants. */
792 return holder.value ?? authorError(`${path} did not compile`);
793 }
794 /** Compile one author node without applying any consumer root restriction. */
795 function compileValueSchema(input, path) {
796 const holder = {};
797 runSchemaCompiler({
798 kind: "value",
799 input,
800 path,
801 allowRequired: false,
802 destination: {
803 kind: "root",
804 holder
805 }
806 });
807 /* v8 ignore next -- the root task assigns before scheduling any descendants. */
808 return holder.value ?? authorError(`${path} did not compile`);
809 }
810 /**
811 * Compile one author-facing value schema to the enforced raw JSON Schema
812 * subset. The author-only `json` node becomes an annotation-only schema.
813 * @param spec - schema for any JSON-value root.
814 * @returns The asserted raw schema projection.
815 */
816 function valueSchemaSpecToJsonSchema(spec) {
817 const schema = compileValueSchema(spec, "schema");
818 assertSupportedJsonSchema(schema);
819 return schema;
820 }
821 /**
822 * Compile the implicit open parameter object into raw JSON Schema.
823 * @param spec - per-property parameter definitions.
824 * @returns An object-rooted raw schema with no implicit-root openness override.
825 */
826 function parameterSchemaSpecToJsonSchema(spec) {
827 const compiled = compilePropertyMap(spec, "parameters");
828 const schema = {
829 type: "object",
830 properties: compiled.properties,
831 ...compiled.required === void 0 ? {} : { required: compiled.required }
832 };
833 assertSupportedJsonSchema(schema);
834 return schema;
835 }
836 /** Invalid model-generated arguments for a typed tool. */
837 var ToolArgsError = class extends HarnessError {
838 /** Individual violations in schema-walk order. */
839 violations;
840 constructor(violations) {
841 super(`invalid arguments: ${violations.join("; ")}`, "INVALID_ARGS");
842 this.name = "ToolArgsError";
843 this.violations = violations;
844 }
845 };
846 /**
847 * Validate model-generated arguments against an implicit parameter schema.
848 * @param spec - declared parameter schema.
849 * @param args - candidate arguments, however malformed.
850 * @returns Path-qualified violations; empty means valid.
851 */
852 function validateArgs(spec, args) {
853 return validateJsonSchemaValue(parameterSchemaSpecToJsonSchema(spec), args, "");
854 }
855 /**
856 * Define a first-party tool with inferred arguments and strict execution
857 * validation. Replay-only presenters validate softly and fall back to generic
858 * rendering for obsolete logged arguments.
859 * @param options - typed definition and optional finalizer and presenters.
860 * @returns A registry-ready definition.
861 */
862 function defineTool(options) {
863 const userExecute = options.execute;
864 const userFinalizeContent = options.finalizeContent;
865 const userProjectContent = options.projectContent;
866 const userRender = options.output.render;
867 const userPresentationMeta = options.output.presentationMeta;
868 const userPresentCall = options.presentCall;
869 const userPresentResult = options.presentResult;
870 const userIsConcurrencySafe = options.isConcurrencySafe;
871 if (options.timeoutMs !== void 0 && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) throw new Error(`defineTool(${options.name}): timeoutMs must be a positive finite number`);
872 const parameters = parameterSchemaSpecToJsonSchema(options.parameters);
873 const outputSchema = valueSchemaSpecToJsonSchema(options.output.schema);
874 const validate = (args) => validateJsonSchemaValue(parameters, args, "");
875 const tool = {
876 name: options.name,
877 description: options.description,
878 parameters,
879 output: {
880 schema: outputSchema,
881 render(args, value) {
882 return userRender(args, value);
883 },
884 ...userPresentationMeta !== void 0 ? { presentationMeta(args, value) {
885 return userPresentationMeta(args, value);
886 } } : {}
887 },
888 ...options.deferLoading === true ? { deferLoading: options.deferLoading } : {},
889 ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
890 async execute(args, exec) {
891 const violations = validate(args);
892 if (violations.length > 0) throw new ToolArgsError(violations);
893 return userExecute(args, exec);
894 }
895 };
896 if (userProjectContent) tool.projectContent = (exec, result) => userProjectContent(exec, result);
897 if (userFinalizeContent) tool.finalizeContent = (exec, result) => userFinalizeContent(exec, result);
898 if (userPresentCall) tool.presentCall = (args) => {
899 if (validate(args).length > 0) return void 0;
900 return userPresentCall(args);
901 };
902 if (userPresentResult) tool.presentResult = (args, result) => {
903 if (validate(args).length > 0) return void 0;
904 return userPresentResult(args, result);
905 };
906 if (userIsConcurrencySafe) tool.isConcurrencySafe = (args) => {
907 if (validate(args).length > 0) return false;
908 return userIsConcurrencySafe(args);
909 };
910 return tool;
911 }
912 //#endregion
913 // ---- END VERBATIM ----
914
915 // ---- VERBATIM @deepseek-ai/dsh-tools@0.1.7-alpha.2 lib/index.js:2528-2558 ----
916 const TOOL_ABORTED = "ABORTED";
917 /** Canonical error code for cancellation before a tool body was invoked. */
918 const TOOL_ABORTED_BEFORE_DISPATCH = "ABORTED_BEFORE_DISPATCH";
919 /**
920 * Thrown (internally) when the model requests a tool that isn't registered.
921 * Extends {@link HarnessError} (`code: 'UNKNOWN_TOOL'`) so an unknown-tool
922 * failure is as routable as a tool-thrown one — retry/sandbox/replay code can
923 * distinguish it from a tool body's own error.
924 */
925 var ToolNotFoundError = class extends HarnessError {
926 /**
927 * @param toolName - the name the caller asked for.
928 * @param reachableFrom - how the model reaches this tool instead, when the
929 * name IS visible and only the presentation denies calling it directly.
930 * Omitted for a name that is registered nowhere.
931 */
932 constructor(toolName, reachableFrom) {
933 super(reachableFrom === void 0 ? `unknown tool "${toolName}"` : `unknown tool "${toolName}": ${reachableFrom}`, "UNKNOWN_TOOL");
934 this.name = "ToolNotFoundError";
935 }
936 };
937 /** Thrown when a tool body or post-policy value violates its declared output. */
938 var ToolOutputError = class extends HarnessError {
939 /** Schema/value violations in validation order. */
940 violations;
941 constructor(toolName, violations) {
942 super(`tool "${toolName}" returned invalid output: ${violations.join("; ")}`, "INVALID_TOOL_OUTPUT");
943 this.name = "ToolOutputError";
944 this.violations = violations;
945 }
946 };
947 // ---- END VERBATIM ----
948
949 export {
950 JsonSchemaError,
951 TOOL_ABORTED,
952 TOOL_ABORTED_BEFORE_DISPATCH,
953 ToolArgsError,
954 ToolNotFoundError,
955 ToolOutputError,
956 assertObjectJsonSchema,
957 assertSupportedJsonSchema,
958 defineTool,
959 parameterSchemaSpecToJsonSchema,
960 validateArgs,
961 validateJsonSchemaValue,
962 valueSchemaSpecToJsonSchema,
963 }
964
964 lines JAVASCRIPT