@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.
- package/README.md +163 -0
- package/dist/_virtual/_rolldown/runtime.js +37 -0
- package/dist/client.d.ts +33 -0
- package/dist/client.js +395 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +132 -0
- package/dist/error-message.d.ts +1 -0
- package/dist/error-message.js +48 -0
- package/dist/hooks.d.ts +8 -0
- package/dist/hooks.js +415 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +43 -0
- package/dist/node_modules/.pnpm/@knowhere-ai_sdk@0.1.1/node_modules/@knowhere-ai/sdk/dist/index.js +717 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/adapters/adapters.js +83 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/adapters/fetch.js +170 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/adapters/xhr.js +106 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/axios.js +57 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/cancel/CancelToken.js +90 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/cancel/CanceledError.js +20 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/cancel/isCancel.js +6 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/Axios.js +174 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/AxiosError.js +70 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/AxiosHeaders.js +204 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/InterceptorManager.js +60 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/buildFullPath.js +20 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/dispatchRequest.js +52 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/mergeConfig.js +81 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/settle.js +18 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/core/transformData.js +25 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/defaults/index.js +107 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/defaults/transitional.js +9 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/env/data.js +4 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +50 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/HttpStatusCode.js +77 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/bind.js +15 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/buildURL.js +40 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/combineURLs.js +14 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/composeSignals.js +39 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/cookies.js +31 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/formDataToJSON.js +67 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/isAbsoluteURL.js +14 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/isAxiosError.js +14 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/isURLSameOrigin.js +8 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/parseHeaders.js +53 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/parseProtocol.js +7 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/progressEventReducer.js +38 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/resolveConfig.js +36 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/speedometer.js +36 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/spread.js +29 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/throttle.js +38 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/toFormData.js +151 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/toURLEncodedForm.js +18 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/trackStream.js +69 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/helpers/validator.js +76 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/browser/classes/Blob.js +4 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/browser/classes/FormData.js +4 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/browser/classes/URLSearchParams.js +5 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/browser/index.js +22 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/common/utils.js +46 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/platform/index.js +9 -0
- package/dist/node_modules/.pnpm/axios@1.13.6/node_modules/axios/lib/utils.js +698 -0
- package/dist/node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/browser.js +426 -0
- package/dist/node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js +3110 -0
- package/dist/parser.d.ts +16 -0
- package/dist/parser.js +323 -0
- package/dist/session.d.ts +11 -0
- package/dist/session.js +78 -0
- package/dist/store.d.ts +62 -0
- package/dist/store.js +482 -0
- package/dist/text.d.ts +10 -0
- package/dist/text.js +34 -0
- package/dist/tools.d.ts +9 -0
- package/dist/tools.js +1177 -0
- package/dist/tracker-progress.d.ts +8 -0
- package/dist/tracker-progress.js +197 -0
- package/dist/types.d.ts +247 -0
- package/dist/types.js +9 -0
- package/openclaw.plugin.json +107 -0
- package/package.json +61 -0
- 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 };
|
package/dist/client.d.ts
ADDED
|
@@ -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 };
|
package/dist/config.d.ts
ADDED
|
@@ -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;
|