@huaweidevtools/aishell-dsh-llm 0.0.1-beta.2 → 0.0.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,25 +1,1053 @@
1
1
  import { createCredentialsStore } from "@huaweidevtools/aishell-dsh-credentials-store";
2
- import { LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
2
+ import { IMAGE_OFFLOAD_REQUIRED_CODE, LlmAdapter, LlmError, contentHasImage, offloadedImageText, projectOffloadedImages, requestImageHandleText, requiredImageOffload, resolveImageAttachmentAccess } from "@deepseek-ai/dsh-llm";
3
+ import { requestImageDimensions } from "@deepseek-ai/dsh-attachment";
3
4
  import { brandString } from "@deepseek-ai/dsh-brand";
4
5
  import { EventSourceParserStream } from "eventsource-parser/stream";
5
6
  import { sign } from "@huaweidevtools/aishell-dsh-credentials-store/signer";
7
+ import { readStoredPair } from "@huaweidevtools/aishell-dsh-credentials-store/accounts";
6
8
 
9
+ //#region ../../node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.5/node_modules/@deepseek-ai/cosmokit/lib/index.js
10
+ /** Return true when a value is `null` or `undefined`. */
11
+ function isNullable(value) {
12
+ return value === null || value === void 0;
13
+ }
14
+ /** Return true for non-array object values. */
15
+ function isPlainObject(data) {
16
+ return data && typeof data === "object" && !Array.isArray(data);
17
+ }
18
+ /** Filter object entries and return a new object. */
19
+ function filterKeys(object, filter) {
20
+ return Object.fromEntries(Object.entries(object).filter(([key, value]) => filter(key, value)));
21
+ }
22
+ /** Map object values while preserving the original key set. */
23
+ function mapValues(object, transform) {
24
+ return Object.fromEntries(Object.entries(object).map(([key, value]) => [key, transform(value, key)]));
25
+ }
26
+ /** Pick selected keys from an object, optionally including `undefined` values. */
27
+ function pick(source, keys, forced) {
28
+ if (!keys) return { ...source };
29
+ const result = {};
30
+ for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
31
+ return result;
32
+ }
33
+ /** Shared config references used by schema validators and plugin runtimes. */
34
+ const write = Symbol.for("cosmokit.volatile.write");
35
+ function snapshot(value, ancestors = /* @__PURE__ */ new Set()) {
36
+ if (typeof value === "function") throw new TypeError("volatile config cannot contain functions");
37
+ if (value === null || typeof value !== "object") return value;
38
+ if (ancestors.has(value)) throw new TypeError("volatile config cannot contain cycles");
39
+ ancestors.add(value);
40
+ try {
41
+ if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshot(item, ancestors)));
42
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("volatile config objects must be plain objects or arrays");
43
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item, ancestors)])));
44
+ } finally {
45
+ ancestors.delete(value);
46
+ }
47
+ }
48
+ /**
49
+ * Create a detached reference containing an immutable copy of the supplied data.
50
+ * @param value - validated config data; class instances and functions are unsupported.
51
+ * @returns a reference whose value is updated only by its owning runtime.
52
+ */
53
+ function createVolatile(value) {
54
+ let current = snapshot(value);
55
+ return Object.freeze({
56
+ get: () => current,
57
+ [write]: (value$1) => {
58
+ current = value$1;
59
+ }
60
+ });
61
+ }
62
+ /**
63
+ * Identify references across ESM/CJS copies of the shared library.
64
+ * @param value - a parsed config value.
65
+ * @returns whether the value implements the shared reference protocol.
66
+ */
67
+ function isVolatile(value) {
68
+ return typeof value === "object" && value !== null && write in value;
69
+ }
70
+ /** Test values using `instanceof` with a `toStringTag` fallback. */
71
+ function is(type, value) {
72
+ if (arguments.length === 1) return (value$1) => is(type, value$1);
73
+ return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
74
+ }
75
+ function isArrayBufferLike(value) {
76
+ return is("ArrayBuffer", value) || is("SharedArrayBuffer", value);
77
+ }
78
+ function isArrayBufferSource(value) {
79
+ return isArrayBufferLike(value) || ArrayBuffer.isView(value);
80
+ }
81
+ /** Binary source detection and base64/hex conversion helpers. */
82
+ var Binary;
83
+ (function(Binary$1) {
84
+ Binary$1.is = isArrayBufferLike;
85
+ Binary$1.isSource = isArrayBufferSource;
86
+ function fromSource(source) {
87
+ if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
88
+ else return source;
89
+ }
90
+ Binary$1.fromSource = fromSource;
91
+ function toBase64(source) {
92
+ source = fromSource(source);
93
+ if (typeof Buffer !== "undefined") return Buffer.from(source).toString("base64");
94
+ let binary = "";
95
+ const bytes = new Uint8Array(source);
96
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
97
+ return btoa(binary);
98
+ }
99
+ Binary$1.toBase64 = toBase64;
100
+ function fromBase64(source) {
101
+ if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "base64"));
102
+ return Uint8Array.from(atob(source), (c) => c.charCodeAt(0));
103
+ }
104
+ Binary$1.fromBase64 = fromBase64;
105
+ function toHex(source) {
106
+ source = fromSource(source);
107
+ if (typeof Buffer !== "undefined") return Buffer.from(source).toString("hex");
108
+ return Array.from(new Uint8Array(source), (byte) => byte.toString(16).padStart(2, "0")).join("");
109
+ }
110
+ Binary$1.toHex = toHex;
111
+ function fromHex(source) {
112
+ if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "hex"));
113
+ const hex = source.length % 2 === 0 ? source : source.slice(0, source.length - 1);
114
+ const buffer = [];
115
+ for (let i = 0; i < hex.length; i += 2) buffer.push(parseInt(`${hex[i]}${hex[i + 1]}`, 16));
116
+ return Uint8Array.from(buffer).buffer;
117
+ }
118
+ Binary$1.fromHex = fromHex;
119
+ })(Binary || (Binary = {}));
120
+ /** Decode a base64 string into binary data. */
121
+ const base64ToArrayBuffer = Binary.fromBase64;
122
+ /** Encode binary data as base64. */
123
+ const arrayBufferToBase64 = Binary.toBase64;
124
+ /** Decode a hex string into binary data. */
125
+ const hexToArrayBuffer = Binary.fromHex;
126
+ /** Encode binary data as hex. */
127
+ const arrayBufferToHex = Binary.toHex;
128
+ /** Deep-clone common JavaScript values while preserving prototypes and cycles. */
129
+ function clone(source, refs = /* @__PURE__ */ new Map()) {
130
+ if (!source || typeof source !== "object") return source;
131
+ if (is("Date", source)) return new Date(source.valueOf());
132
+ if (is("RegExp", source)) return new RegExp(source.source, source.flags);
133
+ if (isArrayBufferLike(source)) return source.slice(0);
134
+ if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
135
+ const cached = refs.get(source);
136
+ if (cached) return cached;
137
+ if (Array.isArray(source)) {
138
+ const result$1 = [];
139
+ refs.set(source, result$1);
140
+ source.forEach((value, index) => {
141
+ result$1[index] = Reflect.apply(clone, null, [value, refs]);
142
+ });
143
+ return result$1;
144
+ }
145
+ const result = Object.create(Object.getPrototypeOf(source));
146
+ refs.set(source, result);
147
+ for (const key of Reflect.ownKeys(source)) {
148
+ const descriptor = { ...Reflect.getOwnPropertyDescriptor(source, key) };
149
+ if ("value" in descriptor) descriptor.value = Reflect.apply(clone, null, [descriptor.value, refs]);
150
+ Reflect.defineProperty(result, key, descriptor);
151
+ }
152
+ return result;
153
+ }
154
+ /**
155
+ * Compare values recursively, treating two volatile references as equal regardless of value.
156
+ * Strict comparison distinguishes null/undefined, treats opaque objects by identity,
157
+ * compares URLs by normalized href, treats array holes as undefined, and considers distinct cyclic structures unequal.
158
+ * @param a - first value.
159
+ * @param b - second value.
160
+ * @param strict - whether to require strict data equality outside volatile references.
161
+ * @returns whether the values compare equal.
162
+ */
163
+ function deepEqual(a, b, strict) {
164
+ const ancestors = /* @__PURE__ */ new Set();
165
+ function compare(a$1, b$1) {
166
+ if (a$1 === b$1) return true;
167
+ if (isVolatile(a$1) || isVolatile(b$1)) return isVolatile(a$1) && isVolatile(b$1);
168
+ if (!strict && isNullable(a$1) && isNullable(b$1)) return true;
169
+ if (typeof a$1 !== typeof b$1 || typeof a$1 !== "object" || !a$1 || !b$1) return false;
170
+ if (ancestors.has(a$1)) return false;
171
+ function check(test, then) {
172
+ return test(a$1) ? test(b$1) ? then(a$1, b$1) : false : test(b$1) ? false : void 0;
173
+ }
174
+ ancestors.add(a$1);
175
+ try {
176
+ return check(Array.isArray, (a$2, b$2) => {
177
+ if (a$2.length !== b$2.length) return false;
178
+ for (let index = 0; index < a$2.length; index++) if (!compare(a$2[index], b$2[index])) return false;
179
+ return true;
180
+ }) ?? check(is("Date"), (a$2, b$2) => a$2.valueOf() === b$2.valueOf()) ?? check(is("URL"), (a$2, b$2) => a$2.href === b$2.href) ?? check(is("RegExp"), (a$2, b$2) => a$2.source === b$2.source && a$2.flags === b$2.flags) ?? check(isArrayBufferLike, (a$2, b$2) => {
181
+ if (a$2.byteLength !== b$2.byteLength) return false;
182
+ const viewA = new Uint8Array(a$2);
183
+ const viewB = new Uint8Array(b$2);
184
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
185
+ return true;
186
+ }) ?? ((!strict || [a$1, b$1].every((value) => Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) && Object.keys({
187
+ ...a$1,
188
+ ...b$1
189
+ }).every((key) => compare(a$1[key], b$1[key])));
190
+ } finally {
191
+ ancestors.delete(a$1);
192
+ }
193
+ }
194
+ return compare(a, b);
195
+ }
196
+ /** Time constants plus parsing and formatting helpers. */
197
+ var Time;
198
+ (function(Time$1) {
199
+ Time$1.millisecond = 1;
200
+ Time$1.second = 1e3;
201
+ Time$1.minute = Time$1.second * 60;
202
+ Time$1.hour = Time$1.minute * 60;
203
+ Time$1.day = Time$1.hour * 24;
204
+ Time$1.week = Time$1.day * 7;
205
+ let timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();
206
+ function setTimezoneOffset(offset) {
207
+ timezoneOffset = offset;
208
+ }
209
+ Time$1.setTimezoneOffset = setTimezoneOffset;
210
+ function getTimezoneOffset() {
211
+ return timezoneOffset;
212
+ }
213
+ Time$1.getTimezoneOffset = getTimezoneOffset;
214
+ function getDateNumber(date = /* @__PURE__ */ new Date(), offset) {
215
+ if (typeof date === "number") date = new Date(date);
216
+ if (offset === void 0) offset = timezoneOffset;
217
+ return Math.floor((date.valueOf() / Time$1.minute - offset) / 1440);
218
+ }
219
+ Time$1.getDateNumber = getDateNumber;
220
+ function fromDateNumber(value, offset) {
221
+ const date = new Date(value * Time$1.day);
222
+ if (offset === void 0) offset = timezoneOffset;
223
+ return new Date(+date + offset * Time$1.minute);
224
+ }
225
+ Time$1.fromDateNumber = fromDateNumber;
226
+ const numeric = /\d+(?:\.\d+)?/.source;
227
+ const timeRegExp = new RegExp(`^${[
228
+ "w(?:eek(?:s)?)?",
229
+ "d(?:ay(?:s)?)?",
230
+ "h(?:our(?:s)?)?",
231
+ "m(?:in(?:ute)?(?:s)?)?",
232
+ "s(?:ec(?:ond)?(?:s)?)?"
233
+ ].map((unit) => `(${numeric}${unit})?`).join("")}$`);
234
+ function parseTime(source) {
235
+ const capture = timeRegExp.exec(source);
236
+ if (!capture) return 0;
237
+ return (parseFloat(capture[1]) * Time$1.week || 0) + (parseFloat(capture[2]) * Time$1.day || 0) + (parseFloat(capture[3]) * Time$1.hour || 0) + (parseFloat(capture[4]) * Time$1.minute || 0) + (parseFloat(capture[5]) * Time$1.second || 0);
238
+ }
239
+ Time$1.parseTime = parseTime;
240
+ function parseDate(date) {
241
+ const parsed = parseTime(date);
242
+ if (parsed) date = Date.now() + parsed;
243
+ else if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).toLocaleDateString()}-${date}`;
244
+ else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).getFullYear()}-${date}`;
245
+ return date ? new Date(date) : /* @__PURE__ */ new Date();
246
+ }
247
+ Time$1.parseDate = parseDate;
248
+ function format(ms) {
249
+ const abs = Math.abs(ms);
250
+ if (abs >= Time$1.day - Time$1.hour / 2) return Math.round(ms / Time$1.day) + "d";
251
+ else if (abs >= Time$1.hour - Time$1.minute / 2) return Math.round(ms / Time$1.hour) + "h";
252
+ else if (abs >= Time$1.minute - Time$1.second / 2) return Math.round(ms / Time$1.minute) + "m";
253
+ else if (abs >= Time$1.second) return Math.round(ms / Time$1.second) + "s";
254
+ return ms + "ms";
255
+ }
256
+ Time$1.format = format;
257
+ function toDigits(source, length = 2) {
258
+ return source.toString().padStart(length, "0");
259
+ }
260
+ Time$1.toDigits = toDigits;
261
+ function template(template$1, time = /* @__PURE__ */ new Date()) {
262
+ return template$1.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));
263
+ }
264
+ Time$1.template = template;
265
+ })(Time || (Time = {}));
266
+
267
+ //#endregion
268
+ //#region ../../node_modules/.pnpm/@deepseek-ai+schemastery@3.18.4/node_modules/@deepseek-ai/schemastery/lib/index.mjs
269
+ const kSchema = Symbol.for("schemastery");
270
+ const kValidationError = Symbol.for("ValidationError");
271
+ globalThis.__schemastery_index__ ??= 0;
272
+ globalThis.__schemastery_refs__ = void 0;
273
+ var ValidationError = class extends TypeError {
274
+ options;
275
+ name = "ValidationError";
276
+ constructor(message, options) {
277
+ let prefix = "$";
278
+ for (const segment of options.path || []) if (typeof segment === "string") prefix += "." + segment;
279
+ else if (typeof segment === "number") prefix += "[" + segment + "]";
280
+ else if (typeof segment === "symbol") prefix += `[Symbol(${segment.toString()})]`;
281
+ if (prefix.startsWith(".")) prefix = prefix.slice(1);
282
+ super((prefix === "$" ? "" : `${prefix} `) + message);
283
+ this.options = options;
284
+ }
285
+ static is(error) {
286
+ return !!error?.[kValidationError];
287
+ }
288
+ };
289
+ Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
290
+ const Schema = function(options) {
291
+ const schema = function(data, options$1 = {}) {
292
+ return Schema.resolve(data, schema, options$1)[0];
293
+ };
294
+ if (options.refs) {
295
+ const refs = mapValues(options.refs, (options$1) => new Schema(options$1));
296
+ const getRef = (uid) => refs[uid];
297
+ for (const key in refs) {
298
+ const options$1 = refs[key];
299
+ options$1.sKey = getRef(options$1.sKey);
300
+ options$1.inner = getRef(options$1.inner);
301
+ options$1.list = options$1.list && options$1.list.map(getRef);
302
+ options$1.dict = options$1.dict && mapValues(options$1.dict, getRef);
303
+ }
304
+ return refs[options.uid];
305
+ }
306
+ Object.assign(schema, options);
307
+ if (typeof schema.callback === "string") try {
308
+ schema.callback = new Function("return " + schema.callback)();
309
+ } catch {}
310
+ Object.defineProperty(schema, "uid", { value: globalThis.__schemastery_index__++ });
311
+ Object.setPrototypeOf(schema, Schema.prototype);
312
+ schema.meta ||= {};
313
+ schema.toString = schema.toString.bind(schema);
314
+ return schema;
315
+ };
316
+ Schema.prototype = Object.create(Function.prototype);
317
+ Schema.prototype[kSchema] = true;
318
+ Object.defineProperty(Schema.prototype, "~standard", { get() {
319
+ return {
320
+ version: 1,
321
+ vendor: "schemastery",
322
+ validate: (value) => {
323
+ try {
324
+ return { value: Schema.resolve(value, this, {})[0] };
325
+ } catch (error) {
326
+ if (ValidationError.is(error)) return { issues: [{
327
+ message: error.message,
328
+ path: error.options.path
329
+ }] };
330
+ throw error;
331
+ }
332
+ }
333
+ };
334
+ } });
335
+ Schema.ValidationError = ValidationError;
336
+ Schema.prototype.toJSON = function toJSON() {
337
+ if (globalThis.__schemastery_refs__) {
338
+ globalThis.__schemastery_refs__[this.uid] ??= JSON.parse(JSON.stringify({ ...this }));
339
+ return this.uid;
340
+ }
341
+ globalThis.__schemastery_refs__ = { [this.uid]: { ...this } };
342
+ globalThis.__schemastery_refs__[this.uid] = JSON.parse(JSON.stringify({ ...this }));
343
+ const result = {
344
+ uid: this.uid,
345
+ refs: globalThis.__schemastery_refs__
346
+ };
347
+ globalThis.__schemastery_refs__ = void 0;
348
+ return result;
349
+ };
350
+ Schema.prototype.set = function set(key, value) {
351
+ this.dict[key] = value;
352
+ return this;
353
+ };
354
+ Schema.prototype.push = function push(value) {
355
+ this.list.push(value);
356
+ return this;
357
+ };
358
+ function mergeDesc(original, messages) {
359
+ const result = typeof original === "string" ? { "": original } : { ...original };
360
+ for (const locale in messages) {
361
+ const value = messages[locale];
362
+ if (value?.$description || value?.$desc) result[locale] = value.$description || value.$desc;
363
+ else if (typeof value === "string") result[locale] = value;
364
+ }
365
+ return result;
366
+ }
367
+ function getInner(value) {
368
+ return value?.$value ?? value?.$inner;
369
+ }
370
+ function extractKeys(data) {
371
+ return filterKeys(data ?? {}, (key) => !key.startsWith("$"));
372
+ }
373
+ Schema.prototype.i18n = function i18n(messages) {
374
+ const schema = Schema(this);
375
+ const desc = mergeDesc(schema.meta.description, messages);
376
+ if (Object.keys(desc).length) schema.meta.description = desc;
377
+ if (schema.dict) schema.dict = mapValues(schema.dict, (inner, key) => {
378
+ return inner.i18n(mapValues(messages, (data) => getInner(data)?.[key] ?? data?.[key]));
379
+ });
380
+ if (schema.list) schema.list = schema.list.map((inner, index) => {
381
+ return inner.i18n(mapValues(messages, (data = {}) => {
382
+ if (Array.isArray(getInner(data))) return getInner(data)[index];
383
+ if (Array.isArray(data)) return data[index];
384
+ return extractKeys(data);
385
+ }));
386
+ });
387
+ if (schema.inner) schema.inner = schema.inner.i18n(mapValues(messages, (data) => {
388
+ if (getInner(data)) return getInner(data);
389
+ return extractKeys(data);
390
+ }));
391
+ if (schema.sKey) schema.sKey = schema.sKey.i18n(mapValues(messages, (data) => data?.$key));
392
+ return schema;
393
+ };
394
+ Schema.prototype.extra = function extra(key, value) {
395
+ const schema = Schema(this);
396
+ schema.meta = {
397
+ ...schema.meta,
398
+ [key]: value
399
+ };
400
+ return schema;
401
+ };
402
+ for (const key of [
403
+ "required",
404
+ "disabled",
405
+ "collapse",
406
+ "hidden",
407
+ "loose"
408
+ ]) Object.assign(Schema.prototype, { [key](value = true) {
409
+ const schema = Schema(this);
410
+ schema.meta = {
411
+ ...schema.meta,
412
+ [key]: value
413
+ };
414
+ return schema;
415
+ } });
416
+ Schema.prototype.deprecated = function deprecated() {
417
+ const schema = Schema(this);
418
+ schema.meta.badges ||= [];
419
+ schema.meta.badges.push({
420
+ text: "deprecated",
421
+ type: "danger"
422
+ });
423
+ return schema;
424
+ };
425
+ Schema.prototype.experimental = function experimental() {
426
+ const schema = Schema(this);
427
+ schema.meta.badges ||= [];
428
+ schema.meta.badges.push({
429
+ text: "experimental",
430
+ type: "warning"
431
+ });
432
+ return schema;
433
+ };
434
+ Schema.prototype.pattern = function pattern(regexp) {
435
+ const schema = Schema(this);
436
+ const pattern$1 = pick(regexp, ["source", "flags"]);
437
+ schema.meta = {
438
+ ...schema.meta,
439
+ pattern: pattern$1
440
+ };
441
+ return schema;
442
+ };
443
+ Schema.prototype.simplify = function simplify(value) {
444
+ if (isVolatile(value)) value = value.get();
445
+ if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
446
+ if (isNullable(value)) return value;
447
+ if (this.type === "object" || this.type === "dict") {
448
+ const result = {};
449
+ for (const key in value) {
450
+ const item = (this.type === "object" ? this.dict[key] : this.inner)?.simplify(value[key]);
451
+ if (this.type === "dict" || !isNullable(item)) result[key] = item;
452
+ }
453
+ if (deepEqual(result, this.meta.default, this.type === "dict")) return null;
454
+ return result;
455
+ } else if (this.type === "array" || this.type === "tuple") {
456
+ const result = [];
457
+ value.forEach((value$1, index) => {
458
+ const schema = this.type === "array" ? this.inner : this.list[index];
459
+ const item = schema ? schema.simplify(value$1) : value$1;
460
+ result.push(item);
461
+ });
462
+ return result;
463
+ } else if (this.type === "intersect") {
464
+ const result = {};
465
+ for (const item of this.list) Object.assign(result, item.simplify(value));
466
+ return result;
467
+ } else if (this.type === "union") for (const schema of this.list) try {
468
+ Schema.resolve(value, schema, {});
469
+ return schema.simplify(value);
470
+ } catch {}
471
+ return value;
472
+ };
473
+ Schema.prototype.toString = function toString(inline) {
474
+ return formatters[this.type]?.(this, inline) ?? `Schema<${this.type}>`;
475
+ };
476
+ Schema.prototype.role = function role(role$1, extra) {
477
+ const schema = Schema(this);
478
+ schema.meta = {
479
+ ...schema.meta,
480
+ role: role$1,
481
+ extra
482
+ };
483
+ return schema;
484
+ };
485
+ for (const key of [
486
+ "default",
487
+ "link",
488
+ "comment",
489
+ "description",
490
+ "max",
491
+ "min",
492
+ "step"
493
+ ]) Object.assign(Schema.prototype, { [key](value) {
494
+ const schema = Schema(this);
495
+ schema.meta = {
496
+ ...schema.meta,
497
+ [key]: value
498
+ };
499
+ return schema;
500
+ } });
501
+ Schema.prototype.volatile = function volatile() {
502
+ if (this.meta.volatile) throw new TypeError("volatile schema is already wrapped");
503
+ return this.extra("volatile", true);
504
+ };
505
+ const resolvers = {};
506
+ const checkedVolatile = Symbol("checked-volatile-schema");
507
+ function validateVolatileSchema(schema, path = [], blocked = false, seen = /* @__PURE__ */ new Map()) {
508
+ const states = seen.get(schema) ?? /* @__PURE__ */ new Set();
509
+ if (states.has(blocked)) return;
510
+ states.add(blocked);
511
+ seen.set(schema, states);
512
+ if (schema.meta?.volatile && blocked) throw new ValidationError("volatile fields require a fixed object path without an enclosing volatile field", { path });
513
+ const nested = blocked || !!schema.meta?.volatile;
514
+ if (schema.dict) for (const [key, child] of Object.entries(schema.dict)) validateVolatileSchema(child, [...path, key], nested, seen);
515
+ if (schema.sKey) validateVolatileSchema(schema.sKey, [...path, "<key>"], true, seen);
516
+ if (schema.inner && (schema.type !== "lazy" || schema.inner[kSchema])) validateVolatileSchema(schema.inner, [...path, "*"], true, seen);
517
+ if (schema.list) for (let index = 0; index < schema.list.length; index++) validateVolatileSchema(schema.list[index], [...path, String(index)], true, seen);
518
+ }
519
+ Schema.extend = function extend(type, resolve) {
520
+ resolvers[type] = resolve;
521
+ };
522
+ Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
523
+ if (!schema) return [data];
524
+ if (!options[checkedVolatile]) {
525
+ validateVolatileSchema(schema, options.path);
526
+ options = {
527
+ ...options,
528
+ [checkedVolatile]: true
529
+ };
530
+ }
531
+ if (schema.meta?.volatile) {
532
+ const inner = Schema(schema);
533
+ inner.meta = {
534
+ ...schema.meta,
535
+ volatile: false
536
+ };
537
+ const [value, adapted] = Schema.resolve(data, inner, options, strict);
538
+ try {
539
+ return [createVolatile(value), adapted];
540
+ } catch (error) {
541
+ throw new ValidationError(error instanceof Error ? error.message : String(error), options);
542
+ }
543
+ }
544
+ if (options.ignore?.(data, schema)) return [data];
545
+ if (isNullable(data) && schema.type !== "lazy") {
546
+ if (schema.meta.required) throw new ValidationError(`missing required value`, options);
547
+ let current = schema;
548
+ let fallback = schema.meta.default;
549
+ while (current?.type === "intersect" && isNullable(fallback)) {
550
+ current = current.list[0];
551
+ fallback = current?.meta.default;
552
+ }
553
+ if (isNullable(fallback)) return [data];
554
+ data = clone(fallback);
555
+ }
556
+ const callback = resolvers[schema.type];
557
+ if (!callback) throw new ValidationError(`unsupported type "${schema.type}"`, options);
558
+ try {
559
+ return callback(data, schema, options, strict);
560
+ } catch (error) {
561
+ if (!schema.meta.loose) throw error;
562
+ return [schema.meta.default];
563
+ }
564
+ };
565
+ Schema.from = function from(source) {
566
+ if (isNullable(source)) return Schema.any();
567
+ else if ([
568
+ "string",
569
+ "number",
570
+ "boolean"
571
+ ].includes(typeof source)) return Schema.const(source).required();
572
+ else if (source[kSchema]) return source;
573
+ else if (typeof source === "function") switch (source) {
574
+ case String: return Schema.string().required();
575
+ case Number: return Schema.number().required();
576
+ case Boolean: return Schema.boolean().required();
577
+ case Function: return Schema.function().required();
578
+ default: return Schema.is(source).required();
579
+ }
580
+ else throw new TypeError(`cannot infer schema from ${source}`);
581
+ };
582
+ Schema.lazy = function lazy(builder) {
583
+ const toJSON = () => {
584
+ if (!schema.inner[kSchema]) {
585
+ schema.inner = schema.builder();
586
+ schema.inner.meta = {
587
+ ...schema.meta,
588
+ ...schema.inner.meta
589
+ };
590
+ }
591
+ return schema.inner.toJSON();
592
+ };
593
+ const schema = new Schema({
594
+ type: "lazy",
595
+ builder,
596
+ inner: { toJSON }
597
+ });
598
+ return schema;
599
+ };
600
+ Schema.natural = function natural() {
601
+ return Schema.number().step(1).min(0);
602
+ };
603
+ Schema.percent = function percent() {
604
+ return Schema.number().step(.01).min(0).max(1).role("slider");
605
+ };
606
+ Schema.date = function date() {
607
+ return Schema.union([Schema.is(Date), Schema.transform(Schema.string().role("datetime"), (value, options) => {
608
+ const date$1 = new Date(value);
609
+ if (isNaN(+date$1)) throw new ValidationError(`invalid date "${value}"`, options);
610
+ return date$1;
611
+ }, true)]);
612
+ };
613
+ Schema.regExp = function regExp(flag = "") {
614
+ return Schema.union([Schema.is(RegExp), Schema.transform(Schema.string().role("regexp", { flag }), (value, options) => {
615
+ try {
616
+ return new RegExp(value, flag);
617
+ } catch (e) {
618
+ throw new ValidationError(e.message, options);
619
+ }
620
+ }, true)]);
621
+ };
622
+ Schema.arrayBuffer = function arrayBuffer(encoding) {
623
+ return Schema.union([
624
+ Schema.is(ArrayBuffer),
625
+ Schema.is(SharedArrayBuffer),
626
+ Schema.transform(Schema.any(), (value, options) => {
627
+ if (Binary.isSource(value)) return Binary.fromSource(value);
628
+ throw new ValidationError(`expected ArrayBufferSource but got ${value}`, options);
629
+ }, true),
630
+ ...encoding ? [Schema.transform(Schema.string(), (value, options) => {
631
+ try {
632
+ return encoding === "base64" ? Binary.fromBase64(value) : Binary.fromHex(value);
633
+ } catch (e) {
634
+ throw new ValidationError(e.message, options);
635
+ }
636
+ }, true)] : []
637
+ ]);
638
+ };
639
+ Schema.extend("lazy", (data, schema, options, strict) => {
640
+ if (!schema.inner[kSchema]) {
641
+ schema.inner = schema.builder();
642
+ schema.inner.meta = {
643
+ ...schema.meta,
644
+ ...schema.inner.meta
645
+ };
646
+ validateVolatileSchema(schema.inner, options.path, true);
647
+ }
648
+ return Schema.resolve(data, schema.inner, options, strict);
649
+ });
650
+ Schema.extend("any", (data) => {
651
+ return [data];
652
+ });
653
+ Schema.extend("never", (data, _, options) => {
654
+ throw new ValidationError(`expected nullable but got ${data}`, options);
655
+ });
656
+ Schema.extend("const", (data, { value }, options) => {
657
+ if (deepEqual(data, value)) return [value];
658
+ throw new ValidationError(`expected ${value} but got ${data}`, options);
659
+ });
660
+ function checkWithinRange(data, meta, description, options, skipMin = false) {
661
+ const { max = Infinity, min = -Infinity } = meta;
662
+ if (data > max) throw new ValidationError(`expected ${description} <= ${max} but got ${data}`, options);
663
+ if (data < min && !skipMin) throw new ValidationError(`expected ${description} >= ${min} but got ${data}`, options);
664
+ }
665
+ Schema.extend("string", (data, { meta }, options) => {
666
+ if (typeof data !== "string") throw new ValidationError(`expected string but got ${data}`, options);
667
+ if (meta.pattern) {
668
+ const regexp = new RegExp(meta.pattern.source, meta.pattern.flags);
669
+ if (!regexp.test(data)) throw new ValidationError(`expect string to match regexp ${regexp}`, options);
670
+ }
671
+ checkWithinRange(data.length, meta, "string length", options);
672
+ return [data];
673
+ });
674
+ function decimalShift(data, digits) {
675
+ const str = data.toString();
676
+ if (str.includes("e")) return data * Math.pow(10, digits);
677
+ const index = str.indexOf(".");
678
+ if (index === -1) return data * Math.pow(10, digits);
679
+ const frac = str.slice(index + 1);
680
+ const integer = str.slice(0, index);
681
+ if (frac.length <= digits) return +(integer + frac.padEnd(digits, "0"));
682
+ return +(integer + frac.slice(0, digits) + "." + frac.slice(digits));
683
+ }
684
+ function isMultipleOf(data, min, step) {
685
+ step = Math.abs(step);
686
+ if (!/^\d+\.\d+$/.test(step.toString())) return (data - min) % step === 0;
687
+ const index = step.toString().indexOf(".");
688
+ const digits = step.toString().slice(index + 1).length;
689
+ return Math.abs(decimalShift(data, digits) - decimalShift(min, digits)) % decimalShift(step, digits) === 0;
690
+ }
691
+ Schema.extend("number", (data, { meta }, options) => {
692
+ if (typeof data !== "number") throw new ValidationError(`expected number but got ${data}`, options);
693
+ checkWithinRange(data, meta, "number", options);
694
+ const { step } = meta;
695
+ if (step && !isMultipleOf(data, meta.min ?? 0, step)) throw new ValidationError(`expected number multiple of ${step} but got ${data}`, options);
696
+ return [data];
697
+ });
698
+ Schema.extend("boolean", (data, _, options) => {
699
+ if (typeof data === "boolean") return [data];
700
+ throw new ValidationError(`expected boolean but got ${data}`, options);
701
+ });
702
+ Schema.extend("bitset", (data, { bits, meta }, options) => {
703
+ let value = 0, keys = [];
704
+ if (typeof data === "number") {
705
+ value = data;
706
+ for (const key in bits) if (data & bits[key]) keys.push(key);
707
+ } else if (Array.isArray(data)) {
708
+ keys = data;
709
+ for (const key of keys) {
710
+ if (typeof key !== "string") throw new ValidationError(`expected string but got ${key}`, options);
711
+ if (key in bits) value |= bits[key];
712
+ }
713
+ } else throw new ValidationError(`expected number or array but got ${data}`, options);
714
+ if (value === meta.default) return [value];
715
+ return [value, keys];
716
+ });
717
+ Schema.extend("function", (data, _, options) => {
718
+ if (typeof data === "function") return [data];
719
+ throw new ValidationError(`expected function but got ${data}`, options);
720
+ });
721
+ Schema.extend("is", (data, { constructor }, options) => {
722
+ if (typeof constructor === "function") {
723
+ if (data instanceof constructor) return [data];
724
+ throw new ValidationError(`expected ${constructor.name} but got ${data}`, options);
725
+ } else {
726
+ if (isNullable(data)) throw new ValidationError(`expected ${constructor} but got ${data}`, options);
727
+ let prototype = Object.getPrototypeOf(data);
728
+ while (prototype) {
729
+ if (prototype.constructor?.name === constructor) return [data];
730
+ prototype = Object.getPrototypeOf(prototype);
731
+ }
732
+ throw new ValidationError(`expected ${constructor} but got ${data}`, options);
733
+ }
734
+ });
735
+ function property(data, key, schema, options) {
736
+ try {
737
+ const [value, adapted] = Schema.resolve(data[key], schema, {
738
+ ...options,
739
+ path: [...options.path || [], key]
740
+ });
741
+ if (adapted !== void 0) data[key] = adapted;
742
+ return value;
743
+ } catch (e) {
744
+ if (!options?.autofix) throw e;
745
+ delete data[key];
746
+ return schema.meta.volatile ? createVolatile(schema.meta.default) : schema.meta.default;
747
+ }
748
+ }
749
+ Schema.extend("array", (data, { inner, meta }, options) => {
750
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
751
+ checkWithinRange(data.length, meta, "array length", options, !isNullable(inner.meta.default));
752
+ return [data.map((_, index) => property(data, index, inner, options))];
753
+ });
754
+ Schema.extend("dict", (data, { inner, sKey }, options, strict) => {
755
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
756
+ const result = {};
757
+ for (const key in data) {
758
+ let rKey;
759
+ try {
760
+ rKey = Schema.resolve(key, sKey, options)[0];
761
+ } catch (error) {
762
+ if (strict) continue;
763
+ throw error;
764
+ }
765
+ result[rKey] = property(data, key, inner, options);
766
+ data[rKey] = data[key];
767
+ if (key !== rKey) delete data[key];
768
+ }
769
+ return [result];
770
+ });
771
+ Schema.extend("tuple", (data, { list }, options, strict) => {
772
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
773
+ const result = list.map((inner, index) => property(data, index, inner, options));
774
+ if (strict) return [result];
775
+ result.push(...data.slice(list.length));
776
+ return [result];
777
+ });
778
+ function merge(result, data) {
779
+ for (const key in data) {
780
+ if (key in result) continue;
781
+ result[key] = data[key];
782
+ }
783
+ }
784
+ Schema.extend("object", (data, { dict }, options, strict) => {
785
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
786
+ const result = {};
787
+ for (const key in dict) {
788
+ const value = property(data, key, dict[key], options);
789
+ if (!isNullable(value) || key in data) result[key] = value;
790
+ }
791
+ if (!strict) merge(result, data);
792
+ return [result];
793
+ });
794
+ Schema.extend("union", (data, { list, toString }, options, strict) => {
795
+ const messages = [];
796
+ for (const inner of list) try {
797
+ return Schema.resolve(data, inner, options, strict);
798
+ } catch (error) {
799
+ messages.push(error);
800
+ }
801
+ throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
802
+ });
803
+ Schema.extend("intersect", (data, { list, toString }, options, strict) => {
804
+ if (!list.length) return [data];
805
+ let result;
806
+ for (const inner of list) {
807
+ const value = Schema.resolve(data, inner, options, true)[0];
808
+ if (isNullable(value)) continue;
809
+ if (isNullable(result)) result = value;
810
+ else if (typeof result !== typeof value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
811
+ else if (typeof value === "object") merge(result ??= {}, value);
812
+ else if (result !== value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
813
+ }
814
+ if (!strict && isPlainObject(data)) merge(result, data);
815
+ return [result];
816
+ });
817
+ Schema.extend("transform", (data, { inner, callback, preserve }, options) => {
818
+ const [result, adapted = data] = Schema.resolve(data, inner, options, true);
819
+ if (preserve) return [callback(result)];
820
+ else return [callback(result), callback(adapted)];
821
+ });
822
+ const formatters = {};
823
+ function defineMethod(name$1, keys, format) {
824
+ formatters[name$1] = format;
825
+ Object.assign(Schema, { [name$1](...args) {
826
+ const schema = new Schema({ type: name$1 });
827
+ keys.forEach((key, index) => {
828
+ switch (key) {
829
+ case "sKey":
830
+ schema.sKey = args[index] ?? Schema.string();
831
+ break;
832
+ case "inner":
833
+ schema.inner = Schema.from(args[index]);
834
+ break;
835
+ case "list":
836
+ schema.list = args[index].map(Schema.from);
837
+ break;
838
+ case "dict":
839
+ schema.dict = mapValues(args[index], Schema.from);
840
+ break;
841
+ case "bits":
842
+ schema.bits = {};
843
+ for (const key$1 in args[index]) {
844
+ if (typeof args[index][key$1] !== "number") continue;
845
+ schema.bits[key$1] = args[index][key$1];
846
+ }
847
+ break;
848
+ case "callback": {
849
+ const callback = schema.callback = args[index];
850
+ callback["toJSON"] ||= () => callback.toString();
851
+ break;
852
+ }
853
+ case "constructor": {
854
+ const constructor = schema.constructor = args[index];
855
+ if (typeof constructor === "function") constructor["toJSON"] ||= () => constructor["name"];
856
+ break;
857
+ }
858
+ default: schema[key] = args[index];
859
+ }
860
+ });
861
+ if (name$1 === "object" || name$1 === "dict") schema.meta.default = {};
862
+ else if (name$1 === "array" || name$1 === "tuple") schema.meta.default = [];
863
+ else if (name$1 === "bitset") schema.meta.default = 0;
864
+ return schema;
865
+ } });
866
+ }
867
+ defineMethod("is", ["constructor"], ({ constructor }) => {
868
+ if (typeof constructor === "function") return constructor.name;
869
+ else return constructor;
870
+ });
871
+ defineMethod("any", [], () => "any");
872
+ defineMethod("never", [], () => "never");
873
+ defineMethod("const", ["value"], ({ value }) => typeof value === "string" ? JSON.stringify(value) : value);
874
+ defineMethod("string", [], () => "string");
875
+ defineMethod("number", [], () => "number");
876
+ defineMethod("boolean", [], () => "boolean");
877
+ defineMethod("bitset", ["bits"], () => "bitset");
878
+ defineMethod("function", [], () => "function");
879
+ defineMethod("array", ["inner"], ({ inner }) => `${inner.toString(true)}[]`);
880
+ defineMethod("dict", ["inner", "sKey"], ({ inner, sKey }) => `{ [key: ${sKey.toString()}]: ${inner.toString()} }`);
881
+ defineMethod("tuple", ["list"], ({ list }) => `[${list.map((inner) => inner.toString()).join(", ")}]`);
882
+ defineMethod("object", ["dict"], ({ dict }) => {
883
+ if (Object.keys(dict).length === 0) return "{}";
884
+ return `{ ${Object.entries(dict).map(([key, inner]) => {
885
+ return `${key}${inner.meta.required ? "" : "?"}: ${inner.toString()}`;
886
+ }).join(", ")} }`;
887
+ });
888
+ defineMethod("union", ["list"], ({ list }, inline) => {
889
+ const result = list.map(({ toString: format }) => format()).join(" | ");
890
+ return inline ? `(${result})` : result;
891
+ });
892
+ defineMethod("intersect", ["list"], ({ list }) => {
893
+ return `${list.map((inner) => inner.toString(true)).join(" & ")}`;
894
+ });
895
+ defineMethod("transform", [
896
+ "inner",
897
+ "callback",
898
+ "preserve"
899
+ ], ({ inner }, isInner) => inner.toString(isInner));
900
+
901
+ //#endregion
902
+ //#region src/images.ts
903
+ /**
904
+ * True when any message carries an image block — the guard that decides
905
+ * whether this adapter needs the attachment service at all.
906
+ *
907
+ * @param messages - complete request history.
908
+ * @returns whether any message content holds an image.
909
+ */
910
+ function historyHasImage(messages) {
911
+ return messages.some((message) => contentHasImage(message.content));
912
+ }
913
+ /**
914
+ * Reject image blocks in roles the wire format cannot carry them in.
915
+ *
916
+ * A user or tool-result message maps onto content parts, so an image there is
917
+ * representable. Every other role serializes to a plain string (assistant
918
+ * text plus tool calls, system/developer prose), where an image would be
919
+ * silently dropped — the exact failure this module exists to remove, so it
920
+ * fails loud instead.
921
+ *
922
+ * @param messages - complete request history.
923
+ * @throws LlmError `UNSUPPORTED_CONTENT` naming the offending role.
924
+ */
925
+ function assertImageRoles(messages) {
926
+ for (const message of messages) {
927
+ if (message.role === "user" || message.role === "tool") continue;
928
+ if (contentHasImage(message.content)) throw new LlmError(`aishell-llm cannot represent an image in a ${message.role} message`, "UNSUPPORTED_CONTENT");
929
+ }
930
+ }
931
+ /**
932
+ * Resolve one request version per retained image, keyed by durable attachment
933
+ * id so a reference shared by several occurrences is read once.
934
+ *
935
+ * Occurrences already marked `offloaded` are skipped: their bytes are not sent
936
+ * (serialization replaces them with placeholder text), so reading them would
937
+ * be work whose result nothing consumes.
938
+ *
939
+ * @param messages - complete request history.
940
+ * @param attachments - mounted attachment service.
941
+ * @param budget - projection policy for this route.
942
+ * @param signal - request cancellation, honoured by the attachment read.
943
+ * @returns request versions by durable attachment id.
944
+ */
945
+ async function prepareRequestImages(messages, attachments, budget, signal) {
946
+ const refs = new Map();
947
+ for (const message of messages) for (const block of message.content) {
948
+ if (block.type !== "image" || block.offloaded === true) continue;
949
+ refs.set(block.attachment.attachmentId, block.attachment);
950
+ }
951
+ const ordered = [...refs.values()];
952
+ const versions = await Promise.all(ordered.map((ref) => attachments.readImageRequest(ref, {
953
+ ...requestImageDimensions(ref.width, ref.height, budget.maxPixels),
954
+ maxBytes: budget.maxBytes
955
+ }, signal)));
956
+ return new Map(ordered.map((ref, index) => [ref.attachmentId, versions[index]]));
957
+ }
958
+ /**
959
+ * Request version for one retained occurrence, or a loud failure.
960
+ *
961
+ * Every image block reaching serialization must have been prepared, so a
962
+ * miss is a bug in this adapter rather than bad input — but the alternative
963
+ * (letting the lookup produce `undefined` and reading `.bytes` off it) turns
964
+ * it into a `TypeError` from deep inside the request body, so it is named.
965
+ *
966
+ * @param images - prepared request versions.
967
+ * @param ref - durable reference of one retained occurrence.
968
+ * @returns the request version carrying that image's bytes.
969
+ * @throws LlmError `INVALID_REQUEST` when the reference was never prepared.
970
+ */
971
+ function requireRequestImage(images, ref) {
972
+ const version = images.get(ref.attachmentId);
973
+ if (version === void 0) throw new LlmError(`aishell-llm request image ${String(ref.attachmentId)} was not prepared`, "INVALID_REQUEST");
974
+ return version;
975
+ }
976
+ /**
977
+ * Replace every offloaded occurrence with its placeholder text.
978
+ *
979
+ * The durable `offloaded` mark is a surface fact shared by all routes; only
980
+ * the placeholder wording is route-owned. Request assembly does not do this
981
+ * for adapters, so an adapter that skips it would send an image whose bytes
982
+ * it deliberately never loaded.
983
+ *
984
+ * @param messages - complete request history.
985
+ * @param resolveAccess - execution-world path resolver, when one is available.
986
+ * @returns the history with offloaded occurrences projected to text.
987
+ */
988
+ function projectOffloaded(messages, resolveAccess) {
989
+ return projectOffloadedImages(messages, (ref) => offloadedImageText(ref, resolveAccess?.(ref)));
990
+ }
991
+ /**
992
+ * Fail the request when its retained images exceed the route's byte budget.
993
+ *
994
+ * This adapter does not offload on its own: the durable decision belongs to
995
+ * the surface, and `dsh-compaction-image-offload` (mounted by `dsh-base`) is
996
+ * the component that records it and retries the step. Throwing the canonical
997
+ * code with the required count is what hands it that decision.
998
+ *
999
+ * @param messages - complete request history, carrying the surface's offload marks.
1000
+ * @param images - prepared request versions.
1001
+ * @param maxBytes - accumulated represented base64 bytes this route accepts.
1002
+ * @throws LlmError `IMAGE_OFFLOAD_REQUIRED` carrying how many more occurrences must be offloaded.
1003
+ */
1004
+ function assertRequestImageBudget(messages, images, maxBytes) {
1005
+ const offloadImages = requiredImageOffload(messages, {
1006
+ representation: "base64",
1007
+ maxBytes
1008
+ }, (block) => requireRequestImage(images, block.attachment).bytes);
1009
+ if (offloadImages > 0) throw new LlmError(`aishell-llm request images exceed the ${maxBytes}-byte base64 bound; ${offloadImages} more oldest occurrence(s) must be offloaded.`, IMAGE_OFFLOAD_REQUIRED_CODE, { offloadImages });
1010
+ }
1011
+
1012
+ //#endregion
7
1013
  //#region src/serialize.ts
8
- /** Build the POST body for `/chat/completions`. */
9
- function serializeRequest(options) {
1014
+ /**
1015
+ * Build the POST body for `/chat/completions`.
1016
+ *
1017
+ * @param options - provider-neutral request.
1018
+ * @param images - prepared request versions, required only when the history carries a retained image.
1019
+ * @param resolveAccess - execution-world path resolver for image handle text, when a provider exposes one.
1020
+ * @returns the OpenAI-compatible request body.
1021
+ */
1022
+ function serializeRequest(options, images, resolveAccess) {
10
1023
  const messages = [];
11
- for (const message of options.messages) if (message.role === "user") {
12
- const textBlocks = message.content.filter((block) => block.type === "text" || block.type === "reasoning");
1024
+ if (options.system !== void 0) messages.push({
1025
+ role: "system",
1026
+ content: options.system
1027
+ });
1028
+ let pendingToolImages = [];
1029
+ for (const [index, message] of options.messages.entries()) if (message.role === "user") messages.push({
1030
+ role: "user",
1031
+ content: contentFor(message.content, images, resolveAccess) ?? blocksToText(message.content)
1032
+ });
1033
+ else if (message.role === "tool") {
1034
+ const toolText = blocksToText(message.content);
1035
+ const hasImages = message.content.some((block) => block.type === "image");
13
1036
  messages.push({
14
- role: "user",
15
- content: textBlocks.length > 0 ? blocksToText(textBlocks) : ""
1037
+ role: "tool",
1038
+ tool_call_id: String(message.toolCallId),
1039
+ content: toolText.length > 0 || !hasImages ? toolText : TOOL_IMAGES_ONLY
16
1040
  });
17
- } else if (message.role === "tool") messages.push({
18
- role: "tool",
19
- tool_call_id: String(message.toolCallId),
20
- content: blocksToText(message.content)
21
- });
22
- else if (message.role === "developer") {
1041
+ if (hasImages) pendingToolImages.push(message.content);
1042
+ if (isLastToolOfRun(options.messages, index)) {
1043
+ const hoisted = hoistedToolImages(pendingToolImages, images, resolveAccess);
1044
+ pendingToolImages = [];
1045
+ if (hoisted !== void 0) messages.push({
1046
+ role: "user",
1047
+ content: hoisted
1048
+ });
1049
+ }
1050
+ } else if (message.role === "developer") {
23
1051
  const text = blocksToText(message.content);
24
1052
  if (text) messages.push({
25
1053
  role: "system",
@@ -49,6 +1077,122 @@ function serializeRequest(options) {
49
1077
  ...options.stop !== void 0 ? { stop: options.stop } : {}
50
1078
  };
51
1079
  }
1080
+ /** Intro line naming what the hoisted parts are, matching the ecosystem's wording. */
1081
+ const TOOL_IMAGES_INTRO = "Attached image(s) from tool result:";
1082
+ /** Stand-in for a tool result whose only content was an image, which cannot ride the tool message itself. */
1083
+ const TOOL_IMAGES_ONLY = "(see attached image)";
1084
+ /**
1085
+ * Whether the tool-role message at `index` is the last of its consecutive run.
1086
+ *
1087
+ * Tool results arrive as a run, one per call the assistant issued in a single
1088
+ * turn, and they must stay adjacent to that assistant turn on the wire. The
1089
+ * hoisted user turn therefore waits for the run's end rather than following
1090
+ * whichever result happened to carry the image.
1091
+ *
1092
+ * @param messages - the complete request history.
1093
+ * @param index - position of the tool-role message being serialized.
1094
+ * @returns whether no further tool-role message follows.
1095
+ */
1096
+ function isLastToolOfRun(messages, index) {
1097
+ return messages[index + 1]?.role !== "tool";
1098
+ }
1099
+ /**
1100
+ * Parts for one tool-result run's images, hoisted into a user turn.
1101
+ *
1102
+ * An OpenAI tool message carries text only, so an image a tool returned has
1103
+ * nowhere to go in its own message; sending it as a following `user` turn is
1104
+ * how the one OpenAI-path serializer in the ecosystem handles the same
1105
+ * situation. The tool messages keep their text (or a placeholder), so the
1106
+ * model is not left with an empty result to explain away.
1107
+ *
1108
+ * @param run - content blocks of every image-bearing tool result in one run.
1109
+ * @param images - prepared request versions.
1110
+ * @param resolveAccess - execution-world path resolver, when available.
1111
+ * @returns the user-turn part list, or `undefined` when the run carried no image.
1112
+ */
1113
+ function hoistedToolImages(run, images, resolveAccess) {
1114
+ if (run.length === 0) return void 0;
1115
+ if (images === void 0) throw new LlmError("aishell-llm request images were not prepared", "INVALID_REQUEST");
1116
+ const parts = [{
1117
+ type: "text",
1118
+ text: TOOL_IMAGES_INTRO
1119
+ }];
1120
+ for (const blocks of run) for (const block of blocks) {
1121
+ if (block.type !== "image") continue;
1122
+ const version = requireRequestImage(images, block.attachment);
1123
+ parts.push({
1124
+ type: "text",
1125
+ text: requestImageHandleText(block.attachment, version, resolveAccess?.(block.attachment))
1126
+ });
1127
+ parts.push({
1128
+ type: "image_url",
1129
+ image_url: { url: dataUrl(version) }
1130
+ });
1131
+ }
1132
+ return parts;
1133
+ }
1134
+ /**
1135
+ * Content for one user message: a parts list when it carries an image,
1136
+ * otherwise `undefined` so the caller falls back to plain text.
1137
+ *
1138
+ * Each image is preceded by a text part carrying its stable model-facing
1139
+ * handle, which names the image and the dimensions it was projected to — so a
1140
+ * model that cannot use the bytes can still refer to the occurrence, and one
1141
+ * that can reads what was actually sent rather than what was uploaded.
1142
+ *
1143
+ * @param blocks - content blocks of one user message.
1144
+ * @param images - prepared request versions.
1145
+ * @param resolveAccess - execution-world path resolver, when available.
1146
+ * @returns the part list, or `undefined` when the message has no image.
1147
+ */
1148
+ function contentFor(blocks, images, resolveAccess) {
1149
+ if (!blocks.some((block) => block.type === "image")) return void 0;
1150
+ if (images === void 0) throw new LlmError("aishell-llm request images were not prepared", "INVALID_REQUEST");
1151
+ const parts = [];
1152
+ for (const block of blocks) {
1153
+ if (block.type === "image") {
1154
+ const version = requireRequestImage(images, block.attachment);
1155
+ parts.push({
1156
+ type: "text",
1157
+ text: requestImageHandleText(block.attachment, version, resolveAccess?.(block.attachment))
1158
+ });
1159
+ parts.push({
1160
+ type: "image_url",
1161
+ image_url: { url: dataUrl(version) }
1162
+ });
1163
+ continue;
1164
+ }
1165
+ if (block.type === "text" || block.type === "reasoning") {
1166
+ if (block.text.length > 0) parts.push({
1167
+ type: "text",
1168
+ text: block.text
1169
+ });
1170
+ }
1171
+ }
1172
+ return parts;
1173
+ }
1174
+ /**
1175
+ * Inline data URL for one prepared image.
1176
+ *
1177
+ * Media type comes from the request *version*, not the source reference: the
1178
+ * attachment service may re-encode during projection (that is what the byte
1179
+ * target is for), so the version is the only description of the bytes actually
1180
+ * being sent.
1181
+ *
1182
+ * Encoded through `btoa` over the byte string rather than `Buffer`: this
1183
+ * package compiles host and browser halves in one project under `types: []`
1184
+ * with the DOM lib, so the Node global is neither declared nor reachable here.
1185
+ * `btoa` is a global in both Node 22+ and every browser, which is why it is
1186
+ * the one that typechecks in both halves.
1187
+ *
1188
+ * @param version - prepared request version.
1189
+ * @returns a base64 data URL.
1190
+ */
1191
+ function dataUrl(version) {
1192
+ let binary = "";
1193
+ for (const byte of version.data) binary += String.fromCharCode(byte);
1194
+ return `data:${version.mediaType};base64,${btoa(binary)}`;
1195
+ }
52
1196
  /** Type guard for tool-call blocks. */
53
1197
  function isToolCallBlock(block) {
54
1198
  return block.type === "tool-call";
@@ -81,7 +1225,8 @@ function serializeTool(tool) {
81
1225
  //#region src/sse.ts
82
1226
  const DONE = "[DONE]";
83
1227
  async function* parseSse(stream) {
84
- const events = stream.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream());
1228
+ const decoder = new TextDecoderStream();
1229
+ const events = stream.pipeThrough(decoder).pipeThrough(new EventSourceParserStream());
85
1230
  for await (const { data } of events) {
86
1231
  yield data;
87
1232
  if (data === DONE) return;
@@ -260,12 +1405,50 @@ async function* translate(payloads) {
260
1405
  };
261
1406
  }
262
1407
 
1408
+ //#endregion
1409
+ //#region src/provider-name.ts
1410
+ /**
1411
+ * The aishell provider's display name, shared by both plugin halves.
1412
+ *
1413
+ * The Host half registers it on the configurable-provider directory (the
1414
+ * Models page renders it as the provider row's name), and the browser half's
1415
+ * row shadow finds that row by matching the rendered name against this
1416
+ * literal (see `client/provider-row-shadow.ts`). It must stay a single
1417
+ * literal string — the shadow compares `textContent` with it.
1418
+ *
1419
+ * It is also the name `providerInfo()` reports, so the picker and the Models
1420
+ * row call the route the same thing: this package's whole surface is one
1421
+ * product, and qualifying it by cloud ("AI Shell (Huawei Cloud)") added a
1422
+ * distinction no reader needed — there is no second AI Shell to tell it
1423
+ * apart from.
1424
+ */
1425
+ const PROVIDER_DISPLAY_NAME = "AI Shell";
1426
+
263
1427
  //#endregion
264
1428
  //#region src/adapter.ts
265
- /** Credential store account names for Huawei Cloud credentials. */
266
- const AK_ACCOUNT = "HW_ACCESS_KEY";
267
- const SK_ACCOUNT = "HW_SECRET_KEY";
268
- const TOKEN_ACCOUNT = "HW_SECURITY_TOKEN";
1429
+ /**
1430
+ * Whether a gateway error body reports an id the gateway does not carry.
1431
+ *
1432
+ * Both fields are accepted because the gateway emits them from one definition
1433
+ * and either proves the same fact; a proxy that strips one should not disable
1434
+ * the diagnosis. A body that is not the gateway's JSON is not a match.
1435
+ *
1436
+ * @param text - the error response body, as received.
1437
+ * @returns whether this is the "model not configured on this gateway" answer.
1438
+ */
1439
+ function isModelNotFound(text) {
1440
+ let body;
1441
+ try {
1442
+ body = JSON.parse(text);
1443
+ } catch {
1444
+ return false;
1445
+ }
1446
+ if (typeof body !== "object" || body === null) return false;
1447
+ const envelope = body;
1448
+ if (envelope.error_code === "AISHELL.1004") return true;
1449
+ const inner = envelope.error;
1450
+ return typeof inner === "object" && inner !== null && inner.code === "model_not_found";
1451
+ }
269
1452
  /**
270
1453
  * Map the gateway's `modalities` array (e.g. `["text","image"]`) to DSH's
271
1454
  * `ModelModality` vocabulary. DSH currently defines only `'text' | 'image'`;
@@ -281,11 +1464,16 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
281
1464
  constructor(opts) {
282
1465
  super();
283
1466
  this.opts = opts;
1467
+ this.imageAccess = (ref) => {
1468
+ const attachments = this.opts.resolveAttachments?.();
1469
+ if (attachments === void 0) return void 0;
1470
+ return resolveImageAttachmentAccess(attachments, (hostPath) => this.opts.resolveHostPath?.(hostPath), ref);
1471
+ };
284
1472
  }
285
1473
  providerInfo(provider) {
286
1474
  return {
287
1475
  id: provider,
288
- name: "AI Shell"
1476
+ name: PROVIDER_DISPLAY_NAME
289
1477
  };
290
1478
  }
291
1479
  /**
@@ -297,8 +1485,7 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
297
1485
  async fetchWireModels() {
298
1486
  const now = Date.now();
299
1487
  if (this.modelsCache !== void 0 && this.modelsCache.expires > now) return this.modelsCache.promise;
300
- const creds = this.readCredentials();
301
- if (!creds?.ak) return [];
1488
+ if (!this.hasCredentials()) return [];
302
1489
  const promise = (async () => {
303
1490
  const response = await this.signedFetch("/models", "GET", void 0, void 0);
304
1491
  if (!response.ok) return [];
@@ -316,7 +1503,7 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
316
1503
  return wireModels.map((m) => ({
317
1504
  provider,
318
1505
  id: m.id,
319
- name: m.id,
1506
+ name: m.name ?? m.id,
320
1507
  ...mapModalities(m.modalities)
321
1508
  }));
322
1509
  }
@@ -326,7 +1513,7 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
326
1513
  const base = {
327
1514
  provider,
328
1515
  id: model,
329
- name: model,
1516
+ name: wire?.name ?? model,
330
1517
  ...mapModalities(wire?.modalities)
331
1518
  };
332
1519
  if (wire === void 0) return base;
@@ -343,22 +1530,40 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
343
1530
  })) } } : {}
344
1531
  };
345
1532
  }
346
- /** Read AK/SK (and optional SecurityToken) from the credential store. */
1533
+ /**
1534
+ * Whether a usable AK/SK pair is stored.
1535
+ *
1536
+ * The one predicate behind every "can this route do anything" question:
1537
+ * `fetchWireModels` returns an empty catalog without it, and the Models-page
1538
+ * card reports readiness from it. Kept in one place so the catalog and the
1539
+ * card cannot disagree about whether this route is usable.
1540
+ *
1541
+ * @returns whether both halves of the signing credential are present.
1542
+ */
1543
+ hasCredentials() {
1544
+ return this.readCredentials() !== void 0;
1545
+ }
1546
+ /**
1547
+ * Read the AK/SK pair from the credential store.
1548
+ *
1549
+ * Delegates to the shared reader: this adapter, `aishell-e2b`,
1550
+ * `aishell-credentials` and `aishell-deploy` all read the same record, and a
1551
+ * local copy of that rule is how two of them end up disagreeing about what
1552
+ * "credentials are configured" means.
1553
+ *
1554
+ * Read per request, never captured — that is what makes a rotation in the
1555
+ * credential form apply to the very next call.
1556
+ */
347
1557
  readCredentials() {
348
- const ak = this.opts.keyring.get(AK_ACCOUNT);
349
- const sk = this.opts.keyring.get(SK_ACCOUNT);
350
- if (!ak || !sk) return void 0;
351
- const securityToken = this.opts.keyring.get(TOKEN_ACCOUNT);
352
- return {
353
- ak,
354
- sk,
355
- ...securityToken ? { securityToken } : {}
356
- };
1558
+ return readStoredPair(this.opts.keyring);
357
1559
  }
358
- /** Read credentials or throw MISSING_CREDENTIAL — every signing code path goes through here. */
1560
+ /**
1561
+ * Read credentials or throw `MISSING_CREDENTIAL` — every signing code path
1562
+ * goes through here.
1563
+ */
359
1564
  requireCredentials() {
360
1565
  const creds = this.readCredentials();
361
- if (!creds) throw new LlmError("aishell-llm: HW_ACCESS_KEY and HW_SECRET_KEY not found in credential store (service \"aishell-dsh\"); store them first", "MISSING_CREDENTIAL");
1566
+ if (!creds) throw new LlmError("aishell-llm: not authenticated with Huawei Cloud IAM", "MISSING_CREDENTIAL");
362
1567
  return creds;
363
1568
  }
364
1569
  /**
@@ -367,9 +1572,9 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
367
1572
  */
368
1573
  async signedFetch(path, method, body, signal) {
369
1574
  const creds = this.requireCredentials();
370
- const url = new URL(this.opts.baseUrl);
371
- const headers = sign(method, url.host, path, {}, body ?? "", creds);
372
- return fetch(`${this.opts.baseUrl}${path}`, {
1575
+ const requestUrl = new URL(`${this.opts.resolveBaseUrl()}${path}`);
1576
+ const headers = sign(method, requestUrl.host, requestUrl.pathname, {}, body ?? "", creds);
1577
+ return fetch(requestUrl, {
373
1578
  method,
374
1579
  headers: {
375
1580
  host: headers.host,
@@ -382,18 +1587,66 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
382
1587
  ...signal ? { signal } : {}
383
1588
  });
384
1589
  }
1590
+ /**
1591
+ * Diagnose an "unsupported model" rejection and append what to do about it.
1592
+ *
1593
+ * The gateway answers a model id it does not carry with `AISHELL.1004`
1594
+ * (`model_not_found`, HTTP 404 — see {@link isModelNotFound}). The default
1595
+ * selection is a pinned literal (it must be readable before any credential or
1596
+ * network call could enumerate the catalog), so the day the catalog drops
1597
+ * that id, every fresh session fails here. Rather than leave the user to
1598
+ * reverse-engineer the supported set, the failure itself asks the gateway
1599
+ * (the catalog cache usually makes this a second signed GET already in
1600
+ * flight) and names the models it currently accepts.
1601
+ *
1602
+ * Returns '' for any other failure, so the message stays untouched there.
1603
+ */
1604
+ async unsupportedModelHint(text, options) {
1605
+ if (!isModelNotFound(text)) return "";
1606
+ const ids = (await this.fetchWireModels().catch(() => [])).map((m) => m.id).filter((id) => id.length > 0);
1607
+ if (ids.length === 0) return "";
1608
+ return ` (this gateway currently supports: ${ids.join(", ")}; switch the model in the picker — the switch also becomes your saved default. Requested: ${options.model})`;
1609
+ }
1610
+ /**
1611
+ * Turn the harness history into the wire body, loading image bytes on the
1612
+ * way.
1613
+ *
1614
+ * Deliberately the whole image pipeline and nothing else, so `stream` stays
1615
+ * about transport. Ordering matters and mirrors the official adapters:
1616
+ * resolve the attachment service, prepare one request version per retained
1617
+ * image, fail the request if the budget is exceeded (handing
1618
+ * `dsh-compaction-image-offload` its decision), and only then project the
1619
+ * offloaded occurrences to placeholder text — a mark set by an earlier
1620
+ * request's offload decision, which serialization must replace rather than
1621
+ * look up bytes for.
1622
+ */
1623
+ async buildBody(options) {
1624
+ if (!historyHasImage(options.messages)) return JSON.stringify(serializeRequest(options, void 0, this.imageAccess));
1625
+ assertImageRoles(options.messages);
1626
+ const attachments = this.opts.resolveAttachments?.();
1627
+ if (attachments === void 0) throw new LlmError("aishell-llm image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
1628
+ const messages = options.messages;
1629
+ const images = await prepareRequestImages(messages, attachments, this.opts.resolveImageBudget(), options.signal);
1630
+ assertRequestImageBudget(messages, images, this.opts.resolveMaxRequestImageBytes());
1631
+ const projected = [...projectOffloaded(messages, this.imageAccess)];
1632
+ return JSON.stringify(serializeRequest({
1633
+ ...options,
1634
+ messages: projected
1635
+ }, images, this.imageAccess));
1636
+ }
385
1637
  async *stream(options) {
386
- const body = JSON.stringify(serializeRequest(options));
1638
+ const body = await this.buildBody(options);
387
1639
  let response;
388
1640
  try {
389
1641
  response = await this.signedFetch("/chat/completions", "POST", body, options.signal);
390
1642
  } catch (error) {
391
1643
  if (options.signal?.aborted) throw error;
1644
+ if (error instanceof LlmError) throw error;
392
1645
  throw new LlmError(`aishell-llm: request failed: ${error instanceof Error ? error.message : String(error)}`, "NETWORK_ERROR");
393
1646
  }
394
1647
  if (!response.ok) {
395
1648
  const text = await response.text().catch(() => "");
396
- throw new LlmError(`aishell-llm: gateway returned ${response.status}: ${text}`, "PROVIDER_ERROR", { status: response.status });
1649
+ throw new LlmError(`aishell-llm: gateway returned ${response.status}: ${text}` + await this.unsupportedModelHint(text, options), "PROVIDER_ERROR", { status: response.status });
397
1650
  }
398
1651
  if (!response.body) throw new LlmError("aishell-llm: gateway returned no body", "EMPTY_RESPONSE");
399
1652
  yield* translate(parseSse(response.body));
@@ -403,21 +1656,65 @@ GatewayAdapter.MODELS_TTL_MS = 6e4;
403
1656
 
404
1657
  //#endregion
405
1658
  //#region src/index.ts
406
- /** Default chat API base URL. */
407
- const DEFAULT_BASE_URL = "https://api.aishell.openbuildlab.com";
1659
+ /** Default chat API base URL. Both `/chat/completions` and `/models` hang below `/v1`. */
1660
+ const DEFAULT_BASE_URL = "https://openai.aishell.openbuildlab.com/v1";
1661
+ /**
1662
+ * Default total-pixel cap one request image is scaled into, and the encoded
1663
+ * byte target the encoder aims for. Both match the pi-ai defaults, for the
1664
+ * same reason: the gateway publishes no image budget, so the route needs a
1665
+ * bound that keeps one large upload from dominating a request.
1666
+ */
1667
+ const DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
1668
+ const DEFAULT_REQUEST_IMAGE_MAX_BYTES = 1048576;
1669
+ /**
1670
+ * Default accumulated base64 bytes one request may carry before its oldest
1671
+ * images must be offloaded. Above this the request fails with
1672
+ * `IMAGE_OFFLOAD_REQUIRED` and `dsh-compaction-image-offload` (mounted by
1673
+ * `dsh-base`) replaces the oldest occurrences with placeholders. Again the
1674
+ * pi-ai default.
1675
+ */
1676
+ const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 20971520;
1677
+ /**
1678
+ * Settings schema for this entry.
1679
+ *
1680
+ * The schema exists for two reasons beyond configurability: the settings
1681
+ * mirror only lists namespaces whose plugin declares a Config, and the Models
1682
+ * page derives a provider row's existence from that listing — without a
1683
+ * schema, the `aishell` route registers its adapter but never shows up as a
1684
+ * configurable provider.
1685
+ */
1686
+ const Config = Schema.object({
1687
+ baseUrl: Schema.string().default(DEFAULT_BASE_URL).volatile(),
1688
+ requestImagePixelBudget: Schema.number().step(1).min(1).default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET).volatile(),
1689
+ requestImageMaxBytes: Schema.number().step(1).min(1).default(DEFAULT_REQUEST_IMAGE_MAX_BYTES).volatile(),
1690
+ maxRequestImageBytes: Schema.number().step(1).min(1).default(DEFAULT_MAX_REQUEST_IMAGE_BYTES).volatile()
1691
+ });
408
1692
  const name = "aishell-llm";
409
1693
  const inject = ["llm"];
410
1694
  /** The provider route this plugin owns. */
411
1695
  const PROVIDER = "aishell";
412
- function apply(ctx, config = {}) {
413
- const store = createCredentialsStore(config.keyringService ? { service: config.keyringService } : {});
1696
+ function apply(ctx, config) {
1697
+ const store = createCredentialsStore();
414
1698
  const adapter = new GatewayAdapter({
415
- baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
416
- keyring: store
1699
+ resolveBaseUrl: () => config.baseUrl.get(),
1700
+ keyring: store,
1701
+ resolveAttachments: () => ctx.get("attachments"),
1702
+ resolveHostPath: (hostPath) => ctx.get("fs")?.processPathFromHostPath(hostPath),
1703
+ resolveImageBudget: () => ({
1704
+ maxPixels: config.requestImagePixelBudget.get(),
1705
+ maxBytes: config.requestImageMaxBytes.get()
1706
+ }),
1707
+ resolveMaxRequestImageBytes: () => config.maxRequestImageBytes.get()
417
1708
  });
418
1709
  ctx.llm.registerAdapter([PROVIDER], adapter);
1710
+ ctx.llm.registerConfigurableProviders([{
1711
+ provider: PROVIDER,
1712
+ displayName: PROVIDER_DISPLAY_NAME,
1713
+ settingsNs: ctx.fiber.entry?.options.id ?? name,
1714
+ settingsPath: []
1715
+ }]);
419
1716
  }
420
1717
 
421
1718
  //#endregion
422
- export { PROVIDER, apply, inject, name };
1719
+ export { Config, PROVIDER, PROVIDER_DISPLAY_NAME, apply, inject, name };
423
1720
  //# sourceMappingURL=index.js.map