@ontos-ai/knowhere-claw 0.1.0-beta.0 → 0.1.2

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/dist/tools.js CHANGED
@@ -1,13 +1,16 @@
1
1
  import { isRecord } from "./types.js";
2
- import { assertKnowhereApiKey } from "./config.js";
2
+ import { assertKnowhereApiKey, formatPaymentRequiredMessage, isPaymentRequiredError, persistApiKey } from "./config.js";
3
+ import { normalizeForGrep, normalizeWhitespace, sanitizeStringArray, slugify } from "./text.js";
3
4
  import { formatErrorMessage } from "./error-message.js";
4
5
  import { KnowhereClient } from "./client.js";
5
- import { normalizeForGrep, normalizeWhitespace, sanitizeStringArray } from "./text.js";
6
+ import { deliverChannelMessage } from "./channel-delivery.js";
6
7
  import { sendTrackerProgress } from "./tracker-progress.js";
7
- import path from "node:path";
8
8
  import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/core";
9
11
  //#region src/tools.ts
10
12
  const PREVIEW_SUMMARY_MAX_CHARS = 120;
13
+ const INGEST_TRACKER_LANGUAGES = new Set(["ch", "en"]);
11
14
  function textResult(text) {
12
15
  return {
13
16
  content: [{
@@ -17,6 +20,10 @@ function textResult(text) {
17
20
  details: {}
18
21
  };
19
22
  }
23
+ function rethrowWithPaymentHint(error) {
24
+ if (isPaymentRequiredError(error)) throw new Error(formatPaymentRequiredMessage());
25
+ throw error;
26
+ }
20
27
  function deriveStoredDocumentDisplayName(document) {
21
28
  return document.originalFileName || document.title;
22
29
  }
@@ -89,6 +96,59 @@ function buildIngestProgressLabel(params) {
89
96
  return params.url;
90
97
  }
91
98
  }
99
+ function readIngestTrackerLanguage(value) {
100
+ const normalized = readString(value)?.toLowerCase();
101
+ if (!normalized) return "en";
102
+ if (INGEST_TRACKER_LANGUAGES.has(normalized)) return normalized;
103
+ return "en";
104
+ }
105
+ function buildIngestTrackerSuccessText(params) {
106
+ if (params.lang === "ch") return `通知:\`${params.label}\` 已解析完成,可以使用了。任务 ID:\`${params.jobId}\`。`;
107
+ return `Notice: \`${params.label}\` is parsed and ready. Job ID: \`${params.jobId}\`.`;
108
+ }
109
+ function buildIngestTrackerFailureText(params) {
110
+ if (params.lang === "ch") return params.jobId ? `通知:解析 \`${params.label}\` 失败(任务 \`${params.jobId}\`):${params.errorText}` : `通知:解析 \`${params.label}\` 失败:${params.errorText}`;
111
+ return params.jobId ? `Notice: failed to parse \`${params.label}\` (job \`${params.jobId}\`): ${params.errorText}` : `Notice: failed to parse \`${params.label}\`: ${params.errorText}`;
112
+ }
113
+ function queueBackgroundIngestFollowUp(params) {
114
+ const sessionKey = readString(params.sessionKey);
115
+ if (!sessionKey) {
116
+ params.api.logger.warn(`knowhere: background ingest follow-up skipped because sessionKey is missing contextKey=${params.followUp.contextKey}`);
117
+ return false;
118
+ }
119
+ try {
120
+ if (!params.api.runtime.system.enqueueSystemEvent(params.followUp.sessionEventText, {
121
+ sessionKey,
122
+ contextKey: params.followUp.contextKey
123
+ })) {
124
+ params.api.logger.info(`knowhere: background ingest follow-up not queued sessionKey=${sessionKey} contextKey=${params.followUp.contextKey}`);
125
+ return false;
126
+ }
127
+ params.api.runtime.system.requestHeartbeatNow({
128
+ reason: params.followUp.heartbeatReason,
129
+ sessionKey
130
+ });
131
+ params.api.logger.info(`knowhere: background ingest follow-up queued sessionKey=${sessionKey} contextKey=${params.followUp.contextKey}`);
132
+ return true;
133
+ } catch (error) {
134
+ params.api.logger.warn(`knowhere: background ingest follow-up queue failed sessionKey=${sessionKey} contextKey=${params.followUp.contextKey}. ${formatErrorMessage(error)}`);
135
+ return false;
136
+ }
137
+ }
138
+ async function notifyBackgroundIngestOutcome(params) {
139
+ if (await sendTrackerProgress({
140
+ api: params.api,
141
+ context: params.context,
142
+ sessionKey: params.sessionKey,
143
+ channelRoute: params.channelRoute,
144
+ text: params.followUp.trackerText
145
+ })) return;
146
+ queueBackgroundIngestFollowUp({
147
+ api: params.api,
148
+ sessionKey: params.sessionKey,
149
+ followUp: params.followUp
150
+ });
151
+ }
92
152
  function normalizeParsingParams(rawParsing) {
93
153
  const parsing = isRecord(rawParsing) ? rawParsing : {};
94
154
  const result = {};
@@ -196,6 +256,146 @@ function findResultFile(browseIndex, relativePath) {
196
256
  function isTextReadableResultFile(fileRecord) {
197
257
  return fileRecord.kind !== "image";
198
258
  }
259
+ const IMAGE_EXTENSION_MIME_TYPES = {
260
+ ".png": "image/png",
261
+ ".jpg": "image/jpeg",
262
+ ".jpeg": "image/jpeg",
263
+ ".gif": "image/gif",
264
+ ".webp": "image/webp",
265
+ ".svg": "image/svg+xml",
266
+ ".bmp": "image/bmp",
267
+ ".tiff": "image/tiff",
268
+ ".tif": "image/tiff"
269
+ };
270
+ function inferImageMimeType(filePath) {
271
+ return IMAGE_EXTENSION_MIME_TYPES[path.extname(filePath).toLowerCase()] || "image/png";
272
+ }
273
+ async function buildImageToolResult(params) {
274
+ const mimeType = inferImageMimeType(params.absolutePath);
275
+ const stagedImage = await stageImageResultFileForDelivery({
276
+ absolutePath: params.absolutePath,
277
+ documentTitle: params.documentTitle,
278
+ relativePath: params.filePath,
279
+ workspaceDir: params.workspaceDir
280
+ });
281
+ const stagedImagePath = stagedImage.stagedPath;
282
+ const fileName = path.basename(stagedImagePath);
283
+ const caption = `${params.documentTitle} - ${params.filePath}`;
284
+ const directDelivery = await deliverChannelMessage({
285
+ api: params.api,
286
+ operationLabel: "read result image",
287
+ context: params.context,
288
+ sessionKey: params.sessionKey,
289
+ channelRoute: params.channelRoute,
290
+ text: caption,
291
+ mediaUrl: stagedImagePath,
292
+ mediaLocalRoots: [path.dirname(stagedImagePath)]
293
+ });
294
+ if (directDelivery.delivered) {
295
+ const payload = {
296
+ scope: params.scopeLabel,
297
+ docId: params.docId,
298
+ documentTitle: params.documentTitle,
299
+ file: params.file,
300
+ mode: "image_sent",
301
+ data: {
302
+ mimeType,
303
+ sourceRelativePath: params.filePath,
304
+ stagedPath: stagedImagePath,
305
+ fileName,
306
+ caption,
307
+ note: "Image already sent to the current channel by the plugin. Do not call read on stagedPath. Do not call the message tool or attach this file again. If you reply, send only a brief confirmation.",
308
+ delivery: {
309
+ method: "direct_runtime",
310
+ surface: directDelivery.surface,
311
+ target: directDelivery.to,
312
+ accountId: directDelivery.accountId
313
+ }
314
+ }
315
+ };
316
+ return {
317
+ content: [{
318
+ type: "text",
319
+ text: `${JSON.stringify(payload, null, 2)}\n`
320
+ }],
321
+ details: payload
322
+ };
323
+ }
324
+ const sendWithMessageTool = {
325
+ action: "send",
326
+ path: stagedImagePath,
327
+ filePath: stagedImagePath,
328
+ filename: fileName,
329
+ caption
330
+ };
331
+ const replyFallback = stagedImage.workspaceRelativePath ? {
332
+ instructions: "If the message tool is unavailable, send your normal user-visible reply and include this exact line on its own line to attach the image.",
333
+ workspaceRelativePath: stagedImage.workspaceRelativePath,
334
+ replyWithMediaDirective: `MEDIA:${stagedImage.workspaceRelativePath}`
335
+ } : void 0;
336
+ const note = replyFallback ? "Image bytes are not inlined. Do not call read on stagedPath. If the user wants to see this image, use the message tool with sendWithMessageTool. If the message tool is unavailable, send your user-visible reply normally and include replyFallback.replyWithMediaDirective on its own line." : "Image bytes are not inlined. Do not call read on stagedPath. If the user wants to see this image, call the message tool with sendWithMessageTool.";
337
+ const nextActionInstructions = replyFallback ? "Do not call read on stagedPath. Call the message tool with sendWithMessageTool to attach this image. If the message tool is unavailable, use replyFallback.replyWithMediaDirective in your normal reply instead." : "Do not call read on stagedPath. Call the message tool with sendWithMessageTool to attach this image.";
338
+ const payload = {
339
+ scope: params.scopeLabel,
340
+ docId: params.docId,
341
+ documentTitle: params.documentTitle,
342
+ file: params.file,
343
+ mode: "image_attachment",
344
+ data: {
345
+ mimeType,
346
+ sourceRelativePath: params.filePath,
347
+ stagedPath: stagedImagePath,
348
+ fileName,
349
+ caption,
350
+ note,
351
+ nextAction: {
352
+ tool: "message",
353
+ instructions: nextActionInstructions,
354
+ args: sendWithMessageTool
355
+ },
356
+ sendWithMessageTool,
357
+ ...replyFallback ? { replyFallback } : {}
358
+ }
359
+ };
360
+ return {
361
+ content: [{
362
+ type: "text",
363
+ text: `${JSON.stringify(payload, null, 2)}\n`
364
+ }],
365
+ details: payload
366
+ };
367
+ }
368
+ function normalizeWorkspaceDir(workspaceDir) {
369
+ const trimmed = readString(workspaceDir);
370
+ return trimmed ? path.resolve(trimmed) : void 0;
371
+ }
372
+ function toWorkspaceRelativeMediaPath(params) {
373
+ const relativePath = path.relative(params.workspaceDir, params.stagedPath);
374
+ if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) return;
375
+ const normalizedRelativePath = relativePath.split(path.sep).join("/");
376
+ return normalizedRelativePath.startsWith("./") || normalizedRelativePath.startsWith("../") ? normalizedRelativePath : `./${normalizedRelativePath}`;
377
+ }
378
+ async function stageImageResultFileForDelivery(params) {
379
+ const extension = path.extname(params.relativePath) || path.extname(params.absolutePath) || ".png";
380
+ const imageBaseName = path.basename(params.relativePath, extension) || "image";
381
+ const workspaceDir = normalizeWorkspaceDir(params.workspaceDir);
382
+ let stagedDir;
383
+ if (workspaceDir) {
384
+ const workspaceStageRoot = path.join(workspaceDir, ".openclaw");
385
+ await fs.mkdir(workspaceStageRoot, { recursive: true });
386
+ stagedDir = await fs.mkdtemp(path.join(workspaceStageRoot, "knowhere-read-result-file-"));
387
+ } else stagedDir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "knowhere-read-result-file-"));
388
+ const stagedFileName = `${slugify(`${params.documentTitle}-${imageBaseName}`, "knowhere-image")}${extension.toLowerCase()}`;
389
+ const stagedPath = path.join(stagedDir, stagedFileName);
390
+ await fs.copyFile(params.absolutePath, stagedPath);
391
+ return {
392
+ stagedPath,
393
+ ...workspaceDir ? { workspaceRelativePath: toWorkspaceRelativeMediaPath({
394
+ workspaceDir,
395
+ stagedPath
396
+ }) } : {}
397
+ };
398
+ }
199
399
  function stripUtf8Bom(text) {
200
400
  return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
201
401
  }
@@ -292,7 +492,7 @@ function createIngestTool(params) {
292
492
  return {
293
493
  name: "knowhere_ingest_document",
294
494
  label: "Knowhere Ingest",
295
- description: "Parse a local file or remote URL with Knowhere and store the result in the current scope. This uploads the document to the Knowhere API, waits for parsing to complete, downloads the result package, and extracts it locally. After ingestion, use knowhere_preview_document to see the document structure, knowhere_grep to search its content, or knowhere_read_result_file to access raw result files. Provide either filePath or url, not both.",
495
+ description: "Parse a local file or remote URL with Knowhere and store the result in the current scope. Before calling this for a document that might already be stored in the current scope, use knowhere_list_documents and reuse the existing stored document when Source, File, or Title clearly match unless the user explicitly asks for a fresh parse or overwrite. When the user provides a URL to a document (PDF link, web page, etc.), pass it as the url parameter Knowhere fetches it directly, no local download needed. Returns immediately with a job ID while parsing continues in the background. Use knowhere_get_job_status to check progress at any time. Use lang to control the language of the direct tracker follow-up (`en` by default, `ch` for Chinese). Provide either filePath or url, not both.",
296
496
  parameters: {
297
497
  type: "object",
298
498
  additionalProperties: false,
@@ -334,6 +534,10 @@ function createIngestTool(params) {
334
534
  type: "boolean",
335
535
  description: "Replace an existing stored document with the same docId."
336
536
  },
537
+ lang: {
538
+ type: "string",
539
+ description: "Language for the direct tracker follow-up message sent after background parsing completes or fails. Supports en and ch; unsupported values fall back to en."
540
+ },
337
541
  parsing: {
338
542
  type: "object",
339
543
  additionalProperties: false,
@@ -387,31 +591,33 @@ function createIngestTool(params) {
387
591
  });
388
592
  const tags = sanitizeStringArray(paramsRecord.tags);
389
593
  const overwrite = readBoolean(paramsRecord.overwrite, false);
390
- let jobId;
391
- await sendTrackerProgress({
392
- api: params.api,
393
- sessionKey: params.ctx.sessionKey,
394
- text: `Tracker: creating Knowhere job for \`${progressLabel}\` for this request.`
594
+ const trackerLanguage = readIngestTrackerLanguage(paramsRecord.lang);
595
+ const sessionKey = params.ctx.sessionKey;
596
+ const sourceType = urlParam ? "url" : "file";
597
+ const channelRoute = await params.store.resolveChannelRoute({ sessionKey });
598
+ params.api.logger.info(`knowhere: knowhere_ingest_document starting background ingest scope=${scope.label} sourceType=${sourceType} label=${JSON.stringify(progressLabel)} overwrite=${overwrite} docId=${docId ?? "auto"} dataId=${dataId ?? "none"} lang=${trackerLanguage} routeState=${channelRoute ? "resolved" : "missing"} routeAccountId=${channelRoute?.accountId ?? "none"}`);
599
+ let resolveJobCreated;
600
+ const jobCreatedPromise = new Promise((resolve) => {
601
+ resolveJobCreated = resolve;
395
602
  });
396
- try {
397
- const ingestResult = await client.ingestDocument({
398
- filePath: resolvedFilePath,
399
- fileName: requestedFileName,
400
- url: urlParam,
401
- dataId,
402
- parsingParams: normalizeParsingParams(paramsRecord.parsing),
403
- onJobCreated: async (job) => {
404
- jobId = job.job_id;
405
- await sendTrackerProgress({
406
- api: params.api,
407
- sessionKey: params.ctx.sessionKey,
408
- text: `Tracker: ingesting \`${progressLabel}\` into Knowhere for this request. Job ID: \`${job.job_id}\`.`
409
- });
410
- }
411
- });
412
- const fileName = requestedFileName || (resolvedFilePath ? path.basename(resolvedFilePath) : null);
413
- const document = await params.store.saveDownloadedDocument(scope, {
414
- sourceType: urlParam ? "url" : "file",
603
+ let createdJobId;
604
+ const fileName = requestedFileName || (resolvedFilePath ? path.basename(resolvedFilePath) : null);
605
+ const ingestPromise = client.ingestDocument({
606
+ filePath: resolvedFilePath,
607
+ fileName: requestedFileName,
608
+ url: urlParam,
609
+ dataId,
610
+ parsingParams: normalizeParsingParams(paramsRecord.parsing),
611
+ onJobCreated: (job) => {
612
+ createdJobId = job.job_id;
613
+ params.api.logger.info(`knowhere: knowhere_ingest_document job created scope=${scope.label} jobId=${job.job_id} label=${JSON.stringify(progressLabel)}`);
614
+ resolveJobCreated(job);
615
+ }
616
+ });
617
+ ingestPromise.then(async (ingestResult) => {
618
+ params.api.logger.info(`knowhere: knowhere_ingest_document download completed scope=${scope.label} jobId=${ingestResult.job.job_id}; storing extracted result`);
619
+ const storedDocument = await params.store.saveDownloadedDocument(scope, {
620
+ sourceType,
415
621
  source: urlParam || resolvedFilePath || "",
416
622
  fileName,
417
623
  docId,
@@ -422,30 +628,59 @@ function createIngestTool(params) {
422
628
  jobResult: ingestResult.jobResult,
423
629
  downloadedResult: ingestResult.downloadedResult
424
630
  }, { overwrite });
425
- await sendTrackerProgress({
631
+ params.api.logger.info(`knowhere: knowhere_ingest_document stored document scope=${scope.label} jobId=${ingestResult.job.job_id} docId=${storedDocument.id} label=${JSON.stringify(progressLabel)}`);
632
+ await notifyBackgroundIngestOutcome({
426
633
  api: params.api,
427
- sessionKey: params.ctx.sessionKey,
428
- text: `Tracker: \`${progressLabel}\` is parsed and ready. Job ID: \`${ingestResult.job.job_id}\`. The agent can use it now.`
634
+ context: params.ctx,
635
+ sessionKey,
636
+ channelRoute,
637
+ followUp: {
638
+ trackerText: buildIngestTrackerSuccessText({
639
+ label: progressLabel,
640
+ jobId: ingestResult.job.job_id,
641
+ lang: trackerLanguage
642
+ }),
643
+ sessionEventText: `Knowhere background ingest completed for "${progressLabel}". The parsed document is stored as "${storedDocument.id}" in the current scope and is ready to use. Send one short follow-up to the user now telling them the parsing for "${progressLabel}" is completed and the file is ready.`,
644
+ contextKey: `knowhere:ingest-complete:${ingestResult.job.job_id}`,
645
+ heartbeatReason: "knowhere-ingest-complete"
646
+ }
429
647
  });
430
- return textResult([
431
- "Ingest complete.",
432
- ...buildStoredDocumentSummaryLines({
433
- document,
434
- scopeLabel: scope.label,
435
- includeSource: true,
436
- includeJobId: true
437
- }),
438
- "Next: read manifest.json with knowhere_read_result_file or preview the document with knowhere_preview_document."
439
- ].join("\n"));
440
- } catch (error) {
441
- const errorText = formatErrorMessage(error);
442
- await sendTrackerProgress({
648
+ }).catch(async (error) => {
649
+ const errorText = isPaymentRequiredError(error) ? formatPaymentRequiredMessage() : formatErrorMessage(error);
650
+ params.api.logger.error(`knowhere: knowhere_ingest_document background ingest failed scope=${scope.label} label=${JSON.stringify(progressLabel)} error=${errorText}`);
651
+ await notifyBackgroundIngestOutcome({
443
652
  api: params.api,
444
- sessionKey: params.ctx.sessionKey,
445
- text: `Tracker: failed to ingest \`${progressLabel}\`${jobId ? ` (job \`${jobId}\`)` : ""}: ${errorText}`
653
+ context: params.ctx,
654
+ sessionKey,
655
+ channelRoute,
656
+ followUp: {
657
+ trackerText: buildIngestTrackerFailureText({
658
+ label: progressLabel,
659
+ jobId: createdJobId,
660
+ errorText,
661
+ lang: trackerLanguage
662
+ }),
663
+ sessionEventText: `Knowhere background ingest failed for "${progressLabel}". Send one short follow-up to the user now telling them the parsing failed. Error: ${errorText}`,
664
+ contextKey: `knowhere:ingest-failed:${createdJobId ?? progressLabel}`,
665
+ heartbeatReason: "knowhere-ingest-failed"
666
+ }
446
667
  });
447
- throw error;
668
+ });
669
+ const earlyFailure = Symbol("earlyFailure");
670
+ const createdJob = await Promise.race([jobCreatedPromise, ingestPromise.then(() => earlyFailure, (err) => {
671
+ rethrowWithPaymentHint(err);
672
+ })]);
673
+ if (typeof createdJob === "symbol") {
674
+ params.api.logger.warn(`knowhere: knowhere_ingest_document ingest completed before job-created callback scope=${scope.label} label=${JSON.stringify(progressLabel)}`);
675
+ return textResult("Ingest completed synchronously. Use knowhere_list_documents to find the stored document.");
448
676
  }
677
+ return textResult([
678
+ "Ingest job created. Parsing in background.",
679
+ `Job ID: ${createdJob.job_id}`,
680
+ `File: ${progressLabel}`,
681
+ `Scope: ${scope.label}`,
682
+ "Use knowhere_get_job_status to check progress at any time."
683
+ ].join("\n"));
449
684
  }
450
685
  };
451
686
  }
@@ -472,8 +707,11 @@ function createJobStatusTool(params) {
472
707
  config: params.config
473
708
  });
474
709
  const scope = params.store.resolveScope(params.ctx);
475
- const job = await client.getJob(jobId);
710
+ params.api.logger.info(`knowhere: knowhere_get_job_status fetching remote status scope=${scope.label} jobId=${jobId}`);
711
+ const job = await client.getJob(jobId).catch(rethrowWithPaymentHint);
476
712
  const matchingDocuments = (await params.store.listDocuments(scope)).filter((document) => document.jobId === job.job_id);
713
+ params.api.logger.info(`knowhere: knowhere_get_job_status fetched status scope=${scope.label} jobId=${job.job_id} status=${job.status} storedDocuments=${matchingDocuments.length}`);
714
+ if (job.error?.message) params.api.logger.warn(`knowhere: knowhere_get_job_status job returned error scope=${scope.label} jobId=${job.job_id} error=${job.error.message}`);
477
715
  const lines = [
478
716
  `Knowhere job ${job.job_id}`,
479
717
  `Scope: ${scope.label}`,
@@ -568,15 +806,21 @@ function createJobListTool(params) {
568
806
  config: params.config
569
807
  });
570
808
  const scope = params.store.resolveScope(params.ctx);
809
+ const jobStatus = readString(paramsRecord.jobStatus);
810
+ const jobType = readString(paramsRecord.jobType);
811
+ const recentDays = readRecentDays(paramsRecord.recentDays);
812
+ const startTime = readString(paramsRecord.startTime);
813
+ const endTime = readString(paramsRecord.endTime);
814
+ params.api.logger.info(`knowhere: knowhere_list_jobs listing jobs scope=${scope.label} page=${page} pageSize=${pageSize} jobStatus=${jobStatus ?? "any"} jobType=${jobType ?? "any"} recentDays=${recentDays ?? "none"} startTime=${startTime ?? "none"} endTime=${endTime ?? "none"}`);
571
815
  const [jobList, documents] = await Promise.all([client.listJobs({
572
816
  page,
573
817
  pageSize,
574
- jobStatus: readString(paramsRecord.jobStatus),
575
- jobType: readString(paramsRecord.jobType),
576
- recentDays: readRecentDays(paramsRecord.recentDays),
577
- startTime: readString(paramsRecord.startTime),
578
- endTime: readString(paramsRecord.endTime)
579
- }), params.store.listDocuments(scope)]);
818
+ jobStatus,
819
+ jobType,
820
+ recentDays,
821
+ startTime,
822
+ endTime
823
+ }), params.store.listDocuments(scope)]).catch(rethrowWithPaymentHint);
580
824
  const documentsByJobId = /* @__PURE__ */ new Map();
581
825
  for (const document of documents) {
582
826
  if (!document.jobId) continue;
@@ -584,6 +828,7 @@ function createJobListTool(params) {
584
828
  entries.push(document);
585
829
  documentsByJobId.set(document.jobId, entries);
586
830
  }
831
+ params.api.logger.info(`knowhere: knowhere_list_jobs listed jobs scope=${scope.label} returned=${jobList.jobs.length} total=${jobList.total} storedDocuments=${documents.length}`);
587
832
  return textResult(formatJobList({
588
833
  jobList,
589
834
  documentsByJobId
@@ -634,27 +879,31 @@ function createImportCompletedJobTool(params) {
634
879
  config: params.config
635
880
  });
636
881
  const scope = params.store.resolveScope(params.ctx);
637
- const importResult = await client.getCompletedJobResult(jobId);
882
+ params.api.logger.info(`knowhere: knowhere_import_completed_job importing completed job scope=${scope.label} jobId=${jobId} overwrite=${readBoolean(paramsRecord.overwrite, false)}`);
883
+ const importResult = await client.getCompletedJobResult(jobId).catch(rethrowWithPaymentHint);
638
884
  const overwrite = readBoolean(paramsRecord.overwrite, false);
639
885
  const tags = mergeTags(sanitizeStringArray(paramsRecord.tags), ["history-imported", `job:${importResult.jobResult.job_id}`]);
640
886
  const fileName = importResult.jobResult.file_name || null;
641
887
  const sourceType = importResult.jobResult.source_type === "url" ? "url" : "file";
888
+ params.api.logger.info(`knowhere: knowhere_import_completed_job downloaded job result scope=${scope.label} jobId=${importResult.jobResult.job_id} sourceType=${sourceType}`);
889
+ const document = await params.store.saveDownloadedDocument(scope, {
890
+ sourceType,
891
+ source: buildHistoryJobSource(importResult.jobResult.job_id),
892
+ sourceLabel: buildHistoryJobSourceLabel(importResult.jobResult.job_id, fileName),
893
+ fileName,
894
+ docId: readString(paramsRecord.docId),
895
+ title: readString(paramsRecord.title),
896
+ dataId: importResult.jobResult.data_id || void 0,
897
+ tags,
898
+ job: importResult.job,
899
+ jobResult: importResult.jobResult,
900
+ downloadedResult: importResult.downloadedResult
901
+ }, { overwrite });
902
+ params.api.logger.info(`knowhere: knowhere_import_completed_job stored imported document scope=${scope.label} jobId=${importResult.jobResult.job_id} docId=${document.id}`);
642
903
  return textResult([
643
904
  "Import complete.",
644
905
  ...buildStoredDocumentSummaryLines({
645
- document: await params.store.saveDownloadedDocument(scope, {
646
- sourceType,
647
- source: buildHistoryJobSource(importResult.jobResult.job_id),
648
- sourceLabel: buildHistoryJobSourceLabel(importResult.jobResult.job_id, fileName),
649
- fileName,
650
- docId: readString(paramsRecord.docId),
651
- title: readString(paramsRecord.title),
652
- dataId: importResult.jobResult.data_id || void 0,
653
- tags,
654
- job: importResult.job,
655
- jobResult: importResult.jobResult,
656
- downloadedResult: importResult.downloadedResult
657
- }, { overwrite }),
906
+ document,
658
907
  scopeLabel: scope.label,
659
908
  includeSource: true,
660
909
  includeJobId: true
@@ -735,11 +984,26 @@ function buildNormalizedFields(chunk) {
735
984
  fields.set("chunk.path", normalizeForGrep(chunk.path || ""));
736
985
  return fields;
737
986
  }
987
+ function buildGrepHints(params) {
988
+ const hints = [];
989
+ const maxHints = 3;
990
+ if (params.totalMatches === 0 && params.conditionCount > 0) hints.push("No matches. Try broadening: remove a condition, use a shorter pattern, or check for typos. Call knowhere_preview_document to see the document structure first.");
991
+ if (hints.length < maxHints && params.totalMatches > params.returned) {
992
+ let hint = `Showing ${params.returned} of ${params.totalMatches} matches. Add another condition (e.g., target chunk.path to a specific section) to narrow results.`;
993
+ if (!params.hasPathCondition) hint += " Use knowhere_preview_document to find section paths.";
994
+ hints.push(hint);
995
+ }
996
+ if (hints.length < maxHints && params.truncatedStrings) if (params.returned > 3) hints.push(`Fields truncated at ${params.maxStringChars} chars. Reduce maxResults to 1-3 and increase maxStringChars to 12000-20000 for full content.`);
997
+ else hints.push(`Fields truncated at ${params.maxStringChars} chars. Increase maxStringChars (up to 20000) for full content.`);
998
+ if (hints.length < maxHints && params.totalMatches >= 1 && params.totalMatches <= 5 && !params.includeContext) hints.push("Tip: set includeContext=true to discover sibling chunks in the same section.");
999
+ if (hints.length < maxHints && params.totalChunks > 0 && params.totalMatches > params.totalChunks * .5 && params.conditionCount <= 1) hints.push("Pattern matches over half the document. Add a second condition to narrow.");
1000
+ return hints;
1001
+ }
738
1002
  function createGrepTool(params) {
739
1003
  return {
740
1004
  name: "knowhere_grep",
741
1005
  label: "Knowhere Grep",
742
- description: "Search a stored document's chunks with composable AND conditions. Returns matching chunks with content, summary, keywords, path, and chunkId. Supports substring and regex matching with text normalization (HTML stripping, LaTeX cleanup, unicode normalization). Omit conditions to list all chunks. Omit the target field in a condition to search across all text fields (content, summary, keywords, path) — this is the recommended default. When answering questions from results, cite the chunkId and path. Tip: set maxStringChars up to 20000 when you need full untruncated content from a small number of results (e.g., maxResults=1). The default 4000 may truncate long chunks.",
1006
+ description: "Search a stored document's chunks with composable AND conditions. Returns matching chunks with content, summary, keywords, path, and chunkId. Supports substring and regex matching with text normalization (HTML stripping, LaTeX cleanup, unicode normalization). Omit conditions to list all chunks. Omit the target field in a condition to search across all text fields (content, summary, keywords, path) — this is the recommended default. When answering questions from results, cite the chunkId and path. Tip: set maxStringChars up to 20000 when you need full untruncated content from a small number of results (e.g., maxResults=1). The default 4000 may truncate long chunks. Search strategy: (1) Start with knowhere_preview_document to see document structure. (2) Search broadly with a single short pattern, then narrow by adding conditions. (3) If zero results, broaden or try synonyms. If too many, add a path condition. (4) Once you find the right chunks, re-query with maxResults=1-3 and maxStringChars=12000-20000 to read full content.",
743
1007
  parameters: {
744
1008
  type: "object",
745
1009
  additionalProperties: false,
@@ -809,13 +1073,17 @@ function createGrepTool(params) {
809
1073
  if (!docId) throw new Error("docId is required.");
810
1074
  const scope = params.store.resolveScope(params.ctx);
811
1075
  const payload = await params.store.loadDocumentPayload(scope, docId);
812
- if (!payload) return textResult(formatStoredDocumentNotFound(docId, scope.label));
1076
+ if (!payload) {
1077
+ params.api.logger.warn(`knowhere: knowhere_grep document not found scope=${scope.label} docId=${docId}`);
1078
+ return textResult(formatStoredDocumentNotFound(docId, scope.label));
1079
+ }
813
1080
  const conditions = parseGrepConditions(paramsRecord.conditions);
814
1081
  const outerRegex = readBoolean(paramsRecord.regex, false);
815
1082
  const outerCaseSensitive = readBoolean(paramsRecord.caseSensitive, false);
816
1083
  const includeContext = readBoolean(paramsRecord.includeContext, false);
817
1084
  const maxResults = Math.min(50, Math.max(1, Math.trunc(readNumber(paramsRecord.maxResults, 10))));
818
1085
  const maxStringChars = Math.min(2e4, Math.max(100, Math.trunc(readNumber(paramsRecord.maxStringChars, 4e3))));
1086
+ params.api.logger.info(`knowhere: knowhere_grep searching document scope=${scope.label} docId=${docId} conditions=${conditions.length} regex=${outerRegex} caseSensitive=${outerCaseSensitive} includeContext=${includeContext} maxResults=${maxResults} maxStringChars=${maxStringChars}`);
819
1087
  const pathChunkIndex = includeContext ? new Map(payload.browseIndex.paths.map((p) => [p.path, p.chunkIds])) : void 0;
820
1088
  const sortedChunks = sortChunksByBrowseOrder(payload.chunks, payload.browseIndex);
821
1089
  const results = [];
@@ -871,13 +1139,28 @@ function createGrepTool(params) {
871
1139
  if (entry.siblingChunkIds) projected.siblingChunkIds = entry.siblingChunkIds;
872
1140
  return projected;
873
1141
  }), maxStringChars);
874
- return formatJsonToolResult({
1142
+ params.api.logger.info(`knowhere: knowhere_grep completed search scope=${scope.label} docId=${docId} returned=${results.length} totalMatches=${totalMatches} truncated=${truncated.truncated}`);
1143
+ const hasPathCondition = conditions.some((c) => c.target === "chunk.path");
1144
+ const hints = buildGrepHints({
1145
+ totalMatches,
1146
+ returned: results.length,
1147
+ maxResults,
1148
+ maxStringChars,
1149
+ truncatedStrings: truncated.truncated,
1150
+ conditionCount: conditions.length,
1151
+ includeContext,
1152
+ totalChunks: sortedChunks.length,
1153
+ hasPathCondition
1154
+ });
1155
+ const jsonResult = formatJsonToolResult({
875
1156
  totalMatches,
876
1157
  returned: results.length,
877
1158
  results: truncated.value,
878
1159
  maxStringChars,
879
1160
  truncatedStrings: truncated.truncated
880
1161
  });
1162
+ if (hints.length === 0) return jsonResult;
1163
+ return textResult(`${jsonResult.content[0].text}\n---\n${hints.join("\n")}`);
881
1164
  }
882
1165
  };
883
1166
  }
@@ -885,7 +1168,7 @@ function createReadResultFileTool(params) {
885
1168
  return {
886
1169
  name: "knowhere_read_result_file",
887
1170
  label: "Knowhere Read Result File",
888
- description: "Read a raw result file from the stored document's extracted ZIP package. Common files: manifest.json (parsing metadata), hierarchy.json (document structure), kb.csv (knowledge base export), or table HTML files (e.g., tables/table-1.html). Use mode='json' for JSON files, mode='csv' for CSV files, or mode='text' (default) for everything else. Increase maxStringChars (up to 20000) for large files.",
1171
+ description: "Read a raw result file from the stored document's extracted ZIP package. Common files: manifest.json (parsing metadata), hierarchy.json (document structure), kb.csv (knowledge base export), table HTML files (e.g., tables/table-1.html), or image assets (e.g., images/img-0.png). Image files are staged into a local attachment path and sent directly to the current channel when routing can be resolved. If direct delivery is unavailable, the tool returns a message-tool handoff and, when the run has a workspace, a workspace-relative MEDIA fallback for a normal assistant reply. When the result mode is image_attachment, do not call generic file-read tools on data.stagedPath; use data.sendWithMessageTool or data.replyFallback as returned. When the result mode is image_sent, the plugin already delivered the image. Use mode='json' for JSON files, mode='csv' for CSV files, or mode='text' (default) for everything else. Increase maxStringChars (up to 20000) for large files.",
889
1172
  parameters: {
890
1173
  type: "object",
891
1174
  additionalProperties: false,
@@ -924,40 +1207,76 @@ function createReadResultFileTool(params) {
924
1207
  if (!filePath) throw new Error("filePath is required.");
925
1208
  const scope = params.store.resolveScope(params.ctx);
926
1209
  const payload = await params.store.loadDocumentPayload(scope, docId);
927
- if (!payload) return textResult(formatStoredDocumentNotFound(docId, scope.label));
1210
+ if (!payload) {
1211
+ params.api.logger.warn(`knowhere: knowhere_read_result_file document not found scope=${scope.label} docId=${docId}`);
1212
+ return textResult(formatStoredDocumentNotFound(docId, scope.label));
1213
+ }
928
1214
  const resultFile = findResultFile(payload.browseIndex, filePath);
929
- if (!resultFile) return textResult([
930
- "Result file not found.",
931
- `File path: ${filePath}`,
932
- `Document ID: ${docId}`,
933
- `Scope: ${scope.label}`
934
- ].join("\n"));
935
- if (!isTextReadableResultFile(resultFile)) return textResult([
936
- "Result file is not readable as text through this tool.",
937
- `File path: ${filePath}`,
938
- `Kind: ${resultFile.kind}`,
939
- `Document ID: ${docId}`,
940
- `Scope: ${scope.label}`
941
- ].join("\n"));
1215
+ if (!resultFile) {
1216
+ params.api.logger.warn(`knowhere: knowhere_read_result_file result file not found scope=${scope.label} docId=${docId} filePath=${filePath}`);
1217
+ return textResult([
1218
+ "Result file not found.",
1219
+ `File path: ${filePath}`,
1220
+ `Document ID: ${docId}`,
1221
+ `Scope: ${scope.label}`
1222
+ ].join("\n"));
1223
+ }
1224
+ if (resultFile.kind === "image") {
1225
+ const absolutePath = params.store.getResultFileAbsolutePath(scope, docId, filePath);
1226
+ const channelRoute = await params.store.resolveChannelRoute({ sessionKey: params.ctx.sessionKey });
1227
+ params.api.logger.info(`knowhere: knowhere_read_result_file staging image asset scope=${scope.label} docId=${docId} filePath=${filePath}`);
1228
+ return await buildImageToolResult({
1229
+ api: params.api,
1230
+ absolutePath,
1231
+ channelRoute,
1232
+ context: params.ctx,
1233
+ docId: payload.document.id,
1234
+ documentTitle: payload.document.title,
1235
+ filePath,
1236
+ file: resultFile,
1237
+ sessionKey: params.ctx.sessionKey,
1238
+ scopeLabel: scope.label,
1239
+ workspaceDir: params.ctx.workspaceDir
1240
+ });
1241
+ }
1242
+ if (!isTextReadableResultFile(resultFile)) {
1243
+ params.api.logger.warn(`knowhere: knowhere_read_result_file unreadable result kind scope=${scope.label} docId=${docId} filePath=${filePath} kind=${resultFile.kind}`);
1244
+ return textResult([
1245
+ "Result file is not readable as text through this tool.",
1246
+ `File path: ${filePath}`,
1247
+ `Kind: ${resultFile.kind}`,
1248
+ `Document ID: ${docId}`,
1249
+ `Scope: ${scope.label}`
1250
+ ].join("\n"));
1251
+ }
942
1252
  const storedFile = await params.store.readResultFile(scope, docId, filePath);
943
- if (!storedFile) return textResult(formatStoredDocumentNotFound(docId, scope.label));
944
- if (storedFile.text === null) return textResult([
945
- "Result file not found.",
946
- `File path: ${filePath}`,
947
- `Document ID: ${docId}`,
948
- `Scope: ${scope.label}`
949
- ].join("\n"));
1253
+ if (!storedFile) {
1254
+ params.api.logger.warn(`knowhere: knowhere_read_result_file payload disappeared scope=${scope.label} docId=${docId} filePath=${filePath}`);
1255
+ return textResult(formatStoredDocumentNotFound(docId, scope.label));
1256
+ }
1257
+ if (storedFile.text === null) {
1258
+ params.api.logger.warn(`knowhere: knowhere_read_result_file text content missing scope=${scope.label} docId=${docId} filePath=${filePath}`);
1259
+ return textResult([
1260
+ "Result file not found.",
1261
+ `File path: ${filePath}`,
1262
+ `Document ID: ${docId}`,
1263
+ `Scope: ${scope.label}`
1264
+ ].join("\n"));
1265
+ }
950
1266
  const mode = readResultFileReadMode(paramsRecord.mode);
951
1267
  const maxStringChars = Math.min(2e4, Math.max(100, Math.trunc(readNumber(paramsRecord.maxStringChars, 4e3))));
952
1268
  const normalizedText = stripUtf8Bom(storedFile.text);
1269
+ params.api.logger.info(`knowhere: knowhere_read_result_file reading file scope=${scope.label} docId=${docId} filePath=${filePath} kind=${resultFile.kind} mode=${mode} maxStringChars=${maxStringChars}`);
953
1270
  if (mode === "json") {
954
1271
  let parsedJson;
955
1272
  try {
956
1273
  parsedJson = JSON.parse(normalizedText);
957
1274
  } catch (error) {
1275
+ params.api.logger.warn(`knowhere: knowhere_read_result_file invalid json scope=${scope.label} docId=${docId} filePath=${filePath} error=${formatErrorMessage(error)}`);
958
1276
  throw new Error(`Result file ${filePath} is not valid JSON. ${formatErrorMessage(error)}`, { cause: error });
959
1277
  }
960
1278
  const truncatedJson = truncateJsonValue(parsedJson, maxStringChars);
1279
+ params.api.logger.info(`knowhere: knowhere_read_result_file parsed json scope=${scope.label} docId=${docId} filePath=${filePath} truncated=${truncatedJson.truncated}`);
961
1280
  return formatJsonToolResult({
962
1281
  scope: scope.label,
963
1282
  docId: payload.document.id,
@@ -970,6 +1289,7 @@ function createReadResultFileTool(params) {
970
1289
  });
971
1290
  }
972
1291
  const data = mode === "csv" ? buildCsvFilePayload(normalizedText, maxStringChars) : buildTextFilePayload(normalizedText, maxStringChars);
1292
+ params.api.logger.info(`knowhere: knowhere_read_result_file prepared text payload scope=${scope.label} docId=${docId} filePath=${filePath} lineCount=${data.lineCount}`);
973
1293
  return formatJsonToolResult({
974
1294
  scope: scope.label,
975
1295
  docId: payload.document.id,
@@ -1001,7 +1321,10 @@ function createPreviewDocumentTool(params) {
1001
1321
  if (!docId) throw new Error("docId is required.");
1002
1322
  const scope = params.store.resolveScope(params.ctx);
1003
1323
  const payload = await params.store.loadDocumentPayload(scope, docId);
1004
- if (!payload) return textResult(formatStoredDocumentNotFound(docId, scope.label));
1324
+ if (!payload) {
1325
+ params.api.logger.warn(`knowhere: knowhere_preview_document document not found scope=${scope.label} docId=${docId}`);
1326
+ return textResult(formatStoredDocumentNotFound(docId, scope.label));
1327
+ }
1005
1328
  const { document } = payload;
1006
1329
  const pathSummaryMap = /* @__PURE__ */ new Map();
1007
1330
  for (const chunk of payload.chunks) {
@@ -1021,6 +1344,7 @@ function createPreviewDocumentTool(params) {
1021
1344
  const pathByName = /* @__PURE__ */ new Map();
1022
1345
  for (const p of payload.browseIndex.paths) pathByName.set(p.path, p);
1023
1346
  const roots = payload.browseIndex.paths.filter((p) => p.depth === 1);
1347
+ params.api.logger.info(`knowhere: knowhere_preview_document building preview scope=${scope.label} docId=${docId} paths=${payload.browseIndex.paths.length} roots=${roots.length} chunks=${payload.chunks.length}`);
1024
1348
  if (roots.length > 0) {
1025
1349
  lines.push("");
1026
1350
  lines.push("## Table of Contents");
@@ -1046,6 +1370,7 @@ function createPreviewDocumentTool(params) {
1046
1370
  } else {
1047
1371
  lines.push("");
1048
1372
  lines.push("No structural paths available for this document.");
1373
+ params.api.logger.warn(`knowhere: knowhere_preview_document no structural paths scope=${scope.label} docId=${docId}`);
1049
1374
  }
1050
1375
  return textResult(lines.join("\n"));
1051
1376
  }
@@ -1055,7 +1380,7 @@ function createListTool(params) {
1055
1380
  return {
1056
1381
  name: "knowhere_list_documents",
1057
1382
  label: "Knowhere List",
1058
- description: "List all Knowhere documents stored in the current scope. Returns each document's ID, title, source, chunk count, tags, and last-updated timestamp. Use this first to discover available documents and their docId values before calling other tools.",
1383
+ description: "List all Knowhere documents stored in the current scope. Returns each document's ID, title, source, chunk count, tags, and last-updated timestamp. Use this first to discover available documents, check whether a file or URL is already stored, and find the right docId before calling other tools.",
1059
1384
  parameters: {
1060
1385
  type: "object",
1061
1386
  additionalProperties: false,
@@ -1063,7 +1388,9 @@ function createListTool(params) {
1063
1388
  },
1064
1389
  execute: async () => {
1065
1390
  const scope = params.store.resolveScope(params.ctx);
1066
- return textResult(formatDocumentList(await params.store.listDocuments(scope), scope.label));
1391
+ const documents = await params.store.listDocuments(scope);
1392
+ params.api.logger.info(`knowhere: knowhere_list_documents listed documents scope=${scope.label} count=${documents.length}`);
1393
+ return textResult(formatDocumentList(documents, scope.label));
1067
1394
  }
1068
1395
  };
1069
1396
  }
@@ -1085,9 +1412,13 @@ function createRemoveTool(params) {
1085
1412
  const docId = readString((isRecord(rawParams) ? rawParams : {}).docId);
1086
1413
  if (!docId) throw new Error("docId is required.");
1087
1414
  const scope = params.store.resolveScope(params.ctx);
1415
+ params.api.logger.info(`knowhere: knowhere_remove_document removing document scope=${scope.label} docId=${docId}`);
1088
1416
  const removed = await params.store.removeDocument(scope, docId);
1089
- if (!removed) return textResult(formatStoredDocumentNotFound(docId, scope.label));
1090
- params.autoGroundingController?.forgetDocument(scope, removed.id);
1417
+ if (!removed) {
1418
+ params.api.logger.warn(`knowhere: knowhere_remove_document document not found scope=${scope.label} docId=${docId}`);
1419
+ return textResult(formatStoredDocumentNotFound(docId, scope.label));
1420
+ }
1421
+ params.api.logger.info(`knowhere: knowhere_remove_document removed document scope=${scope.label} docId=${removed.id}`);
1091
1422
  return textResult([
1092
1423
  "Removed stored document.",
1093
1424
  `Document ID: ${removed.id}`,
@@ -1113,9 +1444,38 @@ function createClearScopeTool(params) {
1113
1444
  execute: async (_toolCallId, rawParams) => {
1114
1445
  const paramsRecord = isRecord(rawParams) ? rawParams : {};
1115
1446
  const scope = params.store.resolveScope(params.ctx);
1116
- if (!readBoolean(paramsRecord.confirm, false)) return textResult(`Set confirm=true to clear scope ${scope.label}.`);
1117
- params.autoGroundingController?.forgetScope(scope);
1118
- return textResult(formatScopeClearResult(await params.store.clearScope(scope), scope.label));
1447
+ if (!readBoolean(paramsRecord.confirm, false)) {
1448
+ params.api.logger.warn(`knowhere: knowhere_clear_scope skipped without confirm scope=${scope.label}`);
1449
+ return textResult(`Set confirm=true to clear scope ${scope.label}.`);
1450
+ }
1451
+ params.api.logger.info(`knowhere: knowhere_clear_scope clearing scope scope=${scope.label}`);
1452
+ const removedDocuments = await params.store.clearScope(scope);
1453
+ params.api.logger.info(`knowhere: knowhere_clear_scope cleared scope scope=${scope.label} removed=${removedDocuments.length}`);
1454
+ return textResult(formatScopeClearResult(removedDocuments, scope.label));
1455
+ }
1456
+ };
1457
+ }
1458
+ function createSetApiKeyTool(params) {
1459
+ return {
1460
+ name: "knowhere_set_api_key",
1461
+ label: "Set Knowhere API Key",
1462
+ description: "Save a Knowhere API key so the plugin can authenticate with the Knowhere service. The key is persisted in the plugin state directory and loaded automatically on next startup.",
1463
+ parameters: {
1464
+ type: "object",
1465
+ additionalProperties: false,
1466
+ properties: { apiKey: {
1467
+ type: "string",
1468
+ description: "The Knowhere API key to store."
1469
+ } },
1470
+ required: ["apiKey"]
1471
+ },
1472
+ execute: async (_toolCallId, rawParams) => {
1473
+ const apiKey = readString((isRecord(rawParams) ? rawParams : {}).apiKey);
1474
+ if (!apiKey) throw new Error("apiKey is required.");
1475
+ await persistApiKey(params.config.storageDir, apiKey);
1476
+ params.config.apiKey = apiKey;
1477
+ params.api.logger.info("knowhere: API key saved to plugin state");
1478
+ return textResult("Knowhere API key saved successfully. All Knowhere tools are now ready to use.");
1119
1479
  }
1120
1480
  };
1121
1481
  }
@@ -1146,30 +1506,38 @@ function createKnowhereToolFactory(params) {
1146
1506
  ctx
1147
1507
  }),
1148
1508
  createGrepTool({
1509
+ api: params.api,
1149
1510
  store: params.store,
1150
1511
  ctx
1151
1512
  }),
1152
1513
  createReadResultFileTool({
1514
+ api: params.api,
1153
1515
  store: params.store,
1154
1516
  ctx
1155
1517
  }),
1156
1518
  createPreviewDocumentTool({
1519
+ api: params.api,
1157
1520
  store: params.store,
1158
1521
  ctx
1159
1522
  }),
1160
1523
  createListTool({
1524
+ api: params.api,
1161
1525
  store: params.store,
1162
1526
  ctx
1163
1527
  }),
1164
1528
  createRemoveTool({
1529
+ api: params.api,
1165
1530
  store: params.store,
1166
- ctx,
1167
- autoGroundingController: params.autoGroundingController
1531
+ ctx
1168
1532
  }),
1169
1533
  createClearScopeTool({
1534
+ api: params.api,
1170
1535
  store: params.store,
1171
- ctx,
1172
- autoGroundingController: params.autoGroundingController
1536
+ ctx
1537
+ }),
1538
+ createSetApiKeyTool({
1539
+ api: params.api,
1540
+ config: params.config
1173
1541
  })
1174
1542
  ];
1175
1543
  }