@klarkxy/dsh-git-commit 0.3.4 → 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,12 +1,14 @@
1
1
  import { _ as normalizeFallback, b as normalizeModelRoute, c as MAX_UNTRACKED_PREVIEW_CHARS, g as defaultSettings, h as SETTINGS_KEY, l as PLUGIN_NAME, o as MAX_MANIFEST_CHARS, p as RPC_CHANNEL, t as COMMIT_PLAN_FEATURE, y as modelRouteOverride } from "./contracts2.js";
2
2
  import { createRequire } from "node:module";
3
+ import { AsyncLocalStorage } from "node:async_hooks";
4
+ import { appendFileSync, constants, existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, unlinkSync } from "node:fs";
5
+ import { homedir, tmpdir } from "node:os";
6
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { fileURLToPath } from "node:url";
3
8
  import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
4
9
  import { execFile } from "node:child_process";
5
10
  import { createHash } from "node:crypto";
6
- import { constants } from "node:fs";
7
11
  import { copyFile, lstat, mkdtemp, open, readFile, readlink, realpath, rm } from "node:fs/promises";
8
- import { tmpdir } from "node:os";
9
- import { isAbsolute, join, relative, resolve } from "node:path";
10
12
  import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
11
13
  import { z } from "zod";
12
14
  //#region ../dsh-plugin-kit/src/contracts.ts
@@ -60,12 +62,648 @@ function getAuxiliaryModel(host) {
60
62
  return modelRouteOverride(shared.snapshot().model ?? void 0);
61
63
  }
62
64
  //#endregion
65
+ //#region ../dsh-plugin-kit/src/log-shared.ts
66
+ const PLUGIN_LOG_LEVELS = [
67
+ "debug",
68
+ "info",
69
+ "warn",
70
+ "error",
71
+ "fault"
72
+ ];
73
+ const DEFAULT_PLUGIN_LOG_LEVEL = "info";
74
+ const PLUGIN_LOG_MAX_MESSAGE_CHARS = 16e3;
75
+ const SOURCE_RE = /^[a-z][a-z0-9-]{0,63}$/;
76
+ const EVENT_RE = /^[a-z][a-z0-9._-]{0,127}$/;
77
+ const CONTEXT_KEY_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,63}$/;
78
+ 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;
79
+ const CHAT_PAYLOAD_RE = /"role"\s*:\s*"(?:system|user|assistant|tool)"|"(?:messages|content|prompt|input_text|input_image|image_url)"\s*:\s*[\[{"']/i;
80
+ const CREDENTIAL_BODY_RE = /-----BEGIN (?:RSA |EC |OPENSSH |PRIVATE )?PRIVATE KEY-----|"type"\s*:\s*"service_account"|credentials\.json|\.dsh[/\\](?:credentials|auth)/i;
81
+ const AUTH_HEADER_RE = /\b(Authorization|Proxy-Authorization|Cookie|Set-Cookie)\s*:\s*[^\r\n]*/gi;
82
+ const BEARER_RE = /\bBearer\s+[A-Za-z0-9._\-+=/]{8,}/g;
83
+ 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;
84
+ const SECRET_QUERY_RE = /([?&](?:token|api[_-]?key|key|secret|password|bootstrap|code|sig|signature|credential|password)=)[^\s&#"']+/gi;
85
+ const URL_CREDENTIAL_RE = /\b(https?:\/\/)[^\s/:@]+:[^\s/@]+@/gi;
86
+ const BOOTSTRAP_RE = /\/bootstrap\/[^\s"'?&#]+/gi;
87
+ 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;
88
+ const PATH_RE = /(?:[A-Za-z]:\\|\\\\)[^\s"'`<>|]+|(?:\/(?:home|Users|tmp|var|root)\/|~\/)[^\s"'`<>|]+/g;
89
+ function isPluginLogLevel(value) {
90
+ return typeof value === "string" && PLUGIN_LOG_LEVELS.includes(value);
91
+ }
92
+ function pluginLogLevelEnabled(level, minimum) {
93
+ return PLUGIN_LOG_LEVELS.indexOf(level) >= PLUGIN_LOG_LEVELS.indexOf(minimum);
94
+ }
95
+ function normalizePluginLogSource(raw) {
96
+ const stripped = String(raw || "").trim().replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
97
+ const bounded = (/^[a-z]/.test(stripped) ? stripped : `p-${stripped}`).slice(0, 64).replace(/-+$/g, "");
98
+ return SOURCE_RE.test(bounded) ? bounded : "dsh-plugin";
99
+ }
100
+ function normalizePluginLogEvent(raw) {
101
+ const cleaned = String(raw || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 128);
102
+ return EVENT_RE.test(cleaned) ? cleaned : "plugin.event";
103
+ }
104
+ /**
105
+ * Parse a level specification like `debug` or `dsh-zhihu=debug,dsh-dot:warn,info`.
106
+ * Source-specific rules win over a bare fallback level.
107
+ */
108
+ function parsePluginLogLevel(spec, source, fallback = DEFAULT_PLUGIN_LOG_LEVEL) {
109
+ if (!spec || typeof spec !== "string") return fallback;
110
+ const normalizedSource = source ? normalizePluginLogSource(source) : void 0;
111
+ let wildcard;
112
+ for (const token of spec.split(",").map((part) => part.trim()).filter(Boolean)) {
113
+ const lowered = token.toLowerCase();
114
+ if (isPluginLogLevel(lowered)) {
115
+ wildcard = lowered;
116
+ continue;
117
+ }
118
+ const match = /^([^=:]+)[=:]([a-z]+)$/i.exec(token);
119
+ if (!match) continue;
120
+ const target = match[1].trim();
121
+ const candidate = match[2].trim().toLowerCase();
122
+ if (!isPluginLogLevel(candidate)) continue;
123
+ if (target === "*") {
124
+ wildcard = candidate;
125
+ continue;
126
+ }
127
+ if (normalizedSource && normalizePluginLogSource(target) === normalizedSource) return candidate;
128
+ }
129
+ return wildcard ?? fallback;
130
+ }
131
+ function sanitizePluginLogText(text, maxChars = PLUGIN_LOG_MAX_MESSAGE_CHARS) {
132
+ if (!text) return "";
133
+ if (CREDENTIAL_BODY_RE.test(text)) return "[omitted credential material]";
134
+ if (CHAT_PAYLOAD_RE.test(text)) return "[omitted chat payload]";
135
+ return truncateLogText(text.replace(AUTH_HEADER_RE, "$1: [redacted]").replace(BEARER_RE, "Bearer [redacted]").replace(SECRET_FIELD_RE, (full, prefix) => {
136
+ const value = full.slice(prefix.length);
137
+ if (value.startsWith("\"")) return `${prefix}"[redacted]"`;
138
+ if (value.startsWith("'")) return `${prefix}'[redacted]'`;
139
+ return `${prefix}[redacted]`;
140
+ }).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);
141
+ }
142
+ function sanitizePluginLogContext(input) {
143
+ if (!input) return void 0;
144
+ const output = {};
145
+ for (const [key, value] of Object.entries(input).slice(0, 32)) {
146
+ if (value === void 0 || !CONTEXT_KEY_RE.test(key)) continue;
147
+ if (typeof value === "number" && !Number.isFinite(value)) continue;
148
+ if (SECRET_KEY_RE.test(key)) {
149
+ output[key] = "[redacted]";
150
+ continue;
151
+ }
152
+ output[key] = typeof value === "string" ? sanitizePluginLogText(value, 1e3) : value;
153
+ }
154
+ if (!Object.keys(output).length) return void 0;
155
+ if (JSON.stringify(output).length <= 8e3) return output;
156
+ return {
157
+ omitted: true,
158
+ reason: "Diagnostic context exceeded its size limit."
159
+ };
160
+ }
161
+ function sanitizePluginLogError(error) {
162
+ if (error === void 0) return void 0;
163
+ const row = error && typeof error === "object" ? error : {};
164
+ const rawMessage = typeof row.message === "string" ? row.message : String(error);
165
+ const result = {
166
+ name: typeof row.name === "string" && row.name.trim() ? truncateLogText(row.name.trim(), 100) : "Error",
167
+ message: sanitizePluginLogText(rawMessage) || "Unknown error"
168
+ };
169
+ if (typeof row.stack === "string" && row.stack.trim()) result.stack = sanitizePluginLogText(row.stack, 24e3);
170
+ if (typeof row.code === "string" && /^[A-Za-z0-9_./:-]{1,100}$/.test(row.code)) result.code = row.code;
171
+ else if (typeof row.code === "number" && Number.isFinite(row.code)) result.code = String(row.code);
172
+ return result;
173
+ }
174
+ function mergePluginLogContext(base, extra) {
175
+ if (!base) return extra;
176
+ if (!extra) return base;
177
+ return {
178
+ ...base,
179
+ ...extra
180
+ };
181
+ }
182
+ function buildPluginLogEntry(input, meta) {
183
+ const entry = {
184
+ version: 1,
185
+ at: meta.at.toISOString(),
186
+ level: isPluginLogLevel(input.level) ? input.level : "info",
187
+ source: normalizePluginLogSource(meta.source),
188
+ surface: meta.surface,
189
+ ...meta.profile ? { profile: meta.profile } : {},
190
+ event: normalizePluginLogEvent(input.event),
191
+ message: sanitizePluginLogText(input.message || "No diagnostic message was provided.", PLUGIN_LOG_MAX_MESSAGE_CHARS),
192
+ ...meta.pid !== void 0 ? { pid: meta.pid } : {}
193
+ };
194
+ const context = sanitizePluginLogContext(mergePluginLogContext(meta.correlation, input.context));
195
+ if (context) entry.context = context;
196
+ const error = sanitizePluginLogError(input.error);
197
+ if (error) entry.error = error;
198
+ return entry;
199
+ }
200
+ function createChildPluginLogger(parent, bound) {
201
+ return {
202
+ get source() {
203
+ return parent.source;
204
+ },
205
+ get file() {
206
+ return parent.file;
207
+ },
208
+ get lastWriteError() {
209
+ return parent.lastWriteError;
210
+ },
211
+ debug: (event, message, context) => parent.debug(event, message, mergePluginLogContext(bound, context)),
212
+ info: (event, message, context) => parent.info(event, message, mergePluginLogContext(bound, context)),
213
+ warn: (event, message, error, context) => parent.warn(event, message, error, mergePluginLogContext(bound, context)),
214
+ error: (event, message, error, context) => parent.error(event, message, error, mergePluginLogContext(bound, context)),
215
+ fault: (event, message, error, context) => parent.fault(event, message, error, mergePluginLogContext(bound, context)),
216
+ write: (input) => parent.write({
217
+ ...input,
218
+ context: mergePluginLogContext(bound, input.context)
219
+ }),
220
+ child: (next) => createChildPluginLogger(parent, mergePluginLogContext(bound, next) ?? {})
221
+ };
222
+ }
223
+ function truncateLogText(text, maxChars) {
224
+ if (text.length <= maxChars) return text;
225
+ const marker = "[truncated]\n";
226
+ if (maxChars <= 12) return text.slice(0, Math.max(0, maxChars));
227
+ return `${marker}${text.slice(-(maxChars - 12))}`;
228
+ }
229
+ const PROFILE_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
230
+ 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$/;
231
+ const correlationStorage = new AsyncLocalStorage();
232
+ const processFileLoggers = /* @__PURE__ */ new Map();
233
+ let operationSequence = 0;
234
+ function safeProfileName(profile) {
235
+ const trimmed = String(profile ?? "").trim();
236
+ if (PROFILE_RE.test(trimmed)) return trimmed.toLowerCase();
237
+ const cleaned = trimmed.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
238
+ return cleaned && PROFILE_RE.test(cleaned) ? cleaned : "default";
239
+ }
240
+ function pluginLogDirectory(home, profile = "default") {
241
+ return join(resolve(home), "logs", "plugins", safeProfileName(profile));
242
+ }
243
+ function resolveHostLogTarget(ctx, options = {}) {
244
+ const env = options.env ?? process.env;
245
+ if (options.directory?.trim()) {
246
+ const dir = resolve(options.directory.trim());
247
+ return {
248
+ directory: dir,
249
+ anchor: dir,
250
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
251
+ };
252
+ }
253
+ if (options.home?.trim()) {
254
+ const home = resolve(options.home.trim());
255
+ const profile = safeProfileName(options.profile);
256
+ return {
257
+ directory: pluginLogDirectory(home, profile),
258
+ anchor: home,
259
+ profile
260
+ };
261
+ }
262
+ if (env.DSH_PLUGIN_LOG_DIR?.trim()) {
263
+ const dir = resolve(env.DSH_PLUGIN_LOG_DIR.trim());
264
+ return {
265
+ directory: dir,
266
+ anchor: dir,
267
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
268
+ };
269
+ }
270
+ const host = ctx && typeof ctx === "object" ? ctx : void 0;
271
+ let profileCtx;
272
+ try {
273
+ profileCtx = host?.get?.("profileContext", false);
274
+ } catch {
275
+ try {
276
+ profileCtx = host?.get?.("profileContext");
277
+ } catch {
278
+ profileCtx = void 0;
279
+ }
280
+ }
281
+ if (typeof profileCtx?.home === "string" && profileCtx.home.trim() && typeof profileCtx.dir === "string" && profileCtx.dir.trim()) {
282
+ const home = resolve(profileCtx.home.trim());
283
+ const dir = resolve(profileCtx.dir.trim());
284
+ if (isInside(home, dir)) {
285
+ const profile = safeProfileName(typeof profileCtx.name === "string" ? profileCtx.name : basename(dir));
286
+ return {
287
+ directory: pluginLogDirectory(home, profile),
288
+ anchor: home,
289
+ profile
290
+ };
291
+ }
292
+ }
293
+ if (!Boolean(env.VITEST || env.NODE_TEST_CONTEXT) && typeof host?.baseUrl === "string" && host.baseUrl.trim()) {
294
+ const basePath = pathFromBaseUrl(host.baseUrl);
295
+ const configuredHome = resolve((env.DSH_HOME?.trim() || join(homedir(), ".dsh")).trim());
296
+ const profilesRoot = join(configuredHome, "profiles");
297
+ if (basePath && isInside(profilesRoot, basePath) && !samePath(profilesRoot, basePath)) {
298
+ const firstSegment = basePath.slice(profilesRoot.length).replace(/^[/\\]+/, "").split(/[/\\]/)[0];
299
+ if (firstSegment) {
300
+ const profile = safeProfileName(firstSegment);
301
+ return {
302
+ directory: pluginLogDirectory(configuredHome, profile),
303
+ anchor: configuredHome,
304
+ profile
305
+ };
306
+ }
307
+ }
308
+ }
309
+ return {
310
+ directory: null,
311
+ anchor: null,
312
+ ...options.profile ? { profile: safeProfileName(options.profile) } : {}
313
+ };
314
+ }
315
+ /**
316
+ * Synchronous, append-only rotating JSONL logger modeled on DSH Spaces
317
+ * diagnostics: 7-day or 100 MiB active rotation, 7-day / 100 MiB archive
318
+ * retention, secret and path redaction, and non-throwing write resilience.
319
+ */
320
+ var RotatingPluginLogger = class {
321
+ source;
322
+ file;
323
+ lastError = null;
324
+ size = 0;
325
+ openedAt = 0;
326
+ initialized = false;
327
+ writing = false;
328
+ sequence = 0;
329
+ loggedWriteFailure = false;
330
+ recent = [];
331
+ directory;
332
+ anchor;
333
+ profile;
334
+ minimum;
335
+ now;
336
+ pid;
337
+ maxFileBytes;
338
+ maxArchiveBytes;
339
+ maxAgeMs;
340
+ recentLimit;
341
+ constructor(source, options = {}) {
342
+ this.source = normalizePluginLogSource(source);
343
+ const env = options.env ?? process.env;
344
+ const resolved = options.directory !== void 0 || options.home !== void 0 ? resolveHostLogTarget(void 0, options) : {
345
+ directory: null,
346
+ anchor: options.anchor ?? null,
347
+ profile: options.profile
348
+ };
349
+ this.directory = resolved.directory;
350
+ this.anchor = options.anchor ?? resolved.anchor;
351
+ this.profile = options.profile ?? resolved.profile;
352
+ this.file = this.directory ? join(this.directory, `${this.source}.jsonl`) : null;
353
+ this.minimum = options.minimumLevel ?? parsePluginLogLevel(env.DSH_PLUGIN_LOG_LEVEL, this.source, "info");
354
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
355
+ this.pid = options.pid ?? process.pid;
356
+ this.maxFileBytes = options.maxFileBytes ?? 104857600;
357
+ this.maxArchiveBytes = options.maxArchiveBytes ?? 104857600;
358
+ this.maxAgeMs = options.maxAgeMs ?? 6048e5;
359
+ this.recentLimit = Math.max(1, options.recentLimit ?? 200);
360
+ }
361
+ get lastWriteError() {
362
+ return this.lastError;
363
+ }
364
+ recentEntries(limit = this.recentLimit) {
365
+ return this.recent.slice(-Math.max(0, limit));
366
+ }
367
+ debug(event, message, context) {
368
+ return this.write({
369
+ level: "debug",
370
+ event,
371
+ message,
372
+ ...context ? { context } : {}
373
+ });
374
+ }
375
+ info(event, message, context) {
376
+ return this.write({
377
+ level: "info",
378
+ event,
379
+ message,
380
+ ...context ? { context } : {}
381
+ });
382
+ }
383
+ warn(event, message, error, context) {
384
+ return this.write({
385
+ level: "warn",
386
+ event,
387
+ message,
388
+ ...error !== void 0 ? { error } : {},
389
+ ...context ? { context } : {}
390
+ });
391
+ }
392
+ error(event, message, error, context) {
393
+ return this.write({
394
+ level: "error",
395
+ event,
396
+ message,
397
+ ...error !== void 0 ? { error } : {},
398
+ ...context ? { context } : {}
399
+ });
400
+ }
401
+ fault(event, message, error, context) {
402
+ return this.write({
403
+ level: "fault",
404
+ event,
405
+ message,
406
+ ...error !== void 0 ? { error } : {},
407
+ ...context ? { context } : {}
408
+ });
409
+ }
410
+ child(context) {
411
+ return createChildPluginLogger(this, context);
412
+ }
413
+ write(input) {
414
+ if (!pluginLogLevelEnabled(input.level, this.minimum) || this.writing) return false;
415
+ const at = this.now();
416
+ let entry;
417
+ let line;
418
+ try {
419
+ entry = buildPluginLogEntry(input, {
420
+ at,
421
+ source: this.source,
422
+ surface: "host",
423
+ profile: this.profile,
424
+ pid: this.pid,
425
+ correlation: correlationStorage.getStore()
426
+ });
427
+ line = `${JSON.stringify(entry)}\n`;
428
+ } catch {
429
+ return false;
430
+ }
431
+ this.pushRecent(entry);
432
+ if (!this.directory || !this.file) return true;
433
+ this.writing = true;
434
+ try {
435
+ this.openManagedDirectory();
436
+ this.prepare(at);
437
+ if (this.size > 0 && (this.size + Buffer.byteLength(line) > this.maxFileBytes || at.getTime() - this.openedAt >= this.maxAgeMs)) this.rotate(at);
438
+ appendFileSync(this.file, line, {
439
+ encoding: "utf8",
440
+ mode: 384
441
+ });
442
+ this.size += Buffer.byteLength(line);
443
+ this.prune(at);
444
+ this.lastError = null;
445
+ this.loggedWriteFailure = false;
446
+ return true;
447
+ } catch (error) {
448
+ const detail = sanitizePluginLogText(error instanceof Error ? error.message : String(error), 500);
449
+ this.lastError = `The plugin diagnostic log could not be written: ${detail}`;
450
+ this.initialized = false;
451
+ if (!this.loggedWriteFailure) {
452
+ this.loggedWriteFailure = true;
453
+ try {
454
+ process.stderr.write(`DSH Plugin (${this.source}): ${this.lastError}. The original operation continues unchanged.\n`);
455
+ } catch {}
456
+ }
457
+ return false;
458
+ } finally {
459
+ this.writing = false;
460
+ }
461
+ }
462
+ pushRecent(entry) {
463
+ this.recent.push(entry);
464
+ const overflow = this.recent.length - this.recentLimit;
465
+ if (overflow > 0) this.recent.splice(0, overflow);
466
+ }
467
+ prepare(at) {
468
+ if (this.initialized || !this.file) return;
469
+ this.openManagedDirectory();
470
+ const stat = this.regularFile(this.file);
471
+ this.size = stat?.size ?? 0;
472
+ const stamps = stat ? [stat.birthtimeMs, stat.mtimeMs].filter((value) => value > 0) : [];
473
+ this.openedAt = stamps.length ? Math.min(...stamps) : at.getTime();
474
+ this.initialized = true;
475
+ }
476
+ rotate(at) {
477
+ if (!this.directory || !this.file) return;
478
+ const stamp = at.toISOString().replace(/:/g, "-");
479
+ const name = `${this.source}.${stamp}.${this.pid}.${this.sequence++}.jsonl`;
480
+ if (!ARCHIVE_RE.test(name)) throw new Error("Diagnostic log archive name is invalid.");
481
+ const archive = join(this.directory, name);
482
+ if (lexists(archive)) throw new Error("Diagnostic log archive already exists.");
483
+ try {
484
+ renameSync(this.file, archive);
485
+ } catch (error) {
486
+ if (error.code === "ENOENT") {
487
+ this.size = 0;
488
+ this.openedAt = at.getTime();
489
+ return;
490
+ }
491
+ throw error;
492
+ }
493
+ this.size = 0;
494
+ this.openedAt = at.getTime();
495
+ }
496
+ rotateStaleActive(at) {
497
+ if (!this.file) return;
498
+ const stat = this.regularFile(this.file);
499
+ if (!stat || stat.size === 0) return;
500
+ if (at.getTime() - stat.mtimeMs < this.maxAgeMs) return;
501
+ this.rotate(at);
502
+ }
503
+ prune(at) {
504
+ if (!this.directory) return;
505
+ this.rotateStaleActive(at);
506
+ const archives = [];
507
+ let total = 0;
508
+ for (const name of readdirSync(this.directory)) {
509
+ if (!ARCHIVE_RE.test(name) || !name.startsWith(`${this.source}.`)) continue;
510
+ const file = join(this.directory, name);
511
+ const stat = lstatSync(file);
512
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("Diagnostic log archive is not a regular file.");
513
+ if (at.getTime() - stat.mtimeMs >= this.maxAgeMs) {
514
+ try {
515
+ unlinkSync(file);
516
+ } catch {}
517
+ continue;
518
+ }
519
+ archives.push({
520
+ name,
521
+ size: stat.size,
522
+ mtime: stat.mtimeMs
523
+ });
524
+ total += stat.size;
525
+ }
526
+ archives.sort((left, right) => left.mtime - right.mtime || left.name.localeCompare(right.name));
527
+ while (total > this.maxArchiveBytes && archives.length) {
528
+ const oldest = archives.shift();
529
+ try {
530
+ unlinkSync(join(this.directory, oldest.name));
531
+ } catch {}
532
+ total -= oldest.size;
533
+ }
534
+ }
535
+ openManagedDirectory() {
536
+ if (!this.directory) return;
537
+ if (this.anchor) {
538
+ if ((existsSync(this.anchor) ? lstatSync(this.anchor) : void 0)?.isSymbolicLink()) throw new Error("Diagnostic log anchor cannot be a symbolic link.");
539
+ }
540
+ mkdirSync(this.directory, {
541
+ recursive: true,
542
+ mode: 448
543
+ });
544
+ const stat = lstatSync(this.directory);
545
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Diagnostic log directory is not a real directory.");
546
+ const realDir = realpathSync(this.directory);
547
+ if (this.anchor) {
548
+ if (!isInside(realpathSync(this.anchor), realDir)) throw new Error("Diagnostic log directory escaped its managed root.");
549
+ }
550
+ }
551
+ regularFile(file) {
552
+ try {
553
+ const stat = lstatSync(file);
554
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("Diagnostic log is not a regular file.");
555
+ const expected = join(realpathSync(dirname(file)), basename(file));
556
+ if (!samePath(realpathSync(file), expected)) throw new Error("Diagnostic log path could not be verified.");
557
+ return stat;
558
+ } catch (error) {
559
+ if (error.code === "ENOENT") return void 0;
560
+ throw error;
561
+ }
562
+ }
563
+ };
564
+ function createPluginLogger(ctxOrSource, maybeSource, maybeOptions) {
565
+ const hasCtx = typeof ctxOrSource !== "string";
566
+ const ctx = hasCtx ? ctxOrSource : void 0;
567
+ const source = hasCtx ? String(maybeSource ?? "dsh-plugin") : ctxOrSource;
568
+ const options = (hasCtx ? maybeOptions : maybeSource) ?? {};
569
+ const normalized = normalizePluginLogSource(source);
570
+ const target = resolveHostLogTarget(ctx, options);
571
+ let backing;
572
+ 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) {
573
+ const key = resolve(target.directory, `${normalized}.jsonl`);
574
+ let shared = processFileLoggers.get(key);
575
+ if (!shared) {
576
+ shared = new RotatingPluginLogger(normalized, {
577
+ ...options,
578
+ directory: target.directory,
579
+ anchor: target.anchor,
580
+ profile: target.profile
581
+ });
582
+ processFileLoggers.set(key, shared);
583
+ }
584
+ backing = shared;
585
+ } else backing = new RotatingPluginLogger(normalized, {
586
+ ...options,
587
+ ...target.directory ? {
588
+ directory: target.directory,
589
+ anchor: target.anchor
590
+ } : {},
591
+ ...target.profile ? { profile: target.profile } : {}
592
+ });
593
+ if (options.forwardHostWarnError === false || !ctx || typeof ctx !== "object") return backing;
594
+ return wrapWithHostForwarding(backing, ctx);
595
+ }
596
+ function wrapWithHostForwarding(backing, host) {
597
+ const forward = (input) => {
598
+ if (input.level !== "warn" && input.level !== "error" && input.level !== "fault") return;
599
+ try {
600
+ const hostLogger = host.logger;
601
+ if (!hostLogger) return;
602
+ const detail = input.error instanceof Error ? `${input.message} (${input.error.message})` : input.error !== void 0 ? `${input.message} (${String(input.error)})` : input.message;
603
+ const text = sanitizePluginLogText(`${backing.source} [${input.event}]: ${detail}`, 1e3);
604
+ if (input.level === "warn") {
605
+ if (typeof hostLogger.warn === "function") hostLogger.warn(text);
606
+ } else if (typeof hostLogger.error === "function") hostLogger.error(text);
607
+ else if (typeof hostLogger.warn === "function") hostLogger.warn(text);
608
+ } catch {}
609
+ };
610
+ const wrapped = {
611
+ get source() {
612
+ return backing.source;
613
+ },
614
+ get file() {
615
+ return backing.file;
616
+ },
617
+ get lastWriteError() {
618
+ return backing.lastWriteError;
619
+ },
620
+ debug: (event, message, context) => backing.debug(event, message, context),
621
+ info: (event, message, context) => backing.info(event, message, context),
622
+ warn: (event, message, error, context) => {
623
+ const wrote = backing.warn(event, message, error, context);
624
+ forward({
625
+ level: "warn",
626
+ event,
627
+ message,
628
+ error,
629
+ context
630
+ });
631
+ return wrote;
632
+ },
633
+ error: (event, message, error, context) => {
634
+ const wrote = backing.error(event, message, error, context);
635
+ forward({
636
+ level: "error",
637
+ event,
638
+ message,
639
+ error,
640
+ context
641
+ });
642
+ return wrote;
643
+ },
644
+ fault: (event, message, error, context) => {
645
+ const wrote = backing.fault(event, message, error, context);
646
+ forward({
647
+ level: "fault",
648
+ event,
649
+ message,
650
+ error,
651
+ context
652
+ });
653
+ return wrote;
654
+ },
655
+ write: (input) => {
656
+ const wrote = backing.write(input);
657
+ forward(input);
658
+ return wrote;
659
+ },
660
+ child: (context) => createChildPluginLogger(wrapped, context)
661
+ };
662
+ return wrapped;
663
+ }
664
+ function nextPluginOperationId(prefix = "op") {
665
+ return `${normalizePluginLogSource(prefix)}-${process.pid}-${++operationSequence}`;
666
+ }
667
+ function withPluginLogContext(context, action) {
668
+ const merged = mergePluginLogContext(correlationStorage.getStore(), context) ?? {};
669
+ return correlationStorage.run(merged, action);
670
+ }
671
+ function lexists(file) {
672
+ try {
673
+ lstatSync(file);
674
+ return true;
675
+ } catch (error) {
676
+ if (error.code === "ENOENT") return false;
677
+ throw error;
678
+ }
679
+ }
680
+ function pathFromBaseUrl(baseUrl) {
681
+ try {
682
+ return baseUrl.includes("://") ? resolve(fileURLToPath(baseUrl)) : resolve(baseUrl);
683
+ } catch {
684
+ return null;
685
+ }
686
+ }
687
+ function samePath(left, right) {
688
+ const a = resolve(left);
689
+ const b = resolve(right);
690
+ return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
691
+ }
692
+ function isInside(root, target) {
693
+ const rootResolved = resolve(root);
694
+ const targetResolved = resolve(target);
695
+ if (samePath(rootResolved, targetResolved)) return true;
696
+ const prefix = rootResolved.endsWith(sep) ? rootResolved : rootResolved + sep;
697
+ return process.platform === "win32" ? targetResolved.toLowerCase().startsWith(prefix.toLowerCase()) : targetResolved.startsWith(prefix);
698
+ }
699
+ //#endregion
63
700
  //#region ../dsh-plugin-kit/src/host-rpc.ts
64
701
  /** The shared authenticated HTTP boundary. Each plugin still owns its RPC envelope semantics. */
65
702
  function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
66
703
  const bodyLimit = options.maxBodyBytes ?? 65536;
67
704
  if (!Number.isSafeInteger(bodyLimit) || bodyLimit <= 0) throw new Error("Invalid RPC body limit");
68
705
  const isEndpoint = options.isEndpoint ?? ((endpoint) => /^[a-z.]+$/.test(endpoint));
706
+ const log = options.logger;
69
707
  return ctx.webServer.register({
70
708
  kind: "prefix",
71
709
  path: channel,
@@ -76,29 +714,54 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
76
714
  else res.writeHead(status);
77
715
  res.end(override ?? options.errorBody?.(status, error) ?? (status === 500 ? "rpc request failed" : ""));
78
716
  };
717
+ let endpoint = "";
79
718
  try {
80
719
  if (typeof ctx.connection.requestRejection !== "function") {
720
+ log?.error("rpc.admission.unavailable", "Host RPC admission policy is unavailable.", void 0, {
721
+ channel,
722
+ status: 503
723
+ });
81
724
  reject(503);
82
725
  return;
83
726
  }
84
727
  const rejection = ctx.connection.requestRejection(req);
85
728
  if (rejection !== void 0) {
729
+ log?.warn("rpc.admission.rejected", "Host RPC request rejected by connection policy.", void 0, {
730
+ channel,
731
+ status: rejection
732
+ });
86
733
  reject(rejection);
87
734
  return;
88
735
  }
89
736
  const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
90
- const endpoint = pathname.startsWith(`${channel}/`) ? pathname.slice(channel.length + 1) : "";
737
+ endpoint = pathname.startsWith(`${channel}/`) ? pathname.slice(channel.length + 1) : "";
91
738
  if (req.method !== "POST" || !isEndpoint(endpoint)) {
739
+ log?.debug("rpc.route.rejected", "Host RPC route or method rejected.", {
740
+ channel,
741
+ endpoint: endpoint || null,
742
+ method: req.method ?? null,
743
+ status: 404
744
+ });
92
745
  reject(404);
93
746
  return;
94
747
  }
95
748
  const contentType = String(req.headers["content-type"] ?? "").split(";", 1)[0]?.trim();
96
749
  if ((options.caseInsensitiveContentType ? contentType.toLowerCase() : contentType) !== "application/json") {
750
+ log?.debug("rpc.content-type.rejected", "Host RPC content-type rejected.", {
751
+ channel,
752
+ endpoint,
753
+ status: 415
754
+ });
97
755
  reject(415);
98
756
  return;
99
757
  }
100
758
  const guarded = options.beforeBody?.(req, endpoint);
101
759
  if (guarded) {
760
+ log?.debug("rpc.guard.rejected", "Host RPC request rejected before body read.", {
761
+ channel,
762
+ endpoint,
763
+ status: guarded.status
764
+ });
102
765
  reject(guarded.status, void 0, guarded.body);
103
766
  return;
104
767
  }
@@ -112,6 +775,11 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
112
775
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
113
776
  size += buffer.length;
114
777
  if (size > bodyLimit) {
778
+ log?.warn("rpc.body.too-large", "Host RPC request exceeded body limit.", void 0, {
779
+ channel,
780
+ endpoint,
781
+ maxBodyBytes: bodyLimit
782
+ });
115
783
  reject(413);
116
784
  req.destroy();
117
785
  return;
@@ -122,11 +790,26 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
122
790
  try {
123
791
  body = JSON.parse(Buffer.concat(chunks).toString("utf8") || (options.emptyBodyAsNull ? "null" : ""));
124
792
  } catch {
793
+ log?.debug("rpc.body.invalid-json", "Host RPC request body is not valid JSON.", {
794
+ channel,
795
+ endpoint,
796
+ status: 400
797
+ });
125
798
  reject(400);
126
799
  return;
127
800
  }
128
- await dispatch(endpoint, body, controller.signal, res);
801
+ if (log) await withPluginLogContext({
802
+ rpcChannel: channel,
803
+ rpcEndpoint: endpoint,
804
+ operationId: nextPluginOperationId("rpc")
805
+ }, () => dispatch(endpoint, body, controller.signal, res));
806
+ else await dispatch(endpoint, body, controller.signal, res);
129
807
  } catch (error) {
808
+ log?.error("rpc.dispatch.failed", "Unhandled host RPC handler error.", error, {
809
+ channel,
810
+ endpoint: endpoint || null,
811
+ status: 500
812
+ });
130
813
  reject(500, error);
131
814
  }
132
815
  }
@@ -147,19 +830,41 @@ function sendHostRpcResponse(response, rpcId, result, noStore = true) {
147
830
  }
148
831
  /** Safe host RPC registration. Missing requestRejection fails closed. */
149
832
  function registerHostRpc(ctx, channel, call, options = {}) {
833
+ const log = options.logger;
150
834
  return registerHostRpcTransport(ctx, channel, async (endpoint, body, signal, res) => {
151
835
  if (!body || typeof body !== "object" || Array.isArray(body)) {
836
+ log?.debug("rpc.envelope.invalid", "Host RPC envelope must be a JSON object.", {
837
+ channel,
838
+ endpoint
839
+ });
152
840
  res.writeHead(400);
153
841
  res.end();
154
842
  return;
155
843
  }
156
844
  const request = body;
157
845
  if (request.type !== "client-request" || request.method !== endpoint || typeof request.rpcId !== "string" || options.requirePayload && !Object.hasOwn(request, "payload")) {
846
+ log?.debug("rpc.envelope.invalid", "Host RPC envelope failed validation.", {
847
+ channel,
848
+ endpoint
849
+ });
158
850
  res.writeHead(400);
159
851
  res.end();
160
852
  return;
161
853
  }
854
+ const started = Date.now();
162
855
  const raw = await call(endpoint, request.payload, signal);
856
+ const durationMs = Date.now() - started;
857
+ if (raw.ok) log?.debug("rpc.call.completed", "Host RPC call succeeded.", {
858
+ channel,
859
+ endpoint,
860
+ durationMs
861
+ });
862
+ else log?.warn("rpc.call.error", "Host RPC call returned an application error.", void 0, {
863
+ channel,
864
+ endpoint,
865
+ code: raw.error?.code ?? null,
866
+ durationMs
867
+ });
163
868
  const result = raw.ok ? raw : {
164
869
  ...raw,
165
870
  error: {
@@ -465,7 +1170,13 @@ function setSharedModel(state, ctx, input, expectedRevision) {
465
1170
  if ((Boolean(row.provider) || Boolean(row.model)) && !modelRouteOverride(normalized)) return Promise.reject(/* @__PURE__ */ new Error("Invalid model route"));
466
1171
  const commit = state.tail.then(async () => {
467
1172
  if (state.closed || !state.domain) throw new Error("Auxiliary model settings are unloading; retry in a moment");
468
- if (expectedRevision !== state.data.revision) throw conflictError("Auxiliary model settings changed; reload and retry");
1173
+ if (expectedRevision !== state.data.revision) {
1174
+ createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).warn("auxiliary-model.conflict", "Auxiliary model update rejected due to revision conflict.", void 0, {
1175
+ expectedRevision: Number(expectedRevision),
1176
+ currentRevision: state.data.revision
1177
+ });
1178
+ throw conflictError("Auxiliary model settings changed; reload and retry");
1179
+ }
469
1180
  const next = {
470
1181
  revision: state.data.revision + 1,
471
1182
  model: modelRouteOverride(normalized) ?? null
@@ -475,6 +1186,11 @@ function setSharedModel(state, ctx, input, expectedRevision) {
475
1186
  model: next.model
476
1187
  });
477
1188
  state.data = next;
1189
+ createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).info("auxiliary-model.updated", "Updated global auxiliary model default.", {
1190
+ revision: next.revision,
1191
+ provider: next.model?.provider ?? null,
1192
+ model: next.model?.model ?? null
1193
+ });
478
1194
  notifyModelSettingsChanged(ctx.root, AUXILIARY_MODEL_NS, next.revision);
479
1195
  return next;
480
1196
  });
@@ -482,6 +1198,7 @@ function setSharedModel(state, ctx, input, expectedRevision) {
482
1198
  return commit;
483
1199
  }
484
1200
  function initScope(state, ctx, scope) {
1201
+ const log = createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false });
485
1202
  const generation = ++state.generation;
486
1203
  let disposed = false;
487
1204
  scope.effect(() => () => {
@@ -513,6 +1230,11 @@ function initScope(state, ctx, scope) {
513
1230
  };
514
1231
  state.error = void 0;
515
1232
  state.available = true;
1233
+ log.info("auxiliary-model.opened", "Shared auxiliary model store opened.", {
1234
+ generation,
1235
+ revision: state.data.revision,
1236
+ hasModel: Boolean(state.data.model)
1237
+ });
516
1238
  scope.effect(() => registerModelSettings(scope, {
517
1239
  ns: AUXILIARY_MODEL_NS,
518
1240
  title: {
@@ -596,7 +1318,10 @@ function initScope(state, ctx, scope) {
596
1318
  message: `Unknown endpoint: ${endpoint}`
597
1319
  }
598
1320
  };
599
- }, { isEndpoint: (endpoint) => /^[a-z.]+$/.test(endpoint) }));
1321
+ }, {
1322
+ isEndpoint: (endpoint) => /^[a-z.]+$/.test(endpoint),
1323
+ logger: log
1324
+ }));
600
1325
  createModelSettingsChanges(scope, AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_NS);
601
1326
  scope.effect(() => () => {
602
1327
  if (state.generation === generation) {
@@ -605,6 +1330,8 @@ function initScope(state, ctx, scope) {
605
1330
  }
606
1331
  });
607
1332
  })().catch((error) => {
1333
+ if (error && typeof error === "object" && error[RECOVERABLE]) log.debug("auxiliary-model.superseded", "Shared auxiliary model init superseded during open.", { generation });
1334
+ else log.error("auxiliary-model.init-failed", "Shared auxiliary model store failed to open.", error, { generation });
608
1335
  if (error && typeof error === "object") error.auxGeneration = generation;
609
1336
  if (state.generation === generation) {
610
1337
  state.available = false;
@@ -628,6 +1355,7 @@ function teardown(state, ctx) {
628
1355
  try {
629
1356
  await state.fiber?.dispose();
630
1357
  } catch (error) {
1358
+ createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).fault("auxiliary-model.dispose-failed", "Shared auxiliary model owner fiber failed to dispose.", error);
631
1359
  try {
632
1360
  ctx.logger?.warn("auxiliary-model: fiber dispose failed", error);
633
1361
  } catch {}
@@ -3843,57 +4571,89 @@ async function callLlmText(llm, request) {
3843
4571
  request.signal.throwIfAborted();
3844
4572
  const live = () => request.isCurrent?.() !== false && !request.signal.aborted;
3845
4573
  if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
3846
- const effort = request.route.reasoningEffort?.trim();
3847
- const prepared = await llm.prepareCall({
4574
+ const started = Date.now();
4575
+ const logBase = {
4576
+ plugin: request.plugin,
4577
+ feature: request.feature ?? null,
3848
4578
  provider: request.route.provider,
3849
4579
  model: request.route.model,
3850
- ...effort ? { reasoningEffort: ReasoningEffortId(effort) } : {},
3851
- ...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {}
3852
- }, request.signal, request.configure);
3853
- if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
3854
- const maxTokens = request.maxTokens === void 0 ? prepared.config.maxTokens : Math.min(request.maxTokens, prepared.config.maxTokens ?? request.maxTokens);
3855
- const options = {
3856
- provider: prepared.config.provider,
3857
- model: prepared.config.model,
3858
- ...prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {},
3859
- ...maxTokens !== void 0 ? { maxTokens } : {},
3860
- ...prepared.config.temperature !== void 0 ? { temperature: prepared.config.temperature } : {},
3861
- ...prepared.config.stop ? { stop: [...prepared.config.stop] } : {},
3862
- system: request.system,
3863
- messages: [createUserMessage({
3864
- source: producerMessageSource(request.plugin),
3865
- content: [{
3866
- type: "text",
3867
- text: buildAuxiliaryUserText(request)
3868
- }]
3869
- })],
3870
- signal: request.signal,
3871
- ...request.purpose ? { purpose: request.purpose } : {},
3872
- ...request.sessionId ? { sessionId: request.sessionId } : {}
4580
+ purpose: request.purpose ?? null,
4581
+ sessionId: request.sessionId ?? null
3873
4582
  };
3874
- const assembler = await readStream(prepared.stream(options), request.signal, live);
3875
- if (request.signal.aborted || assembler.finish.kind === "aborted") throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
3876
- if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
3877
- if (assembler.finish.kind === "error") {
3878
- const code = assembler.finish.failure.code;
3879
- throw Object.assign(new Error(assembler.finish.failure.message || "模型调用失败。"), { code });
4583
+ request.logger?.debug("llm.call.started", "Auxiliary model call started.", logBase);
4584
+ try {
4585
+ const effort = request.route.reasoningEffort?.trim();
4586
+ const prepared = await llm.prepareCall({
4587
+ provider: request.route.provider,
4588
+ model: request.route.model,
4589
+ ...effort ? { reasoningEffort: ReasoningEffortId(effort) } : {},
4590
+ ...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {}
4591
+ }, request.signal, request.configure);
4592
+ if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
4593
+ const maxTokens = request.maxTokens === void 0 ? prepared.config.maxTokens : Math.min(request.maxTokens, prepared.config.maxTokens ?? request.maxTokens);
4594
+ const options = {
4595
+ provider: prepared.config.provider,
4596
+ model: prepared.config.model,
4597
+ ...prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {},
4598
+ ...maxTokens !== void 0 ? { maxTokens } : {},
4599
+ ...prepared.config.temperature !== void 0 ? { temperature: prepared.config.temperature } : {},
4600
+ ...prepared.config.stop ? { stop: [...prepared.config.stop] } : {},
4601
+ system: request.system,
4602
+ messages: [createUserMessage({
4603
+ source: producerMessageSource(request.plugin),
4604
+ content: [{
4605
+ type: "text",
4606
+ text: buildAuxiliaryUserText(request)
4607
+ }]
4608
+ })],
4609
+ signal: request.signal,
4610
+ ...request.purpose ? { purpose: request.purpose } : {},
4611
+ ...request.sessionId ? { sessionId: request.sessionId } : {}
4612
+ };
4613
+ const assembler = await readStream(prepared.stream(options), request.signal, live);
4614
+ if (request.signal.aborted || assembler.finish.kind === "aborted") throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
4615
+ if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
4616
+ if (assembler.finish.kind === "error") {
4617
+ const code = assembler.finish.failure.code;
4618
+ throw Object.assign(new Error(assembler.finish.failure.message || "模型调用失败。"), { code });
4619
+ }
4620
+ if (assembler.blocks().some((block) => block.type === "tool-call") || assembler.finish.kind === "tool-calls") throw new Error("这次调用不使用工具。");
4621
+ if (assembler.finish.kind !== "stop") throw new Error("模型调用未完成。");
4622
+ const usage = assembler.usage;
4623
+ request.logger?.debug("llm.call.completed", "Auxiliary model call completed.", {
4624
+ ...logBase,
4625
+ provider: prepared.config.provider,
4626
+ model: prepared.config.model,
4627
+ durationMs: Date.now() - started,
4628
+ ...usage?.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
4629
+ ...usage?.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
4630
+ ...usage?.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {}
4631
+ });
4632
+ return {
4633
+ text: joinText(assembler),
4634
+ provider: prepared.config.provider,
4635
+ model: prepared.config.model,
4636
+ ...usage ? {
4637
+ inputTokens: usage.inputTokens,
4638
+ outputTokens: usage.outputTokens,
4639
+ ...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
4640
+ ...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
4641
+ ...usage.cacheWriteTokens !== void 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
4642
+ ...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {}
4643
+ } : {}
4644
+ };
4645
+ } catch (error) {
4646
+ const durationMs = Date.now() - started;
4647
+ if (request.signal.aborted || error instanceof Error && error.name === "AbortError") request.logger?.debug("llm.call.cancelled", "Auxiliary model call cancelled.", {
4648
+ ...logBase,
4649
+ durationMs
4650
+ });
4651
+ else request.logger?.warn("llm.call.failed", "Auxiliary model call failed.", error, {
4652
+ ...logBase,
4653
+ durationMs
4654
+ });
4655
+ throw error;
3880
4656
  }
3881
- if (assembler.blocks().some((block) => block.type === "tool-call") || assembler.finish.kind === "tool-calls") throw new Error("这次调用不使用工具。");
3882
- if (assembler.finish.kind !== "stop") throw new Error("模型调用未完成。");
3883
- const usage = assembler.usage;
3884
- return {
3885
- text: joinText(assembler),
3886
- provider: prepared.config.provider,
3887
- model: prepared.config.model,
3888
- ...usage ? {
3889
- inputTokens: usage.inputTokens,
3890
- outputTokens: usage.outputTokens,
3891
- ...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
3892
- ...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
3893
- ...usage.cacheWriteTokens !== void 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
3894
- ...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {}
3895
- } : {}
3896
- };
3897
4657
  }
3898
4658
  //#endregion
3899
4659
  //#region src/git.ts