@cloudbase/agent-adapter-adp 0.0.15 → 0.0.18

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/dist/index.mjs CHANGED
@@ -29,7 +29,7 @@ function camelToSnakeKeys(obj) {
29
29
  }
30
30
 
31
31
  // src/constant.ts
32
- var MIME_TYPES = {
32
+ var DOCUMENT_MIME_TYPES = {
33
33
  "text/plain": "txt",
34
34
  "application/msword": "doc",
35
35
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
@@ -37,6 +37,15 @@ var MIME_TYPES = {
37
37
  "application/vnd.ms-powerpoint": "ppt",
38
38
  "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx"
39
39
  };
40
+ var IMAGE_MIME_TYPES = {
41
+ "image/png": "png",
42
+ "image/jpeg": "jpg",
43
+ "image/bmp": "bmp"
44
+ };
45
+ var MIME_TYPES = {
46
+ ...DOCUMENT_MIME_TYPES,
47
+ ...IMAGE_MIME_TYPES
48
+ };
40
49
 
41
50
  // src/types.ts
42
51
  var ReplyMethod = /* @__PURE__ */ ((ReplyMethod2) => {
@@ -72,6 +81,7 @@ var CurrentNodeStatus = /* @__PURE__ */ ((CurrentNodeStatus2) => {
72
81
  // src/agent.ts
73
82
  import { randomUUID } from "crypto";
74
83
  import { lke } from "tencentcloud-sdk-nodejs-lke";
84
+ import COS from "cos-nodejs-sdk-v5";
75
85
  var AdpAgentError = class extends Error {
76
86
  constructor(message, code) {
77
87
  super(message);
@@ -82,73 +92,85 @@ var AdpAgentError = class extends Error {
82
92
  var AdpAgent = class extends AbstractAgent {
83
93
  constructor(config) {
84
94
  super(config);
95
+ this.finalAppKey = "";
96
+ this.finalCloudCredential = {};
85
97
  this.adpConfig = config.adpConfig;
98
+ this.finalAppKey = this.adpConfig.appKey || this.adpConfig.request?.body?.botAppKey || process.env.ADP_APP_KEY || "";
86
99
  this.reqAppClient = axios.create({
87
100
  baseURL: this.adpConfig.request?.baseUrl || "https://wss.lke.cloud.tencent.com"
88
101
  });
89
102
  const LkeClient = lke.v20231130.Client;
103
+ this.finalCloudCredential = {
104
+ secretId: this.adpConfig.credential?.secretId || process.env.TENCENTCLOUD_SECRETID,
105
+ secretKey: this.adpConfig.credential?.secretKey || process.env.TENCENTCLOUD_SECRETKEY,
106
+ token: this.adpConfig.credential?.token || process.env.TENCENTCLOUD_SESSIONTOKEN
107
+ };
90
108
  this.reqLkeClient = new LkeClient({
91
- credential: {
92
- secretId: this.adpConfig.credential?.secretId || process.env.TENCENTCLOUD_SECRETID,
93
- secretKey: this.adpConfig.credential?.secretKey || process.env.TENCENTCLOUD_SECRETKEY,
94
- token: this.adpConfig.credential?.token || process.env.TENCENTCLOUD_SESSIONTOKEN
95
- }
109
+ credential: this.finalCloudCredential
96
110
  });
97
111
  }
98
112
  generateRequestBody({
99
113
  message,
100
114
  fileInfos,
101
- runId,
102
- threadId,
103
- forwardedProps
115
+ input
104
116
  }) {
117
+ const { state, runId, threadId, forwardedProps } = input;
105
118
  const requestBody = {
106
119
  incremental: true,
107
120
  stream: "enable",
108
121
  ...this.adpConfig.request?.body || {},
109
122
  ...forwardedProps || {},
110
- botAppKey: this.adpConfig.appKey || this.adpConfig.request?.body?.botAppKey || process.env.ADP_APP_KEY,
111
- visitorBizId: forwardedProps?.visitorBizId || randomUUID(),
123
+ botAppKey: this.finalAppKey,
124
+ visitorBizId: state?.__request_context__?.id || forwardedProps?.visitorBizId || randomUUID(),
112
125
  requestId: runId,
113
- sessionId: threadId || randomUUID(),
126
+ sessionId: threadId,
114
127
  content: message,
115
128
  fileInfos
116
129
  };
117
130
  return requestBody;
118
131
  }
119
- // @ts-ignore
120
132
  run(input) {
121
133
  return new Observable((subscriber) => {
122
134
  this._run(subscriber, input);
123
135
  });
124
136
  }
125
137
  async _run(subscriber, input) {
138
+ let thinkingMessageSet = /* @__PURE__ */ new Set();
139
+ let thinkFinishedMessageSet = /* @__PURE__ */ new Set();
126
140
  try {
127
- const { messages, runId, threadId, forwardedProps } = input;
141
+ const { runId } = input;
142
+ const threadId = input.threadId || randomUUID();
128
143
  subscriber.next({
129
144
  type: EventType.RUN_STARTED,
130
145
  runId,
131
146
  threadId
132
147
  });
133
- if (!this.adpConfig.appKey && !process.env.ADP_APP_KEY) {
148
+ if (!this.finalAppKey) {
134
149
  throw new AdpAgentError(
135
- "ADP app key is required, check your env variables or config passed with the adapter",
150
+ "ADP_APP_KEY is required, check your env variables or config passed with the adapter",
136
151
  "MISSING_APP_KEY"
137
152
  );
138
153
  }
139
- const { messages: docExtractedMessages, fileInfos } = await extractDocuments(messages, this.adpConfig, this.reqLkeClient);
140
- const { message, trimmed } = convertAGUIMessagesToAdpMessages(docExtractedMessages);
154
+ const latestUserMessage = input.messages.filter((m) => m.role === "user").pop();
155
+ if (!latestUserMessage) {
156
+ throw new AdpAgentError(
157
+ "Message content format error, or empty content",
158
+ "INVALID_MESSAGE_FORMAT"
159
+ );
160
+ }
161
+ const { message: docExtractedMessage, fileInfos } = await this.extractDocuments(latestUserMessage, threadId, subscriber);
162
+ const message = await this.convertAGUIMessagesToAdpMessages(docExtractedMessage);
141
163
  if (!message) {
142
164
  throw new AdpAgentError(
143
165
  "Message content format error, or empty content",
144
- "INVALID_MESSAGE_CONTENT"
166
+ "INVALID_MESSAGE_FORMAT"
145
167
  );
146
168
  }
147
- if (trimmed > 0) {
169
+ if (input.messages.length > 1) {
148
170
  subscriber.next({
149
171
  type: EventType.RAW,
150
172
  rawEvent: {
151
- message: `ADP handles message history itself, so that a total of ${trimmed} messages before and including last assistant message will be trimmed.`,
173
+ message: `ADP handles message history itself, so that a total of ${input.messages.length - 1} messages before and including last assistant message will be trimmed.`,
152
174
  type: "warn"
153
175
  }
154
176
  });
@@ -156,9 +178,7 @@ var AdpAgent = class extends AbstractAgent {
156
178
  const requestBody = this.generateRequestBody({
157
179
  message,
158
180
  fileInfos,
159
- runId,
160
- threadId,
161
- forwardedProps
181
+ input
162
182
  });
163
183
  const response = await this.reqAppClient.post(
164
184
  this.adpConfig.request?.endpoint || "/v1/qbot/chat/sse",
@@ -169,7 +189,6 @@ var AdpAgent = class extends AbstractAgent {
169
189
  let buffer = "";
170
190
  let interruptRequested = false;
171
191
  let thinkingStart = false;
172
- let thinkingMessageSet = /* @__PURE__ */ new Set();
173
192
  for await (const chunk of sseStream) {
174
193
  buffer += chunk.toString();
175
194
  const parts = buffer.split("\n\n");
@@ -196,6 +215,22 @@ var AdpAgent = class extends AbstractAgent {
196
215
  }
197
216
  switch (data.type) {
198
217
  case "reply": {
218
+ const messageId = data.payload.record_id;
219
+ const isFinal = data.payload.is_final;
220
+ if (thinkingStart) {
221
+ thinkingStart = false;
222
+ for (const index of thinkingMessageSet) {
223
+ subscriber.next({
224
+ type: EventType.THINKING_TEXT_MESSAGE_END,
225
+ messageId: `${messageId}-think-${index}`
226
+ });
227
+ }
228
+ thinkingMessageSet.clear();
229
+ subscriber.next({
230
+ type: EventType.THINKING_END,
231
+ messageId
232
+ });
233
+ }
199
234
  if (data.payload.is_from_self) {
200
235
  if (data.payload.is_evil) {
201
236
  throw new AdpAgentError(
@@ -206,8 +241,6 @@ var AdpAgent = class extends AbstractAgent {
206
241
  continue;
207
242
  }
208
243
  }
209
- const messageId = data.payload.record_id;
210
- const isFinal = data.payload.is_final;
211
244
  data.payload.content = data.payload.content.replace(
212
245
  /\\n/g,
213
246
  "\n\n"
@@ -287,51 +320,47 @@ var AdpAgent = class extends AbstractAgent {
287
320
  });
288
321
  }
289
322
  data.payload.procedures.forEach((procedure) => {
290
- if (!thinkingMessageSet.has(messageId)) {
291
- thinkingMessageSet.add(messageId);
323
+ const index = procedure.index.toString();
324
+ if (!thinkingMessageSet.has(index) && !thinkFinishedMessageSet.has(index)) {
325
+ thinkingMessageSet.add(index);
292
326
  subscriber.next({
293
327
  type: EventType.THINKING_TEXT_MESSAGE_START,
294
- messageId,
328
+ messageId: `${messageId}-think-${index}`,
295
329
  delta: procedure.debugging.content
296
330
  });
297
331
  } else {
298
332
  if (procedure.status === "processing") {
299
333
  subscriber.next({
300
334
  type: EventType.THINKING_TEXT_MESSAGE_CONTENT,
301
- messageId,
335
+ messageId: `${messageId}-think-${index}`,
302
336
  delta: procedure.debugging.content
303
337
  });
304
338
  } else {
305
- thinkingMessageSet.delete(messageId);
306
- subscriber.next({
307
- type: EventType.THINKING_TEXT_MESSAGE_END,
308
- messageId
309
- });
339
+ thinkingMessageSet.delete(index);
340
+ if (!thinkFinishedMessageSet.has(index)) {
341
+ thinkFinishedMessageSet.add(index);
342
+ subscriber.next({
343
+ type: EventType.THINKING_TEXT_MESSAGE_END,
344
+ messageId: `${messageId}-think-${index}`
345
+ });
346
+ }
310
347
  }
311
348
  }
312
349
  });
313
- const allFinished = data.payload.procedures.every(
314
- (procedure) => procedure.status !== "processing"
315
- );
316
- if (allFinished) {
317
- thinkingStart = false;
318
- thinkingMessageSet.clear();
319
- subscriber.next({
320
- type: EventType.THINKING_END,
321
- messageId
322
- });
323
- }
324
350
  break;
325
351
  }
326
352
  case "error": {
327
- console.error(JSON.stringify(data));
353
+ console.error(
354
+ "[ERROR] ADP throws error: ",
355
+ JSON.stringify(data)
356
+ );
328
357
  throw new AdpAgentError(
329
358
  data.error.message,
330
- `ADP_ERROR_${data.error.code}` || "ADP_ERROR_0"
359
+ data.error.code ? `ADP_ERROR_${data.error.code}` : "ADP_ERROR_-1"
331
360
  );
332
361
  }
333
362
  case "token_stat": {
334
- console.debug(JSON.stringify(data));
363
+ console.debug("[DEBUG] ADP token stat: ", JSON.stringify(data));
335
364
  break;
336
365
  }
337
366
  case "reference": {
@@ -361,7 +390,7 @@ var AdpAgent = class extends AbstractAgent {
361
390
  }
362
391
  subscriber.complete();
363
392
  } catch (e) {
364
- console.error(JSON.stringify(e));
393
+ console.error("[ERROR] Uncaught error: ", JSON.stringify(e));
365
394
  let code = "UNKNOWN_ERROR";
366
395
  let message = JSON.stringify(e);
367
396
  if (e instanceof AxiosError) {
@@ -377,31 +406,75 @@ var AdpAgent = class extends AbstractAgent {
377
406
  message: `Sorry, an error occurred while running the agent: Error code ${code}, ${message}`
378
407
  });
379
408
  subscriber.complete();
409
+ } finally {
410
+ thinkingMessageSet.clear();
411
+ thinkFinishedMessageSet.clear();
380
412
  }
381
413
  }
382
- };
383
- function convertAGUIMessagesToAdpMessages(messages) {
384
- let result = "";
385
- let trimmed = messages.length;
386
- for (const message of messages.reverse()) {
387
- if (message.role === "assistant") {
388
- break;
389
- }
414
+ async convertAGUIMessagesToAdpMessages(message) {
415
+ let result = "";
390
416
  if (message.role === "user") {
391
- trimmed--;
392
417
  let content = "";
393
418
  if (typeof message.content === "string") {
394
419
  content = message.content;
395
420
  } else {
396
- content = message.content.reduce((acc, cur) => {
397
- if (cur.type === "text") {
398
- return acc + `${cur.text} `;
399
- } else if (cur.type === "binary") {
400
- return acc + `![${cur.filename}](${cur.url}) `;
401
- } else {
402
- return acc;
421
+ if (this.adpConfig.enableUpload) {
422
+ if (!this.finalCloudCredential.token) {
423
+ if (!this.finalCloudCredential.secretId) {
424
+ throw new AdpAgentError(
425
+ "TENCENTCLOUD_SECRETID is required, check your env variables or config passed with the adapter",
426
+ "MISSING_SECRET_ID"
427
+ );
428
+ }
429
+ if (!this.finalCloudCredential.secretKey) {
430
+ throw new AdpAgentError(
431
+ "TENCENTCLOUD_SECRETKEY is required, check your env variables or config passed with the adapter",
432
+ "MISSING_SECRET_KEY"
433
+ );
434
+ }
403
435
  }
404
- }, "").trim();
436
+ const imageMap = /* @__PURE__ */ new Map();
437
+ const imagesToUpload = [];
438
+ message.content.forEach((item) => {
439
+ if (item.type === "binary") {
440
+ if (Object.keys(IMAGE_MIME_TYPES).includes(item.mimeType)) {
441
+ imagesToUpload.push(item);
442
+ }
443
+ }
444
+ });
445
+ if (imagesToUpload.length) {
446
+ await this.uploadToCos(
447
+ imagesToUpload,
448
+ (data, _, file) => {
449
+ imageMap.set(
450
+ file.id || file.filename,
451
+ `https://${data.Location}`
452
+ );
453
+ },
454
+ (fileName, error) => {
455
+ throw new AdpAgentError(
456
+ `Upload image ${fileName} failed: ${error}`,
457
+ "UPLOAD_IMAGE_FAILED"
458
+ );
459
+ }
460
+ );
461
+ }
462
+ content = message.content.reduce((acc, cur) => {
463
+ if (cur.type === "text") {
464
+ return acc + `${cur.text} `;
465
+ } else if (cur.type === "binary") {
466
+ if (Object.keys(IMAGE_MIME_TYPES).includes(cur.mimeType)) {
467
+ if (imageMap.has(cur.id || cur.filename)) {
468
+ return acc + `![${cur.filename}](${imageMap.get(cur.id || cur.filename)}) `;
469
+ } else {
470
+ return acc;
471
+ }
472
+ } else return acc;
473
+ } else {
474
+ return acc;
475
+ }
476
+ }, "").trim();
477
+ }
405
478
  }
406
479
  result = `${message.role}: ${content}
407
480
  ${result}`;
@@ -409,36 +482,214 @@ ${result}`;
409
482
  result = `${message.role}: ${message.content}
410
483
  ${result}`;
411
484
  }
485
+ return result.trim();
412
486
  }
413
- return { message: result.trim(), trimmed };
414
- }
415
- async function extractDocuments(messages, config, reqLkeClient) {
416
- const documentFiles = [];
417
- const newMessages = messages.map((msg) => {
418
- if (msg.role === "user" && Array.isArray(msg.content)) {
487
+ async extractDocuments(message, threadId, subscriber) {
488
+ const documentFiles = [];
489
+ let newMessage;
490
+ if (message.role === "user" && Array.isArray(message.content)) {
419
491
  let newContent = [];
420
- msg.content.forEach((item) => {
492
+ message.content.forEach((item) => {
421
493
  if (item.type === "text") {
422
494
  newContent.push(item);
423
495
  } else if (item.type === "binary") {
424
- if (Object.keys(MIME_TYPES).includes(item.mimeType)) {
496
+ if (Object.keys(DOCUMENT_MIME_TYPES).includes(item.mimeType)) {
425
497
  documentFiles.push(item);
498
+ } else {
499
+ newContent.push(item);
426
500
  }
427
501
  }
428
502
  });
429
- return {
430
- ...msg,
503
+ newMessage = {
504
+ ...message,
431
505
  content: newContent
432
506
  };
433
- } else return msg;
434
- });
435
- const documentAddresses = [];
436
- const fileInfos = [];
437
- return {
438
- messages: newMessages,
439
- fileInfos
440
- };
441
- }
507
+ } else newMessage = message;
508
+ const fileInfos = [];
509
+ const successedFiles = [];
510
+ const failedFiles = [];
511
+ if (this.adpConfig.enableUpload) {
512
+ if (!this.finalCloudCredential.token) {
513
+ if (!this.finalCloudCredential.secretId) {
514
+ throw new AdpAgentError(
515
+ "TENCENTCLOUD_SECRETID is required, check your env variables or config passed with the adapter",
516
+ "MISSING_SECRET_ID"
517
+ );
518
+ }
519
+ if (!this.finalCloudCredential.secretKey) {
520
+ throw new AdpAgentError(
521
+ "TENCENTCLOUD_SECRETKEY is required, check your env variables or config passed with the adapter",
522
+ "MISSING_SECRET_KEY"
523
+ );
524
+ }
525
+ }
526
+ if (documentFiles.length) {
527
+ try {
528
+ await this.uploadToCos(
529
+ documentFiles,
530
+ (data, cosParams, file) => {
531
+ successedFiles.push({ data, cosParams, file });
532
+ },
533
+ (fileName, error) => {
534
+ failedFiles.push({ fileName, error });
535
+ }
536
+ );
537
+ } catch (e) {
538
+ console.error("Document upload failed: ", JSON.stringify(e));
539
+ }
540
+ }
541
+ if (successedFiles.length) {
542
+ for (const { data: cosData, cosParams, file } of successedFiles) {
543
+ const extName = MIME_TYPES[file.mimeType];
544
+ const requestBody = {
545
+ sessionId: threadId,
546
+ botAppKey: this.finalAppKey,
547
+ requestId: randomUUID(),
548
+ cosBucket: cosParams.Bucket,
549
+ fileType: extName,
550
+ fileName: file.filename,
551
+ cosUrl: cosParams.UploadPath,
552
+ cosHash: cosData.headers?.["x-cos-hash-crc64ecma"] || "",
553
+ eTag: cosData.ETag,
554
+ size: file.data?.length.toString() || "0"
555
+ };
556
+ const response = await this.reqAppClient.post(
557
+ this.adpConfig.request?.docParseEndpoint || "/v1/qbot/chat/docParse",
558
+ camelToSnakeKeys(requestBody),
559
+ { responseType: "stream" }
560
+ );
561
+ const sseStream = response.data;
562
+ let buffer = "";
563
+ for await (const chunk of sseStream) {
564
+ buffer += chunk.toString();
565
+ const parts = buffer.split("\n\n");
566
+ buffer = parts.pop() || "";
567
+ for (const part of parts) {
568
+ if (!part.trim()) continue;
569
+ const event = { data: "", event: "" };
570
+ for (const line of part.split("\n")) {
571
+ if (line.startsWith("data:")) {
572
+ event.data += line.slice(5);
573
+ } else if (line.startsWith("event:")) {
574
+ event.event = line.slice(6);
575
+ }
576
+ }
577
+ if (event.data) {
578
+ let data;
579
+ try {
580
+ data = JSON.parse(event.data);
581
+ } catch (e) {
582
+ throw new AdpAgentError(
583
+ `ADP returned invalid data: ${event.data}`,
584
+ "INVALID_DATA"
585
+ );
586
+ }
587
+ switch (data.type) {
588
+ case "parsing": {
589
+ subscriber.next({
590
+ type: EventType.RAW,
591
+ rawEvent: {
592
+ message: `Parsing document ${file.filename}: ${data.payload.process}%`,
593
+ type: "info"
594
+ }
595
+ });
596
+ if (data.payload.is_final) {
597
+ if (data.payload.error_message) {
598
+ subscriber.next({
599
+ type: EventType.RAW,
600
+ rawEvent: {
601
+ message: `Parsing document ${file.filename} failed: ${data.payload.error_message}`,
602
+ type: "error"
603
+ }
604
+ });
605
+ } else {
606
+ const fileNameNoExt = file.filename.split(".").splice(0, -1).join(".");
607
+ fileInfos.push({
608
+ docId: data.payload.doc_id,
609
+ fileName: fileNameNoExt,
610
+ fileType: extName,
611
+ fileSize: file.data?.length.toString() || "0",
612
+ fileUrl: `https://${cosData.Location}`
613
+ });
614
+ }
615
+ }
616
+ break;
617
+ }
618
+ default: {
619
+ break;
620
+ }
621
+ }
622
+ }
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+ return {
629
+ message: newMessage,
630
+ fileInfos,
631
+ failedFiles
632
+ };
633
+ }
634
+ async getCosParams(mimeType) {
635
+ const extName = MIME_TYPES?.[mimeType];
636
+ if (!extName) {
637
+ throw new Error(`Unsupported mime type: ${mimeType}`);
638
+ }
639
+ const isPicture = Object.keys(IMAGE_MIME_TYPES).includes(mimeType);
640
+ const bizIdRes = await this.reqLkeClient.DescribeRobotBizIDByAppKey({
641
+ AppKey: this.finalAppKey
642
+ });
643
+ const storageRes = await this.reqLkeClient.DescribeStorageCredential({
644
+ BotBizId: bizIdRes.BotBizId,
645
+ FileType: extName,
646
+ IsPublic: isPicture,
647
+ TypeKey: "realtime"
648
+ });
649
+ return storageRes;
650
+ }
651
+ async uploadToCos(files, onSuccess, onError) {
652
+ const promises = [];
653
+ for (const file of files) {
654
+ const extName = MIME_TYPES?.[file.mimeType];
655
+ if (!extName) {
656
+ onError(file.filename || "unknown-file", "Unsupported file type");
657
+ continue;
658
+ }
659
+ const cosParams = await this.getCosParams(file.mimeType);
660
+ const cosClient = new COS({
661
+ SecretId: cosParams.Credentials?.TmpSecretId,
662
+ SecretKey: cosParams.Credentials?.TmpSecretKey,
663
+ SecurityToken: cosParams.Credentials?.Token
664
+ });
665
+ if (!cosParams.Bucket || !cosParams.Region || !cosParams.UploadPath) {
666
+ onError(file.filename || "unknown-file", "Failed to get COS params");
667
+ continue;
668
+ }
669
+ const promise = new Promise((resolve, reject) => {
670
+ cosClient.putObject(
671
+ {
672
+ Bucket: cosParams.Bucket,
673
+ Region: cosParams.Region,
674
+ Key: cosParams.UploadPath,
675
+ Body: file.data
676
+ },
677
+ (err, data) => {
678
+ if (err) {
679
+ onError(file.filename || "unknown-file", err.message);
680
+ reject();
681
+ } else {
682
+ onSuccess(data, cosParams, file);
683
+ resolve();
684
+ }
685
+ }
686
+ );
687
+ });
688
+ promises.push(promise);
689
+ }
690
+ await Promise.allSettled(promises);
691
+ }
692
+ };
442
693
  export {
443
694
  AdpAgent,
444
695
  AdpAgentError,