@kweaver-ai/kweaver-sdk 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +19 -1
  2. package/README.zh.md +19 -1
  3. package/dist/api/agent-chat.d.ts +7 -1
  4. package/dist/api/agent-chat.js +146 -40
  5. package/dist/api/agent-list.js +13 -13
  6. package/dist/api/business-domains.js +9 -5
  7. package/dist/api/context-loader.js +4 -1
  8. package/dist/api/conversations.js +4 -9
  9. package/dist/api/dataflow2.d.ts +95 -0
  10. package/dist/api/dataflow2.js +80 -0
  11. package/dist/api/headers.d.ts +2 -0
  12. package/dist/api/headers.js +7 -2
  13. package/dist/api/skills.js +2 -10
  14. package/dist/api/vega.d.ts +0 -16
  15. package/dist/api/vega.js +0 -33
  16. package/dist/auth/oauth.d.ts +7 -6
  17. package/dist/auth/oauth.js +170 -80
  18. package/dist/cli.js +21 -1
  19. package/dist/client.d.ts +9 -0
  20. package/dist/client.js +48 -8
  21. package/dist/commands/auth.js +103 -42
  22. package/dist/commands/bkn-schema.js +22 -0
  23. package/dist/commands/call.js +8 -5
  24. package/dist/commands/dataflow.d.ts +1 -0
  25. package/dist/commands/dataflow.js +251 -0
  26. package/dist/commands/explore-bkn.d.ts +79 -0
  27. package/dist/commands/explore-bkn.js +273 -0
  28. package/dist/commands/explore-chat.d.ts +3 -0
  29. package/dist/commands/explore-chat.js +193 -0
  30. package/dist/commands/explore-vega.d.ts +3 -0
  31. package/dist/commands/explore-vega.js +71 -0
  32. package/dist/commands/explore.d.ts +9 -0
  33. package/dist/commands/explore.js +258 -0
  34. package/dist/commands/vega.js +2 -104
  35. package/dist/config/no-auth.d.ts +3 -0
  36. package/dist/config/no-auth.js +5 -0
  37. package/dist/config/store.d.ts +8 -0
  38. package/dist/config/store.js +22 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/kweaver.d.ts +5 -0
  42. package/dist/kweaver.js +32 -2
  43. package/dist/resources/bkn.js +2 -3
  44. package/dist/resources/knowledge-networks.js +3 -8
  45. package/dist/resources/vega.d.ts +0 -6
  46. package/dist/resources/vega.js +1 -10
  47. package/dist/templates/explorer/app.js +136 -0
  48. package/dist/templates/explorer/bkn.js +747 -0
  49. package/dist/templates/explorer/chat.js +980 -0
  50. package/dist/templates/explorer/dashboard.js +82 -0
  51. package/dist/templates/explorer/index.html +35 -0
  52. package/dist/templates/explorer/style.css +2440 -0
  53. package/dist/templates/explorer/vega.js +291 -0
  54. package/dist/utils/browser.js +33 -10
  55. package/dist/utils/http.d.ts +3 -0
  56. package/dist/utils/http.js +37 -1
  57. package/package.json +9 -5
package/README.md CHANGED
@@ -142,7 +142,7 @@ const skillMd = await client.skills.fetchContent("skill-id");
142
142
  ## CLI Reference
143
143
 
144
144
  ```
145
- kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k]
145
+ kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
146
146
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (headless login)
147
147
  kweaver auth export [url|alias] [--json] (export command to run on a headless host)
148
148
  kweaver auth status/list/use/delete/logout
@@ -150,6 +150,7 @@ kweaver config show / list-bd / set-bd <value> # platform business domain —
150
150
  kweaver token
151
151
  kweaver ds list/get/delete/tables/connect
152
152
  kweaver ds import-csv <ds_id> --files <glob> [--table-prefix <p>] [--batch-size 500] [--recreate]
153
+ kweaver dataflow list/run/runs/logs
153
154
  kweaver dataview list/find/get/query/delete
154
155
  kweaver bkn list/get/stats/export/create/update/delete
155
156
  kweaver bkn create-from-ds <ds_id> --name <name> [--tables t1,t2] [--build]
@@ -169,6 +170,22 @@ kweaver context-loader kn-search/query-object-instance/...
169
170
  kweaver call <path> [-X METHOD] [-d BODY] [-H header]
170
171
  ```
171
172
 
173
+ ### Dataflow CLI examples
174
+
175
+ ```bash
176
+ kweaver dataflow list
177
+ kweaver dataflow run <dagId> --file ./demo.pdf
178
+ kweaver dataflow run <dagId> --url https://example.com/demo.pdf --name demo.pdf
179
+ kweaver dataflow runs <dagId>
180
+ kweaver dataflow runs <dagId> --since 2026-04-01
181
+ kweaver dataflow logs <dagId> <instanceId>
182
+ kweaver dataflow logs <dagId> <instanceId> --detail
183
+ ```
184
+
185
+ `kweaver dataflow runs --since` filters one local natural day. If the value cannot be parsed by `new Date(...)`, the CLI falls back to the most recent 20 runs. `kweaver dataflow logs` defaults to summary output; add `--detail` to print indented `input` and `output` payloads.
186
+
187
+ **No-auth platforms:** If OAuth is not enabled, use `kweaver auth <url> --no-auth` (or run a normal `auth login`; a **404** on `POST /oauth2/clients` switches to no-auth automatically). Credentials are still saved under `~/.kweaver/` and work with `auth use` / `auth list`. Optional: `KWEAVER_NO_AUTH=1` with `KWEAVER_BASE_URL` when no token env is set. SDK: `new KWeaverClient({ baseUrl, auth: false })` or `kweaver.configure({ baseUrl, auth: false })`.
188
+
172
189
  ## Environment Variables
173
190
 
174
191
  | Variable | Description |
@@ -176,6 +193,7 @@ kweaver call <path> [-X METHOD] [-d BODY] [-H header]
176
193
  | `KWEAVER_BASE_URL` | KWeaver instance URL |
177
194
  | `KWEAVER_BUSINESS_DOMAIN` | Business domain identifier |
178
195
  | `KWEAVER_TOKEN` | Access token |
196
+ | `KWEAVER_NO_AUTH` | Set to `1`/`true`/`yes` to use no-auth sentinel when `KWEAVER_TOKEN` is unset (with `KWEAVER_BASE_URL` or active platform) |
179
197
  | `KWEAVER_TLS_INSECURE` | Set to `1` or `true` to skip TLS certificate verification for all HTTPS in the process (dev only; prefer `kweaver auth … --insecure` which saves per platform) |
180
198
  | `NODE_TLS_REJECT_UNAUTHORIZED` | Node.js built-in TLS switch: set to `0` to skip certificate verification for HTTPS in this process. The `kweaver` CLI sets this when `KWEAVER_TLS_INSECURE` is set or the saved token has insecure TLS (same scope as above; dev only). |
181
199
 
package/README.zh.md CHANGED
@@ -131,13 +131,14 @@ const skillMd = await client.skills.fetchContent("skill-id");
131
131
  ## 命令速查
132
132
 
133
133
  ```
134
- kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k]
134
+ kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
135
135
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (无浏览器登录)
136
136
  kweaver auth export [url|alias] [--json] (导出在无浏览器机器上运行的命令)
137
137
  kweaver auth status/list/use/delete/logout
138
138
  kweaver config show / list-bd / set-bd <value> # 平台业务域,登录后优先
139
139
  kweaver token
140
140
  kweaver ds list/get/delete/tables/connect
141
+ kweaver dataflow list/run/runs/logs
141
142
  kweaver dataview list/find/get/query/delete
142
143
  kweaver bkn list/get/stats/export/create/update/delete
143
144
  kweaver bkn object-type list/get/create/update/delete/query/properties
@@ -153,6 +154,22 @@ kweaver context-loader kn-search/query-object-instance/...
153
154
  kweaver call <path> [-X METHOD] [-d BODY] [-H header]
154
155
  ```
155
156
 
157
+ ### Dataflow CLI 示例
158
+
159
+ ```bash
160
+ kweaver dataflow list
161
+ kweaver dataflow run <dagId> --file ./demo.pdf
162
+ kweaver dataflow run <dagId> --url https://example.com/demo.pdf --name demo.pdf
163
+ kweaver dataflow runs <dagId>
164
+ kweaver dataflow runs <dagId> --since 2026-04-01
165
+ kweaver dataflow logs <dagId> <instanceId>
166
+ kweaver dataflow logs <dagId> <instanceId> --detail
167
+ ```
168
+
169
+ `kweaver dataflow runs --since` 会按本地自然日过滤;如果参数无法被 `new Date(...)` 解析,CLI 会回退到最近 20 条运行记录。`kweaver dataflow logs` 默认输出摘要;加上 `--detail` 会打印带缩进的 `input` 和 `output` 载荷。
170
+
171
+ **无 OAuth 的平台:** 使用 `kweaver auth <url> --no-auth`,或照常 `auth login`;若 `POST /oauth2/clients` 返回 **404**,CLI 会提示并自动保存为 no-auth。凭据仍在 `~/.kweaver/`,可用 `auth use` / `auth list` 切换。可选环境变量 `KWEAVER_NO_AUTH=1`(未设置 `KWEAVER_TOKEN` 时)配合 `KWEAVER_BASE_URL`。SDK:`new KWeaverClient({ baseUrl, auth: false })` 或 `kweaver.configure({ baseUrl, auth: false })`。
172
+
156
173
  ## 环境变量
157
174
 
158
175
  | 变量 | 说明 |
@@ -160,6 +177,7 @@ kweaver call <path> [-X METHOD] [-d BODY] [-H header]
160
177
  | `KWEAVER_BASE_URL` | KWeaver 实例地址 |
161
178
  | `KWEAVER_BUSINESS_DOMAIN` | 业务域标识 |
162
179
  | `KWEAVER_TOKEN` | 访问令牌 |
180
+ | `KWEAVER_NO_AUTH` | 设为 `1`/`true`/`yes` 且未设置 `KWEAVER_TOKEN` 时使用 no-auth 占位(需 `KWEAVER_BASE_URL` 或已选平台) |
163
181
  | `KWEAVER_TLS_INSECURE` | 设为 `1` 或 `true` 时跳过 TLS 证书校验(仅开发;更推荐 `kweaver auth … --insecure` 以按平台持久化) |
164
182
  | `NODE_TLS_REJECT_UNAUTHORIZED` | Node.js 内置 TLS 开关:设为 `0` 时在本进程内跳过 HTTPS 证书校验。`kweaver` 在 `KWEAVER_TLS_INSECURE` 生效或已保存 token 为不安全 TLS 时会设置此项(范围同上;仅开发)。 |
165
183
 
@@ -58,9 +58,15 @@ export declare function processIncrementalUpdate(data: {
58
58
  }, result: Record<string, unknown>): void;
59
59
  export declare function sendChatRequest(options: SendChatRequestOptions): Promise<ChatResult>;
60
60
  export interface SendChatRequestStreamCallbacks {
61
- onTextDelta: (fullText: string) => void;
61
+ onTextDelta: (fullText: string, currentSegmentText: string) => void;
62
62
  /** Optional: called when message.content.middle_answer.progress updates (tool/skill steps). */
63
63
  onProgress?: (progress: ProgressItem[]) => void;
64
+ /** Optional: called when a text segment is completed and a new phase starts. */
65
+ onSegmentComplete?: (segmentText: string, segmentIndex: number) => void;
66
+ /** Optional: called when answer_type_other changes (step metadata like status, tool info). */
67
+ onStepMeta?: (meta: Record<string, unknown>) => void;
68
+ /** Optional: called as soon as conversationId is discovered in the stream. */
69
+ onConversationId?: (conversationId: string) => void;
64
70
  }
65
71
  /**
66
72
  * Stream-only entry point for TUI: same as sendChatRequest with stream=true,
@@ -1,4 +1,5 @@
1
- import { fetchTextOrThrow, HttpError } from "../utils/http.js";
1
+ import { isNoAuth } from "../config/no-auth.js";
2
+ import { fetchTextOrThrow, fetchWithRetry, HttpError } from "../utils/http.js";
2
3
  import { normalizeDisplayText } from "../utils/display-text.js";
3
4
  const CHAT_PATH = "/api/agent-factory/v1/app";
4
5
  const AGENT_INFO_PATH = "/api/agent-factory/v3/agent-market/agent";
@@ -13,16 +14,19 @@ export function buildAgentInfoUrl(baseUrl, agentId, version) {
13
14
  export async function fetchAgentInfo(options) {
14
15
  const { baseUrl, accessToken, agentId, version, businessDomain = "bd_public" } = options;
15
16
  const url = buildAgentInfoUrl(baseUrl, agentId, version);
17
+ const agentHeaders = {
18
+ accept: "application/json, text/plain, */*",
19
+ "x-business-domain": businessDomain,
20
+ "x-language": "zh-CN",
21
+ "x-requested-with": "XMLHttpRequest",
22
+ };
23
+ if (!isNoAuth(accessToken)) {
24
+ agentHeaders.Authorization = `Bearer ${accessToken}`;
25
+ agentHeaders.token = accessToken;
26
+ }
16
27
  const { body } = await fetchTextOrThrow(url, {
17
28
  method: "GET",
18
- headers: {
19
- accept: "application/json, text/plain, */*",
20
- Authorization: `Bearer ${accessToken}`,
21
- token: accessToken,
22
- "x-business-domain": businessDomain,
23
- "x-language": "zh-CN",
24
- "x-requested-with": "XMLHttpRequest",
25
- },
29
+ headers: agentHeaders,
26
30
  });
27
31
  const data = JSON.parse(body);
28
32
  if (!data.id || !data.key) {
@@ -67,6 +71,19 @@ function stringFromAnswerObject(obj) {
67
71
  const parts = [d, c ? `code: ${c}` : "", s ? `solution: ${s}` : ""].filter(Boolean);
68
72
  return parts.join("\n");
69
73
  }
74
+ /** Format answer_type_other which may be an array of strings or an object. */
75
+ function stringFromAnswerTypeOther(other) {
76
+ if (Array.isArray(other)) {
77
+ const strings = other.filter((s) => typeof s === "string" && s);
78
+ if (strings.length > 0)
79
+ return JSON.stringify(strings);
80
+ return "";
81
+ }
82
+ if (other && typeof other === "object") {
83
+ return stringFromAnswerObject(other);
84
+ }
85
+ return "";
86
+ }
70
87
  export function extractText(data) {
71
88
  if (!data || typeof data !== "object")
72
89
  return "";
@@ -74,44 +91,44 @@ export function extractText(data) {
74
91
  const fa = obj.final_answer;
75
92
  if (fa?.answer && typeof fa.answer === "object") {
76
93
  const ans = fa.answer;
77
- if (typeof ans.text === "string")
94
+ if (typeof ans.text === "string" && ans.text)
78
95
  return ans.text;
79
96
  }
80
- if (typeof fa?.text === "string") {
97
+ if (typeof fa?.text === "string" && fa.text) {
81
98
  return fa.text;
82
99
  }
100
+ // Check answer_type_other at final_answer level (for content_type "other")
101
+ if (fa?.answer_type_other) {
102
+ const desc = stringFromAnswerTypeOther(fa.answer_type_other);
103
+ if (desc)
104
+ return desc;
105
+ }
83
106
  const msg = obj.message;
84
- if (typeof msg?.text === "string") {
107
+ if (typeof msg?.text === "string" && msg.text) {
85
108
  return msg.text;
86
109
  }
87
110
  if (msg?.content && typeof msg.content === "object") {
88
111
  const content = msg.content;
89
- if (typeof content.text === "string")
112
+ if (typeof content.text === "string" && content.text)
90
113
  return content.text;
91
114
  const contentFinalAnswer = content.final_answer;
92
115
  if (contentFinalAnswer?.answer && typeof contentFinalAnswer.answer === "object") {
93
116
  const answer = contentFinalAnswer.answer;
94
- if (typeof answer.text === "string")
117
+ if (typeof answer.text === "string" && answer.text)
95
118
  return answer.text;
96
119
  }
97
- if (typeof contentFinalAnswer?.text === "string") {
120
+ if (typeof contentFinalAnswer?.text === "string" && contentFinalAnswer.text) {
98
121
  return contentFinalAnswer.text;
99
122
  }
100
- const other = contentFinalAnswer?.answer_type_other;
101
- if (other && typeof other === "object") {
102
- const desc = stringFromAnswerObject(other);
123
+ // Check answer_type_other at content.final_answer level
124
+ if (contentFinalAnswer?.answer_type_other) {
125
+ const desc = stringFromAnswerTypeOther(contentFinalAnswer.answer_type_other);
103
126
  if (desc)
104
127
  return desc;
105
128
  }
106
129
  }
107
- const topOther = fa?.answer_type_other;
108
- if (topOther && typeof topOther === "object") {
109
- const desc = stringFromAnswerObject(topOther);
110
- if (desc)
111
- return desc;
112
- }
113
130
  const answer = obj.answer;
114
- if (typeof answer?.text === "string") {
131
+ if (typeof answer?.text === "string" && answer.text) {
115
132
  return answer.text;
116
133
  }
117
134
  return "";
@@ -194,7 +211,7 @@ function getProgressFromResult(result) {
194
211
  };
195
212
  });
196
213
  }
197
- function applySseDataLine(line, state, verbose, onTextDelta, onProgress) {
214
+ function applySseDataLine(line, state, verbose, onTextDelta, onProgress, onSegmentComplete, onStepMeta, onConversationId) {
198
215
  if (!line.startsWith("data: ")) {
199
216
  return;
200
217
  }
@@ -208,21 +225,45 @@ function applySseDataLine(line, state, verbose, onTextDelta, onProgress) {
208
225
  if (data.key?.length === 1 && data.key[0] === "conversation_id" && data.action === "upsert") {
209
226
  state.conversationId =
210
227
  typeof data.content === "string" ? data.content : String(data.content ?? "");
228
+ if (state.conversationId && onConversationId) {
229
+ onConversationId(state.conversationId);
230
+ }
231
+ }
232
+ // Detect answer_type_other changes (step metadata: status, end_time, etc.)
233
+ if (data.key && data.key.join(".").includes("answer_type_other") && data.action === "upsert") {
234
+ const ato = getByPath(state.result, ["message", "content", "final_answer", "answer_type_other"]);
235
+ console.error(`[STEP_META] ${JSON.stringify(ato).slice(0, 500)}`);
236
+ if (ato && typeof ato === "object" && onStepMeta) {
237
+ onStepMeta(ato);
238
+ }
211
239
  }
212
240
  const progress = getProgressFromResult(state.result);
213
241
  if (progress.length > 0 && onProgress) {
214
242
  onProgress(progress);
215
243
  }
216
- const text = normalizeDisplayText(extractText(state.result));
217
- if (text && text !== state.lastText) {
244
+ const rawText = normalizeDisplayText(extractText(state.result));
245
+ // Detect when the upstream clears text between steps: previous had content, now empty or
246
+ // significantly shorter (new segment starting). Save the completed segment.
247
+ if (state.prevRawText && (!rawText || rawText.length < state.prevRawText.length * 0.5)) {
248
+ state.completedSegments.push(state.prevRawText);
249
+ if (onSegmentComplete) {
250
+ onSegmentComplete(state.prevRawText, state.completedSegments.length - 1);
251
+ }
252
+ }
253
+ state.prevRawText = rawText;
254
+ // Build full text: completed segments + current segment
255
+ const fullText = state.completedSegments.length > 0
256
+ ? state.completedSegments.join("\n\n") + (rawText ? "\n\n" + rawText : "")
257
+ : rawText;
258
+ if (fullText && fullText !== state.lastText) {
218
259
  if (onTextDelta) {
219
- onTextDelta(text);
260
+ onTextDelta(fullText, rawText);
220
261
  }
221
262
  else {
222
- const delta = text.slice(state.lastText.length);
263
+ const delta = fullText.slice(state.lastText.length);
223
264
  process.stdout.write(delta);
224
265
  }
225
- state.lastText = text;
266
+ state.lastText = fullText;
226
267
  }
227
268
  }
228
269
  catch {
@@ -247,11 +288,13 @@ export async function sendChatRequest(options) {
247
288
  const headers = {
248
289
  "Content-Type": "application/json",
249
290
  accept: stream ? "text/event-stream" : "application/json",
250
- Authorization: `Bearer ${accessToken}`,
251
291
  "Accept-Language": "zh-CN",
252
292
  "x-Language": "zh-CN",
253
293
  "x-business-domain": businessDomain,
254
294
  };
295
+ if (!isNoAuth(accessToken)) {
296
+ headers.Authorization = `Bearer ${accessToken}`;
297
+ }
255
298
  if (verbose) {
256
299
  console.error(`POST ${url}`);
257
300
  const safeHeaders = Object.fromEntries(Object.entries(headers).map(([k, v]) => k.toLowerCase() === "authorization" ? [k, "Bearer ***"] : [k, v]));
@@ -260,7 +303,7 @@ export async function sendChatRequest(options) {
260
303
  }
261
304
  let response;
262
305
  try {
263
- response = await fetch(url, {
306
+ response = await fetchWithRetry(url, {
264
307
  method: "POST",
265
308
  headers,
266
309
  body: JSON.stringify(body),
@@ -306,11 +349,13 @@ export async function sendChatRequestStream(options, callbacks) {
306
349
  const headers = {
307
350
  "Content-Type": "application/json",
308
351
  accept: "text/event-stream",
309
- Authorization: `Bearer ${accessToken}`,
310
352
  "Accept-Language": "zh-CN",
311
353
  "x-Language": "zh-CN",
312
354
  "x-business-domain": businessDomain,
313
355
  };
356
+ if (!isNoAuth(accessToken)) {
357
+ headers.Authorization = `Bearer ${accessToken}`;
358
+ }
314
359
  if (verbose) {
315
360
  console.error(`POST ${url}`);
316
361
  const safeHeaders = Object.fromEntries(Object.entries(headers).map(([k, v]) => k.toLowerCase() === "authorization" ? [k, "Bearer ***"] : [k, v]));
@@ -319,7 +364,7 @@ export async function sendChatRequestStream(options, callbacks) {
319
364
  }
320
365
  let response;
321
366
  try {
322
- response = await fetch(url, {
367
+ response = await fetchWithRetry(url, {
323
368
  method: "POST",
324
369
  headers,
325
370
  body: JSON.stringify(body),
@@ -335,29 +380,75 @@ export async function sendChatRequestStream(options, callbacks) {
335
380
  throw new HttpError(response.status, response.statusText, text);
336
381
  }
337
382
  if (contentType.includes("text/event-stream")) {
338
- return handleStreamResponse(response, verbose, callbacks.onTextDelta, callbacks.onProgress);
383
+ return handleStreamResponse(response, verbose, callbacks.onTextDelta, callbacks.onProgress, callbacks.onSegmentComplete, callbacks.onStepMeta, callbacks.onConversationId);
339
384
  }
340
385
  const text = await response.text();
341
386
  const json = parseJsonResponse(text);
342
387
  const resultText = normalizeDisplayText(extractText(json));
343
388
  const convId = json.conversation_id;
344
- callbacks.onTextDelta(resultText);
389
+ callbacks.onTextDelta(resultText, resultText);
345
390
  return { text: resultText, conversationId: convId, progress: getProgressFromResult(json) };
346
391
  }
347
- async function handleStreamResponse(response, verbose, onTextDelta, onProgress) {
392
+ async function handleStreamResponse(response, verbose, onTextDelta, onProgress, onSegmentComplete, onStepMeta, onConversationId) {
348
393
  const reader = response.body?.getReader();
349
394
  if (!reader) {
350
395
  throw new Error("No response body for stream");
351
396
  }
352
397
  const decoder = new TextDecoder();
353
398
  let buffer = "";
399
+ let pendingEventType = "";
354
400
  const state = {
355
401
  result: {},
356
402
  conversationId: undefined,
357
403
  lastText: "",
404
+ completedSegments: [],
405
+ prevRawText: "",
358
406
  };
359
407
  const applyLine = (line) => {
360
- applySseDataLine(line, state, verbose, onTextDelta, onProgress);
408
+ // Track SSE event type (e.g., "event:error")
409
+ if (line.startsWith("event:")) {
410
+ pendingEventType = line.slice(6).trim();
411
+ return;
412
+ }
413
+ // If we have an error event type, handle the data line as an error
414
+ if (pendingEventType === "error" && line.startsWith("data: ")) {
415
+ pendingEventType = "";
416
+ const errStr = line.slice(6).trim();
417
+ // Emit as error text rather than processing as incremental update
418
+ if (onTextDelta) {
419
+ let errMsg = errStr;
420
+ let errDetail = "";
421
+ try {
422
+ const errObj = JSON.parse(errStr);
423
+ errMsg = errObj.description || errObj.details || errStr;
424
+ if (errObj.solution && errObj.solution !== "无")
425
+ errMsg += "\n💡 " + errObj.solution;
426
+ // Collect all remaining fields as detail context
427
+ const detailFields = {};
428
+ for (const [k, v] of Object.entries(errObj)) {
429
+ if (k !== "description" && k !== "solution" && v != null && v !== "") {
430
+ detailFields[k] = v;
431
+ }
432
+ }
433
+ if (Object.keys(detailFields).length > 0) {
434
+ errDetail = "\n\n<details><summary>详细错误信息</summary>\n\n```json\n" + JSON.stringify(detailFields, null, 2) + "\n```\n</details>";
435
+ }
436
+ }
437
+ catch {
438
+ if (verbose)
439
+ console.error("Failed to parse SSE error JSON:", errStr);
440
+ }
441
+ const errText = "⚠️ " + errMsg + errDetail;
442
+ const fullText = state.completedSegments.length > 0
443
+ ? state.completedSegments.join("\n\n") + "\n\n" + errText
444
+ : errText;
445
+ onTextDelta(fullText, errText);
446
+ state.lastText = fullText;
447
+ }
448
+ return;
449
+ }
450
+ pendingEventType = "";
451
+ applySseDataLine(line, state, verbose, onTextDelta, onProgress, onSegmentComplete, onStepMeta, onConversationId);
361
452
  };
362
453
  while (true) {
363
454
  const { done, value } = await reader.read();
@@ -376,6 +467,21 @@ async function handleStreamResponse(response, verbose, onTextDelta, onProgress)
376
467
  if (!onTextDelta && state.lastText && !state.lastText.endsWith("\n")) {
377
468
  process.stdout.write("\n");
378
469
  }
379
- const finalText = normalizeDisplayText(extractText(state.result) || state.lastText);
470
+ // Fallback: try to extract conversationId from accumulated result if not found in stream
471
+ if (!state.conversationId) {
472
+ const r = state.result;
473
+ const candidate = r.conversation_id ??
474
+ r.message?.conversation_id ??
475
+ r.conversationId;
476
+ if (typeof candidate === "string" && candidate) {
477
+ state.conversationId = candidate;
478
+ if (onConversationId)
479
+ onConversationId(candidate);
480
+ }
481
+ }
482
+ const rawFinal = normalizeDisplayText(extractText(state.result));
483
+ const finalText = state.completedSegments.length > 0
484
+ ? state.completedSegments.join("\n\n") + (rawFinal ? "\n\n" + rawFinal : "")
485
+ : rawFinal || state.lastText;
380
486
  return { text: finalText, conversationId: state.conversationId };
381
487
  }
@@ -1,4 +1,4 @@
1
- import { HttpError } from "../utils/http.js";
1
+ import { HttpError, fetchWithRetry } from "../utils/http.js";
2
2
  import { buildHeaders } from "./headers.js";
3
3
  export async function listAgents(options) {
4
4
  const { baseUrl, accessToken, businessDomain = "bd_public", name = "", offset = 0, limit = 50, category_id = "", custom_space_id = "", is_to_square = 1, } = options;
@@ -12,7 +12,7 @@ export async function listAgents(options) {
12
12
  custom_space_id,
13
13
  is_to_square,
14
14
  });
15
- const response = await fetch(url, {
15
+ const response = await fetchWithRetry(url, {
16
16
  method: "POST",
17
17
  headers: buildHeaders(accessToken, businessDomain),
18
18
  body,
@@ -27,7 +27,7 @@ export async function getAgent(options) {
27
27
  const { baseUrl, accessToken, agentId, businessDomain = "bd_public", } = options;
28
28
  const base = baseUrl.replace(/\/+$/, "");
29
29
  const url = `${base}/api/agent-factory/v3/agent/${encodeURIComponent(agentId)}`;
30
- const response = await fetch(url, {
30
+ const response = await fetchWithRetry(url, {
31
31
  method: "GET",
32
32
  headers: buildHeaders(accessToken, businessDomain),
33
33
  });
@@ -41,7 +41,7 @@ export async function getAgentByKey(options) {
41
41
  const { baseUrl, accessToken, key, businessDomain = "bd_public", } = options;
42
42
  const base = baseUrl.replace(/\/+$/, "");
43
43
  const url = `${base}/api/agent-factory/v3/agent/by-key/${encodeURIComponent(key)}`;
44
- const response = await fetch(url, {
44
+ const response = await fetchWithRetry(url, {
45
45
  method: "GET",
46
46
  headers: buildHeaders(accessToken, businessDomain),
47
47
  });
@@ -55,7 +55,7 @@ export async function createAgent(options) {
55
55
  const { baseUrl, accessToken, body, businessDomain = "bd_public", } = options;
56
56
  const base = baseUrl.replace(/\/+$/, "");
57
57
  const url = `${base}/api/agent-factory/v3/agent`;
58
- const response = await fetch(url, {
58
+ const response = await fetchWithRetry(url, {
59
59
  method: "POST",
60
60
  headers: {
61
61
  ...buildHeaders(accessToken, businessDomain),
@@ -73,7 +73,7 @@ export async function updateAgent(options) {
73
73
  const { baseUrl, accessToken, agentId, body, businessDomain = "bd_public", } = options;
74
74
  const base = baseUrl.replace(/\/+$/, "");
75
75
  const url = `${base}/api/agent-factory/v3/agent/${encodeURIComponent(agentId)}`;
76
- const response = await fetch(url, {
76
+ const response = await fetchWithRetry(url, {
77
77
  method: "PUT",
78
78
  headers: {
79
79
  ...buildHeaders(accessToken, businessDomain),
@@ -91,7 +91,7 @@ export async function deleteAgent(options) {
91
91
  const { baseUrl, accessToken, agentId, businessDomain = "bd_public", } = options;
92
92
  const base = baseUrl.replace(/\/+$/, "");
93
93
  const url = `${base}/api/agent-factory/v3/agent/${encodeURIComponent(agentId)}`;
94
- const response = await fetch(url, {
94
+ const response = await fetchWithRetry(url, {
95
95
  method: "DELETE",
96
96
  headers: buildHeaders(accessToken, businessDomain),
97
97
  });
@@ -113,7 +113,7 @@ export async function publishAgent(options) {
113
113
  publish_to_bes: ["skill_agent"],
114
114
  pms_control: null,
115
115
  });
116
- const response = await fetch(url, {
116
+ const response = await fetchWithRetry(url, {
117
117
  method: "POST",
118
118
  headers: {
119
119
  ...buildHeaders(accessToken, businessDomain),
@@ -131,7 +131,7 @@ export async function unpublishAgent(options) {
131
131
  const { baseUrl, accessToken, agentId, businessDomain = "bd_public", } = options;
132
132
  const base = baseUrl.replace(/\/+$/, "");
133
133
  const url = `${base}/api/agent-factory/v3/agent/${encodeURIComponent(agentId)}/unpublish`;
134
- const response = await fetch(url, {
134
+ const response = await fetchWithRetry(url, {
135
135
  method: "PUT",
136
136
  headers: buildHeaders(accessToken, businessDomain),
137
137
  });
@@ -154,7 +154,7 @@ export async function listPersonalAgents(options) {
154
154
  params.append("publish_to_be", publish_to_be);
155
155
  params.append("size", String(size));
156
156
  const url = `${base}/api/agent-factory/v3/personal-space/agent-list?${params.toString()}`;
157
- const response = await fetch(url, {
157
+ const response = await fetchWithRetry(url, {
158
158
  method: "GET",
159
159
  headers: buildHeaders(accessToken, businessDomain),
160
160
  });
@@ -176,7 +176,7 @@ export async function listPublishedAgentTemplates(options) {
176
176
  params.append("pagination_marker_str", pagination_marker_str);
177
177
  params.append("size", String(size));
178
178
  const url = `${base}/api/agent-factory/v3/published/agent-tpl?${params.toString()}`;
179
- const response = await fetch(url, {
179
+ const response = await fetchWithRetry(url, {
180
180
  method: "GET",
181
181
  headers: buildHeaders(accessToken, businessDomain),
182
182
  });
@@ -190,7 +190,7 @@ export async function getPublishedAgentTemplate(options) {
190
190
  const { baseUrl, accessToken, templateId, businessDomain = "bd_public", } = options;
191
191
  const base = baseUrl.replace(/\/+$/, "");
192
192
  const url = `${base}/api/agent-factory/v3/published/agent-tpl/${encodeURIComponent(templateId)}`;
193
- const response = await fetch(url, {
193
+ const response = await fetchWithRetry(url, {
194
194
  method: "GET",
195
195
  headers: buildHeaders(accessToken, businessDomain),
196
196
  });
@@ -204,7 +204,7 @@ export async function listAgentCategories(options) {
204
204
  const { baseUrl, accessToken, businessDomain = "bd_public", } = options;
205
205
  const base = baseUrl.replace(/\/+$/, "");
206
206
  const url = `${base}/api/agent-factory/v3/category`;
207
- const response = await fetch(url, {
207
+ const response = await fetchWithRetry(url, {
208
208
  method: "GET",
209
209
  headers: buildHeaders(accessToken, businessDomain),
210
210
  });
@@ -1,3 +1,4 @@
1
+ import { isNoAuth } from "../config/no-auth.js";
1
2
  import { HttpError } from "../utils/http.js";
2
3
  async function withTlsInsecure(tlsInsecure, fn) {
3
4
  if (!tlsInsecure) {
@@ -26,13 +27,16 @@ export async function listBusinessDomains(options) {
26
27
  const base = baseUrl.replace(/\/+$/, "");
27
28
  const url = `${base}/api/business-system/v1/business-domain`;
28
29
  return withTlsInsecure(tlsInsecure, async () => {
30
+ const headers = {
31
+ accept: "application/json, text/plain, */*",
32
+ };
33
+ if (!isNoAuth(accessToken)) {
34
+ headers.authorization = `Bearer ${accessToken}`;
35
+ headers.token = accessToken;
36
+ }
29
37
  const response = await fetch(url, {
30
38
  method: "GET",
31
- headers: {
32
- accept: "application/json, text/plain, */*",
33
- authorization: `Bearer ${accessToken}`,
34
- token: accessToken,
35
- },
39
+ headers,
36
40
  });
37
41
  const body = await response.text();
38
42
  if (!response.ok) {
@@ -1,3 +1,4 @@
1
+ import { isNoAuth } from "../config/no-auth.js";
1
2
  import { fetchTextOrThrow } from "../utils/http.js";
2
3
  const MCP_PROTOCOL_VERSION = "2024-11-05";
3
4
  const SESSION_TTL_MS = 300_000; // 5 minutes
@@ -9,10 +10,12 @@ function buildHeaders(options, sessionId) {
9
10
  const headers = {
10
11
  "Content-Type": "application/json",
11
12
  Accept: "application/json, text/event-stream",
12
- Authorization: `Bearer ${options.accessToken}`,
13
13
  "X-Kn-ID": options.knId,
14
14
  "MCP-Protocol-Version": MCP_PROTOCOL_VERSION,
15
15
  };
16
+ if (!isNoAuth(options.accessToken)) {
17
+ headers.Authorization = `Bearer ${options.accessToken}`;
18
+ }
16
19
  if (sessionId) {
17
20
  headers["MCP-Session-Id"] = sessionId;
18
21
  }
@@ -1,3 +1,4 @@
1
+ import { buildHeaders } from "./headers.js";
1
2
  function buildConversationsUrl(baseUrl, agentKey) {
2
3
  const base = baseUrl.replace(/\/+$/, "");
3
4
  return `${base}/api/agent-factory/v1/app/${agentKey}/conversation`;
@@ -19,9 +20,7 @@ export async function listConversations(opts) {
19
20
  method: "GET",
20
21
  headers: {
21
22
  accept: "application/json",
22
- authorization: `Bearer ${accessToken}`,
23
- token: accessToken,
24
- "x-business-domain": businessDomain,
23
+ ...buildHeaders(accessToken, businessDomain),
25
24
  },
26
25
  });
27
26
  const body = await response.text();
@@ -42,9 +41,7 @@ export async function getTracesByConversation(opts) {
42
41
  headers: {
43
42
  "Content-Type": "application/json",
44
43
  accept: "application/json",
45
- authorization: `Bearer ${accessToken}`,
46
- token: accessToken,
47
- "x-business-domain": businessDomain,
44
+ ...buildHeaders(accessToken, businessDomain),
48
45
  },
49
46
  body: JSON.stringify({
50
47
  agent_id: agentId,
@@ -71,9 +68,7 @@ export async function listMessages(opts) {
71
68
  method: "GET",
72
69
  headers: {
73
70
  accept: "application/json",
74
- authorization: `Bearer ${accessToken}`,
75
- token: accessToken,
76
- "x-business-domain": businessDomain,
71
+ ...buildHeaders(accessToken, businessDomain),
77
72
  },
78
73
  });
79
74
  const body = await response.text();