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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +163 -0
  2. package/dist/_virtual/_rolldown/runtime.js +37 -0
  3. package/dist/client.d.ts +33 -0
  4. package/dist/client.js +395 -0
  5. package/dist/config.d.ts +6 -0
  6. package/dist/config.js +132 -0
  7. package/dist/error-message.d.ts +1 -0
  8. package/dist/error-message.js +48 -0
  9. package/dist/hooks.d.ts +8 -0
  10. package/dist/hooks.js +415 -0
  11. package/dist/index.d.ts +9 -0
  12. package/dist/index.js +43 -0
  13. package/dist/node_modules/.pnpm/@knowhere-ai_sdk@0.1.1/node_modules/@knowhere-ai/sdk/dist/index.js +717 -0
  14. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/adapters/adapters.js +83 -0
  15. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/adapters/fetch.js +170 -0
  16. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/adapters/xhr.js +106 -0
  17. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/axios.js +57 -0
  18. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/cancel/CancelToken.js +90 -0
  19. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/cancel/CanceledError.js +20 -0
  20. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/cancel/isCancel.js +6 -0
  21. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/Axios.js +174 -0
  22. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/AxiosError.js +70 -0
  23. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/AxiosHeaders.js +204 -0
  24. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/InterceptorManager.js +60 -0
  25. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/buildFullPath.js +20 -0
  26. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/dispatchRequest.js +52 -0
  27. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/mergeConfig.js +81 -0
  28. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/settle.js +18 -0
  29. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/transformData.js +25 -0
  30. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/defaults/index.js +107 -0
  31. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/defaults/transitional.js +9 -0
  32. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/env/data.js +4 -0
  33. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +50 -0
  34. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/HttpStatusCode.js +77 -0
  35. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/bind.js +15 -0
  36. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/buildURL.js +40 -0
  37. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/combineURLs.js +14 -0
  38. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/composeSignals.js +39 -0
  39. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/cookies.js +31 -0
  40. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/formDataToJSON.js +67 -0
  41. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/isAbsoluteURL.js +14 -0
  42. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/isAxiosError.js +14 -0
  43. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/isURLSameOrigin.js +8 -0
  44. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/parseHeaders.js +53 -0
  45. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/parseProtocol.js +7 -0
  46. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/progressEventReducer.js +38 -0
  47. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/resolveConfig.js +36 -0
  48. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/speedometer.js +36 -0
  49. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/spread.js +29 -0
  50. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/throttle.js +38 -0
  51. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/toFormData.js +151 -0
  52. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/toURLEncodedForm.js +18 -0
  53. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/trackStream.js +69 -0
  54. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/validator.js +76 -0
  55. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/browser/classes/Blob.js +4 -0
  56. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/browser/classes/FormData.js +4 -0
  57. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/browser/classes/URLSearchParams.js +5 -0
  58. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/browser/index.js +22 -0
  59. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/common/utils.js +46 -0
  60. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/index.js +9 -0
  61. package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/utils.js +698 -0
  62. package/dist/node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/browser.js +426 -0
  63. package/dist/node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js +3110 -0
  64. package/dist/parser.d.ts +16 -0
  65. package/dist/parser.js +323 -0
  66. package/dist/session.d.ts +11 -0
  67. package/dist/session.js +78 -0
  68. package/dist/store.d.ts +62 -0
  69. package/dist/store.js +482 -0
  70. package/dist/text.d.ts +10 -0
  71. package/dist/text.js +34 -0
  72. package/dist/tools.d.ts +9 -0
  73. package/dist/tools.js +1177 -0
  74. package/dist/tracker-progress.d.ts +8 -0
  75. package/dist/tracker-progress.js +197 -0
  76. package/dist/types.d.ts +247 -0
  77. package/dist/types.js +9 -0
  78. package/openclaw.plugin.json +107 -0
  79. package/package.json +61 -0
  80. package/skills/knowhere/SKILL.md +243 -0
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # Knowhere OpenClaw Plugin
2
+
3
+ Developer guide for the Knowhere plugin that integrates with OpenClaw.
4
+
5
+ This repository is intentionally focused on runtime behavior, storage, and
6
+ integration details. Agent-facing usage guidance lives in
7
+ [`skills/knowhere/SKILL.md`](./skills/knowhere/SKILL.md).
8
+
9
+ ## What this plugin does
10
+
11
+ The plugin uses Knowhere for parsing and job orchestration, then stores the
12
+ returned result package inside OpenClaw-managed local storage.
13
+
14
+ Its responsibilities are:
15
+
16
+ - register the `knowhere_*` tools
17
+ - optionally auto-ingest supported attachments
18
+ - persist extracted Knowhere result packages by scope
19
+ - expose browse-first path, chunk, context, and raw-file access back to agents
20
+ - inject compact document availability or status context when `autoGrounding` is enabled
21
+
22
+ The package targets Node `>=22.12.0` and builds to `dist/` with Rolldown plus
23
+ TypeScript declarations.
24
+
25
+ ## Repository layout
26
+
27
+ - [`src/index.ts`](./src/index.ts): plugin entrypoint and registration
28
+ - [`src/config.ts`](./src/config.ts): config schema and resolution
29
+ - [`src/client.ts`](./src/client.ts): Knowhere API client
30
+ - [`src/store.ts`](./src/store.ts): scoped local storage and index maintenance
31
+ - [`src/parser.ts`](./src/parser.ts): ZIP extraction and stored result readers
32
+ - [`src/tools.ts`](./src/tools.ts): `knowhere_*` tool definitions and response formatting
33
+ - [`src/hooks.ts`](./src/hooks.ts): auto-grounding and background attachment ingest
34
+ - [`skills/knowhere/SKILL.md`](./skills/knowhere/SKILL.md): agent usage instructions
35
+ - [`smoketest/run-tool.ts`](./smoketest/run-tool.ts): tool smoke-test entrypoint
36
+ - [`openclaw.plugin.json`](./openclaw.plugin.json): plugin manifest, config schema, and bundled skill declaration
37
+
38
+ ## Storage model
39
+
40
+ The store is scope-aware. Supported `scopeMode` values are `session`, `agent`,
41
+ and `global`.
42
+
43
+ Each scope is stored under the resolved plugin storage directory with this
44
+ shape:
45
+
46
+ ```text
47
+ <scope>/
48
+ index.json
49
+ documents/
50
+ <docId>/
51
+ metadata.json
52
+ browse-index.json
53
+ result/
54
+ manifest.json
55
+ chunks.json
56
+ hierarchy.json
57
+ full.md
58
+ ...
59
+ ```
60
+
61
+ Storage roles:
62
+
63
+ - `index.json`: internal per-scope cache of document summaries; rebuildable
64
+ - `metadata.json`: plugin-local mapping layer for one stored document
65
+ - `browse-index.json`: plugin-local browse index for path navigation and result-file inventory
66
+ - `result/`: untouched extracted Knowhere result package files
67
+
68
+ `index.json` is an optimization, not the primary source of truth. If its schema
69
+ version no longer matches the current store code, the plugin rebuilds it from
70
+ the per-document directories.
71
+
72
+ ## Runtime model
73
+
74
+ The plugin has three main runtime surfaces:
75
+
76
+ - tools: explicit ingest, browse, raw-file read, job, preview, and cleanup operations
77
+ - hooks: background attachment ingest plus prompt-time document/status injection
78
+ - skill: agent guidance for when and how to use the tools
79
+
80
+ The primary browse workflow is:
81
+
82
+ 1. `knowhere_read_result_file` on `manifest.json`
83
+ 2. `knowhere_preview_document`
84
+ 3. `knowhere_grep` for text search
85
+ 4. `knowhere_read_result_file` again for `hierarchy.json`, `kb.csv`, or table HTML when needed
86
+
87
+ `autoGrounding: true` enables hooks. `autoGrounding: false` leaves the plugin in
88
+ manual tool mode while still loading the bundled skill.
89
+
90
+ ## Development
91
+
92
+ Install dependencies:
93
+
94
+ ```bash
95
+ pnpm install
96
+ ```
97
+
98
+ Required validation after every code change:
99
+
100
+ ```bash
101
+ pnpm fmt
102
+ pnpm typecheck
103
+ pnpm lint
104
+ pnpm build
105
+ ```
106
+
107
+ Useful scripts:
108
+
109
+ - `pnpm build`: bundle runtime code and emit declarations
110
+ - `pnpm fmt`: format the repository with Oxc Formatter
111
+ - `pnpm fmt:check`: check formatting without writing changes
112
+ - `pnpm lint`: run Oxlint in type-aware mode
113
+ - `pnpm lint:fix`: run Oxlint with fixes
114
+ - `pnpm typecheck`: run `tsgo --noEmit`
115
+ - `pnpm smoke:tools -- ...`: execute one plugin tool through the real registration path
116
+ - `pnpm clean`: remove `dist/`
117
+
118
+ ## OpenClaw integration
119
+
120
+ Minimal plugin config:
121
+
122
+ ```json5
123
+ {
124
+ plugins: {
125
+ load: {
126
+ paths: ["/absolute/path/to/knowhere-openclaw-plugin"],
127
+ },
128
+ entries: {
129
+ knowhere: {
130
+ enabled: true,
131
+ config: {
132
+ apiKey: "sk_...",
133
+ scopeMode: "session",
134
+ autoGrounding: true,
135
+ },
136
+ },
137
+ },
138
+ },
139
+ }
140
+ ```
141
+
142
+ Config notes:
143
+
144
+ - `apiKey` falls back to `KNOWHERE_API_KEY`
145
+ - `baseUrl` falls back to `KNOWHERE_BASE_URL`
146
+ - `storageDir` defaults to the OpenClaw state directory under `plugins/<plugin-id>`
147
+ - `scopeMode` controls document sharing boundaries
148
+ - `maxContextChars`, polling, and timeout settings are declared in [`openclaw.plugin.json`](./openclaw.plugin.json)
149
+
150
+ If you use skill filters in OpenClaw, allow the bundled `knowhere` skill or the
151
+ agents will have the tools without the intended usage guidance.
152
+
153
+ ## Packaging
154
+
155
+ The published package includes:
156
+
157
+ - `dist/`
158
+ - `skills/`
159
+ - [`openclaw.plugin.json`](./openclaw.plugin.json)
160
+ - `README.md`
161
+
162
+ That is enough for OpenClaw to load the plugin runtime and bundled skill from
163
+ the installed package root.
@@ -0,0 +1,37 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
20
+ key = keys[i];
21
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
22
+ get: ((k) => from[k]).bind(null, key),
23
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
24
+ });
25
+ }
26
+ return to;
27
+ };
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
29
+ value: mod,
30
+ enumerable: true
31
+ }) : target, mod));
32
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
33
+ if (typeof require !== "undefined") return require.apply(this, arguments);
34
+ throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
35
+ });
36
+ //#endregion
37
+ export { __commonJSMin, __exportAll, __require, __toESM };
@@ -0,0 +1,33 @@
1
+ import type { KnowhereIngestRequest, KnowhereIngestResult, KnowhereJobList, KnowhereJobListRequest, KnowhereJobResult } from "./types";
2
+ export declare class KnowhereClient {
3
+ private readonly apiKey;
4
+ private readonly baseUrl;
5
+ private readonly requestTimeoutMs;
6
+ private readonly uploadTimeoutMs;
7
+ private readonly pollIntervalMs;
8
+ private readonly pollTimeoutMs;
9
+ private readonly logger;
10
+ private readonly sdk;
11
+ constructor(params: {
12
+ apiKey: string;
13
+ baseUrl: string;
14
+ requestTimeoutMs: number;
15
+ uploadTimeoutMs: number;
16
+ pollIntervalMs: number;
17
+ pollTimeoutMs: number;
18
+ logger: {
19
+ debug?: (message: string) => void;
20
+ info: (message: string) => void;
21
+ };
22
+ });
23
+ ingestDocument(params: KnowhereIngestRequest): Promise<KnowhereIngestResult>;
24
+ private logPollProgress;
25
+ getJob(jobId: string): Promise<KnowhereJobResult>;
26
+ listJobs(options?: KnowhereJobListRequest): Promise<KnowhereJobList>;
27
+ getCompletedJobResult(jobId: string): Promise<KnowhereIngestResult>;
28
+ private downloadResultArchive;
29
+ private uploadFile;
30
+ private requestJson;
31
+ private requestBuffer;
32
+ private request;
33
+ }
package/dist/client.js ADDED
@@ -0,0 +1,395 @@
1
+ import { isRecord } from "./types.js";
2
+ import { Knowhere } from "./node_modules/.pnpm/@knowhere-ai_sdk@0.1.1/node_modules/@knowhere-ai/sdk/dist/index.js";
3
+ import { formatErrorMessage } from "./error-message.js";
4
+ import path from "node:path";
5
+ import { createHash } from "node:crypto";
6
+ import { openAsBlob } from "node:fs";
7
+ //#region src/client.ts
8
+ const RETRYABLE_STATUS_CODES = new Set([
9
+ 409,
10
+ 500,
11
+ 502,
12
+ 503,
13
+ 504
14
+ ]);
15
+ const RETRYABLE_UPLOAD_STATUS_CODES = new Set([
16
+ 500,
17
+ 502,
18
+ 503,
19
+ 504
20
+ ]);
21
+ const USER_AGENT = "knowhere-openclaw-plugin/0.1.0";
22
+ function createRetryableError(message, retryable) {
23
+ const error = new Error(message);
24
+ error.retryable = retryable;
25
+ return error;
26
+ }
27
+ function sleep(ms) {
28
+ return new Promise((resolve) => setTimeout(resolve, ms));
29
+ }
30
+ function normalizeInlineText(value) {
31
+ return value.replace(/\s+/g, " ").trim();
32
+ }
33
+ function stringifyPayload(value) {
34
+ try {
35
+ return JSON.stringify(value);
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ function buildApiErrorMessage(status, payload) {
41
+ if (typeof payload === "string") {
42
+ const normalized = normalizeInlineText(payload);
43
+ if (normalized) return `HTTP ${status}: ${normalized}`;
44
+ }
45
+ if (isRecord(payload)) {
46
+ const nested = isRecord(payload.error) ? payload.error : payload;
47
+ const code = typeof nested.code === "string" ? nested.code : null;
48
+ const message = typeof nested.message === "string" ? nested.message : null;
49
+ const requestId = typeof nested.request_id === "string" ? nested.request_id : null;
50
+ const detailsPayload = Object.hasOwn(nested, "details") ? nested.details : void 0;
51
+ const detailsText = detailsPayload === void 0 ? null : stringifyPayload(detailsPayload);
52
+ const parts = [
53
+ code,
54
+ message ? normalizeInlineText(message) : null,
55
+ requestId ? `request_id=${requestId}` : null,
56
+ detailsText ? `details=${detailsText}` : null
57
+ ].filter((value) => Boolean(value));
58
+ if (parts.length > 0) return parts.join(" ");
59
+ const payloadText = stringifyPayload(payload);
60
+ if (payloadText) return `HTTP ${status}: ${payloadText}`;
61
+ }
62
+ return `HTTP ${status}`;
63
+ }
64
+ function buildChainedErrorMessage(prefix, error) {
65
+ const detail = formatErrorMessage(error);
66
+ return detail ? `${prefix} ${detail}` : prefix;
67
+ }
68
+ async function readApiErrorPayload(response) {
69
+ const normalized = (await response.clone().text()).trim();
70
+ if (!normalized) return null;
71
+ try {
72
+ return JSON.parse(normalized);
73
+ } catch {
74
+ return normalized;
75
+ }
76
+ }
77
+ function readRetryAfterSeconds(value) {
78
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
79
+ if (typeof value === "string" && value.trim()) {
80
+ const parsed = Number(value);
81
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
82
+ }
83
+ return null;
84
+ }
85
+ function extractRetryAfterSeconds(payload, response) {
86
+ if (isRecord(payload)) {
87
+ const nested = isRecord(payload.error) ? payload.error : payload;
88
+ const retryAfter = readRetryAfterSeconds((isRecord(nested.details) ? nested.details : null)?.retry_after);
89
+ if (retryAfter !== null) return retryAfter;
90
+ }
91
+ return readRetryAfterSeconds(response.headers.get("retry-after"));
92
+ }
93
+ function readFiniteNumber(value, fieldName) {
94
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`Knowhere API returned an invalid ${fieldName} value.`);
95
+ return value;
96
+ }
97
+ function toIsoString(value) {
98
+ if (value instanceof Date && !Number.isNaN(value.valueOf())) return value.toISOString();
99
+ if (typeof value === "string" && value.trim()) return value.trim();
100
+ return null;
101
+ }
102
+ function normalizeSdkJob(job) {
103
+ return {
104
+ job_id: job.jobId,
105
+ status: job.status,
106
+ source_type: job.sourceType,
107
+ data_id: job.dataId ?? null,
108
+ created_at: toIsoString(job.createdAt),
109
+ upload_url: job.uploadUrl ?? null,
110
+ upload_headers: job.uploadHeaders ?? null,
111
+ expires_in: job.expiresIn ?? null
112
+ };
113
+ }
114
+ function normalizeSdkJobError(error) {
115
+ if (!error) return null;
116
+ return {
117
+ code: error.code,
118
+ message: error.message,
119
+ request_id: error.requestId,
120
+ details: error.details
121
+ };
122
+ }
123
+ function normalizeSdkJobResult(jobResult) {
124
+ return {
125
+ job_id: jobResult.jobId,
126
+ status: jobResult.status,
127
+ source_type: jobResult.sourceType,
128
+ data_id: jobResult.dataId ?? null,
129
+ created_at: toIsoString(jobResult.createdAt),
130
+ progress: jobResult.progress ?? void 0,
131
+ error: normalizeSdkJobError(jobResult.error),
132
+ result: isRecord(jobResult.result) ? jobResult.result : null,
133
+ result_url: jobResult.resultUrl ?? null,
134
+ result_url_expires_at: toIsoString(jobResult.resultUrlExpiresAt),
135
+ file_name: jobResult.fileName ?? null,
136
+ file_extension: jobResult.fileExtension ?? null,
137
+ model: jobResult.model ?? null,
138
+ ocr_enabled: typeof jobResult.ocrEnabled === "boolean" ? jobResult.ocrEnabled : null,
139
+ duration_seconds: typeof jobResult.durationSeconds === "number" ? jobResult.durationSeconds : null,
140
+ credits_spent: typeof jobResult.creditsSpent === "number" ? jobResult.creditsSpent : null
141
+ };
142
+ }
143
+ function normalizeApiJobResult(raw) {
144
+ if (!isRecord(raw) || typeof raw.job_id !== "string") throw new Error("Knowhere API returned an invalid job result payload.");
145
+ return raw;
146
+ }
147
+ function normalizeApiJobList(raw) {
148
+ if (!isRecord(raw) || !Array.isArray(raw.jobs)) throw new Error("Knowhere API returned an invalid job list payload.");
149
+ return {
150
+ jobs: raw.jobs.map((job) => normalizeApiJobResult(job)),
151
+ total: readFiniteNumber(raw.total, "total"),
152
+ page: readFiniteNumber(raw.page, "page"),
153
+ pageSize: readFiniteNumber(raw.page_size, "page_size"),
154
+ totalPages: readFiniteNumber(raw.total_pages, "total_pages")
155
+ };
156
+ }
157
+ function buildSyntheticJob(jobResult) {
158
+ return {
159
+ job_id: jobResult.job_id,
160
+ status: jobResult.status,
161
+ source_type: jobResult.source_type,
162
+ data_id: jobResult.data_id ?? null,
163
+ created_at: jobResult.created_at ?? null,
164
+ upload_url: null,
165
+ upload_headers: null,
166
+ expires_in: null
167
+ };
168
+ }
169
+ function toSdkParsingParams(parsingParams) {
170
+ if (!parsingParams) return;
171
+ const mapped = {
172
+ ...parsingParams.model ? { model: parsingParams.model } : {},
173
+ ...typeof parsingParams.ocr_enabled === "boolean" ? { ocrEnabled: parsingParams.ocr_enabled } : {},
174
+ ...parsingParams.kb_dir ? { kbDir: parsingParams.kb_dir } : {},
175
+ ...parsingParams.doc_type ? { docType: parsingParams.doc_type } : {},
176
+ ...typeof parsingParams.smart_title_parse === "boolean" ? { smartTitleParse: parsingParams.smart_title_parse } : {},
177
+ ...typeof parsingParams.summary_image === "boolean" ? { summaryImage: parsingParams.summary_image } : {},
178
+ ...typeof parsingParams.summary_table === "boolean" ? { summaryTable: parsingParams.summary_table } : {},
179
+ ...typeof parsingParams.summary_txt === "boolean" ? { summaryTxt: parsingParams.summary_txt } : {},
180
+ ...parsingParams.add_frag_desc ? { addFragDesc: parsingParams.add_frag_desc } : {}
181
+ };
182
+ return Object.keys(mapped).length > 0 ? mapped : void 0;
183
+ }
184
+ var KnowhereClient = class {
185
+ apiKey;
186
+ baseUrl;
187
+ requestTimeoutMs;
188
+ uploadTimeoutMs;
189
+ pollIntervalMs;
190
+ pollTimeoutMs;
191
+ logger;
192
+ sdk;
193
+ constructor(params) {
194
+ this.apiKey = params.apiKey;
195
+ this.baseUrl = params.baseUrl.replace(/\/+$/, "");
196
+ this.requestTimeoutMs = params.requestTimeoutMs;
197
+ this.uploadTimeoutMs = params.uploadTimeoutMs;
198
+ this.pollIntervalMs = params.pollIntervalMs;
199
+ this.pollTimeoutMs = params.pollTimeoutMs;
200
+ this.logger = params.logger;
201
+ this.sdk = new Knowhere({
202
+ apiKey: this.apiKey,
203
+ baseURL: this.baseUrl,
204
+ timeout: this.requestTimeoutMs,
205
+ uploadTimeout: this.uploadTimeoutMs,
206
+ maxRetries: 5,
207
+ defaultHeaders: { "User-Agent": USER_AGENT }
208
+ });
209
+ }
210
+ async ingestDocument(params) {
211
+ const sourceType = params.url ? "url" : "file";
212
+ const parsingParams = toSdkParsingParams(params.parsingParams);
213
+ const resolvedFileName = typeof params.fileName === "string" && params.fileName.trim() ? params.fileName.trim() : params.filePath ? path.basename(params.filePath) : void 0;
214
+ const job = await this.sdk.jobs.create({
215
+ sourceType,
216
+ ...sourceType === "url" ? { sourceUrl: params.url } : { fileName: resolvedFileName },
217
+ ...params.dataId ? { dataId: params.dataId } : {},
218
+ ...parsingParams ? { parsingParams } : {}
219
+ });
220
+ this.logger.info(`knowhere: created ingest job ${job.jobId} sourceType=${sourceType} requestTimeoutMs=${this.requestTimeoutMs} pollIntervalMs=${this.pollIntervalMs} pollTimeoutMs=${this.pollTimeoutMs}`);
221
+ await params.onJobCreated?.(normalizeSdkJob(job));
222
+ if (sourceType === "file") {
223
+ if (!params.filePath) throw new Error("filePath is required when sourceType=file.");
224
+ if (!job.uploadUrl) throw new Error(`Knowhere job ${job.jobId} did not return an uploadUrl.`);
225
+ const fileBlob = await openAsBlob(params.filePath);
226
+ this.logger.info(`knowhere: uploading local file for job ${job.jobId} path=${params.filePath} bytes=${fileBlob.size}`);
227
+ await this.uploadFile(job.uploadUrl, job.uploadHeaders, fileBlob);
228
+ this.logger.info(`knowhere: upload completed for job ${job.jobId}`);
229
+ }
230
+ let updateChain = Promise.resolve();
231
+ const enqueueJobUpdate = (jobResult) => {
232
+ if (!params.onJobUpdated) return;
233
+ const normalizedJobResult = normalizeSdkJobResult(jobResult);
234
+ updateChain = updateChain.then(() => Promise.resolve(params.onJobUpdated?.(normalizedJobResult)));
235
+ };
236
+ const sdkJobResult = await this.sdk.jobs.wait(job.jobId, {
237
+ pollInterval: this.pollIntervalMs,
238
+ pollTimeout: this.pollTimeoutMs,
239
+ onProgress: (progress) => {
240
+ this.logPollProgress(job.jobId, progress);
241
+ enqueueJobUpdate(progress.jobResult);
242
+ }
243
+ });
244
+ this.logger.info(`knowhere: polling completed for job ${job.jobId} status=${sdkJobResult.status}`);
245
+ enqueueJobUpdate(sdkJobResult);
246
+ await updateChain;
247
+ const jobResult = normalizeSdkJobResult(sdkJobResult);
248
+ if (!jobResult.result_url) throw new Error(`Knowhere job ${jobResult.job_id} completed without a result_url.`);
249
+ return {
250
+ job: normalizeSdkJob(job),
251
+ jobResult,
252
+ downloadedResult: await this.downloadResultArchive(jobResult.result_url)
253
+ };
254
+ }
255
+ logPollProgress(jobId, progress) {
256
+ const parts = [
257
+ `knowhere: polling job ${jobId}`,
258
+ `status=${progress.status}`,
259
+ `elapsedSeconds=${progress.elapsedSeconds}`
260
+ ];
261
+ const progressText = stringifyPayload(progress.jobResult.progress);
262
+ if (progressText && progressText !== "null") parts.push(`progress=${normalizeInlineText(progressText)}`);
263
+ const errorMessage = progress.jobResult.error?.message;
264
+ if (errorMessage) parts.push(`error=${normalizeInlineText(errorMessage)}`);
265
+ (this.logger.debug ?? this.logger.info)(parts.join(" "));
266
+ }
267
+ async getJob(jobId) {
268
+ const normalizedJobId = jobId.trim();
269
+ if (!normalizedJobId) throw new Error("jobId is required.");
270
+ return normalizeSdkJobResult(await this.sdk.jobs.get(normalizedJobId));
271
+ }
272
+ async listJobs(options = {}) {
273
+ const params = new URLSearchParams();
274
+ if (typeof options.page === "number") params.set("page", String(options.page));
275
+ if (typeof options.pageSize === "number") params.set("page_size", String(options.pageSize));
276
+ if (typeof options.jobStatus === "string" && options.jobStatus.trim()) params.set("job_status", options.jobStatus.trim());
277
+ if (typeof options.jobType === "string" && options.jobType.trim()) params.set("job_type", options.jobType.trim());
278
+ if (typeof options.recentDays === "number") params.set("recent_days", String(options.recentDays));
279
+ if (typeof options.startTime === "string" && options.startTime.trim()) params.set("start_time", options.startTime.trim());
280
+ if (typeof options.endTime === "string" && options.endTime.trim()) params.set("end_time", options.endTime.trim());
281
+ const target = params.size > 0 ? `/v1/jobs/page?${params.toString()}` : "/v1/jobs/page";
282
+ return normalizeApiJobList(await this.requestJson("GET", target, void 0, { timeoutMs: this.requestTimeoutMs }));
283
+ }
284
+ async getCompletedJobResult(jobId) {
285
+ const normalizedJobId = jobId.trim();
286
+ if (!normalizedJobId) throw new Error("jobId is required.");
287
+ const jobResult = await this.getJob(normalizedJobId);
288
+ if (jobResult.status !== "done") throw new Error(`Knowhere job ${jobResult.job_id} is not completed. Current status: ${jobResult.status}.`);
289
+ if (!jobResult.result_url) throw new Error(`Knowhere job ${jobResult.job_id} completed without a result_url.`);
290
+ return {
291
+ job: buildSyntheticJob(jobResult),
292
+ jobResult,
293
+ downloadedResult: await this.downloadResultArchive(jobResult.result_url)
294
+ };
295
+ }
296
+ async downloadResultArchive(resultUrl) {
297
+ const resultZip = await this.requestBuffer(resultUrl, {
298
+ auth: false,
299
+ timeoutMs: this.uploadTimeoutMs
300
+ });
301
+ return {
302
+ zipBytes: resultZip,
303
+ rawZipSha1: createHash("sha1").update(resultZip).digest("hex")
304
+ };
305
+ }
306
+ async uploadFile(uploadUrl, uploadHeaders, fileBlob) {
307
+ const headers = { ...uploadHeaders ?? {} };
308
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-length")) headers["Content-Length"] = String(fileBlob.size);
309
+ let lastError;
310
+ for (let attempt = 0; attempt <= 2; attempt += 1) try {
311
+ const response = await fetch(uploadUrl, {
312
+ method: "PUT",
313
+ headers,
314
+ body: fileBlob,
315
+ signal: AbortSignal.timeout(this.uploadTimeoutMs)
316
+ });
317
+ if (response.ok) return;
318
+ const payload = await readApiErrorPayload(response);
319
+ if (attempt < 2 && RETRYABLE_UPLOAD_STATUS_CODES.has(response.status)) {
320
+ await sleep(Math.min(16e3, 1e3 * 2 ** attempt));
321
+ continue;
322
+ }
323
+ throw createRetryableError(buildApiErrorMessage(response.status, payload), false);
324
+ } catch (error) {
325
+ lastError = error;
326
+ const retryable = error instanceof Error && "retryable" in error ? error.retryable !== false : true;
327
+ if (attempt >= 2 || !retryable) break;
328
+ await sleep(Math.min(16e3, 1e3 * 2 ** attempt));
329
+ }
330
+ throw new Error(buildChainedErrorMessage("Knowhere upload failed.", lastError), { cause: lastError });
331
+ }
332
+ async requestJson(method, target, body, options = {}) {
333
+ const text = await (await this.request(method, target, body, {
334
+ ...options,
335
+ headers: {
336
+ Accept: "application/json",
337
+ ...body === void 0 ? {} : { "Content-Type": "application/json" },
338
+ ...options.headers ?? {}
339
+ }
340
+ })).text();
341
+ if (!text) return {};
342
+ try {
343
+ return JSON.parse(text);
344
+ } catch (error) {
345
+ throw new Error(`Knowhere API returned invalid JSON for ${method} ${target}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
346
+ }
347
+ }
348
+ async requestBuffer(target, options = {}) {
349
+ const arrayBuffer = await (await this.request("GET", target, void 0, {
350
+ ...options,
351
+ headers: {
352
+ Accept: "application/octet-stream",
353
+ ...options.headers ?? {}
354
+ }
355
+ })).arrayBuffer();
356
+ return Buffer.from(arrayBuffer);
357
+ }
358
+ async request(method, target, body, options = {}) {
359
+ const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
360
+ const retries = Number.isFinite(options.retries) ? options.retries : 2;
361
+ const url = /^https?:\/\//i.test(target) ? target : new URL(target.replace(/^\/+/, ""), `${this.baseUrl}/`).toString();
362
+ let lastError;
363
+ for (let attempt = 0; attempt <= retries; attempt += 1) try {
364
+ const response = await fetch(url, {
365
+ method,
366
+ headers: {
367
+ "User-Agent": USER_AGENT,
368
+ ...options.auth === false ? {} : { Authorization: `Bearer ${this.apiKey}` },
369
+ ...options.headers ?? {}
370
+ },
371
+ body: body === void 0 ? void 0 : JSON.stringify(body),
372
+ signal: AbortSignal.timeout(timeoutMs)
373
+ });
374
+ if (!response.ok) {
375
+ const payload = await readApiErrorPayload(response);
376
+ const retryAfter = extractRetryAfterSeconds(payload, response);
377
+ const canRetry = RETRYABLE_STATUS_CODES.has(response.status) || response.status === 429 && retryAfter !== null;
378
+ if (attempt < retries && canRetry) {
379
+ await sleep(retryAfter !== null ? retryAfter * 1e3 : Math.min(3e4, 500 * 2 ** attempt));
380
+ continue;
381
+ }
382
+ throw createRetryableError(buildApiErrorMessage(response.status, payload), false);
383
+ }
384
+ return response;
385
+ } catch (error) {
386
+ lastError = error;
387
+ const retryable = error instanceof Error && "retryable" in error ? error.retryable !== false : true;
388
+ if (attempt >= retries || !retryable) break;
389
+ await sleep(Math.min(3e4, 500 * 2 ** attempt));
390
+ }
391
+ throw new Error(buildChainedErrorMessage(`Knowhere request failed for ${method} ${target}.`, lastError), { cause: lastError });
392
+ }
393
+ };
394
+ //#endregion
395
+ export { KnowhereClient };
@@ -0,0 +1,6 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ import type { JsonSchemaObject, ResolvedKnowhereConfig } from "./types";
3
+ export declare const DEFAULT_BASE_URL = "https://api.knowhereto.ai";
4
+ export declare const knowherePluginConfigSchema: JsonSchemaObject;
5
+ export declare function resolveKnowhereConfig(api: OpenClawPluginApi): ResolvedKnowhereConfig;
6
+ export declare function assertKnowhereApiKey(config: ResolvedKnowhereConfig): void;