@mengyuly/dsh-ponytail 0.1.5 → 0.2.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/CHANGELOG.md +81 -0
- package/README.md +52 -12
- package/dist-provenance.json +2 -1
- package/lib/index.js +133 -856
- package/lib/types/index.d.ts +14 -1
- package/lib/types/instructions.d.ts +9 -15
- package/lib/types/modes.d.ts +16 -7
- package/package.json +3 -2
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { Service } from "@deepseek-ai/cordis";
|
|
3
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
4
|
import { mkdirSync, readFileSync, renameSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
@@ -93,798 +94,6 @@ function createUserMessage(input) {
|
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
//#endregion
|
|
96
|
-
//#region ../../../vendor/cosmokit/src/misc.ts
|
|
97
|
-
/** Return true when a value is `null` or `undefined`. */
|
|
98
|
-
function isNullable(value) {
|
|
99
|
-
return value === null || value === void 0;
|
|
100
|
-
}
|
|
101
|
-
/** Return true for non-array object values. */
|
|
102
|
-
function isPlainObject(data) {
|
|
103
|
-
return data && typeof data === "object" && !Array.isArray(data);
|
|
104
|
-
}
|
|
105
|
-
/** Filter object entries and return a new object. */
|
|
106
|
-
function filterKeys(object, filter) {
|
|
107
|
-
return Object.fromEntries(Object.entries(object).filter(([key, value]) => filter(key, value)));
|
|
108
|
-
}
|
|
109
|
-
/** Map object values while preserving the original key set. */
|
|
110
|
-
function mapValues(object, transform) {
|
|
111
|
-
return Object.fromEntries(Object.entries(object).map(([key, value]) => [key, transform(value, key)]));
|
|
112
|
-
}
|
|
113
|
-
/** Pick selected keys from an object, optionally including `undefined` values. */
|
|
114
|
-
function pick(source, keys, forced) {
|
|
115
|
-
if (!keys) return { ...source };
|
|
116
|
-
const result = {};
|
|
117
|
-
for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
//#endregion
|
|
121
|
-
//#region ../../../vendor/cosmokit/src/types.ts
|
|
122
|
-
/** Test values using `instanceof` with a `toStringTag` fallback. */
|
|
123
|
-
function is(type, value) {
|
|
124
|
-
if (arguments.length === 1) return (value) => is(type, value);
|
|
125
|
-
return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
|
|
126
|
-
}
|
|
127
|
-
function isArrayBufferLike(value) {
|
|
128
|
-
return is("ArrayBuffer", value) || is("SharedArrayBuffer", value);
|
|
129
|
-
}
|
|
130
|
-
function isArrayBufferSource(value) {
|
|
131
|
-
return isArrayBufferLike(value) || ArrayBuffer.isView(value);
|
|
132
|
-
}
|
|
133
|
-
let Binary;
|
|
134
|
-
(function(_Binary) {
|
|
135
|
-
_Binary.is = isArrayBufferLike;
|
|
136
|
-
_Binary.isSource = isArrayBufferSource;
|
|
137
|
-
function fromSource(source) {
|
|
138
|
-
if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
|
|
139
|
-
else return source;
|
|
140
|
-
}
|
|
141
|
-
_Binary.fromSource = fromSource;
|
|
142
|
-
function toBase64(source) {
|
|
143
|
-
source = fromSource(source);
|
|
144
|
-
if (typeof Buffer !== "undefined") return Buffer.from(source).toString("base64");
|
|
145
|
-
let binary = "";
|
|
146
|
-
const bytes = new Uint8Array(source);
|
|
147
|
-
for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
|
|
148
|
-
return btoa(binary);
|
|
149
|
-
}
|
|
150
|
-
_Binary.toBase64 = toBase64;
|
|
151
|
-
function fromBase64(source) {
|
|
152
|
-
if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "base64"));
|
|
153
|
-
return Uint8Array.from(atob(source), (c) => c.charCodeAt(0));
|
|
154
|
-
}
|
|
155
|
-
_Binary.fromBase64 = fromBase64;
|
|
156
|
-
function toHex(source) {
|
|
157
|
-
source = fromSource(source);
|
|
158
|
-
if (typeof Buffer !== "undefined") return Buffer.from(source).toString("hex");
|
|
159
|
-
return Array.from(new Uint8Array(source), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
160
|
-
}
|
|
161
|
-
_Binary.toHex = toHex;
|
|
162
|
-
function fromHex(source) {
|
|
163
|
-
if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "hex"));
|
|
164
|
-
const hex = source.length % 2 === 0 ? source : source.slice(0, source.length - 1);
|
|
165
|
-
const buffer = [];
|
|
166
|
-
for (let i = 0; i < hex.length; i += 2) buffer.push(parseInt(`${hex[i]}${hex[i + 1]}`, 16));
|
|
167
|
-
return Uint8Array.from(buffer).buffer;
|
|
168
|
-
}
|
|
169
|
-
_Binary.fromHex = fromHex;
|
|
170
|
-
})(Binary || (Binary = {}));
|
|
171
|
-
Binary.fromBase64;
|
|
172
|
-
Binary.toBase64;
|
|
173
|
-
Binary.fromHex;
|
|
174
|
-
Binary.toHex;
|
|
175
|
-
/** Deep-clone common JavaScript values while preserving prototypes and cycles. */
|
|
176
|
-
function clone(source, refs = /* @__PURE__ */ new Map()) {
|
|
177
|
-
if (!source || typeof source !== "object") return source;
|
|
178
|
-
if (is("Date", source)) return new Date(source.valueOf());
|
|
179
|
-
if (is("RegExp", source)) return new RegExp(source.source, source.flags);
|
|
180
|
-
if (isArrayBufferLike(source)) return source.slice(0);
|
|
181
|
-
if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
|
|
182
|
-
const cached = refs.get(source);
|
|
183
|
-
if (cached) return cached;
|
|
184
|
-
if (Array.isArray(source)) {
|
|
185
|
-
const result = [];
|
|
186
|
-
refs.set(source, result);
|
|
187
|
-
source.forEach((value, index) => {
|
|
188
|
-
result[index] = Reflect.apply(clone, null, [value, refs]);
|
|
189
|
-
});
|
|
190
|
-
return result;
|
|
191
|
-
}
|
|
192
|
-
const result = Object.create(Object.getPrototypeOf(source));
|
|
193
|
-
refs.set(source, result);
|
|
194
|
-
for (const key of Reflect.ownKeys(source)) {
|
|
195
|
-
const descriptor = { ...Reflect.getOwnPropertyDescriptor(source, key) };
|
|
196
|
-
if ("value" in descriptor) descriptor.value = Reflect.apply(clone, null, [descriptor.value, refs]);
|
|
197
|
-
Reflect.defineProperty(result, key, descriptor);
|
|
198
|
-
}
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
/** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */
|
|
202
|
-
function deepEqual(a, b, strict) {
|
|
203
|
-
if (a === b) return true;
|
|
204
|
-
if (!strict && isNullable(a) && isNullable(b)) return true;
|
|
205
|
-
if (typeof a !== typeof b) return false;
|
|
206
|
-
if (typeof a !== "object") return false;
|
|
207
|
-
if (!a || !b) return false;
|
|
208
|
-
function check(test, then) {
|
|
209
|
-
return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
|
|
210
|
-
}
|
|
211
|
-
return check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
|
|
212
|
-
if (a.byteLength !== b.byteLength) return false;
|
|
213
|
-
const viewA = new Uint8Array(a);
|
|
214
|
-
const viewB = new Uint8Array(b);
|
|
215
|
-
for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
|
|
216
|
-
return true;
|
|
217
|
-
}) ?? Object.keys({
|
|
218
|
-
...a,
|
|
219
|
-
...b
|
|
220
|
-
}).every((key) => deepEqual(a[key], b[key], strict));
|
|
221
|
-
}
|
|
222
|
-
//#endregion
|
|
223
|
-
//#region ../../../vendor/cosmokit/src/time.ts
|
|
224
|
-
let Time;
|
|
225
|
-
(function(_Time) {
|
|
226
|
-
_Time.millisecond = 1;
|
|
227
|
-
const second = _Time.second = 1e3;
|
|
228
|
-
const minute = _Time.minute = second * 60;
|
|
229
|
-
const hour = _Time.hour = minute * 60;
|
|
230
|
-
const day = _Time.day = hour * 24;
|
|
231
|
-
const week = _Time.week = day * 7;
|
|
232
|
-
let timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
233
|
-
function setTimezoneOffset(offset) {
|
|
234
|
-
timezoneOffset = offset;
|
|
235
|
-
}
|
|
236
|
-
_Time.setTimezoneOffset = setTimezoneOffset;
|
|
237
|
-
function getTimezoneOffset() {
|
|
238
|
-
return timezoneOffset;
|
|
239
|
-
}
|
|
240
|
-
_Time.getTimezoneOffset = getTimezoneOffset;
|
|
241
|
-
function getDateNumber(date = /* @__PURE__ */ new Date(), offset) {
|
|
242
|
-
if (typeof date === "number") date = new Date(date);
|
|
243
|
-
if (offset === void 0) offset = timezoneOffset;
|
|
244
|
-
return Math.floor((date.valueOf() / minute - offset) / 1440);
|
|
245
|
-
}
|
|
246
|
-
_Time.getDateNumber = getDateNumber;
|
|
247
|
-
function fromDateNumber(value, offset) {
|
|
248
|
-
const date = new Date(value * day);
|
|
249
|
-
if (offset === void 0) offset = timezoneOffset;
|
|
250
|
-
return new Date(+date + offset * minute);
|
|
251
|
-
}
|
|
252
|
-
_Time.fromDateNumber = fromDateNumber;
|
|
253
|
-
const numeric = /\d+(?:\.\d+)?/.source;
|
|
254
|
-
const timeRegExp = new RegExp(`^${[
|
|
255
|
-
"w(?:eek(?:s)?)?",
|
|
256
|
-
"d(?:ay(?:s)?)?",
|
|
257
|
-
"h(?:our(?:s)?)?",
|
|
258
|
-
"m(?:in(?:ute)?(?:s)?)?",
|
|
259
|
-
"s(?:ec(?:ond)?(?:s)?)?"
|
|
260
|
-
].map((unit) => `(${numeric}${unit})?`).join("")}$`);
|
|
261
|
-
function parseTime(source) {
|
|
262
|
-
const capture = timeRegExp.exec(source);
|
|
263
|
-
if (!capture) return 0;
|
|
264
|
-
return (parseFloat(capture[1]) * week || 0) + (parseFloat(capture[2]) * day || 0) + (parseFloat(capture[3]) * hour || 0) + (parseFloat(capture[4]) * minute || 0) + (parseFloat(capture[5]) * second || 0);
|
|
265
|
-
}
|
|
266
|
-
_Time.parseTime = parseTime;
|
|
267
|
-
function parseDate(date) {
|
|
268
|
-
const parsed = parseTime(date);
|
|
269
|
-
if (parsed) date = Date.now() + parsed;
|
|
270
|
-
else if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).toLocaleDateString()}-${date}`;
|
|
271
|
-
else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).getFullYear()}-${date}`;
|
|
272
|
-
return date ? new Date(date) : /* @__PURE__ */ new Date();
|
|
273
|
-
}
|
|
274
|
-
_Time.parseDate = parseDate;
|
|
275
|
-
function format(ms) {
|
|
276
|
-
const abs = Math.abs(ms);
|
|
277
|
-
if (abs >= day - hour / 2) return Math.round(ms / day) + "d";
|
|
278
|
-
else if (abs >= hour - minute / 2) return Math.round(ms / hour) + "h";
|
|
279
|
-
else if (abs >= minute - second / 2) return Math.round(ms / minute) + "m";
|
|
280
|
-
else if (abs >= second) return Math.round(ms / second) + "s";
|
|
281
|
-
return ms + "ms";
|
|
282
|
-
}
|
|
283
|
-
_Time.format = format;
|
|
284
|
-
function toDigits(source, length = 2) {
|
|
285
|
-
return source.toString().padStart(length, "0");
|
|
286
|
-
}
|
|
287
|
-
_Time.toDigits = toDigits;
|
|
288
|
-
function template(template, time = /* @__PURE__ */ new Date()) {
|
|
289
|
-
return template.replace("yyyy", time.getFullYear().toString()).replace("yy", time.getFullYear().toString().slice(2)).replace("MM", toDigits(time.getMonth() + 1)).replace("dd", toDigits(time.getDate())).replace("hh", toDigits(time.getHours())).replace("mm", toDigits(time.getMinutes())).replace("ss", toDigits(time.getSeconds())).replace("SSS", toDigits(time.getMilliseconds(), 3));
|
|
290
|
-
}
|
|
291
|
-
_Time.template = template;
|
|
292
|
-
})(Time || (Time = {}));
|
|
293
|
-
//#endregion
|
|
294
|
-
//#region ../../../vendor/schemastery/src/index.ts
|
|
295
|
-
const kSchema = Symbol.for("schemastery");
|
|
296
|
-
const kValidationError = Symbol.for("ValidationError");
|
|
297
|
-
globalThis.__schemastery_index__ ??= 0;
|
|
298
|
-
globalThis.__schemastery_refs__ = void 0;
|
|
299
|
-
var ValidationError = class extends TypeError {
|
|
300
|
-
options;
|
|
301
|
-
name = "ValidationError";
|
|
302
|
-
constructor(message, options) {
|
|
303
|
-
let prefix = "$";
|
|
304
|
-
for (const segment of options.path || []) if (typeof segment === "string") prefix += "." + segment;
|
|
305
|
-
else if (typeof segment === "number") prefix += "[" + segment + "]";
|
|
306
|
-
else if (typeof segment === "symbol") prefix += `[Symbol(${segment.toString()})]`;
|
|
307
|
-
if (prefix.startsWith(".")) prefix = prefix.slice(1);
|
|
308
|
-
super((prefix === "$" ? "" : `${prefix} `) + message);
|
|
309
|
-
this.options = options;
|
|
310
|
-
}
|
|
311
|
-
static is(error) {
|
|
312
|
-
return !!error?.[kValidationError];
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
|
|
316
|
-
const Schema = function(options) {
|
|
317
|
-
const schema = function(data, options = {}) {
|
|
318
|
-
return Schema.resolve(data, schema, options)[0];
|
|
319
|
-
};
|
|
320
|
-
if (options.refs) {
|
|
321
|
-
const refs = mapValues(options.refs, (options) => new Schema(options));
|
|
322
|
-
const getRef = (uid) => refs[uid];
|
|
323
|
-
for (const key in refs) {
|
|
324
|
-
const options = refs[key];
|
|
325
|
-
options.sKey = getRef(options.sKey);
|
|
326
|
-
options.inner = getRef(options.inner);
|
|
327
|
-
options.list = options.list && options.list.map(getRef);
|
|
328
|
-
options.dict = options.dict && mapValues(options.dict, getRef);
|
|
329
|
-
}
|
|
330
|
-
return refs[options.uid];
|
|
331
|
-
}
|
|
332
|
-
Object.assign(schema, options);
|
|
333
|
-
if (typeof schema.callback === "string") try {
|
|
334
|
-
schema.callback = new Function("return " + schema.callback)();
|
|
335
|
-
} catch {}
|
|
336
|
-
Object.defineProperty(schema, "uid", { value: globalThis.__schemastery_index__++ });
|
|
337
|
-
Object.setPrototypeOf(schema, Schema.prototype);
|
|
338
|
-
schema.meta ||= {};
|
|
339
|
-
schema.toString = schema.toString.bind(schema);
|
|
340
|
-
return schema;
|
|
341
|
-
};
|
|
342
|
-
Schema.prototype = Object.create(Function.prototype);
|
|
343
|
-
Schema.prototype[kSchema] = true;
|
|
344
|
-
Object.defineProperty(Schema.prototype, "~standard", { get() {
|
|
345
|
-
return {
|
|
346
|
-
version: 1,
|
|
347
|
-
vendor: "schemastery",
|
|
348
|
-
validate: (value) => {
|
|
349
|
-
try {
|
|
350
|
-
return { value: Schema.resolve(value, this, {})[0] };
|
|
351
|
-
} catch (error) {
|
|
352
|
-
if (ValidationError.is(error)) return { issues: [{
|
|
353
|
-
message: error.message,
|
|
354
|
-
path: error.options.path
|
|
355
|
-
}] };
|
|
356
|
-
throw error;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
};
|
|
360
|
-
} });
|
|
361
|
-
Schema.ValidationError = ValidationError;
|
|
362
|
-
Schema.prototype.toJSON = function toJSON() {
|
|
363
|
-
if (globalThis.__schemastery_refs__) {
|
|
364
|
-
globalThis.__schemastery_refs__[this.uid] ??= JSON.parse(JSON.stringify({ ...this }));
|
|
365
|
-
return this.uid;
|
|
366
|
-
}
|
|
367
|
-
globalThis.__schemastery_refs__ = { [this.uid]: { ...this } };
|
|
368
|
-
globalThis.__schemastery_refs__[this.uid] = JSON.parse(JSON.stringify({ ...this }));
|
|
369
|
-
const result = {
|
|
370
|
-
uid: this.uid,
|
|
371
|
-
refs: globalThis.__schemastery_refs__
|
|
372
|
-
};
|
|
373
|
-
globalThis.__schemastery_refs__ = void 0;
|
|
374
|
-
return result;
|
|
375
|
-
};
|
|
376
|
-
Schema.prototype.set = function set(key, value) {
|
|
377
|
-
this.dict[key] = value;
|
|
378
|
-
return this;
|
|
379
|
-
};
|
|
380
|
-
Schema.prototype.push = function push(value) {
|
|
381
|
-
this.list.push(value);
|
|
382
|
-
return this;
|
|
383
|
-
};
|
|
384
|
-
function mergeDesc(original, messages) {
|
|
385
|
-
const result = typeof original === "string" ? { "": original } : { ...original };
|
|
386
|
-
for (const locale in messages) {
|
|
387
|
-
const value = messages[locale];
|
|
388
|
-
if (value?.$description || value?.$desc) result[locale] = value.$description || value.$desc;
|
|
389
|
-
else if (typeof value === "string") result[locale] = value;
|
|
390
|
-
}
|
|
391
|
-
return result;
|
|
392
|
-
}
|
|
393
|
-
function getInner(value) {
|
|
394
|
-
return value?.$value ?? value?.$inner;
|
|
395
|
-
}
|
|
396
|
-
function extractKeys(data) {
|
|
397
|
-
return filterKeys(data ?? {}, (key) => !key.startsWith("$"));
|
|
398
|
-
}
|
|
399
|
-
Schema.prototype.i18n = function i18n(messages) {
|
|
400
|
-
const schema = Schema(this);
|
|
401
|
-
const desc = mergeDesc(schema.meta.description, messages);
|
|
402
|
-
if (Object.keys(desc).length) schema.meta.description = desc;
|
|
403
|
-
if (schema.dict) schema.dict = mapValues(schema.dict, (inner, key) => {
|
|
404
|
-
return inner.i18n(mapValues(messages, (data) => getInner(data)?.[key] ?? data?.[key]));
|
|
405
|
-
});
|
|
406
|
-
if (schema.list) schema.list = schema.list.map((inner, index) => {
|
|
407
|
-
return inner.i18n(mapValues(messages, (data = {}) => {
|
|
408
|
-
if (Array.isArray(getInner(data))) return getInner(data)[index];
|
|
409
|
-
if (Array.isArray(data)) return data[index];
|
|
410
|
-
return extractKeys(data);
|
|
411
|
-
}));
|
|
412
|
-
});
|
|
413
|
-
if (schema.inner) schema.inner = schema.inner.i18n(mapValues(messages, (data) => {
|
|
414
|
-
if (getInner(data)) return getInner(data);
|
|
415
|
-
return extractKeys(data);
|
|
416
|
-
}));
|
|
417
|
-
if (schema.sKey) schema.sKey = schema.sKey.i18n(mapValues(messages, (data) => data?.$key));
|
|
418
|
-
return schema;
|
|
419
|
-
};
|
|
420
|
-
Schema.prototype.extra = function extra(key, value) {
|
|
421
|
-
const schema = Schema(this);
|
|
422
|
-
schema.meta = {
|
|
423
|
-
...schema.meta,
|
|
424
|
-
[key]: value
|
|
425
|
-
};
|
|
426
|
-
return schema;
|
|
427
|
-
};
|
|
428
|
-
for (const key of [
|
|
429
|
-
"required",
|
|
430
|
-
"disabled",
|
|
431
|
-
"collapse",
|
|
432
|
-
"hidden",
|
|
433
|
-
"loose"
|
|
434
|
-
]) Object.assign(Schema.prototype, { [key](value = true) {
|
|
435
|
-
const schema = Schema(this);
|
|
436
|
-
schema.meta = {
|
|
437
|
-
...schema.meta,
|
|
438
|
-
[key]: value
|
|
439
|
-
};
|
|
440
|
-
return schema;
|
|
441
|
-
} });
|
|
442
|
-
Schema.prototype.deprecated = function deprecated() {
|
|
443
|
-
const schema = Schema(this);
|
|
444
|
-
schema.meta.badges ||= [];
|
|
445
|
-
schema.meta.badges.push({
|
|
446
|
-
text: "deprecated",
|
|
447
|
-
type: "danger"
|
|
448
|
-
});
|
|
449
|
-
return schema;
|
|
450
|
-
};
|
|
451
|
-
Schema.prototype.experimental = function experimental() {
|
|
452
|
-
const schema = Schema(this);
|
|
453
|
-
schema.meta.badges ||= [];
|
|
454
|
-
schema.meta.badges.push({
|
|
455
|
-
text: "experimental",
|
|
456
|
-
type: "warning"
|
|
457
|
-
});
|
|
458
|
-
return schema;
|
|
459
|
-
};
|
|
460
|
-
Schema.prototype.pattern = function pattern(regexp) {
|
|
461
|
-
const schema = Schema(this);
|
|
462
|
-
const pattern = pick(regexp, ["source", "flags"]);
|
|
463
|
-
schema.meta = {
|
|
464
|
-
...schema.meta,
|
|
465
|
-
pattern
|
|
466
|
-
};
|
|
467
|
-
return schema;
|
|
468
|
-
};
|
|
469
|
-
Schema.prototype.simplify = function simplify(value) {
|
|
470
|
-
if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
|
|
471
|
-
if (isNullable(value)) return value;
|
|
472
|
-
if (this.type === "object" || this.type === "dict") {
|
|
473
|
-
const result = {};
|
|
474
|
-
for (const key in value) {
|
|
475
|
-
const item = (this.type === "object" ? this.dict[key] : this.inner)?.simplify(value[key]);
|
|
476
|
-
if (this.type === "dict" || !isNullable(item)) result[key] = item;
|
|
477
|
-
}
|
|
478
|
-
if (deepEqual(result, this.meta.default, this.type === "dict")) return null;
|
|
479
|
-
return result;
|
|
480
|
-
} else if (this.type === "array" || this.type === "tuple") {
|
|
481
|
-
const result = [];
|
|
482
|
-
value.forEach((value, index) => {
|
|
483
|
-
const schema = this.type === "array" ? this.inner : this.list[index];
|
|
484
|
-
const item = schema ? schema.simplify(value) : value;
|
|
485
|
-
result.push(item);
|
|
486
|
-
});
|
|
487
|
-
return result;
|
|
488
|
-
} else if (this.type === "intersect") {
|
|
489
|
-
const result = {};
|
|
490
|
-
for (const item of this.list) Object.assign(result, item.simplify(value));
|
|
491
|
-
return result;
|
|
492
|
-
} else if (this.type === "union") for (const schema of this.list) try {
|
|
493
|
-
Schema.resolve(value, schema, {});
|
|
494
|
-
return schema.simplify(value);
|
|
495
|
-
} catch {}
|
|
496
|
-
return value;
|
|
497
|
-
};
|
|
498
|
-
Schema.prototype.toString = function toString(inline) {
|
|
499
|
-
return formatters[this.type]?.(this, inline) ?? `Schema<${this.type}>`;
|
|
500
|
-
};
|
|
501
|
-
Schema.prototype.role = function role(role, extra) {
|
|
502
|
-
const schema = Schema(this);
|
|
503
|
-
schema.meta = {
|
|
504
|
-
...schema.meta,
|
|
505
|
-
role,
|
|
506
|
-
extra
|
|
507
|
-
};
|
|
508
|
-
return schema;
|
|
509
|
-
};
|
|
510
|
-
for (const key of [
|
|
511
|
-
"default",
|
|
512
|
-
"link",
|
|
513
|
-
"comment",
|
|
514
|
-
"description",
|
|
515
|
-
"max",
|
|
516
|
-
"min",
|
|
517
|
-
"step"
|
|
518
|
-
]) Object.assign(Schema.prototype, { [key](value) {
|
|
519
|
-
const schema = Schema(this);
|
|
520
|
-
schema.meta = {
|
|
521
|
-
...schema.meta,
|
|
522
|
-
[key]: value
|
|
523
|
-
};
|
|
524
|
-
return schema;
|
|
525
|
-
} });
|
|
526
|
-
const resolvers = {};
|
|
527
|
-
Schema.extend = function extend(type, resolve) {
|
|
528
|
-
resolvers[type] = resolve;
|
|
529
|
-
};
|
|
530
|
-
Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
|
|
531
|
-
if (!schema) return [data];
|
|
532
|
-
if (options.ignore?.(data, schema)) return [data];
|
|
533
|
-
if (isNullable(data) && schema.type !== "lazy") {
|
|
534
|
-
if (schema.meta.required) throw new ValidationError(`missing required value`, options);
|
|
535
|
-
let current = schema;
|
|
536
|
-
let fallback = schema.meta.default;
|
|
537
|
-
while (current?.type === "intersect" && isNullable(fallback)) {
|
|
538
|
-
current = current.list[0];
|
|
539
|
-
fallback = current?.meta.default;
|
|
540
|
-
}
|
|
541
|
-
if (isNullable(fallback)) return [data];
|
|
542
|
-
data = clone(fallback);
|
|
543
|
-
}
|
|
544
|
-
const callback = resolvers[schema.type];
|
|
545
|
-
if (!callback) throw new ValidationError(`unsupported type "${schema.type}"`, options);
|
|
546
|
-
try {
|
|
547
|
-
return callback(data, schema, options, strict);
|
|
548
|
-
} catch (error) {
|
|
549
|
-
if (!schema.meta.loose) throw error;
|
|
550
|
-
return [schema.meta.default];
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
Schema.from = function from(source) {
|
|
554
|
-
if (isNullable(source)) return Schema.any();
|
|
555
|
-
else if ([
|
|
556
|
-
"string",
|
|
557
|
-
"number",
|
|
558
|
-
"boolean"
|
|
559
|
-
].includes(typeof source)) return Schema.const(source).required();
|
|
560
|
-
else if (source[kSchema]) return source;
|
|
561
|
-
else if (typeof source === "function") switch (source) {
|
|
562
|
-
case String: return Schema.string().required();
|
|
563
|
-
case Number: return Schema.number().required();
|
|
564
|
-
case Boolean: return Schema.boolean().required();
|
|
565
|
-
case Function: return Schema.function().required();
|
|
566
|
-
default: return Schema.is(source).required();
|
|
567
|
-
}
|
|
568
|
-
else throw new TypeError(`cannot infer schema from ${source}`);
|
|
569
|
-
};
|
|
570
|
-
Schema.lazy = function lazy(builder) {
|
|
571
|
-
const toJSON = () => {
|
|
572
|
-
if (!schema.inner[kSchema]) {
|
|
573
|
-
schema.inner = schema.builder();
|
|
574
|
-
schema.inner.meta = {
|
|
575
|
-
...schema.meta,
|
|
576
|
-
...schema.inner.meta
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
return schema.inner.toJSON();
|
|
580
|
-
};
|
|
581
|
-
const schema = new Schema({
|
|
582
|
-
type: "lazy",
|
|
583
|
-
builder,
|
|
584
|
-
inner: { toJSON }
|
|
585
|
-
});
|
|
586
|
-
return schema;
|
|
587
|
-
};
|
|
588
|
-
Schema.natural = function natural() {
|
|
589
|
-
return Schema.number().step(1).min(0);
|
|
590
|
-
};
|
|
591
|
-
Schema.percent = function percent() {
|
|
592
|
-
return Schema.number().step(.01).min(0).max(1).role("slider");
|
|
593
|
-
};
|
|
594
|
-
Schema.date = function date() {
|
|
595
|
-
return Schema.union([Schema.is(Date), Schema.transform(Schema.string().role("datetime"), (value, options) => {
|
|
596
|
-
const date = new Date(value);
|
|
597
|
-
if (isNaN(+date)) throw new ValidationError(`invalid date "${value}"`, options);
|
|
598
|
-
return date;
|
|
599
|
-
}, true)]);
|
|
600
|
-
};
|
|
601
|
-
Schema.regExp = function regExp(flag = "") {
|
|
602
|
-
return Schema.union([Schema.is(RegExp), Schema.transform(Schema.string().role("regexp", { flag }), (value, options) => {
|
|
603
|
-
try {
|
|
604
|
-
return new RegExp(value, flag);
|
|
605
|
-
} catch (e) {
|
|
606
|
-
throw new ValidationError(e.message, options);
|
|
607
|
-
}
|
|
608
|
-
}, true)]);
|
|
609
|
-
};
|
|
610
|
-
Schema.arrayBuffer = function arrayBuffer(encoding) {
|
|
611
|
-
return Schema.union([
|
|
612
|
-
Schema.is(ArrayBuffer),
|
|
613
|
-
Schema.is(SharedArrayBuffer),
|
|
614
|
-
Schema.transform(Schema.any(), (value, options) => {
|
|
615
|
-
if (Binary.isSource(value)) return Binary.fromSource(value);
|
|
616
|
-
throw new ValidationError(`expected ArrayBufferSource but got ${value}`, options);
|
|
617
|
-
}, true),
|
|
618
|
-
...encoding ? [Schema.transform(Schema.string(), (value, options) => {
|
|
619
|
-
try {
|
|
620
|
-
return encoding === "base64" ? Binary.fromBase64(value) : Binary.fromHex(value);
|
|
621
|
-
} catch (e) {
|
|
622
|
-
throw new ValidationError(e.message, options);
|
|
623
|
-
}
|
|
624
|
-
}, true)] : []
|
|
625
|
-
]);
|
|
626
|
-
};
|
|
627
|
-
Schema.extend("lazy", (data, schema, options, strict) => {
|
|
628
|
-
if (!schema.inner[kSchema]) {
|
|
629
|
-
schema.inner = schema.builder();
|
|
630
|
-
schema.inner.meta = {
|
|
631
|
-
...schema.meta,
|
|
632
|
-
...schema.inner.meta
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
return Schema.resolve(data, schema.inner, options, strict);
|
|
636
|
-
});
|
|
637
|
-
Schema.extend("any", (data) => {
|
|
638
|
-
return [data];
|
|
639
|
-
});
|
|
640
|
-
Schema.extend("never", (data, _, options) => {
|
|
641
|
-
throw new ValidationError(`expected nullable but got ${data}`, options);
|
|
642
|
-
});
|
|
643
|
-
Schema.extend("const", (data, { value }, options) => {
|
|
644
|
-
if (deepEqual(data, value)) return [value];
|
|
645
|
-
throw new ValidationError(`expected ${value} but got ${data}`, options);
|
|
646
|
-
});
|
|
647
|
-
function checkWithinRange(data, meta, description, options, skipMin = false) {
|
|
648
|
-
const { max = Infinity, min = -Infinity } = meta;
|
|
649
|
-
if (data > max) throw new ValidationError(`expected ${description} <= ${max} but got ${data}`, options);
|
|
650
|
-
if (data < min && !skipMin) throw new ValidationError(`expected ${description} >= ${min} but got ${data}`, options);
|
|
651
|
-
}
|
|
652
|
-
Schema.extend("string", (data, { meta }, options) => {
|
|
653
|
-
if (typeof data !== "string") throw new ValidationError(`expected string but got ${data}`, options);
|
|
654
|
-
if (meta.pattern) {
|
|
655
|
-
const regexp = new RegExp(meta.pattern.source, meta.pattern.flags);
|
|
656
|
-
if (!regexp.test(data)) throw new ValidationError(`expect string to match regexp ${regexp}`, options);
|
|
657
|
-
}
|
|
658
|
-
checkWithinRange(data.length, meta, "string length", options);
|
|
659
|
-
return [data];
|
|
660
|
-
});
|
|
661
|
-
function decimalShift(data, digits) {
|
|
662
|
-
const str = data.toString();
|
|
663
|
-
if (str.includes("e")) return data * Math.pow(10, digits);
|
|
664
|
-
const index = str.indexOf(".");
|
|
665
|
-
if (index === -1) return data * Math.pow(10, digits);
|
|
666
|
-
const frac = str.slice(index + 1);
|
|
667
|
-
const integer = str.slice(0, index);
|
|
668
|
-
if (frac.length <= digits) return +(integer + frac.padEnd(digits, "0"));
|
|
669
|
-
return +(integer + frac.slice(0, digits) + "." + frac.slice(digits));
|
|
670
|
-
}
|
|
671
|
-
function isMultipleOf(data, min, step) {
|
|
672
|
-
step = Math.abs(step);
|
|
673
|
-
if (!/^\d+\.\d+$/.test(step.toString())) return (data - min) % step === 0;
|
|
674
|
-
const index = step.toString().indexOf(".");
|
|
675
|
-
const digits = step.toString().slice(index + 1).length;
|
|
676
|
-
return Math.abs(decimalShift(data, digits) - decimalShift(min, digits)) % decimalShift(step, digits) === 0;
|
|
677
|
-
}
|
|
678
|
-
Schema.extend("number", (data, { meta }, options) => {
|
|
679
|
-
if (typeof data !== "number") throw new ValidationError(`expected number but got ${data}`, options);
|
|
680
|
-
checkWithinRange(data, meta, "number", options);
|
|
681
|
-
const { step } = meta;
|
|
682
|
-
if (step && !isMultipleOf(data, meta.min ?? 0, step)) throw new ValidationError(`expected number multiple of ${step} but got ${data}`, options);
|
|
683
|
-
return [data];
|
|
684
|
-
});
|
|
685
|
-
Schema.extend("boolean", (data, _, options) => {
|
|
686
|
-
if (typeof data === "boolean") return [data];
|
|
687
|
-
throw new ValidationError(`expected boolean but got ${data}`, options);
|
|
688
|
-
});
|
|
689
|
-
Schema.extend("bitset", (data, { bits, meta }, options) => {
|
|
690
|
-
let value = 0, keys = [];
|
|
691
|
-
if (typeof data === "number") {
|
|
692
|
-
value = data;
|
|
693
|
-
for (const key in bits) if (data & bits[key]) keys.push(key);
|
|
694
|
-
} else if (Array.isArray(data)) {
|
|
695
|
-
keys = data;
|
|
696
|
-
for (const key of keys) {
|
|
697
|
-
if (typeof key !== "string") throw new ValidationError(`expected string but got ${key}`, options);
|
|
698
|
-
if (key in bits) value |= bits[key];
|
|
699
|
-
}
|
|
700
|
-
} else throw new ValidationError(`expected number or array but got ${data}`, options);
|
|
701
|
-
if (value === meta.default) return [value];
|
|
702
|
-
return [value, keys];
|
|
703
|
-
});
|
|
704
|
-
Schema.extend("function", (data, _, options) => {
|
|
705
|
-
if (typeof data === "function") return [data];
|
|
706
|
-
throw new ValidationError(`expected function but got ${data}`, options);
|
|
707
|
-
});
|
|
708
|
-
Schema.extend("is", (data, { constructor }, options) => {
|
|
709
|
-
if (typeof constructor === "function") {
|
|
710
|
-
if (data instanceof constructor) return [data];
|
|
711
|
-
throw new ValidationError(`expected ${constructor.name} but got ${data}`, options);
|
|
712
|
-
} else {
|
|
713
|
-
if (isNullable(data)) throw new ValidationError(`expected ${constructor} but got ${data}`, options);
|
|
714
|
-
let prototype = Object.getPrototypeOf(data);
|
|
715
|
-
while (prototype) {
|
|
716
|
-
if (prototype.constructor?.name === constructor) return [data];
|
|
717
|
-
prototype = Object.getPrototypeOf(prototype);
|
|
718
|
-
}
|
|
719
|
-
throw new ValidationError(`expected ${constructor} but got ${data}`, options);
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
function property(data, key, schema, options) {
|
|
723
|
-
try {
|
|
724
|
-
const [value, adapted] = Schema.resolve(data[key], schema, {
|
|
725
|
-
...options,
|
|
726
|
-
path: [...options.path || [], key]
|
|
727
|
-
});
|
|
728
|
-
if (adapted !== void 0) data[key] = adapted;
|
|
729
|
-
return value;
|
|
730
|
-
} catch (e) {
|
|
731
|
-
if (!options?.autofix) throw e;
|
|
732
|
-
delete data[key];
|
|
733
|
-
return schema.meta.default;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
Schema.extend("array", (data, { inner, meta }, options) => {
|
|
737
|
-
if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
|
|
738
|
-
checkWithinRange(data.length, meta, "array length", options, !isNullable(inner.meta.default));
|
|
739
|
-
return [data.map((_, index) => property(data, index, inner, options))];
|
|
740
|
-
});
|
|
741
|
-
Schema.extend("dict", (data, { inner, sKey }, options, strict) => {
|
|
742
|
-
if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
|
|
743
|
-
const result = {};
|
|
744
|
-
for (const key in data) {
|
|
745
|
-
let rKey;
|
|
746
|
-
try {
|
|
747
|
-
rKey = Schema.resolve(key, sKey, options)[0];
|
|
748
|
-
} catch (error) {
|
|
749
|
-
if (strict) continue;
|
|
750
|
-
throw error;
|
|
751
|
-
}
|
|
752
|
-
result[rKey] = property(data, key, inner, options);
|
|
753
|
-
data[rKey] = data[key];
|
|
754
|
-
if (key !== rKey) delete data[key];
|
|
755
|
-
}
|
|
756
|
-
return [result];
|
|
757
|
-
});
|
|
758
|
-
Schema.extend("tuple", (data, { list }, options, strict) => {
|
|
759
|
-
if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
|
|
760
|
-
const result = list.map((inner, index) => property(data, index, inner, options));
|
|
761
|
-
if (strict) return [result];
|
|
762
|
-
result.push(...data.slice(list.length));
|
|
763
|
-
return [result];
|
|
764
|
-
});
|
|
765
|
-
function merge(result, data) {
|
|
766
|
-
for (const key in data) {
|
|
767
|
-
if (key in result) continue;
|
|
768
|
-
result[key] = data[key];
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
Schema.extend("object", (data, { dict }, options, strict) => {
|
|
772
|
-
if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
|
|
773
|
-
const result = {};
|
|
774
|
-
for (const key in dict) {
|
|
775
|
-
const value = property(data, key, dict[key], options);
|
|
776
|
-
if (!isNullable(value) || key in data) result[key] = value;
|
|
777
|
-
}
|
|
778
|
-
if (!strict) merge(result, data);
|
|
779
|
-
return [result];
|
|
780
|
-
});
|
|
781
|
-
Schema.extend("union", (data, { list, toString }, options, strict) => {
|
|
782
|
-
const messages = [];
|
|
783
|
-
for (const inner of list) try {
|
|
784
|
-
return Schema.resolve(data, inner, options, strict);
|
|
785
|
-
} catch (error) {
|
|
786
|
-
messages.push(error);
|
|
787
|
-
}
|
|
788
|
-
throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
|
|
789
|
-
});
|
|
790
|
-
Schema.extend("intersect", (data, { list, toString }, options, strict) => {
|
|
791
|
-
if (!list.length) return [data];
|
|
792
|
-
let result;
|
|
793
|
-
for (const inner of list) {
|
|
794
|
-
const value = Schema.resolve(data, inner, options, true)[0];
|
|
795
|
-
if (isNullable(value)) continue;
|
|
796
|
-
if (isNullable(result)) result = value;
|
|
797
|
-
else if (typeof result !== typeof value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
|
|
798
|
-
else if (typeof value === "object") merge(result ??= {}, value);
|
|
799
|
-
else if (result !== value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
|
|
800
|
-
}
|
|
801
|
-
if (!strict && isPlainObject(data)) merge(result, data);
|
|
802
|
-
return [result];
|
|
803
|
-
});
|
|
804
|
-
Schema.extend("transform", (data, { inner, callback, preserve }, options) => {
|
|
805
|
-
const [result, adapted = data] = Schema.resolve(data, inner, options, true);
|
|
806
|
-
if (preserve) return [callback(result)];
|
|
807
|
-
else return [callback(result), callback(adapted)];
|
|
808
|
-
});
|
|
809
|
-
const formatters = {};
|
|
810
|
-
function defineMethod(name, keys, format) {
|
|
811
|
-
formatters[name] = format;
|
|
812
|
-
Object.assign(Schema, { [name](...args) {
|
|
813
|
-
const schema = new Schema({ type: name });
|
|
814
|
-
keys.forEach((key, index) => {
|
|
815
|
-
switch (key) {
|
|
816
|
-
case "sKey":
|
|
817
|
-
schema.sKey = args[index] ?? Schema.string();
|
|
818
|
-
break;
|
|
819
|
-
case "inner":
|
|
820
|
-
schema.inner = Schema.from(args[index]);
|
|
821
|
-
break;
|
|
822
|
-
case "list":
|
|
823
|
-
schema.list = args[index].map(Schema.from);
|
|
824
|
-
break;
|
|
825
|
-
case "dict":
|
|
826
|
-
schema.dict = mapValues(args[index], Schema.from);
|
|
827
|
-
break;
|
|
828
|
-
case "bits":
|
|
829
|
-
schema.bits = {};
|
|
830
|
-
for (const key in args[index]) {
|
|
831
|
-
if (typeof args[index][key] !== "number") continue;
|
|
832
|
-
schema.bits[key] = args[index][key];
|
|
833
|
-
}
|
|
834
|
-
break;
|
|
835
|
-
case "callback": {
|
|
836
|
-
const callback = schema.callback = args[index];
|
|
837
|
-
callback["toJSON"] ||= () => callback.toString();
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
840
|
-
case "constructor": {
|
|
841
|
-
const constructor = schema.constructor = args[index];
|
|
842
|
-
if (typeof constructor === "function") constructor["toJSON"] ||= () => constructor["name"];
|
|
843
|
-
break;
|
|
844
|
-
}
|
|
845
|
-
default: schema[key] = args[index];
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
if (name === "object" || name === "dict") schema.meta.default = {};
|
|
849
|
-
else if (name === "array" || name === "tuple") schema.meta.default = [];
|
|
850
|
-
else if (name === "bitset") schema.meta.default = 0;
|
|
851
|
-
return schema;
|
|
852
|
-
} });
|
|
853
|
-
}
|
|
854
|
-
defineMethod("is", ["constructor"], ({ constructor }) => {
|
|
855
|
-
if (typeof constructor === "function") return constructor.name;
|
|
856
|
-
else return constructor;
|
|
857
|
-
});
|
|
858
|
-
defineMethod("any", [], () => "any");
|
|
859
|
-
defineMethod("never", [], () => "never");
|
|
860
|
-
defineMethod("const", ["value"], ({ value }) => typeof value === "string" ? JSON.stringify(value) : value);
|
|
861
|
-
defineMethod("string", [], () => "string");
|
|
862
|
-
defineMethod("number", [], () => "number");
|
|
863
|
-
defineMethod("boolean", [], () => "boolean");
|
|
864
|
-
defineMethod("bitset", ["bits"], () => "bitset");
|
|
865
|
-
defineMethod("function", [], () => "function");
|
|
866
|
-
defineMethod("array", ["inner"], ({ inner }) => `${inner.toString(true)}[]`);
|
|
867
|
-
defineMethod("dict", ["inner", "sKey"], ({ inner, sKey }) => `{ [key: ${sKey.toString()}]: ${inner.toString()} }`);
|
|
868
|
-
defineMethod("tuple", ["list"], ({ list }) => `[${list.map((inner) => inner.toString()).join(", ")}]`);
|
|
869
|
-
defineMethod("object", ["dict"], ({ dict }) => {
|
|
870
|
-
if (Object.keys(dict).length === 0) return "{}";
|
|
871
|
-
return `{ ${Object.entries(dict).map(([key, inner]) => {
|
|
872
|
-
return `${key}${inner.meta.required ? "" : "?"}: ${inner.toString()}`;
|
|
873
|
-
}).join(", ")} }`;
|
|
874
|
-
});
|
|
875
|
-
defineMethod("union", ["list"], ({ list }, inline) => {
|
|
876
|
-
const result = list.map(({ toString: format }) => format()).join(" | ");
|
|
877
|
-
return inline ? `(${result})` : result;
|
|
878
|
-
});
|
|
879
|
-
defineMethod("intersect", ["list"], ({ list }) => {
|
|
880
|
-
return `${list.map((inner) => inner.toString(true)).join(" & ")}`;
|
|
881
|
-
});
|
|
882
|
-
defineMethod("transform", [
|
|
883
|
-
"inner",
|
|
884
|
-
"callback",
|
|
885
|
-
"preserve"
|
|
886
|
-
], ({ inner }, isInner) => inner.toString(isInner));
|
|
887
|
-
//#endregion
|
|
888
97
|
//#region ../../util/timeout/src/index.ts
|
|
889
98
|
/** Largest delay Node schedules without clamping it to one millisecond. */
|
|
890
99
|
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
@@ -924,22 +133,22 @@ const DEFAULT_RETRYABLE_CODES = Object.freeze([
|
|
|
924
133
|
"TIMEOUT",
|
|
925
134
|
"TRANSPORT"
|
|
926
135
|
]);
|
|
927
|
-
const backoffSchema =
|
|
928
|
-
initialDelayMs:
|
|
929
|
-
maxDelayMs:
|
|
930
|
-
jitterRatio:
|
|
136
|
+
const backoffSchema = z.object({
|
|
137
|
+
initialDelayMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_INITIAL_DELAY_MS),
|
|
138
|
+
maxDelayMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_MAX_DELAY_MS),
|
|
139
|
+
jitterRatio: z.number().min(0).max(1).default(DEFAULT_JITTER_RATIO)
|
|
931
140
|
});
|
|
932
|
-
const normalPolicySchema =
|
|
933
|
-
mode:
|
|
934
|
-
maxRetries:
|
|
935
|
-
retryableCodes:
|
|
141
|
+
const normalPolicySchema = z.object({
|
|
142
|
+
mode: z.const("normal").required(),
|
|
143
|
+
maxRetries: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RETRIES),
|
|
144
|
+
retryableCodes: z.array(z.string()).default([...DEFAULT_RETRYABLE_CODES]),
|
|
936
145
|
backoff: backoffSchema
|
|
937
146
|
});
|
|
938
|
-
const alwaysPolicySchema =
|
|
939
|
-
mode:
|
|
147
|
+
const alwaysPolicySchema = z.object({
|
|
148
|
+
mode: z.const("always").required(),
|
|
940
149
|
backoff: backoffSchema
|
|
941
150
|
});
|
|
942
|
-
|
|
151
|
+
z.union([normalPolicySchema, alwaysPolicySchema]);
|
|
943
152
|
//#endregion
|
|
944
153
|
//#region ../../llm/llm/src/attribution.ts
|
|
945
154
|
/**
|
|
@@ -1261,7 +470,7 @@ var SkillLayer = class {
|
|
|
1261
470
|
}
|
|
1262
471
|
};
|
|
1263
472
|
(class extends Service {
|
|
1264
|
-
static Config =
|
|
473
|
+
static Config = z.object({ collectCacheMaxEntries: z.number().default(DEFAULT_COLLECT_CACHE_ENTRIES) });
|
|
1265
474
|
collectCacheMaxEntries;
|
|
1266
475
|
layers = new ScopedLayers((scope) => new SkillLayer(scope), () => {
|
|
1267
476
|
this.invalidateCache();
|
|
@@ -2157,21 +1366,25 @@ function configPath(env = process.env) {
|
|
|
2157
1366
|
return join(configDir(env), "config.json");
|
|
2158
1367
|
}
|
|
2159
1368
|
/**
|
|
2160
|
-
* Read the configured default with diagnostics:
|
|
2161
|
-
*
|
|
2162
|
-
*
|
|
2163
|
-
* the caller to warn about once.
|
|
1369
|
+
* Read the configured default with diagnostics. Priority:
|
|
1370
|
+
* `PONYTAIL_DEFAULT_MODE` → Cordis profile `defaultMode` → user config file →
|
|
1371
|
+
* `full`. A missing config file is normal and yields no issue; a broken one
|
|
1372
|
+
* yields the fallback mode plus one issue for the caller to warn about once.
|
|
1373
|
+
* @param env - the process environment to read.
|
|
1374
|
+
* @param profileMode - the validated Cordis profile `defaultMode`, or `null`
|
|
1375
|
+
* when the profile config is absent or invalid (invalid values are reported
|
|
1376
|
+
* by the caller; this function only consumes valid ones).
|
|
2164
1377
|
*/
|
|
2165
|
-
function readDefaultModeInfo(env = process.env) {
|
|
1378
|
+
function readDefaultModeInfo(env = process.env, profileMode = null) {
|
|
2166
1379
|
const path = configPath(env);
|
|
2167
1380
|
const envMode = normalizeRuntimeMode(env.PONYTAIL_DEFAULT_MODE);
|
|
2168
1381
|
let configText;
|
|
2169
1382
|
try {
|
|
2170
1383
|
configText = readFileSync(path, "utf8");
|
|
2171
1384
|
} catch (error) {
|
|
2172
|
-
if (error.code === "ENOENT") return { mode: envMode ?? "full" };
|
|
1385
|
+
if (error.code === "ENOENT") return { mode: envMode ?? profileMode ?? "full" };
|
|
2173
1386
|
return {
|
|
2174
|
-
mode: envMode ?? "full",
|
|
1387
|
+
mode: envMode ?? profileMode ?? "full",
|
|
2175
1388
|
issue: {
|
|
2176
1389
|
kind: "read",
|
|
2177
1390
|
detail: `${path}: ${error.message}`
|
|
@@ -2203,6 +1416,10 @@ function readDefaultModeInfo(env = process.env) {
|
|
|
2203
1416
|
mode: envMode,
|
|
2204
1417
|
...configIssue ? { issue: configIssue } : {}
|
|
2205
1418
|
};
|
|
1419
|
+
if (profileMode) return {
|
|
1420
|
+
mode: profileMode,
|
|
1421
|
+
...configIssue ? { issue: configIssue } : {}
|
|
1422
|
+
};
|
|
2206
1423
|
if (configIssue) return {
|
|
2207
1424
|
mode: DEFAULT_MODE,
|
|
2208
1425
|
issue: configIssue
|
|
@@ -2211,10 +1428,19 @@ function readDefaultModeInfo(env = process.env) {
|
|
|
2211
1428
|
}
|
|
2212
1429
|
/**
|
|
2213
1430
|
* Read the configured default for this host: environment variable first, then
|
|
2214
|
-
* the config file, then `full`.
|
|
1431
|
+
* the Cordis profile `defaultMode`, then the user config file, then `full`.
|
|
1432
|
+
*/
|
|
1433
|
+
function readDefaultMode(env = process.env, profileMode = null) {
|
|
1434
|
+
return readDefaultModeInfo(env, profileMode).mode;
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Why a `saved` default is not the effective one — for the `/ponytail default`
|
|
1438
|
+
* result message. `null` means the saved value is effective.
|
|
2215
1439
|
*/
|
|
2216
|
-
function
|
|
2217
|
-
|
|
1440
|
+
function defaultOverrideReason(env, profileMode) {
|
|
1441
|
+
if (normalizeRuntimeMode(env.PONYTAIL_DEFAULT_MODE)) return "PONYTAIL_DEFAULT_MODE";
|
|
1442
|
+
if (profileMode) return "profile configuration";
|
|
1443
|
+
return null;
|
|
2218
1444
|
}
|
|
2219
1445
|
/**
|
|
2220
1446
|
* Persist a new default level to the config file, preserving other fields.
|
|
@@ -2305,55 +1531,102 @@ function isSubagentSession(header) {
|
|
|
2305
1531
|
//#endregion
|
|
2306
1532
|
//#region lib/types/instructions.js
|
|
2307
1533
|
/**
|
|
2308
|
-
*
|
|
2309
|
-
*
|
|
2310
|
-
*
|
|
1534
|
+
* Structured ponytail ruleset composition. Each intensity is built from
|
|
1535
|
+
* explicit fragments — common rules, a never-cut safety boundary list, and
|
|
1536
|
+
* the mode's own rules — instead of filtering one Markdown body with regexes.
|
|
1537
|
+
* The three intensities therefore differ in their actual instructions, not
|
|
1538
|
+
* just in a table row.
|
|
2311
1539
|
*
|
|
2312
1540
|
* @module @deepseek-ai/dsh-ponytail
|
|
2313
1541
|
*/
|
|
1542
|
+
/** Shared identity line, carried by every non-`off` mode. */
|
|
1543
|
+
const INTRO = "You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.";
|
|
2314
1544
|
/**
|
|
2315
|
-
*
|
|
2316
|
-
* active one. Both shape-sensitive spots (the intensity table rows and the
|
|
2317
|
-
* quoted worked examples) are keyed by a mode name; ordinary rules survive
|
|
2318
|
-
* verbatim, even ones whose prose starts with a mode-looking word.
|
|
1545
|
+
* Understanding-and-reuse baseline, identical in every non-`off` mode.
|
|
2319
1546
|
*/
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
1547
|
+
const COMMON_RULES = [
|
|
1548
|
+
"Understand the problem before choosing a solution: read the code the change touches and trace the real flow end to end. Laziness that skips comprehension ships a confident wrong fix.",
|
|
1549
|
+
"Reuse what already exists in this codebase before writing anything new.",
|
|
1550
|
+
"Reach for the standard library, platform-native features, and already-installed dependencies before custom code.",
|
|
1551
|
+
"A non-trivial change leaves ONE minimal runnable check behind (an assert-based self-check or one small test file; no frameworks). Trivial one-liners need no test.",
|
|
1552
|
+
"Explain briefly, but never omit the key decisions."
|
|
1553
|
+
].join("\n");
|
|
1554
|
+
/**
|
|
1555
|
+
* The never-cut list. Every non-`off` mode keeps these; intensities tune how
|
|
1556
|
+
* aggressively code is minimized, never what may be dropped.
|
|
1557
|
+
*/
|
|
1558
|
+
const SAFETY_BOUNDARIES = [
|
|
1559
|
+
"Never cut, in any mode:",
|
|
1560
|
+
"- Input validation at trust boundaries.",
|
|
1561
|
+
"- Error handling that prevents data loss.",
|
|
1562
|
+
"- Security measures.",
|
|
1563
|
+
"- Accessibility basics.",
|
|
1564
|
+
"- Explicit acceptance criteria the user asked for.",
|
|
1565
|
+
"- Understanding the problem and tracing the real flow first.",
|
|
1566
|
+
"- \"Minimal diff\" is not a substitute for \"correct fix\"."
|
|
1567
|
+
].join("\n");
|
|
1568
|
+
const MODE_RULES = {
|
|
1569
|
+
lite: [
|
|
1570
|
+
"Complete what is explicitly asked, including every acceptance criterion.",
|
|
1571
|
+
"Prefer reuse, the standard library, native features, and installed dependencies.",
|
|
1572
|
+
"You may name a simpler alternative in one line, but do not challenge or reject an explicit requirement.",
|
|
1573
|
+
"Output may be a little more complete than full; never cut an acceptance item to save lines."
|
|
1574
|
+
].join("\n"),
|
|
1575
|
+
full: [
|
|
1576
|
+
"The ladder — stop at the first rung that holds:",
|
|
1577
|
+
"1. Does this need to exist at all? (YAGNI)",
|
|
1578
|
+
"2. Does it already exist in this codebase? Reuse it.",
|
|
1579
|
+
"3. Does the standard library do it? Use it.",
|
|
1580
|
+
"4. Does a native platform feature cover it? Use it.",
|
|
1581
|
+
"5. Does an already-installed dependency solve it? Use it.",
|
|
1582
|
+
"6. Can this be one line? Make it one line.",
|
|
1583
|
+
"7. Only then: the minimum code that works.",
|
|
1584
|
+
"Default to the shortest correct implementation; prefer deletion and reuse.",
|
|
1585
|
+
"Fix root causes, not symptoms: one guard in the shared function beats a guard in every caller."
|
|
1586
|
+
].join("\n"),
|
|
1587
|
+
ultra: [
|
|
1588
|
+
"YAGNI extremist: default to deletion before addition.",
|
|
1589
|
+
"Actively question speculative features, caches, abstractions, configuration, and new dependencies.",
|
|
1590
|
+
"Prefer one-liners, the standard library, and native capabilities.",
|
|
1591
|
+
"Minimize files, dependencies, and code — but never the safety boundaries or acceptance criteria above.",
|
|
1592
|
+
"For a complex request: ship the minimal correct version first and state what the full version would require.",
|
|
1593
|
+
"Ultra is not \"refuse everything\": honor explicit user requirements."
|
|
1594
|
+
].join("\n")
|
|
1595
|
+
};
|
|
1596
|
+
const MODE_LABELS = {
|
|
1597
|
+
lite: "Lite",
|
|
1598
|
+
full: "Full",
|
|
1599
|
+
ultra: "Ultra"
|
|
1600
|
+
};
|
|
1601
|
+
/** Compose the complete section text for one intensity. */
|
|
1602
|
+
function render(effective) {
|
|
1603
|
+
return [
|
|
1604
|
+
`PONYTAIL MODE ACTIVE — level: ${effective}`,
|
|
1605
|
+
"",
|
|
1606
|
+
INTRO,
|
|
1607
|
+
"",
|
|
1608
|
+
"## Common rules (all modes)",
|
|
1609
|
+
COMMON_RULES,
|
|
1610
|
+
"",
|
|
1611
|
+
"## Safety boundaries (never cut)",
|
|
1612
|
+
SAFETY_BOUNDARIES,
|
|
1613
|
+
"",
|
|
1614
|
+
`## ${MODE_LABELS[effective]} rules`,
|
|
1615
|
+
MODE_RULES[effective]
|
|
1616
|
+
].join("\n");
|
|
2339
1617
|
}
|
|
2340
1618
|
/**
|
|
2341
|
-
* The
|
|
2342
|
-
*
|
|
2343
|
-
*
|
|
1619
|
+
* The injected ruleset for one intensity, composed from the structured
|
|
1620
|
+
* fragments above. Returns an empty string for `off` (ponytail contributes
|
|
1621
|
+
* nothing). Renders are pure per mode and cached so every turn's bytes stay
|
|
1622
|
+
* identical.
|
|
2344
1623
|
*/
|
|
2345
1624
|
function getPonytailInstructions(mode) {
|
|
2346
1625
|
const effective = normalizeRuntimeMode(mode) ?? "full";
|
|
2347
1626
|
if (effective === "off") return "";
|
|
2348
1627
|
const cached = instructionCache.get(effective);
|
|
2349
1628
|
if (cached !== void 0) return cached;
|
|
2350
|
-
|
|
2351
|
-
try {
|
|
2352
|
-
body = filterSkillBodyForMode(PONYTAIL_SKILL_BODY, effective);
|
|
2353
|
-
} catch {
|
|
2354
|
-
return fallbackInstructions(effective);
|
|
2355
|
-
}
|
|
2356
|
-
const rendered = "PONYTAIL MODE ACTIVE — level: " + effective + "\n\n" + body;
|
|
1629
|
+
const rendered = render(effective);
|
|
2357
1630
|
instructionCache.set(effective, rendered);
|
|
2358
1631
|
return rendered;
|
|
2359
1632
|
}
|
|
@@ -2451,7 +1724,7 @@ function registerCommands(deps, commandCtx) {
|
|
|
2451
1724
|
kind: "error",
|
|
2452
1725
|
text: "Usage: /ponytail default [lite|full|ultra|off]"
|
|
2453
1726
|
};
|
|
2454
|
-
const effective = readDefaultMode();
|
|
1727
|
+
const effective = readDefaultMode(process.env, deps.profileMode);
|
|
2455
1728
|
deps.setDefault(effective);
|
|
2456
1729
|
if (written === effective) {
|
|
2457
1730
|
agent.steer(createUserMessage({
|
|
@@ -2469,10 +1742,11 @@ function registerCommands(deps, commandCtx) {
|
|
|
2469
1742
|
text: `Ponytail default set — new sessions start in ${written}.`
|
|
2470
1743
|
};
|
|
2471
1744
|
}
|
|
1745
|
+
const reason = defaultOverrideReason(process.env, deps.profileMode) ?? "PONYTAIL_DEFAULT_MODE";
|
|
2472
1746
|
agent.steer(createUserMessage({
|
|
2473
1747
|
content: [{
|
|
2474
1748
|
type: "text",
|
|
2475
|
-
text: `PONYTAIL DEFAULT SET — saved ${written}, effective ${effective} (
|
|
1749
|
+
text: `PONYTAIL DEFAULT SET — saved ${written}, effective ${effective} (${reason}).`
|
|
2476
1750
|
}],
|
|
2477
1751
|
source: {
|
|
2478
1752
|
kind: "plugin",
|
|
@@ -2481,7 +1755,7 @@ function registerCommands(deps, commandCtx) {
|
|
|
2481
1755
|
}));
|
|
2482
1756
|
return {
|
|
2483
1757
|
kind: "success",
|
|
2484
|
-
text: `Saved default: ${written}. Effective default: ${effective}, overridden by
|
|
1758
|
+
text: `Saved default: ${written}. Effective default: ${effective}, overridden by ${reason}.`
|
|
2485
1759
|
};
|
|
2486
1760
|
}
|
|
2487
1761
|
if (input === "status") return {
|
|
@@ -2590,7 +1864,9 @@ function descriptionFor(skill) {
|
|
|
2590
1864
|
* Register the always-on ruleset section, the runtime skills, the slash
|
|
2591
1865
|
* commands, and the plain-text deactivation listener.
|
|
2592
1866
|
*/
|
|
2593
|
-
function apply(ctx) {
|
|
1867
|
+
function apply(ctx, config = {}) {
|
|
1868
|
+
const profileMode = normalizeRuntimeMode(config.defaultMode);
|
|
1869
|
+
if (config.defaultMode !== void 0 && profileMode === null) ctx.logger.warn(`[ponytail] profile config defaultMode is not lite|full|ultra|off: ${JSON.stringify(config.defaultMode)}; falling back`);
|
|
2594
1870
|
let defaultMode = null;
|
|
2595
1871
|
const warned = /* @__PURE__ */ new Set();
|
|
2596
1872
|
const warnOnce = (key, message) => {
|
|
@@ -2599,7 +1875,7 @@ function apply(ctx) {
|
|
|
2599
1875
|
ctx.logger.warn(`[ponytail] ${message}`);
|
|
2600
1876
|
};
|
|
2601
1877
|
const refreshDefault = () => {
|
|
2602
|
-
const resolution = readDefaultModeInfo();
|
|
1878
|
+
const resolution = readDefaultModeInfo(process.env, profileMode);
|
|
2603
1879
|
if (resolution.issue) warnOnce(`default:${resolution.issue.kind}`, `${resolution.issue.detail}; using ${resolution.mode}`);
|
|
2604
1880
|
defaultMode = resolution.mode;
|
|
2605
1881
|
return defaultMode;
|
|
@@ -2614,7 +1890,7 @@ function apply(ctx) {
|
|
|
2614
1890
|
if (matcherResult.invalid) warnOnce("matcher:invalid", "PONYTAIL_SUBAGENT_MATCHER is not a valid regular expression; ignoring it (fail-open).");
|
|
2615
1891
|
const configFile = configPath();
|
|
2616
1892
|
const onConfigChange = () => {
|
|
2617
|
-
const resolution = readDefaultModeInfo();
|
|
1893
|
+
const resolution = readDefaultModeInfo(process.env, profileMode);
|
|
2618
1894
|
if (resolution.issue) {
|
|
2619
1895
|
warnOnce(`config:${resolution.issue.kind}`, `${resolution.issue.detail}; keeping the previous default`);
|
|
2620
1896
|
return;
|
|
@@ -2644,6 +1920,7 @@ function apply(ctx) {
|
|
|
2644
1920
|
registerCommands({
|
|
2645
1921
|
ctx,
|
|
2646
1922
|
store,
|
|
1923
|
+
profileMode,
|
|
2647
1924
|
defaultMode: readDefault,
|
|
2648
1925
|
setDefault
|
|
2649
1926
|
}, commandCtx);
|