@kweaver-ai/kweaver-sdk 0.5.2 → 0.6.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 +19 -1
- package/README.zh.md +19 -1
- package/dist/api/agent-chat.d.ts +7 -1
- package/dist/api/agent-chat.js +146 -40
- package/dist/api/agent-list.js +13 -13
- package/dist/api/business-domains.js +9 -5
- package/dist/api/context-loader.js +4 -1
- package/dist/api/conversations.js +4 -9
- package/dist/api/dataflow2.d.ts +95 -0
- package/dist/api/dataflow2.js +80 -0
- package/dist/api/headers.d.ts +2 -0
- package/dist/api/headers.js +7 -2
- package/dist/api/skills.js +2 -10
- package/dist/api/vega.d.ts +0 -16
- package/dist/api/vega.js +0 -33
- package/dist/auth/oauth.d.ts +1 -1
- package/dist/auth/oauth.js +64 -7
- package/dist/cli.js +21 -1
- package/dist/client.d.ts +9 -0
- package/dist/client.js +48 -8
- package/dist/commands/auth.js +80 -32
- package/dist/commands/bkn-schema.js +22 -0
- package/dist/commands/call.js +8 -5
- package/dist/commands/dataflow.d.ts +1 -0
- package/dist/commands/dataflow.js +251 -0
- package/dist/commands/explore-bkn.d.ts +79 -0
- package/dist/commands/explore-bkn.js +273 -0
- package/dist/commands/explore-chat.d.ts +3 -0
- package/dist/commands/explore-chat.js +193 -0
- package/dist/commands/explore-vega.d.ts +3 -0
- package/dist/commands/explore-vega.js +71 -0
- package/dist/commands/explore.d.ts +9 -0
- package/dist/commands/explore.js +258 -0
- package/dist/commands/vega.js +2 -104
- package/dist/config/no-auth.d.ts +3 -0
- package/dist/config/no-auth.js +5 -0
- package/dist/config/store.d.ts +8 -0
- package/dist/config/store.js +22 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kweaver.d.ts +5 -0
- package/dist/kweaver.js +32 -2
- package/dist/resources/bkn.js +2 -3
- package/dist/resources/knowledge-networks.js +3 -8
- package/dist/resources/vega.d.ts +0 -6
- package/dist/resources/vega.js +1 -10
- package/dist/templates/explorer/app.js +136 -0
- package/dist/templates/explorer/bkn.js +747 -0
- package/dist/templates/explorer/chat.js +980 -0
- package/dist/templates/explorer/dashboard.js +82 -0
- package/dist/templates/explorer/index.html +35 -0
- package/dist/templates/explorer/style.css +2440 -0
- package/dist/templates/explorer/vega.js +291 -0
- package/dist/utils/http.d.ts +3 -0
- package/dist/utils/http.js +37 -1
- package/package.json +9 -5
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { HttpError } from "../utils/http.js";
|
|
2
|
+
import { getKnowledgeNetwork, listObjectTypes, listRelationTypes, listActionTypes, } from "../api/knowledge-networks.js";
|
|
3
|
+
import { objectTypeQuery, objectTypeProperties, subgraph } from "../api/ontology-query.js";
|
|
4
|
+
import { semanticSearch } from "../api/semantic-search.js";
|
|
5
|
+
// ── Constants ───────────────────────────────────────────────────────────────
|
|
6
|
+
export const EXPLORE_BOOTSTRAP_RETRY_DELAY_MS = 300;
|
|
7
|
+
export const EXPLORE_BOOTSTRAP_MAX_ATTEMPTS = 2;
|
|
8
|
+
// ── Meta builder ────────────────────────────────────────────────────────────
|
|
9
|
+
export function buildMeta(knRaw, otRaw, rtRaw, atRaw) {
|
|
10
|
+
const kn = JSON.parse(knRaw);
|
|
11
|
+
const otParsed = JSON.parse(otRaw);
|
|
12
|
+
const otItems = (Array.isArray(otParsed) ? otParsed
|
|
13
|
+
: Array.isArray(otParsed.entries) ? otParsed.entries
|
|
14
|
+
: Array.isArray(otParsed.object_types) ? otParsed.object_types
|
|
15
|
+
: []);
|
|
16
|
+
const rtParsed = JSON.parse(rtRaw);
|
|
17
|
+
const rtItems = (Array.isArray(rtParsed) ? rtParsed
|
|
18
|
+
: Array.isArray(rtParsed.entries) ? rtParsed.entries
|
|
19
|
+
: Array.isArray(rtParsed.relation_types) ? rtParsed.relation_types
|
|
20
|
+
: []);
|
|
21
|
+
const atParsed = JSON.parse(atRaw);
|
|
22
|
+
const atItems = (Array.isArray(atParsed) ? atParsed
|
|
23
|
+
: Array.isArray(atParsed.entries) ? atParsed.entries
|
|
24
|
+
: Array.isArray(atParsed.action_types) ? atParsed.action_types
|
|
25
|
+
: []);
|
|
26
|
+
return {
|
|
27
|
+
bkn: { id: kn.id, name: kn.name },
|
|
28
|
+
statistics: {
|
|
29
|
+
object_count: kn.statistics?.object_count ?? 0,
|
|
30
|
+
relation_count: kn.statistics?.relation_count ?? 0,
|
|
31
|
+
},
|
|
32
|
+
objectTypes: otItems.map((o) => {
|
|
33
|
+
const props = o.properties ?? o.data_properties ?? [];
|
|
34
|
+
return {
|
|
35
|
+
id: o.id,
|
|
36
|
+
name: o.name,
|
|
37
|
+
displayKey: o.display_key ?? "",
|
|
38
|
+
propertyCount: props.length,
|
|
39
|
+
properties: props.map((p) => ({
|
|
40
|
+
name: p.name,
|
|
41
|
+
...(p.type !== undefined ? { type: p.type } : {}),
|
|
42
|
+
})),
|
|
43
|
+
};
|
|
44
|
+
}),
|
|
45
|
+
relationTypes: rtItems.map((r) => ({
|
|
46
|
+
id: r.id,
|
|
47
|
+
name: r.name,
|
|
48
|
+
sourceOtId: r.source_object_type_id,
|
|
49
|
+
targetOtId: r.target_object_type_id,
|
|
50
|
+
sourceOtName: r.source_object_type?.name ?? "",
|
|
51
|
+
targetOtName: r.target_object_type?.name ?? "",
|
|
52
|
+
})),
|
|
53
|
+
actionTypes: atItems.map((a) => ({
|
|
54
|
+
id: a.id,
|
|
55
|
+
name: a.name,
|
|
56
|
+
})),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// ── Bootstrap helpers ───────────────────────────────────────────────────────
|
|
60
|
+
function getErrorMessage(error) {
|
|
61
|
+
const parts = [];
|
|
62
|
+
if (error instanceof Error) {
|
|
63
|
+
if (error.message) {
|
|
64
|
+
parts.push(error.message);
|
|
65
|
+
}
|
|
66
|
+
const cause = "cause" in error && error.cause instanceof Error ? error.cause.message : "";
|
|
67
|
+
if (cause) {
|
|
68
|
+
parts.push(cause);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
parts.push(String(error));
|
|
73
|
+
}
|
|
74
|
+
return parts.join(" ").toLowerCase();
|
|
75
|
+
}
|
|
76
|
+
export function isRetryableExploreBootstrapError(error) {
|
|
77
|
+
if (error instanceof HttpError) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const message = getErrorMessage(error);
|
|
81
|
+
if (!message) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return [
|
|
85
|
+
"fetch failed",
|
|
86
|
+
"client network socket disconnected",
|
|
87
|
+
"socket hang up",
|
|
88
|
+
"econnreset",
|
|
89
|
+
"econnrefused",
|
|
90
|
+
"etimedout",
|
|
91
|
+
"tls",
|
|
92
|
+
"secure tls connection",
|
|
93
|
+
].some((token) => message.includes(token));
|
|
94
|
+
}
|
|
95
|
+
function sleep(ms) {
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
setTimeout(resolve, ms);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export async function loadExploreMetaWithRetry(token, knId, businessDomain) {
|
|
101
|
+
for (let attempt = 1; attempt <= EXPLORE_BOOTSTRAP_MAX_ATTEMPTS; attempt++) {
|
|
102
|
+
try {
|
|
103
|
+
const [knRaw, otRaw, rtRaw, atRaw] = await Promise.all([
|
|
104
|
+
getKnowledgeNetwork({
|
|
105
|
+
baseUrl: token.baseUrl,
|
|
106
|
+
accessToken: token.accessToken,
|
|
107
|
+
knId,
|
|
108
|
+
businessDomain,
|
|
109
|
+
include_statistics: true,
|
|
110
|
+
}),
|
|
111
|
+
listObjectTypes({
|
|
112
|
+
baseUrl: token.baseUrl,
|
|
113
|
+
accessToken: token.accessToken,
|
|
114
|
+
knId,
|
|
115
|
+
businessDomain,
|
|
116
|
+
}),
|
|
117
|
+
listRelationTypes({
|
|
118
|
+
baseUrl: token.baseUrl,
|
|
119
|
+
accessToken: token.accessToken,
|
|
120
|
+
knId,
|
|
121
|
+
businessDomain,
|
|
122
|
+
}),
|
|
123
|
+
listActionTypes({
|
|
124
|
+
baseUrl: token.baseUrl,
|
|
125
|
+
accessToken: token.accessToken,
|
|
126
|
+
knId,
|
|
127
|
+
businessDomain,
|
|
128
|
+
}),
|
|
129
|
+
]);
|
|
130
|
+
return buildMeta(knRaw, otRaw, rtRaw, atRaw);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (attempt >= EXPLORE_BOOTSTRAP_MAX_ATTEMPTS || !isRetryableExploreBootstrapError(error)) {
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
await sleep(EXPLORE_BOOTSTRAP_RETRY_DELAY_MS);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw new Error("Failed to load explorer metadata.");
|
|
140
|
+
}
|
|
141
|
+
// ── HTTP helpers ────────────────────────────────────────────────────────────
|
|
142
|
+
const MAX_BODY_BYTES = 1024 * 1024; // 1 MB
|
|
143
|
+
export function readBody(req) {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const chunks = [];
|
|
146
|
+
let size = 0;
|
|
147
|
+
req.on("data", (chunk) => {
|
|
148
|
+
size += chunk.length;
|
|
149
|
+
if (size > MAX_BODY_BYTES) {
|
|
150
|
+
req.destroy();
|
|
151
|
+
reject(new Error("Request body too large"));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
chunks.push(chunk);
|
|
155
|
+
});
|
|
156
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
157
|
+
req.on("error", reject);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
export function jsonResponse(res, status, data) {
|
|
161
|
+
const body = JSON.stringify(data);
|
|
162
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
163
|
+
res.end(body);
|
|
164
|
+
}
|
|
165
|
+
export function handleApiError(res, error) {
|
|
166
|
+
if (error instanceof HttpError) {
|
|
167
|
+
let detail = "";
|
|
168
|
+
try {
|
|
169
|
+
const parsed = JSON.parse(error.body);
|
|
170
|
+
detail = typeof parsed.description === "string" ? parsed.description : "";
|
|
171
|
+
}
|
|
172
|
+
catch { /* ignore */ }
|
|
173
|
+
jsonResponse(res, error.status, {
|
|
174
|
+
error: detail || error.message,
|
|
175
|
+
upstream_status: error.status,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else if (error instanceof Error &&
|
|
179
|
+
"causeMessage" in error &&
|
|
180
|
+
typeof error.causeMessage === "string") {
|
|
181
|
+
// NetworkRequestError — include cause and URL for diagnosis
|
|
182
|
+
const net = error;
|
|
183
|
+
const detail = [net.causeMessage, net.url, net.hint].filter(Boolean).join(" | ");
|
|
184
|
+
console.error(`[network-error] ${detail}`);
|
|
185
|
+
jsonResponse(res, 502, { error: `Upstream unreachable: ${net.causeMessage}` });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
189
|
+
jsonResponse(res, 500, { error: message });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export function registerBknRoutes(meta, getToken, businessDomain) {
|
|
193
|
+
const knId = meta.bkn.id;
|
|
194
|
+
const routes = new Map();
|
|
195
|
+
routes.set("GET /api/bkn/meta", (_req, res) => {
|
|
196
|
+
jsonResponse(res, 200, meta);
|
|
197
|
+
});
|
|
198
|
+
routes.set("POST /api/bkn/instances", async (req, res) => {
|
|
199
|
+
try {
|
|
200
|
+
const bodyStr = await readBody(req);
|
|
201
|
+
const body = JSON.parse(bodyStr);
|
|
202
|
+
const queryBody = JSON.stringify({
|
|
203
|
+
limit: body.limit ?? 50,
|
|
204
|
+
...(body.search_after ? { search_after: body.search_after } : {}),
|
|
205
|
+
...(body.condition ? { condition: body.condition } : {}),
|
|
206
|
+
...(body._instance_identities ? { _instance_identities: body._instance_identities } : {}),
|
|
207
|
+
});
|
|
208
|
+
const t = await getToken();
|
|
209
|
+
const result = await objectTypeQuery({
|
|
210
|
+
baseUrl: t.baseUrl, accessToken: t.accessToken,
|
|
211
|
+
knId, otId: body.otId, body: queryBody, businessDomain,
|
|
212
|
+
});
|
|
213
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
214
|
+
res.end(result);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
handleApiError(res, error);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
routes.set("POST /api/bkn/subgraph", async (req, res) => {
|
|
221
|
+
try {
|
|
222
|
+
const bodyStr = await readBody(req);
|
|
223
|
+
const parsed = JSON.parse(bodyStr);
|
|
224
|
+
const hasRelationPaths = Array.isArray(parsed.relation_type_paths);
|
|
225
|
+
const t = await getToken();
|
|
226
|
+
const result = await subgraph({
|
|
227
|
+
baseUrl: t.baseUrl, accessToken: t.accessToken,
|
|
228
|
+
knId, body: bodyStr, businessDomain,
|
|
229
|
+
...(hasRelationPaths ? { queryType: "relation_path" } : {}),
|
|
230
|
+
});
|
|
231
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
232
|
+
res.end(result);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
handleApiError(res, error);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
routes.set("POST /api/bkn/search", async (req, res) => {
|
|
239
|
+
try {
|
|
240
|
+
const bodyStr = await readBody(req);
|
|
241
|
+
const body = JSON.parse(bodyStr);
|
|
242
|
+
const t = await getToken();
|
|
243
|
+
const result = await semanticSearch({
|
|
244
|
+
baseUrl: t.baseUrl, accessToken: t.accessToken,
|
|
245
|
+
knId, query: body.query, businessDomain,
|
|
246
|
+
maxConcepts: body.maxConcepts,
|
|
247
|
+
});
|
|
248
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
249
|
+
res.end(result);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
handleApiError(res, error);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
routes.set("POST /api/bkn/properties", async (req, res) => {
|
|
256
|
+
try {
|
|
257
|
+
const bodyStr = await readBody(req);
|
|
258
|
+
const body = JSON.parse(bodyStr);
|
|
259
|
+
const { otId, ...rest } = body;
|
|
260
|
+
const t = await getToken();
|
|
261
|
+
const result = await objectTypeProperties({
|
|
262
|
+
baseUrl: t.baseUrl, accessToken: t.accessToken,
|
|
263
|
+
knId, otId, body: JSON.stringify(rest), businessDomain,
|
|
264
|
+
});
|
|
265
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
266
|
+
res.end(result);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
handleApiError(res, error);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
return routes;
|
|
273
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { type TokenProvider } from "./explore-bkn.js";
|
|
3
|
+
export declare function registerChatRoutes(getToken: TokenProvider, businessDomain: string): Map<string, (req: IncomingMessage, res: ServerResponse) => void>;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { listAgents } from "../api/agent-list.js";
|
|
2
|
+
import { fetchAgentInfo, sendChatRequestStream } from "../api/agent-chat.js";
|
|
3
|
+
import { getTracesByConversation } from "../api/conversations.js";
|
|
4
|
+
import { readBody, handleApiError, jsonResponse } from "./explore-bkn.js";
|
|
5
|
+
// ── Chat route handlers ──────────────────────────────────────────────────────
|
|
6
|
+
export function registerChatRoutes(getToken, businessDomain) {
|
|
7
|
+
const routes = new Map();
|
|
8
|
+
// GET /api/chat/agents — list published agents
|
|
9
|
+
routes.set("GET /api/chat/agents", async (_req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const t = await getToken();
|
|
12
|
+
const raw = await listAgents({
|
|
13
|
+
baseUrl: t.baseUrl,
|
|
14
|
+
accessToken: t.accessToken,
|
|
15
|
+
businessDomain,
|
|
16
|
+
limit: 200,
|
|
17
|
+
});
|
|
18
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
19
|
+
res.end(raw);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
handleApiError(res, error);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
// POST /api/chat/send — stream a chat response via SSE
|
|
26
|
+
routes.set("POST /api/chat/send", async (req, res) => {
|
|
27
|
+
let bodyStr;
|
|
28
|
+
try {
|
|
29
|
+
bodyStr = await readBody(req);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
33
|
+
res.end(JSON.stringify({ error: "Failed to read request body" }));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let agentId;
|
|
37
|
+
let message;
|
|
38
|
+
let conversationId;
|
|
39
|
+
let version;
|
|
40
|
+
try {
|
|
41
|
+
const body = JSON.parse(bodyStr);
|
|
42
|
+
agentId = body.agentId ?? "";
|
|
43
|
+
message = body.message ?? "";
|
|
44
|
+
conversationId = body.conversationId;
|
|
45
|
+
version = body.version;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
49
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!agentId || !message) {
|
|
53
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
54
|
+
res.end(JSON.stringify({ error: "agentId and message are required" }));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Fetch agent info to get key + version
|
|
58
|
+
const t = await getToken();
|
|
59
|
+
let agentInfo;
|
|
60
|
+
try {
|
|
61
|
+
agentInfo = await fetchAgentInfo({
|
|
62
|
+
baseUrl: t.baseUrl,
|
|
63
|
+
accessToken: t.accessToken,
|
|
64
|
+
agentId,
|
|
65
|
+
version: version ?? "v0",
|
|
66
|
+
businessDomain,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
handleApiError(res, error);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Set SSE response headers
|
|
74
|
+
res.writeHead(200, {
|
|
75
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
76
|
+
"Cache-Control": "no-cache",
|
|
77
|
+
Connection: "keep-alive",
|
|
78
|
+
"X-Accel-Buffering": "no",
|
|
79
|
+
});
|
|
80
|
+
// SSE heartbeat — keeps connection alive and lets client detect stalls
|
|
81
|
+
const heartbeat = setInterval(() => {
|
|
82
|
+
try {
|
|
83
|
+
res.write(": heartbeat\n\n");
|
|
84
|
+
}
|
|
85
|
+
catch { /* connection gone */ }
|
|
86
|
+
}, 15000);
|
|
87
|
+
// Stream chat response
|
|
88
|
+
try {
|
|
89
|
+
const result = await sendChatRequestStream({
|
|
90
|
+
baseUrl: t.baseUrl,
|
|
91
|
+
accessToken: t.accessToken,
|
|
92
|
+
agentId: agentInfo.id,
|
|
93
|
+
agentKey: agentInfo.key,
|
|
94
|
+
agentVersion: agentInfo.version,
|
|
95
|
+
query: message,
|
|
96
|
+
conversationId,
|
|
97
|
+
stream: true,
|
|
98
|
+
businessDomain,
|
|
99
|
+
}, {
|
|
100
|
+
onTextDelta: (fullText, currentSegmentText) => {
|
|
101
|
+
const event = JSON.stringify({ type: "text", fullText, currentText: currentSegmentText });
|
|
102
|
+
res.write(`data: ${event}\n\n`);
|
|
103
|
+
},
|
|
104
|
+
onProgress: (items) => {
|
|
105
|
+
const event = JSON.stringify({ type: "progress", items });
|
|
106
|
+
res.write(`data: ${event}\n\n`);
|
|
107
|
+
},
|
|
108
|
+
onSegmentComplete: (segmentText, segmentIndex) => {
|
|
109
|
+
const event = JSON.stringify({ type: "segment", text: segmentText, index: segmentIndex });
|
|
110
|
+
res.write(`data: ${event}\n\n`);
|
|
111
|
+
},
|
|
112
|
+
onStepMeta: (meta) => {
|
|
113
|
+
const event = JSON.stringify({ type: "step_meta", meta });
|
|
114
|
+
res.write(`data: ${event}\n\n`);
|
|
115
|
+
},
|
|
116
|
+
onConversationId: (convId) => {
|
|
117
|
+
const event = JSON.stringify({ type: "conversation_id", conversationId: convId });
|
|
118
|
+
res.write(`data: ${event}\n\n`);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
clearInterval(heartbeat);
|
|
122
|
+
const doneEvent = JSON.stringify({
|
|
123
|
+
type: "done",
|
|
124
|
+
conversationId: result.conversationId ?? conversationId ?? "",
|
|
125
|
+
});
|
|
126
|
+
res.write(`data: ${doneEvent}\n\n`);
|
|
127
|
+
res.end();
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
clearInterval(heartbeat);
|
|
131
|
+
// Extract detailed error info — HttpError carries the upstream response body
|
|
132
|
+
let errMsg = error instanceof Error ? error.message : String(error);
|
|
133
|
+
let errDetail;
|
|
134
|
+
if (error && typeof error === "object" && "body" in error) {
|
|
135
|
+
const body = error.body;
|
|
136
|
+
if (body) {
|
|
137
|
+
errDetail = body;
|
|
138
|
+
// Try to extract a human-readable message from JSON body
|
|
139
|
+
try {
|
|
140
|
+
const parsed = JSON.parse(body);
|
|
141
|
+
const desc = parsed.description || parsed.detail || parsed.message || parsed.error;
|
|
142
|
+
if (desc)
|
|
143
|
+
errMsg += `: ${desc}`;
|
|
144
|
+
if (parsed.solution)
|
|
145
|
+
errMsg += ` (${parsed.solution})`;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
errMsg += `: ${body.slice(0, 500)}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!res.headersSent) {
|
|
153
|
+
res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
|
|
154
|
+
res.end(JSON.stringify({ error: errMsg, detail: errDetail }));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const errEvent = JSON.stringify({
|
|
158
|
+
type: "error",
|
|
159
|
+
error: errMsg,
|
|
160
|
+
detail: errDetail,
|
|
161
|
+
});
|
|
162
|
+
res.write(`data: ${errEvent}\n\n`);
|
|
163
|
+
res.end();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// GET /api/chat/trace?agentId=X&conversationId=Y — fetch trace data
|
|
168
|
+
routes.set("GET /api/chat/trace", async (req, res) => {
|
|
169
|
+
try {
|
|
170
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
171
|
+
const agentId = url.searchParams.get("agentId") || "";
|
|
172
|
+
const conversationId = url.searchParams.get("conversationId") || "";
|
|
173
|
+
if (!agentId || !conversationId) {
|
|
174
|
+
jsonResponse(res, 400, { error: "agentId and conversationId are required" });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const t = await getToken();
|
|
178
|
+
const raw = await getTracesByConversation({
|
|
179
|
+
baseUrl: t.baseUrl,
|
|
180
|
+
accessToken: t.accessToken,
|
|
181
|
+
agentId,
|
|
182
|
+
conversationId,
|
|
183
|
+
businessDomain,
|
|
184
|
+
});
|
|
185
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
186
|
+
res.end(raw);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
handleApiError(res, error);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return routes;
|
|
193
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { type TokenProvider } from "./explore-bkn.js";
|
|
3
|
+
export declare function registerVegaRoutes(getToken: TokenProvider, businessDomain: string): Map<string, (req: IncomingMessage, res: ServerResponse) => void>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { listVegaCatalogs, vegaCatalogHealthStatus, listVegaCatalogResources, queryVegaResourceData, } from "../api/vega.js";
|
|
2
|
+
import { readBody, jsonResponse, handleApiError } from "./explore-bkn.js";
|
|
3
|
+
// ── Vega route handlers ──────────────────────────────────────────────────────
|
|
4
|
+
export function registerVegaRoutes(getToken, businessDomain) {
|
|
5
|
+
const routes = new Map();
|
|
6
|
+
// GET /api/vega/catalogs — list catalogs + health status in parallel
|
|
7
|
+
routes.set("GET /api/vega/catalogs", async (_req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const t = await getToken();
|
|
10
|
+
const [catalogsResult, healthResult] = await Promise.allSettled([
|
|
11
|
+
listVegaCatalogs({
|
|
12
|
+
baseUrl: t.baseUrl, accessToken: t.accessToken, businessDomain,
|
|
13
|
+
}),
|
|
14
|
+
vegaCatalogHealthStatus({
|
|
15
|
+
baseUrl: t.baseUrl, accessToken: t.accessToken, businessDomain, ids: "all",
|
|
16
|
+
}),
|
|
17
|
+
]);
|
|
18
|
+
const catalogs = catalogsResult.status === "fulfilled"
|
|
19
|
+
? JSON.parse(catalogsResult.value)
|
|
20
|
+
: { error: String(catalogsResult.reason) };
|
|
21
|
+
const health = healthResult.status === "fulfilled"
|
|
22
|
+
? JSON.parse(healthResult.value)
|
|
23
|
+
: { error: String(healthResult.reason) };
|
|
24
|
+
jsonResponse(res, 200, { catalogs, health });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
handleApiError(res, error);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
// GET /api/vega/catalog-resources?catalogId=<id> — list resources in a catalog
|
|
31
|
+
routes.set("GET /api/vega/catalog-resources", async (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const catalogId = new URL(req.url ?? "/", "http://localhost").searchParams.get("catalogId");
|
|
34
|
+
if (!catalogId) {
|
|
35
|
+
jsonResponse(res, 400, { error: "catalogId query parameter is required" });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const t = await getToken();
|
|
39
|
+
const raw = await listVegaCatalogResources({
|
|
40
|
+
baseUrl: t.baseUrl, accessToken: t.accessToken, businessDomain, id: catalogId,
|
|
41
|
+
});
|
|
42
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
43
|
+
res.end(raw);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
handleApiError(res, error);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// POST /api/vega/query — query resource data
|
|
50
|
+
routes.set("POST /api/vega/query", async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const bodyStr = await readBody(req);
|
|
53
|
+
const body = JSON.parse(bodyStr);
|
|
54
|
+
if (!body.resourceId) {
|
|
55
|
+
jsonResponse(res, 400, { error: "resourceId is required" });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const t = await getToken();
|
|
59
|
+
const raw = await queryVegaResourceData({
|
|
60
|
+
baseUrl: t.baseUrl, accessToken: t.accessToken, businessDomain,
|
|
61
|
+
id: body.resourceId, body: JSON.stringify(body.query ?? {}),
|
|
62
|
+
});
|
|
63
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
64
|
+
res.end(raw);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
handleApiError(res, error);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return routes;
|
|
71
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ExploreOptions {
|
|
2
|
+
knId: string;
|
|
3
|
+
agentId: string;
|
|
4
|
+
port: number;
|
|
5
|
+
open: boolean;
|
|
6
|
+
businessDomain: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseExploreArgs(args: string[]): ExploreOptions;
|
|
9
|
+
export declare function runExploreCommand(args: string[]): Promise<number>;
|