@klarkxy/dsh-current-title 0.3.3 → 0.3.5

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,4 +1,9 @@
1
1
  import { C as normalizeCadence, D as normalizeModelRoute, E as modelRouteOverride, S as isTitleLocaleMode, T as defaultModelRoute, _ as SETTINGS_KEY, b as defaultGenerateConfig, c as DEFAULT_TIMEOUT_MS, d as PLUGIN_NAME, h as RPC_CHANNEL, p as PROVIDER_ID, v as TITLE_CALL_FEATURE, w as normalizePrompt } from "./contracts2.js";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { appendFileSync, existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, unlinkSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { basename, dirname, join, resolve, sep } from "node:path";
6
+ import { fileURLToPath } from "node:url";
2
7
  import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
3
8
  import { BlockAssembler, ReasoningEffortId, createUserMessage } from "@deepseek-ai/dsh-llm";
4
9
  import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
@@ -70,12 +75,648 @@ function getAuxiliaryModel(host) {
70
75
  return modelRouteOverride(shared.snapshot().model ?? void 0);
71
76
  }
72
77
  //#endregion
78
+ //#region ../dsh-plugin-kit/src/log-shared.ts
79
+ const PLUGIN_LOG_LEVELS = [
80
+ "debug",
81
+ "info",
82
+ "warn",
83
+ "error",
84
+ "fault"
85
+ ];
86
+ const DEFAULT_PLUGIN_LOG_LEVEL = "info";
87
+ const PLUGIN_LOG_MAX_MESSAGE_CHARS = 16e3;
88
+ const SOURCE_RE = /^[a-z][a-z0-9-]{0,63}$/;
89
+ const EVENT_RE = /^[a-z][a-z0-9._-]{0,127}$/;
90
+ const CONTEXT_KEY_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,63}$/;
91
+ const SECRET_KEY_RE = /(?:api[_-]?key|access[_-]?token|refresh[_-]?token|bearer|authorization|cookie|set[_-]?cookie|nonce|credential|private[_-]?key|password|secret|app[_-]?secret|client[_-]?secret|z_c0|d_c0)$/i;
92
+ const CHAT_PAYLOAD_RE = /"role"\s*:\s*"(?:system|user|assistant|tool)"|"(?:messages|content|prompt|input_text|input_image|image_url)"\s*:\s*[\[{"']/i;
93
+ const CREDENTIAL_BODY_RE = /-----BEGIN (?:RSA |EC |OPENSSH |PRIVATE )?PRIVATE KEY-----|"type"\s*:\s*"service_account"|credentials\.json|\.dsh[/\\](?:credentials|auth)/i;
94
+ const AUTH_HEADER_RE = /\b(Authorization|Proxy-Authorization|Cookie|Set-Cookie)\s*:\s*[^\r\n]*/gi;
95
+ const BEARER_RE = /\bBearer\s+[A-Za-z0-9._\-+=/]{8,}/g;
96
+ const SECRET_FIELD_RE = /(["']?(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|bearer|authorization|cookie|set-cookie|nonce|credential|private[_-]?key|password|secret|app[_-]?secret|client[_-]?secret|password|z_c0|d_c0)["']?\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,&;}]+)/gi;
97
+ const SECRET_QUERY_RE = /([?&](?:token|api[_-]?key|key|secret|password|bootstrap|code|sig|signature|credential|password)=)[^\s&#"']+/gi;
98
+ const URL_CREDENTIAL_RE = /\b(https?:\/\/)[^\s/:@]+:[^\s/@]+@/gi;
99
+ const BOOTSTRAP_RE = /\/bootstrap\/[^\s"'?&#]+/gi;
100
+ const KNOWN_TOKEN_RE = /\b(?:sk-[A-Za-z0-9_-]{10,}|gh[pousr]_[A-Za-z0-9_]{16,}|github_pat_[A-Za-z0-9_]{20,}|tvly-[A-Za-z0-9_-]{10,}|AKIA[0-9A-Z]{16})\b/g;
101
+ const PATH_RE = /(?:[A-Za-z]:\\|\\\\)[^\s"'`<>|]+|(?:\/(?:home|Users|tmp|var|root)\/|~\/)[^\s"'`<>|]+/g;
102
+ function isPluginLogLevel(value) {
103
+ return typeof value === "string" && PLUGIN_LOG_LEVELS.includes(value);
104
+ }
105
+ function pluginLogLevelEnabled(level, minimum) {
106
+ return PLUGIN_LOG_LEVELS.indexOf(level) >= PLUGIN_LOG_LEVELS.indexOf(minimum);
107
+ }
108
+ function normalizePluginLogSource(raw) {
109
+ const stripped = String(raw || "").trim().replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
110
+ const bounded = (/^[a-z]/.test(stripped) ? stripped : `p-${stripped}`).slice(0, 64).replace(/-+$/g, "");
111
+ return SOURCE_RE.test(bounded) ? bounded : "dsh-plugin";
112
+ }
113
+ function normalizePluginLogEvent(raw) {
114
+ const cleaned = String(raw || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 128);
115
+ return EVENT_RE.test(cleaned) ? cleaned : "plugin.event";
116
+ }
117
+ /**
118
+ * Parse a level specification like `debug` or `dsh-zhihu=debug,dsh-dot:warn,info`.
119
+ * Source-specific rules win over a bare fallback level.
120
+ */
121
+ function parsePluginLogLevel(spec, source, fallback = DEFAULT_PLUGIN_LOG_LEVEL) {
122
+ if (!spec || typeof spec !== "string") return fallback;
123
+ const normalizedSource = source ? normalizePluginLogSource(source) : void 0;
124
+ let wildcard;
125
+ for (const token of spec.split(",").map((part) => part.trim()).filter(Boolean)) {
126
+ const lowered = token.toLowerCase();
127
+ if (isPluginLogLevel(lowered)) {
128
+ wildcard = lowered;
129
+ continue;
130
+ }
131
+ const match = /^([^=:]+)[=:]([a-z]+)$/i.exec(token);
132
+ if (!match) continue;
133
+ const target = match[1].trim();
134
+ const candidate = match[2].trim().toLowerCase();
135
+ if (!isPluginLogLevel(candidate)) continue;
136
+ if (target === "*") {
137
+ wildcard = candidate;
138
+ continue;
139
+ }
140
+ if (normalizedSource && normalizePluginLogSource(target) === normalizedSource) return candidate;
141
+ }
142
+ return wildcard ?? fallback;
143
+ }
144
+ function sanitizePluginLogText(text, maxChars = PLUGIN_LOG_MAX_MESSAGE_CHARS) {
145
+ if (!text) return "";
146
+ if (CREDENTIAL_BODY_RE.test(text)) return "[omitted credential material]";
147
+ if (CHAT_PAYLOAD_RE.test(text)) return "[omitted chat payload]";
148
+ return truncateLogText(text.replace(AUTH_HEADER_RE, "$1: [redacted]").replace(BEARER_RE, "Bearer [redacted]").replace(SECRET_FIELD_RE, (full, prefix) => {
149
+ const value = full.slice(prefix.length);
150
+ if (value.startsWith("\"")) return `${prefix}"[redacted]"`;
151
+ if (value.startsWith("'")) return `${prefix}'[redacted]'`;
152
+ return `${prefix}[redacted]`;
153
+ }).replace(SECRET_QUERY_RE, "$1[redacted]").replace(URL_CREDENTIAL_RE, "$1[redacted]@").replace(BOOTSTRAP_RE, "/bootstrap/[redacted]").replace(KNOWN_TOKEN_RE, "[redacted]").replace(PATH_RE, "[path]"), maxChars);
154
+ }
155
+ function sanitizePluginLogContext(input) {
156
+ if (!input) return void 0;
157
+ const output = {};
158
+ for (const [key, value] of Object.entries(input).slice(0, 32)) {
159
+ if (value === void 0 || !CONTEXT_KEY_RE.test(key)) continue;
160
+ if (typeof value === "number" && !Number.isFinite(value)) continue;
161
+ if (SECRET_KEY_RE.test(key)) {
162
+ output[key] = "[redacted]";
163
+ continue;
164
+ }
165
+ output[key] = typeof value === "string" ? sanitizePluginLogText(value, 1e3) : value;
166
+ }
167
+ if (!Object.keys(output).length) return void 0;
168
+ if (JSON.stringify(output).length <= 8e3) return output;
169
+ return {
170
+ omitted: true,
171
+ reason: "Diagnostic context exceeded its size limit."
172
+ };
173
+ }
174
+ function sanitizePluginLogError(error) {
175
+ if (error === void 0) return void 0;
176
+ const row = error && typeof error === "object" ? error : {};
177
+ const rawMessage = typeof row.message === "string" ? row.message : String(error);
178
+ const result = {
179
+ name: typeof row.name === "string" && row.name.trim() ? truncateLogText(row.name.trim(), 100) : "Error",
180
+ message: sanitizePluginLogText(rawMessage) || "Unknown error"
181
+ };
182
+ if (typeof row.stack === "string" && row.stack.trim()) result.stack = sanitizePluginLogText(row.stack, 24e3);
183
+ if (typeof row.code === "string" && /^[A-Za-z0-9_./:-]{1,100}$/.test(row.code)) result.code = row.code;
184
+ else if (typeof row.code === "number" && Number.isFinite(row.code)) result.code = String(row.code);
185
+ return result;
186
+ }
187
+ function mergePluginLogContext(base, extra) {
188
+ if (!base) return extra;
189
+ if (!extra) return base;
190
+ return {
191
+ ...base,
192
+ ...extra
193
+ };
194
+ }
195
+ function buildPluginLogEntry(input, meta) {
196
+ const entry = {
197
+ version: 1,
198
+ at: meta.at.toISOString(),
199
+ level: isPluginLogLevel(input.level) ? input.level : "info",
200
+ source: normalizePluginLogSource(meta.source),
201
+ surface: meta.surface,
202
+ ...meta.profile ? { profile: meta.profile } : {},
203
+ event: normalizePluginLogEvent(input.event),
204
+ message: sanitizePluginLogText(input.message || "No diagnostic message was provided.", PLUGIN_LOG_MAX_MESSAGE_CHARS),
205
+ ...meta.pid !== void 0 ? { pid: meta.pid } : {}
206
+ };
207
+ const context = sanitizePluginLogContext(mergePluginLogContext(meta.correlation, input.context));
208
+ if (context) entry.context = context;
209
+ const error = sanitizePluginLogError(input.error);
210
+ if (error) entry.error = error;
211
+ return entry;
212
+ }
213
+ function createChildPluginLogger(parent, bound) {
214
+ return {
215
+ get source() {
216
+ return parent.source;
217
+ },
218
+ get file() {
219
+ return parent.file;
220
+ },
221
+ get lastWriteError() {
222
+ return parent.lastWriteError;
223
+ },
224
+ debug: (event, message, context) => parent.debug(event, message, mergePluginLogContext(bound, context)),
225
+ info: (event, message, context) => parent.info(event, message, mergePluginLogContext(bound, context)),
226
+ warn: (event, message, error, context) => parent.warn(event, message, error, mergePluginLogContext(bound, context)),
227
+ error: (event, message, error, context) => parent.error(event, message, error, mergePluginLogContext(bound, context)),
228
+ fault: (event, message, error, context) => parent.fault(event, message, error, mergePluginLogContext(bound, context)),
229
+ write: (input) => parent.write({
230
+ ...input,
231
+ context: mergePluginLogContext(bound, input.context)
232
+ }),
233
+ child: (next) => createChildPluginLogger(parent, mergePluginLogContext(bound, next) ?? {})
234
+ };
235
+ }
236
+ function truncateLogText(text, maxChars) {
237
+ if (text.length <= maxChars) return text;
238
+ const marker = "[truncated]\n";
239
+ if (maxChars <= 12) return text.slice(0, Math.max(0, maxChars));
240
+ return `${marker}${text.slice(-(maxChars - 12))}`;
241
+ }
242
+ const PROFILE_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
243
+ const ARCHIVE_RE = /^[a-z][a-z0-9-]{0,63}\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z\.\d+\.\d+\.jsonl$/;
244
+ const correlationStorage = new AsyncLocalStorage();
245
+ const processFileLoggers = /* @__PURE__ */ new Map();
246
+ let operationSequence = 0;
247
+ function safeProfileName(profile) {
248
+ const trimmed = String(profile ?? "").trim();
249
+ if (PROFILE_RE.test(trimmed)) return trimmed.toLowerCase();
250
+ const cleaned = trimmed.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
251
+ return cleaned && PROFILE_RE.test(cleaned) ? cleaned : "default";
252
+ }
253
+ function pluginLogDirectory(home, profile = "default") {
254
+ return join(resolve(home), "logs", "plugins", safeProfileName(profile));
255
+ }
256
+ function resolveHostLogTarget(ctx, options = {}) {
257
+ const env = options.env ?? process.env;
258
+ if (options.directory?.trim()) {
259
+ const dir = resolve(options.directory.trim());
260
+ return {
261
+ directory: dir,
262
+ anchor: dir,
263
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
264
+ };
265
+ }
266
+ if (options.home?.trim()) {
267
+ const home = resolve(options.home.trim());
268
+ const profile = safeProfileName(options.profile);
269
+ return {
270
+ directory: pluginLogDirectory(home, profile),
271
+ anchor: home,
272
+ profile
273
+ };
274
+ }
275
+ if (env.DSH_PLUGIN_LOG_DIR?.trim()) {
276
+ const dir = resolve(env.DSH_PLUGIN_LOG_DIR.trim());
277
+ return {
278
+ directory: dir,
279
+ anchor: dir,
280
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
281
+ };
282
+ }
283
+ const host = ctx && typeof ctx === "object" ? ctx : void 0;
284
+ let profileCtx;
285
+ try {
286
+ profileCtx = host?.get?.("profileContext", false);
287
+ } catch {
288
+ try {
289
+ profileCtx = host?.get?.("profileContext");
290
+ } catch {
291
+ profileCtx = void 0;
292
+ }
293
+ }
294
+ if (typeof profileCtx?.home === "string" && profileCtx.home.trim() && typeof profileCtx.dir === "string" && profileCtx.dir.trim()) {
295
+ const home = resolve(profileCtx.home.trim());
296
+ const dir = resolve(profileCtx.dir.trim());
297
+ if (isInside(home, dir)) {
298
+ const profile = safeProfileName(typeof profileCtx.name === "string" ? profileCtx.name : basename(dir));
299
+ return {
300
+ directory: pluginLogDirectory(home, profile),
301
+ anchor: home,
302
+ profile
303
+ };
304
+ }
305
+ }
306
+ if (!Boolean(env.VITEST || env.NODE_TEST_CONTEXT) && typeof host?.baseUrl === "string" && host.baseUrl.trim()) {
307
+ const basePath = pathFromBaseUrl(host.baseUrl);
308
+ const configuredHome = resolve((env.DSH_HOME?.trim() || join(homedir(), ".dsh")).trim());
309
+ const profilesRoot = join(configuredHome, "profiles");
310
+ if (basePath && isInside(profilesRoot, basePath) && !samePath(profilesRoot, basePath)) {
311
+ const firstSegment = basePath.slice(profilesRoot.length).replace(/^[/\\]+/, "").split(/[/\\]/)[0];
312
+ if (firstSegment) {
313
+ const profile = safeProfileName(firstSegment);
314
+ return {
315
+ directory: pluginLogDirectory(configuredHome, profile),
316
+ anchor: configuredHome,
317
+ profile
318
+ };
319
+ }
320
+ }
321
+ }
322
+ return {
323
+ directory: null,
324
+ anchor: null,
325
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
326
+ };
327
+ }
328
+ /**
329
+ * Synchronous, append-only rotating JSONL logger modeled on DSH Spaces
330
+ * diagnostics: 7-day or 100 MiB active rotation, 7-day / 100 MiB archive
331
+ * retention, secret and path redaction, and non-throwing write resilience.
332
+ */
333
+ var RotatingPluginLogger = class {
334
+ source;
335
+ file;
336
+ lastError = null;
337
+ size = 0;
338
+ openedAt = 0;
339
+ initialized = false;
340
+ writing = false;
341
+ sequence = 0;
342
+ loggedWriteFailure = false;
343
+ recent = [];
344
+ directory;
345
+ anchor;
346
+ profile;
347
+ minimum;
348
+ now;
349
+ pid;
350
+ maxFileBytes;
351
+ maxArchiveBytes;
352
+ maxAgeMs;
353
+ recentLimit;
354
+ constructor(source, options = {}) {
355
+ this.source = normalizePluginLogSource(source);
356
+ const env = options.env ?? process.env;
357
+ const resolved = options.directory !== void 0 || options.home !== void 0 ? resolveHostLogTarget(void 0, options) : {
358
+ directory: null,
359
+ anchor: options.anchor ?? null,
360
+ profile: options.profile
361
+ };
362
+ this.directory = resolved.directory;
363
+ this.anchor = options.anchor ?? resolved.anchor;
364
+ this.profile = options.profile ?? resolved.profile;
365
+ this.file = this.directory ? join(this.directory, `${this.source}.jsonl`) : null;
366
+ this.minimum = options.minimumLevel ?? parsePluginLogLevel(env.DSH_PLUGIN_LOG_LEVEL, this.source, "info");
367
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
368
+ this.pid = options.pid ?? process.pid;
369
+ this.maxFileBytes = options.maxFileBytes ?? 104857600;
370
+ this.maxArchiveBytes = options.maxArchiveBytes ?? 104857600;
371
+ this.maxAgeMs = options.maxAgeMs ?? 6048e5;
372
+ this.recentLimit = Math.max(1, options.recentLimit ?? 200);
373
+ }
374
+ get lastWriteError() {
375
+ return this.lastError;
376
+ }
377
+ recentEntries(limit = this.recentLimit) {
378
+ return this.recent.slice(-Math.max(0, limit));
379
+ }
380
+ debug(event, message, context) {
381
+ return this.write({
382
+ level: "debug",
383
+ event,
384
+ message,
385
+ ...context ? { context } : {}
386
+ });
387
+ }
388
+ info(event, message, context) {
389
+ return this.write({
390
+ level: "info",
391
+ event,
392
+ message,
393
+ ...context ? { context } : {}
394
+ });
395
+ }
396
+ warn(event, message, error, context) {
397
+ return this.write({
398
+ level: "warn",
399
+ event,
400
+ message,
401
+ ...error !== void 0 ? { error } : {},
402
+ ...context ? { context } : {}
403
+ });
404
+ }
405
+ error(event, message, error, context) {
406
+ return this.write({
407
+ level: "error",
408
+ event,
409
+ message,
410
+ ...error !== void 0 ? { error } : {},
411
+ ...context ? { context } : {}
412
+ });
413
+ }
414
+ fault(event, message, error, context) {
415
+ return this.write({
416
+ level: "fault",
417
+ event,
418
+ message,
419
+ ...error !== void 0 ? { error } : {},
420
+ ...context ? { context } : {}
421
+ });
422
+ }
423
+ child(context) {
424
+ return createChildPluginLogger(this, context);
425
+ }
426
+ write(input) {
427
+ if (!pluginLogLevelEnabled(input.level, this.minimum) || this.writing) return false;
428
+ const at = this.now();
429
+ let entry;
430
+ let line;
431
+ try {
432
+ entry = buildPluginLogEntry(input, {
433
+ at,
434
+ source: this.source,
435
+ surface: "host",
436
+ profile: this.profile,
437
+ pid: this.pid,
438
+ correlation: correlationStorage.getStore()
439
+ });
440
+ line = `${JSON.stringify(entry)}\n`;
441
+ } catch {
442
+ return false;
443
+ }
444
+ this.pushRecent(entry);
445
+ if (!this.directory || !this.file) return true;
446
+ this.writing = true;
447
+ try {
448
+ this.openManagedDirectory();
449
+ this.prepare(at);
450
+ if (this.size > 0 && (this.size + Buffer.byteLength(line) > this.maxFileBytes || at.getTime() - this.openedAt >= this.maxAgeMs)) this.rotate(at);
451
+ appendFileSync(this.file, line, {
452
+ encoding: "utf8",
453
+ mode: 384
454
+ });
455
+ this.size += Buffer.byteLength(line);
456
+ this.prune(at);
457
+ this.lastError = null;
458
+ this.loggedWriteFailure = false;
459
+ return true;
460
+ } catch (error) {
461
+ const detail = sanitizePluginLogText(error instanceof Error ? error.message : String(error), 500);
462
+ this.lastError = `The plugin diagnostic log could not be written: ${detail}`;
463
+ this.initialized = false;
464
+ if (!this.loggedWriteFailure) {
465
+ this.loggedWriteFailure = true;
466
+ try {
467
+ process.stderr.write(`DSH Plugin (${this.source}): ${this.lastError}. The original operation continues unchanged.\n`);
468
+ } catch {}
469
+ }
470
+ return false;
471
+ } finally {
472
+ this.writing = false;
473
+ }
474
+ }
475
+ pushRecent(entry) {
476
+ this.recent.push(entry);
477
+ const overflow = this.recent.length - this.recentLimit;
478
+ if (overflow > 0) this.recent.splice(0, overflow);
479
+ }
480
+ prepare(at) {
481
+ if (this.initialized || !this.file) return;
482
+ this.openManagedDirectory();
483
+ const stat = this.regularFile(this.file);
484
+ this.size = stat?.size ?? 0;
485
+ const stamps = stat ? [stat.birthtimeMs, stat.mtimeMs].filter((value) => value > 0) : [];
486
+ this.openedAt = stamps.length ? Math.min(...stamps) : at.getTime();
487
+ this.initialized = true;
488
+ }
489
+ rotate(at) {
490
+ if (!this.directory || !this.file) return;
491
+ const stamp = at.toISOString().replace(/:/g, "-");
492
+ const name = `${this.source}.${stamp}.${this.pid}.${this.sequence++}.jsonl`;
493
+ if (!ARCHIVE_RE.test(name)) throw new Error("Diagnostic log archive name is invalid.");
494
+ const archive = join(this.directory, name);
495
+ if (lexists(archive)) throw new Error("Diagnostic log archive already exists.");
496
+ try {
497
+ renameSync(this.file, archive);
498
+ } catch (error) {
499
+ if (error.code === "ENOENT") {
500
+ this.size = 0;
501
+ this.openedAt = at.getTime();
502
+ return;
503
+ }
504
+ throw error;
505
+ }
506
+ this.size = 0;
507
+ this.openedAt = at.getTime();
508
+ }
509
+ rotateStaleActive(at) {
510
+ if (!this.file) return;
511
+ const stat = this.regularFile(this.file);
512
+ if (!stat || stat.size === 0) return;
513
+ if (at.getTime() - stat.mtimeMs < this.maxAgeMs) return;
514
+ this.rotate(at);
515
+ }
516
+ prune(at) {
517
+ if (!this.directory) return;
518
+ this.rotateStaleActive(at);
519
+ const archives = [];
520
+ let total = 0;
521
+ for (const name of readdirSync(this.directory)) {
522
+ if (!ARCHIVE_RE.test(name) || !name.startsWith(`${this.source}.`)) continue;
523
+ const file = join(this.directory, name);
524
+ const stat = lstatSync(file);
525
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("Diagnostic log archive is not a regular file.");
526
+ if (at.getTime() - stat.mtimeMs >= this.maxAgeMs) {
527
+ try {
528
+ unlinkSync(file);
529
+ } catch {}
530
+ continue;
531
+ }
532
+ archives.push({
533
+ name,
534
+ size: stat.size,
535
+ mtime: stat.mtimeMs
536
+ });
537
+ total += stat.size;
538
+ }
539
+ archives.sort((left, right) => left.mtime - right.mtime || left.name.localeCompare(right.name));
540
+ while (total > this.maxArchiveBytes && archives.length) {
541
+ const oldest = archives.shift();
542
+ try {
543
+ unlinkSync(join(this.directory, oldest.name));
544
+ } catch {}
545
+ total -= oldest.size;
546
+ }
547
+ }
548
+ openManagedDirectory() {
549
+ if (!this.directory) return;
550
+ if (this.anchor) {
551
+ if ((existsSync(this.anchor) ? lstatSync(this.anchor) : void 0)?.isSymbolicLink()) throw new Error("Diagnostic log anchor cannot be a symbolic link.");
552
+ }
553
+ mkdirSync(this.directory, {
554
+ recursive: true,
555
+ mode: 448
556
+ });
557
+ const stat = lstatSync(this.directory);
558
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Diagnostic log directory is not a real directory.");
559
+ const realDir = realpathSync(this.directory);
560
+ if (this.anchor) {
561
+ if (!isInside(realpathSync(this.anchor), realDir)) throw new Error("Diagnostic log directory escaped its managed root.");
562
+ }
563
+ }
564
+ regularFile(file) {
565
+ try {
566
+ const stat = lstatSync(file);
567
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("Diagnostic log is not a regular file.");
568
+ const expected = join(realpathSync(dirname(file)), basename(file));
569
+ if (!samePath(realpathSync(file), expected)) throw new Error("Diagnostic log path could not be verified.");
570
+ return stat;
571
+ } catch (error) {
572
+ if (error.code === "ENOENT") return void 0;
573
+ throw error;
574
+ }
575
+ }
576
+ };
577
+ function createPluginLogger(ctxOrSource, maybeSource, maybeOptions) {
578
+ const hasCtx = typeof ctxOrSource !== "string";
579
+ const ctx = hasCtx ? ctxOrSource : void 0;
580
+ const source = hasCtx ? String(maybeSource ?? "dsh-plugin") : ctxOrSource;
581
+ const options = (hasCtx ? maybeOptions : maybeSource) ?? {};
582
+ const normalized = normalizePluginLogSource(source);
583
+ const target = resolveHostLogTarget(ctx, options);
584
+ let backing;
585
+ if (Boolean(target.directory && options.minimumLevel === void 0 && options.now === void 0 && options.pid === void 0 && options.maxFileBytes === void 0 && options.maxArchiveBytes === void 0 && options.maxAgeMs === void 0) && target.directory) {
586
+ const key = resolve(target.directory, `${normalized}.jsonl`);
587
+ let shared = processFileLoggers.get(key);
588
+ if (!shared) {
589
+ shared = new RotatingPluginLogger(normalized, {
590
+ ...options,
591
+ directory: target.directory,
592
+ anchor: target.anchor,
593
+ profile: target.profile
594
+ });
595
+ processFileLoggers.set(key, shared);
596
+ }
597
+ backing = shared;
598
+ } else backing = new RotatingPluginLogger(normalized, {
599
+ ...options,
600
+ ...target.directory ? {
601
+ directory: target.directory,
602
+ anchor: target.anchor
603
+ } : {},
604
+ ...target.profile ? { profile: target.profile } : {}
605
+ });
606
+ if (options.forwardHostWarnError === false || !ctx || typeof ctx !== "object") return backing;
607
+ return wrapWithHostForwarding(backing, ctx);
608
+ }
609
+ function wrapWithHostForwarding(backing, host) {
610
+ const forward = (input) => {
611
+ if (input.level !== "warn" && input.level !== "error" && input.level !== "fault") return;
612
+ try {
613
+ const hostLogger = host.logger;
614
+ if (!hostLogger) return;
615
+ const detail = input.error instanceof Error ? `${input.message} (${input.error.message})` : input.error !== void 0 ? `${input.message} (${String(input.error)})` : input.message;
616
+ const text = sanitizePluginLogText(`${backing.source} [${input.event}]: ${detail}`, 1e3);
617
+ if (input.level === "warn") {
618
+ if (typeof hostLogger.warn === "function") hostLogger.warn(text);
619
+ } else if (typeof hostLogger.error === "function") hostLogger.error(text);
620
+ else if (typeof hostLogger.warn === "function") hostLogger.warn(text);
621
+ } catch {}
622
+ };
623
+ const wrapped = {
624
+ get source() {
625
+ return backing.source;
626
+ },
627
+ get file() {
628
+ return backing.file;
629
+ },
630
+ get lastWriteError() {
631
+ return backing.lastWriteError;
632
+ },
633
+ debug: (event, message, context) => backing.debug(event, message, context),
634
+ info: (event, message, context) => backing.info(event, message, context),
635
+ warn: (event, message, error, context) => {
636
+ const wrote = backing.warn(event, message, error, context);
637
+ forward({
638
+ level: "warn",
639
+ event,
640
+ message,
641
+ error,
642
+ context
643
+ });
644
+ return wrote;
645
+ },
646
+ error: (event, message, error, context) => {
647
+ const wrote = backing.error(event, message, error, context);
648
+ forward({
649
+ level: "error",
650
+ event,
651
+ message,
652
+ error,
653
+ context
654
+ });
655
+ return wrote;
656
+ },
657
+ fault: (event, message, error, context) => {
658
+ const wrote = backing.fault(event, message, error, context);
659
+ forward({
660
+ level: "fault",
661
+ event,
662
+ message,
663
+ error,
664
+ context
665
+ });
666
+ return wrote;
667
+ },
668
+ write: (input) => {
669
+ const wrote = backing.write(input);
670
+ forward(input);
671
+ return wrote;
672
+ },
673
+ child: (context) => createChildPluginLogger(wrapped, context)
674
+ };
675
+ return wrapped;
676
+ }
677
+ function nextPluginOperationId(prefix = "op") {
678
+ return `${normalizePluginLogSource(prefix)}-${process.pid}-${++operationSequence}`;
679
+ }
680
+ function withPluginLogContext(context, action) {
681
+ const merged = mergePluginLogContext(correlationStorage.getStore(), context) ?? {};
682
+ return correlationStorage.run(merged, action);
683
+ }
684
+ function lexists(file) {
685
+ try {
686
+ lstatSync(file);
687
+ return true;
688
+ } catch (error) {
689
+ if (error.code === "ENOENT") return false;
690
+ throw error;
691
+ }
692
+ }
693
+ function pathFromBaseUrl(baseUrl) {
694
+ try {
695
+ return baseUrl.includes("://") ? resolve(fileURLToPath(baseUrl)) : resolve(baseUrl);
696
+ } catch {
697
+ return null;
698
+ }
699
+ }
700
+ function samePath(left, right) {
701
+ const a = resolve(left);
702
+ const b = resolve(right);
703
+ return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
704
+ }
705
+ function isInside(root, target) {
706
+ const rootResolved = resolve(root);
707
+ const targetResolved = resolve(target);
708
+ if (samePath(rootResolved, targetResolved)) return true;
709
+ const prefix = rootResolved.endsWith(sep) ? rootResolved : rootResolved + sep;
710
+ return process.platform === "win32" ? targetResolved.toLowerCase().startsWith(prefix.toLowerCase()) : targetResolved.startsWith(prefix);
711
+ }
712
+ //#endregion
73
713
  //#region ../dsh-plugin-kit/src/host-rpc.ts
74
714
  /** The shared authenticated HTTP boundary. Each plugin still owns its RPC envelope semantics. */
75
715
  function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
76
716
  const bodyLimit = options.maxBodyBytes ?? 65536;
77
717
  if (!Number.isSafeInteger(bodyLimit) || bodyLimit <= 0) throw new Error("Invalid RPC body limit");
78
718
  const isEndpoint = options.isEndpoint ?? ((endpoint) => /^[a-z.]+$/.test(endpoint));
719
+ const log = options.logger;
79
720
  return ctx.webServer.register({
80
721
  kind: "prefix",
81
722
  path: channel,
@@ -86,29 +727,54 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
86
727
  else res.writeHead(status);
87
728
  res.end(override ?? options.errorBody?.(status, error) ?? (status === 500 ? "rpc request failed" : ""));
88
729
  };
730
+ let endpoint = "";
89
731
  try {
90
732
  if (typeof ctx.connection.requestRejection !== "function") {
733
+ log?.error("rpc.admission.unavailable", "Host RPC admission policy is unavailable.", void 0, {
734
+ channel,
735
+ status: 503
736
+ });
91
737
  reject(503);
92
738
  return;
93
739
  }
94
740
  const rejection = ctx.connection.requestRejection(req);
95
741
  if (rejection !== void 0) {
742
+ log?.warn("rpc.admission.rejected", "Host RPC request rejected by connection policy.", void 0, {
743
+ channel,
744
+ status: rejection
745
+ });
96
746
  reject(rejection);
97
747
  return;
98
748
  }
99
749
  const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
100
- const endpoint = pathname.startsWith(`${channel}/`) ? pathname.slice(channel.length + 1) : "";
750
+ endpoint = pathname.startsWith(`${channel}/`) ? pathname.slice(channel.length + 1) : "";
101
751
  if (req.method !== "POST" || !isEndpoint(endpoint)) {
752
+ log?.debug("rpc.route.rejected", "Host RPC route or method rejected.", {
753
+ channel,
754
+ endpoint: endpoint || null,
755
+ method: req.method ?? null,
756
+ status: 404
757
+ });
102
758
  reject(404);
103
759
  return;
104
760
  }
105
761
  const contentType = String(req.headers["content-type"] ?? "").split(";", 1)[0]?.trim();
106
762
  if ((options.caseInsensitiveContentType ? contentType.toLowerCase() : contentType) !== "application/json") {
763
+ log?.debug("rpc.content-type.rejected", "Host RPC content-type rejected.", {
764
+ channel,
765
+ endpoint,
766
+ status: 415
767
+ });
107
768
  reject(415);
108
769
  return;
109
770
  }
110
771
  const guarded = options.beforeBody?.(req, endpoint);
111
772
  if (guarded) {
773
+ log?.debug("rpc.guard.rejected", "Host RPC request rejected before body read.", {
774
+ channel,
775
+ endpoint,
776
+ status: guarded.status
777
+ });
112
778
  reject(guarded.status, void 0, guarded.body);
113
779
  return;
114
780
  }
@@ -122,6 +788,11 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
122
788
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
123
789
  size += buffer.length;
124
790
  if (size > bodyLimit) {
791
+ log?.warn("rpc.body.too-large", "Host RPC request exceeded body limit.", void 0, {
792
+ channel,
793
+ endpoint,
794
+ maxBodyBytes: bodyLimit
795
+ });
125
796
  reject(413);
126
797
  req.destroy();
127
798
  return;
@@ -132,11 +803,26 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
132
803
  try {
133
804
  body = JSON.parse(Buffer.concat(chunks).toString("utf8") || (options.emptyBodyAsNull ? "null" : ""));
134
805
  } catch {
806
+ log?.debug("rpc.body.invalid-json", "Host RPC request body is not valid JSON.", {
807
+ channel,
808
+ endpoint,
809
+ status: 400
810
+ });
135
811
  reject(400);
136
812
  return;
137
813
  }
138
- await dispatch(endpoint, body, controller.signal, res);
814
+ if (log) await withPluginLogContext({
815
+ rpcChannel: channel,
816
+ rpcEndpoint: endpoint,
817
+ operationId: nextPluginOperationId("rpc")
818
+ }, () => dispatch(endpoint, body, controller.signal, res));
819
+ else await dispatch(endpoint, body, controller.signal, res);
139
820
  } catch (error) {
821
+ log?.error("rpc.dispatch.failed", "Unhandled host RPC handler error.", error, {
822
+ channel,
823
+ endpoint: endpoint || null,
824
+ status: 500
825
+ });
140
826
  reject(500, error);
141
827
  }
142
828
  }
@@ -157,19 +843,41 @@ function sendHostRpcResponse(response, rpcId, result, noStore = true) {
157
843
  }
158
844
  /** Safe host RPC registration. Missing requestRejection fails closed. */
159
845
  function registerHostRpc(ctx, channel, call, options = {}) {
846
+ const log = options.logger;
160
847
  return registerHostRpcTransport(ctx, channel, async (endpoint, body, signal, res) => {
161
848
  if (!body || typeof body !== "object" || Array.isArray(body)) {
849
+ log?.debug("rpc.envelope.invalid", "Host RPC envelope must be a JSON object.", {
850
+ channel,
851
+ endpoint
852
+ });
162
853
  res.writeHead(400);
163
854
  res.end();
164
855
  return;
165
856
  }
166
857
  const request = body;
167
858
  if (request.type !== "client-request" || request.method !== endpoint || typeof request.rpcId !== "string" || options.requirePayload && !Object.hasOwn(request, "payload")) {
859
+ log?.debug("rpc.envelope.invalid", "Host RPC envelope failed validation.", {
860
+ channel,
861
+ endpoint
862
+ });
168
863
  res.writeHead(400);
169
864
  res.end();
170
865
  return;
171
866
  }
867
+ const started = Date.now();
172
868
  const raw = await call(endpoint, request.payload, signal);
869
+ const durationMs = Date.now() - started;
870
+ if (raw.ok) log?.debug("rpc.call.completed", "Host RPC call succeeded.", {
871
+ channel,
872
+ endpoint,
873
+ durationMs
874
+ });
875
+ else log?.warn("rpc.call.error", "Host RPC call returned an application error.", void 0, {
876
+ channel,
877
+ endpoint,
878
+ code: raw.error?.code ?? null,
879
+ durationMs
880
+ });
173
881
  const result = raw.ok ? raw : {
174
882
  ...raw,
175
883
  error: {
@@ -475,7 +1183,13 @@ function setSharedModel(state, ctx, input, expectedRevision) {
475
1183
  if ((Boolean(row.provider) || Boolean(row.model)) && !modelRouteOverride(normalized)) return Promise.reject(/* @__PURE__ */ new Error("Invalid model route"));
476
1184
  const commit = state.tail.then(async () => {
477
1185
  if (state.closed || !state.domain) throw new Error("Auxiliary model settings are unloading; retry in a moment");
478
- if (expectedRevision !== state.data.revision) throw conflictError("Auxiliary model settings changed; reload and retry");
1186
+ if (expectedRevision !== state.data.revision) {
1187
+ createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).warn("auxiliary-model.conflict", "Auxiliary model update rejected due to revision conflict.", void 0, {
1188
+ expectedRevision: Number(expectedRevision),
1189
+ currentRevision: state.data.revision
1190
+ });
1191
+ throw conflictError("Auxiliary model settings changed; reload and retry");
1192
+ }
479
1193
  const next = {
480
1194
  revision: state.data.revision + 1,
481
1195
  model: modelRouteOverride(normalized) ?? null
@@ -485,6 +1199,11 @@ function setSharedModel(state, ctx, input, expectedRevision) {
485
1199
  model: next.model
486
1200
  });
487
1201
  state.data = next;
1202
+ createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).info("auxiliary-model.updated", "Updated global auxiliary model default.", {
1203
+ revision: next.revision,
1204
+ provider: next.model?.provider ?? null,
1205
+ model: next.model?.model ?? null
1206
+ });
488
1207
  notifyModelSettingsChanged(ctx.root, AUXILIARY_MODEL_NS, next.revision);
489
1208
  return next;
490
1209
  });
@@ -492,6 +1211,7 @@ function setSharedModel(state, ctx, input, expectedRevision) {
492
1211
  return commit;
493
1212
  }
494
1213
  function initScope(state, ctx, scope) {
1214
+ const log = createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false });
495
1215
  const generation = ++state.generation;
496
1216
  let disposed = false;
497
1217
  scope.effect(() => () => {
@@ -523,6 +1243,11 @@ function initScope(state, ctx, scope) {
523
1243
  };
524
1244
  state.error = void 0;
525
1245
  state.available = true;
1246
+ log.info("auxiliary-model.opened", "Shared auxiliary model store opened.", {
1247
+ generation,
1248
+ revision: state.data.revision,
1249
+ hasModel: Boolean(state.data.model)
1250
+ });
526
1251
  scope.effect(() => registerModelSettings(scope, {
527
1252
  ns: AUXILIARY_MODEL_NS,
528
1253
  title: {
@@ -606,7 +1331,10 @@ function initScope(state, ctx, scope) {
606
1331
  message: `Unknown endpoint: ${endpoint}`
607
1332
  }
608
1333
  };
609
- }, { isEndpoint: (endpoint) => /^[a-z.]+$/.test(endpoint) }));
1334
+ }, {
1335
+ isEndpoint: (endpoint) => /^[a-z.]+$/.test(endpoint),
1336
+ logger: log
1337
+ }));
610
1338
  createModelSettingsChanges(scope, AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_NS);
611
1339
  scope.effect(() => () => {
612
1340
  if (state.generation === generation) {
@@ -615,6 +1343,8 @@ function initScope(state, ctx, scope) {
615
1343
  }
616
1344
  });
617
1345
  })().catch((error) => {
1346
+ if (error && typeof error === "object" && error[RECOVERABLE]) log.debug("auxiliary-model.superseded", "Shared auxiliary model init superseded during open.", { generation });
1347
+ else log.error("auxiliary-model.init-failed", "Shared auxiliary model store failed to open.", error, { generation });
618
1348
  if (error && typeof error === "object") error.auxGeneration = generation;
619
1349
  if (state.generation === generation) {
620
1350
  state.available = false;
@@ -638,6 +1368,7 @@ function teardown(state, ctx) {
638
1368
  try {
639
1369
  await state.fiber?.dispose();
640
1370
  } catch (error) {
1371
+ createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).fault("auxiliary-model.dispose-failed", "Shared auxiliary model owner fiber failed to dispose.", error);
641
1372
  try {
642
1373
  ctx.logger?.warn("auxiliary-model: fiber dispose failed", error);
643
1374
  } catch {}
@@ -831,56 +1562,89 @@ async function callLlmText(llm, request) {
831
1562
  request.signal.throwIfAborted();
832
1563
  const live = () => request.isCurrent?.() !== false && !request.signal.aborted;
833
1564
  if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
834
- const effort = request.route.reasoningEffort?.trim();
835
- const prepared = await llm.prepareCall({
1565
+ const started = Date.now();
1566
+ const logBase = {
1567
+ plugin: request.plugin,
1568
+ feature: request.feature ?? null,
836
1569
  provider: request.route.provider,
837
1570
  model: request.route.model,
838
- ...effort ? { reasoningEffort: ReasoningEffortId(effort) } : {},
839
- maxTokens: request.maxTokens
840
- }, request.signal, request.configure);
841
- if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
842
- const options = {
843
- provider: prepared.config.provider,
844
- model: prepared.config.model,
845
- ...prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {},
846
- ...prepared.config.maxTokens !== void 0 ? { maxTokens: Math.min(request.maxTokens, prepared.config.maxTokens) } : { maxTokens: request.maxTokens },
847
- ...prepared.config.temperature !== void 0 ? { temperature: prepared.config.temperature } : {},
848
- ...prepared.config.stop ? { stop: [...prepared.config.stop] } : {},
849
- system: request.system,
850
- messages: [createUserMessage({
851
- source: producerMessageSource(request.plugin),
852
- content: [{
853
- type: "text",
854
- text: buildAuxiliaryUserText(request)
855
- }]
856
- })],
857
- signal: request.signal,
858
- ...request.purpose ? { purpose: request.purpose } : {},
859
- ...request.sessionId ? { sessionId: request.sessionId } : {}
1571
+ purpose: request.purpose ?? null,
1572
+ sessionId: request.sessionId ?? null
860
1573
  };
861
- const assembler = await readStream(prepared.stream(options), request.signal, live);
862
- if (request.signal.aborted || assembler.finish.kind === "aborted") throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
863
- if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
864
- if (assembler.finish.kind === "error") {
865
- const code = assembler.finish.failure.code;
866
- throw Object.assign(new Error(assembler.finish.failure.message || "模型调用失败。"), { code });
1574
+ request.logger?.debug("llm.call.started", "Auxiliary model call started.", logBase);
1575
+ try {
1576
+ const effort = request.route.reasoningEffort?.trim();
1577
+ const prepared = await llm.prepareCall({
1578
+ provider: request.route.provider,
1579
+ model: request.route.model,
1580
+ ...effort ? { reasoningEffort: ReasoningEffortId(effort) } : {},
1581
+ ...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {}
1582
+ }, request.signal, request.configure);
1583
+ if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
1584
+ const maxTokens = request.maxTokens === void 0 ? prepared.config.maxTokens : Math.min(request.maxTokens, prepared.config.maxTokens ?? request.maxTokens);
1585
+ const options = {
1586
+ provider: prepared.config.provider,
1587
+ model: prepared.config.model,
1588
+ ...prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {},
1589
+ ...maxTokens !== void 0 ? { maxTokens } : {},
1590
+ ...prepared.config.temperature !== void 0 ? { temperature: prepared.config.temperature } : {},
1591
+ ...prepared.config.stop ? { stop: [...prepared.config.stop] } : {},
1592
+ system: request.system,
1593
+ messages: [createUserMessage({
1594
+ source: producerMessageSource(request.plugin),
1595
+ content: [{
1596
+ type: "text",
1597
+ text: buildAuxiliaryUserText(request)
1598
+ }]
1599
+ })],
1600
+ signal: request.signal,
1601
+ ...request.purpose ? { purpose: request.purpose } : {},
1602
+ ...request.sessionId ? { sessionId: request.sessionId } : {}
1603
+ };
1604
+ const assembler = await readStream(prepared.stream(options), request.signal, live);
1605
+ if (request.signal.aborted || assembler.finish.kind === "aborted") throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
1606
+ if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
1607
+ if (assembler.finish.kind === "error") {
1608
+ const code = assembler.finish.failure.code;
1609
+ throw Object.assign(new Error(assembler.finish.failure.message || "模型调用失败。"), { code });
1610
+ }
1611
+ if (assembler.blocks().some((block) => block.type === "tool-call") || assembler.finish.kind === "tool-calls") throw new Error("这次调用不使用工具。");
1612
+ if (assembler.finish.kind !== "stop") throw new Error("模型调用未完成。");
1613
+ const usage = assembler.usage;
1614
+ request.logger?.debug("llm.call.completed", "Auxiliary model call completed.", {
1615
+ ...logBase,
1616
+ provider: prepared.config.provider,
1617
+ model: prepared.config.model,
1618
+ durationMs: Date.now() - started,
1619
+ ...usage?.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
1620
+ ...usage?.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
1621
+ ...usage?.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {}
1622
+ });
1623
+ return {
1624
+ text: joinText(assembler),
1625
+ provider: prepared.config.provider,
1626
+ model: prepared.config.model,
1627
+ ...usage ? {
1628
+ inputTokens: usage.inputTokens,
1629
+ outputTokens: usage.outputTokens,
1630
+ ...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
1631
+ ...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
1632
+ ...usage.cacheWriteTokens !== void 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
1633
+ ...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {}
1634
+ } : {}
1635
+ };
1636
+ } catch (error) {
1637
+ const durationMs = Date.now() - started;
1638
+ if (request.signal.aborted || error instanceof Error && error.name === "AbortError") request.logger?.debug("llm.call.cancelled", "Auxiliary model call cancelled.", {
1639
+ ...logBase,
1640
+ durationMs
1641
+ });
1642
+ else request.logger?.warn("llm.call.failed", "Auxiliary model call failed.", error, {
1643
+ ...logBase,
1644
+ durationMs
1645
+ });
1646
+ throw error;
867
1647
  }
868
- if (assembler.blocks().some((block) => block.type === "tool-call") || assembler.finish.kind === "tool-calls") throw new Error("这次调用不使用工具。");
869
- if (assembler.finish.kind !== "stop") throw new Error("模型调用未完成。");
870
- const usage = assembler.usage;
871
- return {
872
- text: joinText(assembler),
873
- provider: prepared.config.provider,
874
- model: prepared.config.model,
875
- ...usage ? {
876
- inputTokens: usage.inputTokens,
877
- outputTokens: usage.outputTokens,
878
- ...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
879
- ...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
880
- ...usage.cacheWriteTokens !== void 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
881
- ...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {}
882
- } : {}
883
- };
884
1648
  }
885
1649
  //#endregion
886
1650
  //#region src/input.ts