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