@kweaver-ai/kweaver-sdk 0.4.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/bin/kweaver.js +9 -0
- package/dist/api/agent-chat.d.ts +69 -0
- package/dist/api/agent-chat.js +379 -0
- package/dist/api/agent-list.d.ts +12 -0
- package/dist/api/agent-list.js +33 -0
- package/dist/api/context-loader.d.ts +115 -0
- package/dist/api/context-loader.js +259 -0
- package/dist/api/conversations.d.ts +24 -0
- package/dist/api/conversations.js +64 -0
- package/dist/api/knowledge-networks.d.ts +57 -0
- package/dist/api/knowledge-networks.js +158 -0
- package/dist/api/ontology-query.d.ts +75 -0
- package/dist/api/ontology-query.js +238 -0
- package/dist/api/semantic-search.d.ts +12 -0
- package/dist/api/semantic-search.js +34 -0
- package/dist/auth/oauth.d.ts +75 -0
- package/dist/auth/oauth.js +417 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +79 -0
- package/dist/client.d.ts +95 -0
- package/dist/client.js +104 -0
- package/dist/commands/agent-chat.d.ts +12 -0
- package/dist/commands/agent-chat.js +193 -0
- package/dist/commands/agent.d.ts +28 -0
- package/dist/commands/agent.js +431 -0
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +201 -0
- package/dist/commands/bkn.d.ts +70 -0
- package/dist/commands/bkn.js +1371 -0
- package/dist/commands/call.d.ts +14 -0
- package/dist/commands/call.js +151 -0
- package/dist/commands/context-loader.d.ts +1 -0
- package/dist/commands/context-loader.js +383 -0
- package/dist/commands/token.d.ts +2 -0
- package/dist/commands/token.js +24 -0
- package/dist/config/store.d.ts +77 -0
- package/dist/config/store.js +380 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +44 -0
- package/dist/kweaver.d.ts +146 -0
- package/dist/kweaver.js +184 -0
- package/dist/resources/agents.d.ts +37 -0
- package/dist/resources/agents.js +60 -0
- package/dist/resources/bkn.d.ts +45 -0
- package/dist/resources/bkn.js +86 -0
- package/dist/resources/context-loader.d.ts +15 -0
- package/dist/resources/context-loader.js +32 -0
- package/dist/resources/conversations.d.ts +11 -0
- package/dist/resources/conversations.js +17 -0
- package/dist/resources/knowledge-networks.d.ts +65 -0
- package/dist/resources/knowledge-networks.js +167 -0
- package/dist/ui/ChatApp.d.ts +16 -0
- package/dist/ui/ChatApp.js +248 -0
- package/dist/ui/MarkdownBlock.d.ts +5 -0
- package/dist/ui/MarkdownBlock.js +137 -0
- package/dist/ui/display-text.d.ts +1 -0
- package/dist/ui/display-text.js +1 -0
- package/dist/utils/browser.d.ts +1 -0
- package/dist/utils/browser.js +20 -0
- package/dist/utils/display-text.d.ts +3 -0
- package/dist/utils/display-text.js +46 -0
- package/dist/utils/http.d.ts +17 -0
- package/dist/utils/http.js +72 -0
- package/package.json +62 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { URL } from "node:url";
|
|
4
|
+
import { openBrowser } from "../utils/browser.js";
|
|
5
|
+
import { fetchTextOrThrow, HttpError, NetworkRequestError } from "../utils/http.js";
|
|
6
|
+
import { getCurrentPlatform, loadCallbackSession, loadClientConfig, loadTokenConfig, saveCallbackSession, saveClientConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
|
|
7
|
+
export function normalizeBaseUrl(value) {
|
|
8
|
+
return value.replace(/\/+$/, "");
|
|
9
|
+
}
|
|
10
|
+
function randomValue(size = 24) {
|
|
11
|
+
return randomBytes(size).toString("hex");
|
|
12
|
+
}
|
|
13
|
+
export function getAuthorizationSuccessMessage() {
|
|
14
|
+
return "Authorization succeeded. You can close this page and return to the terminal.";
|
|
15
|
+
}
|
|
16
|
+
function toBasicAuth(clientId, clientSecret) {
|
|
17
|
+
const user = encodeURIComponent(clientId);
|
|
18
|
+
const password = encodeURIComponent(clientSecret);
|
|
19
|
+
return `Basic ${Buffer.from(`${user}:${password}`).toString("base64")}`;
|
|
20
|
+
}
|
|
21
|
+
function buildTokenConfig(baseUrl, token) {
|
|
22
|
+
const obtainedAt = new Date().toISOString();
|
|
23
|
+
const expiresAt = token.expires_in === undefined
|
|
24
|
+
? undefined
|
|
25
|
+
: new Date(Date.now() + token.expires_in * 1000).toISOString();
|
|
26
|
+
return {
|
|
27
|
+
baseUrl,
|
|
28
|
+
accessToken: token.access_token,
|
|
29
|
+
tokenType: token.token_type,
|
|
30
|
+
scope: token.scope,
|
|
31
|
+
expiresIn: token.expires_in,
|
|
32
|
+
expiresAt,
|
|
33
|
+
refreshToken: token.refresh_token,
|
|
34
|
+
idToken: token.id_token,
|
|
35
|
+
obtainedAt,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export async function registerClient(options) {
|
|
39
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
40
|
+
const payload = {
|
|
41
|
+
client_name: options.clientName,
|
|
42
|
+
grant_types: ["authorization_code", "implicit", "refresh_token"],
|
|
43
|
+
response_types: ["token id_token", "code", "token"],
|
|
44
|
+
scope: "openid offline all",
|
|
45
|
+
redirect_uris: [options.redirectUri],
|
|
46
|
+
post_logout_redirect_uris: [options.logoutRedirectUri],
|
|
47
|
+
metadata: {
|
|
48
|
+
device: {
|
|
49
|
+
name: options.clientName,
|
|
50
|
+
client_type: "web",
|
|
51
|
+
description: "kweaver CLI",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const { body } = await fetchTextOrThrow(`${baseUrl}/oauth2/clients`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
"content-type": "application/json",
|
|
59
|
+
accept: "application/json",
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify(payload),
|
|
62
|
+
});
|
|
63
|
+
const data = JSON.parse(body);
|
|
64
|
+
const clientConfig = {
|
|
65
|
+
baseUrl,
|
|
66
|
+
clientId: data.client_id,
|
|
67
|
+
clientSecret: data.client_secret,
|
|
68
|
+
redirectUri: options.redirectUri,
|
|
69
|
+
logoutRedirectUri: options.logoutRedirectUri,
|
|
70
|
+
scope: payload.scope,
|
|
71
|
+
lang: options.lang,
|
|
72
|
+
product: options.product,
|
|
73
|
+
xForwardedPrefix: options.xForwardedPrefix,
|
|
74
|
+
};
|
|
75
|
+
saveClientConfig(clientConfig);
|
|
76
|
+
return clientConfig;
|
|
77
|
+
}
|
|
78
|
+
function toSuccessfulLogoutPath(pathname) {
|
|
79
|
+
if (pathname.endsWith("/callback")) {
|
|
80
|
+
return `${pathname.slice(0, -"/callback".length)}/successful-logout`;
|
|
81
|
+
}
|
|
82
|
+
return `${pathname.replace(/\/$/, "")}/successful-logout`;
|
|
83
|
+
}
|
|
84
|
+
function normalizeListenHost(host) {
|
|
85
|
+
return host?.trim() || "127.0.0.1";
|
|
86
|
+
}
|
|
87
|
+
export function buildAuthRedirectConfig(options) {
|
|
88
|
+
const listenHost = normalizeListenHost(options.host);
|
|
89
|
+
const listenPort = options.port;
|
|
90
|
+
if (options.redirectUriOverride) {
|
|
91
|
+
const redirect = new URL(options.redirectUriOverride);
|
|
92
|
+
const logout = new URL(options.redirectUriOverride);
|
|
93
|
+
logout.pathname = toSuccessfulLogoutPath(redirect.pathname);
|
|
94
|
+
logout.search = "";
|
|
95
|
+
logout.hash = "";
|
|
96
|
+
return {
|
|
97
|
+
redirectUri: redirect.toString(),
|
|
98
|
+
logoutRedirectUri: logout.toString(),
|
|
99
|
+
listenHost,
|
|
100
|
+
listenPort,
|
|
101
|
+
callbackPath: redirect.pathname,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
redirectUri: `http://${listenHost}:${listenPort}/callback`,
|
|
106
|
+
logoutRedirectUri: `http://${listenHost}:${listenPort}/successful-logout`,
|
|
107
|
+
listenHost,
|
|
108
|
+
listenPort,
|
|
109
|
+
callbackPath: "/callback",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export async function ensureClientConfig(options) {
|
|
113
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
114
|
+
const redirect = buildAuthRedirectConfig({
|
|
115
|
+
port: options.port,
|
|
116
|
+
host: options.host,
|
|
117
|
+
redirectUriOverride: options.redirectUriOverride,
|
|
118
|
+
});
|
|
119
|
+
const { redirectUri, logoutRedirectUri } = redirect;
|
|
120
|
+
const client = loadClientConfig(baseUrl);
|
|
121
|
+
if (client &&
|
|
122
|
+
!options.forceRegister &&
|
|
123
|
+
client.baseUrl === baseUrl &&
|
|
124
|
+
client.redirectUri === redirectUri &&
|
|
125
|
+
client.clientId &&
|
|
126
|
+
client.clientSecret) {
|
|
127
|
+
return { client, created: false };
|
|
128
|
+
}
|
|
129
|
+
const createdClient = await registerClient({
|
|
130
|
+
baseUrl,
|
|
131
|
+
clientName: options.clientName,
|
|
132
|
+
redirectUri,
|
|
133
|
+
logoutRedirectUri,
|
|
134
|
+
lang: options.lang,
|
|
135
|
+
product: options.product,
|
|
136
|
+
xForwardedPrefix: options.xForwardedPrefix,
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
client: createdClient,
|
|
140
|
+
created: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function buildAuthorizationUrl(client, state = randomValue(12)) {
|
|
144
|
+
const authorizationUrl = new URL(`${client.baseUrl}/oauth2/auth`);
|
|
145
|
+
authorizationUrl.searchParams.set("redirect_uri", client.redirectUri);
|
|
146
|
+
authorizationUrl.searchParams.set("x-forwarded-prefix", client.xForwardedPrefix ?? "");
|
|
147
|
+
authorizationUrl.searchParams.set("client_id", client.clientId);
|
|
148
|
+
authorizationUrl.searchParams.set("scope", client.scope);
|
|
149
|
+
authorizationUrl.searchParams.set("response_type", "code");
|
|
150
|
+
authorizationUrl.searchParams.set("state", state);
|
|
151
|
+
authorizationUrl.searchParams.set("lang", client.lang ?? "zh-cn");
|
|
152
|
+
if (client.product) {
|
|
153
|
+
authorizationUrl.searchParams.set("product", client.product);
|
|
154
|
+
}
|
|
155
|
+
return authorizationUrl.toString();
|
|
156
|
+
}
|
|
157
|
+
function waitForAuthorizationCode(options, expectedState) {
|
|
158
|
+
const { listenHost, listenPort, callbackPath } = options;
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const server = createServer((request, response) => {
|
|
161
|
+
if (!request.url) {
|
|
162
|
+
response.statusCode = 400;
|
|
163
|
+
response.end("Missing request URL");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const callbackUrl = new URL(request.url, `http://${request.headers.host ?? `${listenHost}:${listenPort}`}`);
|
|
167
|
+
if (callbackUrl.pathname !== callbackPath) {
|
|
168
|
+
response.statusCode = 404;
|
|
169
|
+
response.end("Not Found");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const error = callbackUrl.searchParams.get("error");
|
|
173
|
+
if (error) {
|
|
174
|
+
response.statusCode = 400;
|
|
175
|
+
response.end(`Authorization failed: ${error}`);
|
|
176
|
+
server.close();
|
|
177
|
+
reject(new Error(`Authorization failed: ${error}`));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const state = callbackUrl.searchParams.get("state");
|
|
181
|
+
if (state !== expectedState) {
|
|
182
|
+
response.statusCode = 400;
|
|
183
|
+
response.end("State mismatch");
|
|
184
|
+
server.close();
|
|
185
|
+
reject(new Error("State mismatch in OAuth callback"));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const code = callbackUrl.searchParams.get("code");
|
|
189
|
+
if (!code) {
|
|
190
|
+
response.statusCode = 400;
|
|
191
|
+
response.end("Missing authorization code");
|
|
192
|
+
server.close();
|
|
193
|
+
reject(new Error("Missing authorization code"));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
response.statusCode = 200;
|
|
197
|
+
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
198
|
+
response.end(getAuthorizationSuccessMessage());
|
|
199
|
+
server.close();
|
|
200
|
+
resolve({
|
|
201
|
+
code,
|
|
202
|
+
state,
|
|
203
|
+
scope: callbackUrl.searchParams.get("scope") ?? undefined,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
server.on("error", (error) => reject(error));
|
|
207
|
+
server.listen(listenPort, listenHost);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
async function exchangeAuthorizationCode(client, code) {
|
|
211
|
+
const payload = new URLSearchParams({
|
|
212
|
+
grant_type: "authorization_code",
|
|
213
|
+
code,
|
|
214
|
+
redirect_uri: client.redirectUri,
|
|
215
|
+
});
|
|
216
|
+
const { body } = await fetchTextOrThrow(`${client.baseUrl}/oauth2/token`, {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: {
|
|
219
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
220
|
+
accept: "application/json",
|
|
221
|
+
authorization: toBasicAuth(client.clientId, client.clientSecret),
|
|
222
|
+
},
|
|
223
|
+
body: payload.toString(),
|
|
224
|
+
});
|
|
225
|
+
const tokenConfig = buildTokenConfig(client.baseUrl, JSON.parse(body));
|
|
226
|
+
saveTokenConfig(tokenConfig);
|
|
227
|
+
return tokenConfig;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Call the platform's end-session endpoint so the server invalidates the session.
|
|
231
|
+
* Best-effort: failures are ignored so local logout still proceeds.
|
|
232
|
+
*/
|
|
233
|
+
export async function callLogoutEndpoint(client, token) {
|
|
234
|
+
const url = new URL(`${client.baseUrl}/oauth2/signout`);
|
|
235
|
+
if (token?.idToken) {
|
|
236
|
+
url.searchParams.set("id_token_hint", token.idToken);
|
|
237
|
+
}
|
|
238
|
+
url.searchParams.set("post_logout_redirect_uri", client.logoutRedirectUri);
|
|
239
|
+
url.searchParams.set("client_id", client.clientId);
|
|
240
|
+
try {
|
|
241
|
+
const response = await fetch(url.toString(), { method: "GET", redirect: "manual" });
|
|
242
|
+
if (!response.ok && response.status !== 302) {
|
|
243
|
+
const body = await response.text();
|
|
244
|
+
console.error(`Logout endpoint returned ${response.status}: ${body.slice(0, 200)}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
249
|
+
console.error(`Logout endpoint request failed: ${msg}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
export async function refreshAccessToken(client, refreshToken) {
|
|
253
|
+
const payload = new URLSearchParams({
|
|
254
|
+
grant_type: "refresh_token",
|
|
255
|
+
refresh_token: refreshToken,
|
|
256
|
+
});
|
|
257
|
+
const { body } = await fetchTextOrThrow(`${client.baseUrl}/oauth2/token`, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers: {
|
|
260
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
261
|
+
accept: "application/json",
|
|
262
|
+
authorization: toBasicAuth(client.clientId, client.clientSecret),
|
|
263
|
+
},
|
|
264
|
+
body: payload.toString(),
|
|
265
|
+
});
|
|
266
|
+
const parsed = JSON.parse(body);
|
|
267
|
+
const tokenConfig = buildTokenConfig(client.baseUrl, {
|
|
268
|
+
...parsed,
|
|
269
|
+
refresh_token: parsed.refresh_token ?? refreshToken,
|
|
270
|
+
});
|
|
271
|
+
saveTokenConfig(tokenConfig);
|
|
272
|
+
return tokenConfig;
|
|
273
|
+
}
|
|
274
|
+
export async function ensureValidToken() {
|
|
275
|
+
const envToken = process.env.KWEAVER_TOKEN;
|
|
276
|
+
const envBaseUrl = process.env.KWEAVER_BASE_URL;
|
|
277
|
+
if (envToken && envBaseUrl) {
|
|
278
|
+
return {
|
|
279
|
+
baseUrl: normalizeBaseUrl(envBaseUrl),
|
|
280
|
+
accessToken: envToken,
|
|
281
|
+
tokenType: "bearer",
|
|
282
|
+
scope: "openid",
|
|
283
|
+
obtainedAt: new Date().toISOString(),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const currentPlatform = getCurrentPlatform();
|
|
287
|
+
if (!currentPlatform) {
|
|
288
|
+
throw new Error("No active platform selected. Run `kweaver auth <platform-url>` first.");
|
|
289
|
+
}
|
|
290
|
+
const client = loadClientConfig(currentPlatform);
|
|
291
|
+
const token = loadTokenConfig(currentPlatform);
|
|
292
|
+
if (!client || !token) {
|
|
293
|
+
throw new Error(`Missing saved credentials for ${currentPlatform}. Run \`kweaver auth ${currentPlatform}\` first.`);
|
|
294
|
+
}
|
|
295
|
+
if (!token.expiresAt) {
|
|
296
|
+
return token;
|
|
297
|
+
}
|
|
298
|
+
const expiresAtMs = Date.parse(token.expiresAt);
|
|
299
|
+
if (Number.isNaN(expiresAtMs) || expiresAtMs - 60_000 > Date.now()) {
|
|
300
|
+
return token;
|
|
301
|
+
}
|
|
302
|
+
if (!token.refreshToken) {
|
|
303
|
+
throw new Error("Access token expired and no refresh token is available. Run auth login again.");
|
|
304
|
+
}
|
|
305
|
+
return refreshAccessToken(client, token.refreshToken);
|
|
306
|
+
}
|
|
307
|
+
export async function login(options) {
|
|
308
|
+
const redirect = buildAuthRedirectConfig({
|
|
309
|
+
port: options.port,
|
|
310
|
+
host: options.host,
|
|
311
|
+
redirectUriOverride: options.redirectUriOverride,
|
|
312
|
+
});
|
|
313
|
+
const { client, created } = await ensureClientConfig({
|
|
314
|
+
baseUrl: options.baseUrl,
|
|
315
|
+
port: options.port,
|
|
316
|
+
clientName: options.clientName,
|
|
317
|
+
forceRegister: options.forceRegister,
|
|
318
|
+
host: options.host,
|
|
319
|
+
redirectUriOverride: options.redirectUriOverride,
|
|
320
|
+
lang: options.lang,
|
|
321
|
+
product: options.product,
|
|
322
|
+
xForwardedPrefix: options.xForwardedPrefix,
|
|
323
|
+
});
|
|
324
|
+
const state = randomValue(12);
|
|
325
|
+
const authorizationUrl = buildAuthorizationUrl(client, state);
|
|
326
|
+
const waitForCode = waitForAuthorizationCode({
|
|
327
|
+
listenHost: redirect.listenHost,
|
|
328
|
+
listenPort: redirect.listenPort,
|
|
329
|
+
callbackPath: redirect.callbackPath,
|
|
330
|
+
}, state);
|
|
331
|
+
if (options.open) {
|
|
332
|
+
console.log(`Opening browser for authorization: ${authorizationUrl}`);
|
|
333
|
+
const opened = await openBrowser(authorizationUrl);
|
|
334
|
+
if (!opened) {
|
|
335
|
+
console.error("Failed to open a browser automatically. Open this URL manually:");
|
|
336
|
+
console.error(authorizationUrl);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
console.log("Authorization URL:");
|
|
341
|
+
console.log(authorizationUrl);
|
|
342
|
+
console.log("");
|
|
343
|
+
console.log(`Redirect URI: ${client.redirectUri}`);
|
|
344
|
+
console.log(`Waiting for OAuth callback on http://${redirect.listenHost}:${redirect.listenPort}${redirect.callbackPath}`);
|
|
345
|
+
if (redirect.listenHost === "127.0.0.1" || redirect.listenHost === "localhost") {
|
|
346
|
+
console.log("");
|
|
347
|
+
console.log("If your browser is on another machine, use SSH port forwarding first:");
|
|
348
|
+
console.log(`ssh -L ${redirect.listenPort}:127.0.0.1:${redirect.listenPort} user@server`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const callbackResult = await waitForCode;
|
|
352
|
+
const callback = {
|
|
353
|
+
baseUrl: client.baseUrl,
|
|
354
|
+
redirectUri: client.redirectUri,
|
|
355
|
+
code: callbackResult.code,
|
|
356
|
+
state: callbackResult.state,
|
|
357
|
+
scope: callbackResult.scope,
|
|
358
|
+
receivedAt: new Date().toISOString(),
|
|
359
|
+
};
|
|
360
|
+
saveCallbackSession(callback);
|
|
361
|
+
const token = await exchangeAuthorizationCode(client, callbackResult.code);
|
|
362
|
+
setCurrentPlatform(client.baseUrl);
|
|
363
|
+
return { client, token, authorizationUrl, callback, created };
|
|
364
|
+
}
|
|
365
|
+
export function getStoredAuthSummary(baseUrl) {
|
|
366
|
+
const targetBaseUrl = baseUrl ?? getCurrentPlatform() ?? undefined;
|
|
367
|
+
return {
|
|
368
|
+
client: loadClientConfig(targetBaseUrl),
|
|
369
|
+
token: loadTokenConfig(targetBaseUrl),
|
|
370
|
+
callback: loadCallbackSession(targetBaseUrl),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function formatOAuthErrorBody(body) {
|
|
374
|
+
let data;
|
|
375
|
+
try {
|
|
376
|
+
data = JSON.parse(body);
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
if (!data || typeof data.error !== "string") {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
const code = data.error;
|
|
385
|
+
const description = typeof data.error_description === "string" ? data.error_description : "";
|
|
386
|
+
const lines = [`OAuth error: ${code}`];
|
|
387
|
+
if (description) {
|
|
388
|
+
lines.push(description);
|
|
389
|
+
}
|
|
390
|
+
if (code === "invalid_grant") {
|
|
391
|
+
lines.push("");
|
|
392
|
+
lines.push("The refresh token or authorization code is invalid or expired. Run `kweaver auth <platform-url>` again to log in.");
|
|
393
|
+
}
|
|
394
|
+
return lines.join("\n");
|
|
395
|
+
}
|
|
396
|
+
export function formatHttpError(error) {
|
|
397
|
+
if (error instanceof HttpError) {
|
|
398
|
+
const oauthMessage = formatOAuthErrorBody(error.body);
|
|
399
|
+
if (oauthMessage) {
|
|
400
|
+
return `HTTP ${error.status} ${error.statusText}\n\n${oauthMessage}`;
|
|
401
|
+
}
|
|
402
|
+
return `${error.message}\n${error.body}`.trim();
|
|
403
|
+
}
|
|
404
|
+
if (error instanceof NetworkRequestError) {
|
|
405
|
+
return [
|
|
406
|
+
error.message,
|
|
407
|
+
`Method: ${error.method}`,
|
|
408
|
+
`URL: ${error.url}`,
|
|
409
|
+
`Cause: ${error.causeMessage}`,
|
|
410
|
+
`Hint: ${error.hint}`,
|
|
411
|
+
].join("\n").trim();
|
|
412
|
+
}
|
|
413
|
+
if (error instanceof Error) {
|
|
414
|
+
return error.message;
|
|
415
|
+
}
|
|
416
|
+
return String(error);
|
|
417
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv: string[]): Promise<number>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { runAgentCommand } from "./commands/agent.js";
|
|
2
|
+
import { runAuthCommand } from "./commands/auth.js";
|
|
3
|
+
import { runKnCommand } from "./commands/bkn.js";
|
|
4
|
+
import { runCallCommand } from "./commands/call.js";
|
|
5
|
+
import { runContextLoaderCommand } from "./commands/context-loader.js";
|
|
6
|
+
import { runTokenCommand } from "./commands/token.js";
|
|
7
|
+
function printHelp() {
|
|
8
|
+
console.log(`kweaver
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
kweaver auth <platform-url>
|
|
12
|
+
kweaver auth login <platform-url>
|
|
13
|
+
kweaver auth <platform-url> [--alias name] [--no-open] [--host host] [--redirect-uri uri]
|
|
14
|
+
kweaver auth status [platform-url]
|
|
15
|
+
kweaver auth list
|
|
16
|
+
kweaver auth use <platform-url>
|
|
17
|
+
kweaver auth logout [platform-url]
|
|
18
|
+
kweaver auth delete <platform-url>
|
|
19
|
+
kweaver token
|
|
20
|
+
kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [--pretty] [--verbose] [-bd value]
|
|
21
|
+
kweaver agent chat <agent_id> [-m "message"] [--version value] [--conversation-id id] [--stream] [--no-stream] [--verbose] [-bd value]
|
|
22
|
+
kweaver agent list [options]
|
|
23
|
+
kweaver bkn list [options]
|
|
24
|
+
kweaver bkn get <kn-id> [options]
|
|
25
|
+
kweaver bkn search <kn-id> <query> [options]
|
|
26
|
+
kweaver bkn create [options]
|
|
27
|
+
kweaver bkn update <kn-id> [options]
|
|
28
|
+
kweaver bkn delete <kn-id>
|
|
29
|
+
kweaver context-loader [config|kn-search|...]
|
|
30
|
+
kweaver --help
|
|
31
|
+
|
|
32
|
+
Commands:
|
|
33
|
+
auth Login, list, inspect, and switch saved platform auth profiles
|
|
34
|
+
token Print the current access token, refreshing it first if needed
|
|
35
|
+
call Call an API with curl-style flags and auto-injected token headers
|
|
36
|
+
agent Chat with a KWeaver agent (agent chat <id>), list published agents (agent list)
|
|
37
|
+
bkn Business knowledge network (list/get/create/update/delete/export/stats; object-type, subgraph, action-type, action-log)
|
|
38
|
+
context-loader Call context-loader MCP (tools, resources, prompts; kn-search, query-*, etc.)
|
|
39
|
+
help Show this message`);
|
|
40
|
+
}
|
|
41
|
+
export async function run(argv) {
|
|
42
|
+
const [command, ...rest] = argv;
|
|
43
|
+
if (argv.length === 0 || !command || command === "--help" || command === "-h" || command === "help") {
|
|
44
|
+
printHelp();
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
if (command === "auth") {
|
|
48
|
+
return runAuthCommand(rest);
|
|
49
|
+
}
|
|
50
|
+
if (command === "call" || command === "curl") {
|
|
51
|
+
return runCallCommand(rest);
|
|
52
|
+
}
|
|
53
|
+
if (command === "token") {
|
|
54
|
+
return runTokenCommand(rest);
|
|
55
|
+
}
|
|
56
|
+
if (command === "agent") {
|
|
57
|
+
return runAgentCommand(rest);
|
|
58
|
+
}
|
|
59
|
+
if (command === "bkn") {
|
|
60
|
+
return runKnCommand(rest);
|
|
61
|
+
}
|
|
62
|
+
if (command === "context-loader" || command === "context") {
|
|
63
|
+
return runContextLoaderCommand(rest);
|
|
64
|
+
}
|
|
65
|
+
console.error(`Unknown command: ${command}`);
|
|
66
|
+
printHelp();
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
70
|
+
run(process.argv.slice(2))
|
|
71
|
+
.then((code) => {
|
|
72
|
+
process.exit(code);
|
|
73
|
+
})
|
|
74
|
+
.catch((error) => {
|
|
75
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
+
console.error(message);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
79
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { AgentsResource } from "./resources/agents.js";
|
|
2
|
+
import { ConversationsResource } from "./resources/conversations.js";
|
|
3
|
+
import { ContextLoaderResource } from "./resources/context-loader.js";
|
|
4
|
+
import { KnowledgeNetworksResource } from "./resources/knowledge-networks.js";
|
|
5
|
+
import { BknResource } from "./resources/bkn.js";
|
|
6
|
+
/**
|
|
7
|
+
* Shared credentials passed to every resource method.
|
|
8
|
+
* Internal — use KWeaverClient.
|
|
9
|
+
*/
|
|
10
|
+
export interface ClientContext {
|
|
11
|
+
/** Returns the base options that every API function requires. */
|
|
12
|
+
base(): {
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
accessToken: string;
|
|
15
|
+
businessDomain: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface KWeaverClientOptions {
|
|
19
|
+
/**
|
|
20
|
+
* KWeaver platform base URL (e.g. "https://your-kweaver.com").
|
|
21
|
+
* When omitted, reads the active platform saved by `kweaver auth login`.
|
|
22
|
+
*/
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Bearer access token.
|
|
26
|
+
* When omitted, reads the token saved for the active platform.
|
|
27
|
+
*/
|
|
28
|
+
accessToken?: string;
|
|
29
|
+
/**
|
|
30
|
+
* x-business-domain header value. Defaults to "bd_public".
|
|
31
|
+
* Override with KWEAVER_BUSINESS_DOMAIN env var or pass explicitly.
|
|
32
|
+
*/
|
|
33
|
+
businessDomain?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Main entry point for the KWeaver TypeScript SDK.
|
|
37
|
+
*
|
|
38
|
+
* @example Using explicit credentials:
|
|
39
|
+
* ```typescript
|
|
40
|
+
* import { KWeaverClient } from "kweaver-sdk";
|
|
41
|
+
*
|
|
42
|
+
* const client = new KWeaverClient({
|
|
43
|
+
* baseUrl: "https://your-kweaver.com",
|
|
44
|
+
* accessToken: "your-token",
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* const kns = await client.knowledgeNetworks.list();
|
|
48
|
+
* const reply = await client.agents.chat("agent-id", "你好");
|
|
49
|
+
* console.log(reply.text);
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example Using credentials saved by `kweaver auth login` (zero config):
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const client = new KWeaverClient(); // reads ~/.kweaver/
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @example Using environment variables:
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // Set KWEAVER_BASE_URL and KWEAVER_TOKEN
|
|
60
|
+
* const client = new KWeaverClient();
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare class KWeaverClient implements ClientContext {
|
|
64
|
+
private readonly _baseUrl;
|
|
65
|
+
private readonly _accessToken;
|
|
66
|
+
private readonly _businessDomain;
|
|
67
|
+
/** Knowledge network CRUD and schema (object/relation/action types). */
|
|
68
|
+
readonly knowledgeNetworks: KnowledgeNetworksResource;
|
|
69
|
+
/** Agent listing and chat (single-shot and streaming). */
|
|
70
|
+
readonly agents: AgentsResource;
|
|
71
|
+
/** BKN engine: instance queries, subgraph, action execute/poll, action logs. */
|
|
72
|
+
readonly bkn: BknResource;
|
|
73
|
+
/** Conversation and message history. */
|
|
74
|
+
readonly conversations: ConversationsResource;
|
|
75
|
+
constructor(opts?: KWeaverClientOptions);
|
|
76
|
+
/** @internal — used by resource classes to build API call options. */
|
|
77
|
+
base(): {
|
|
78
|
+
baseUrl: string;
|
|
79
|
+
accessToken: string;
|
|
80
|
+
businessDomain: string;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Create a ContextLoaderResource bound to a specific knowledge network.
|
|
84
|
+
*
|
|
85
|
+
* @param mcpUrl Full MCP endpoint URL (e.g. from `kweaver context-loader config show`).
|
|
86
|
+
* @param knId Knowledge network ID to search against.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const cl = client.contextLoader(mcpUrl, "d5iv6c9818p72mpje8pg");
|
|
91
|
+
* const results = await cl.search({ query: "高血压 治疗" });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
contextLoader(mcpUrl: string, knId: string): ContextLoaderResource;
|
|
95
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { getCurrentPlatform, loadTokenConfig, } from "./config/store.js";
|
|
2
|
+
import { AgentsResource } from "./resources/agents.js";
|
|
3
|
+
import { ConversationsResource } from "./resources/conversations.js";
|
|
4
|
+
import { ContextLoaderResource } from "./resources/context-loader.js";
|
|
5
|
+
import { KnowledgeNetworksResource } from "./resources/knowledge-networks.js";
|
|
6
|
+
import { BknResource } from "./resources/bkn.js";
|
|
7
|
+
// ── KWeaverClient ─────────────────────────────────────────────────────────────
|
|
8
|
+
/**
|
|
9
|
+
* Main entry point for the KWeaver TypeScript SDK.
|
|
10
|
+
*
|
|
11
|
+
* @example Using explicit credentials:
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { KWeaverClient } from "kweaver-sdk";
|
|
14
|
+
*
|
|
15
|
+
* const client = new KWeaverClient({
|
|
16
|
+
* baseUrl: "https://your-kweaver.com",
|
|
17
|
+
* accessToken: "your-token",
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const kns = await client.knowledgeNetworks.list();
|
|
21
|
+
* const reply = await client.agents.chat("agent-id", "你好");
|
|
22
|
+
* console.log(reply.text);
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Using credentials saved by `kweaver auth login` (zero config):
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const client = new KWeaverClient(); // reads ~/.kweaver/
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Using environment variables:
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Set KWEAVER_BASE_URL and KWEAVER_TOKEN
|
|
33
|
+
* const client = new KWeaverClient();
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class KWeaverClient {
|
|
37
|
+
_baseUrl;
|
|
38
|
+
_accessToken;
|
|
39
|
+
_businessDomain;
|
|
40
|
+
/** Knowledge network CRUD and schema (object/relation/action types). */
|
|
41
|
+
knowledgeNetworks;
|
|
42
|
+
/** Agent listing and chat (single-shot and streaming). */
|
|
43
|
+
agents;
|
|
44
|
+
/** BKN engine: instance queries, subgraph, action execute/poll, action logs. */
|
|
45
|
+
bkn;
|
|
46
|
+
/** Conversation and message history. */
|
|
47
|
+
conversations;
|
|
48
|
+
constructor(opts = {}) {
|
|
49
|
+
const envUrl = process.env.KWEAVER_BASE_URL;
|
|
50
|
+
const envToken = process.env.KWEAVER_TOKEN;
|
|
51
|
+
const envDomain = process.env.KWEAVER_BUSINESS_DOMAIN;
|
|
52
|
+
// Resolve baseUrl: explicit > env > saved config
|
|
53
|
+
let baseUrl = opts.baseUrl ?? envUrl;
|
|
54
|
+
let accessToken = opts.accessToken ?? envToken;
|
|
55
|
+
if (!baseUrl || !accessToken) {
|
|
56
|
+
const platform = getCurrentPlatform();
|
|
57
|
+
if (platform) {
|
|
58
|
+
const stored = loadTokenConfig(platform);
|
|
59
|
+
if (!baseUrl)
|
|
60
|
+
baseUrl = platform;
|
|
61
|
+
if (!accessToken && stored)
|
|
62
|
+
accessToken = stored.accessToken;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!baseUrl) {
|
|
66
|
+
throw new Error("KWeaverClient: baseUrl is required. " +
|
|
67
|
+
"Pass it explicitly, set KWEAVER_BASE_URL, or run `kweaver auth login`.");
|
|
68
|
+
}
|
|
69
|
+
if (!accessToken) {
|
|
70
|
+
throw new Error("KWeaverClient: accessToken is required. " +
|
|
71
|
+
"Pass it explicitly, set KWEAVER_TOKEN, or run `kweaver auth login`.");
|
|
72
|
+
}
|
|
73
|
+
this._baseUrl = baseUrl.replace(/\/+$/, "");
|
|
74
|
+
this._accessToken = accessToken;
|
|
75
|
+
this._businessDomain = opts.businessDomain ?? envDomain ?? "bd_public";
|
|
76
|
+
this.knowledgeNetworks = new KnowledgeNetworksResource(this);
|
|
77
|
+
this.agents = new AgentsResource(this);
|
|
78
|
+
this.bkn = new BknResource(this);
|
|
79
|
+
this.conversations = new ConversationsResource(this);
|
|
80
|
+
}
|
|
81
|
+
/** @internal — used by resource classes to build API call options. */
|
|
82
|
+
base() {
|
|
83
|
+
return {
|
|
84
|
+
baseUrl: this._baseUrl,
|
|
85
|
+
accessToken: this._accessToken,
|
|
86
|
+
businessDomain: this._businessDomain,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create a ContextLoaderResource bound to a specific knowledge network.
|
|
91
|
+
*
|
|
92
|
+
* @param mcpUrl Full MCP endpoint URL (e.g. from `kweaver context-loader config show`).
|
|
93
|
+
* @param knId Knowledge network ID to search against.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const cl = client.contextLoader(mcpUrl, "d5iv6c9818p72mpje8pg");
|
|
98
|
+
* const results = await cl.search({ query: "高血压 治疗" });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
contextLoader(mcpUrl, knId) {
|
|
102
|
+
return new ContextLoaderResource(this, mcpUrl, knId);
|
|
103
|
+
}
|
|
104
|
+
}
|