@cloudbase/agent-adapter-adp 1.0.1-alpha.13 → 1.0.1-alpha.14

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 CHANGED
@@ -15,6 +15,7 @@ npm install @cloudbase/agent-agents @cloudbase/agent-adapter-adp
15
15
  - **Thinking Events**: Support for thinking/reasoning process events from the model
16
16
  - **Workflow Integration**: Support for ADP workflows with tool call events
17
17
  - **Custom Variables**: Pass custom parameters to workflows and knowledge base
18
+ - **File Upload**: Upload images and documents to COS for multimodal conversations (**requires `enableUpload: true`**)
18
19
 
19
20
  ## Environment Variables
20
21
 
@@ -121,6 +122,55 @@ observable.subscribe({
121
122
  });
122
123
  ```
123
124
 
125
+ ### With File Upload (Images and Documents)
126
+
127
+ When `enableUpload` is **set to `true`**, you can send images and documents in messages. Files are automatically uploaded to Tencent Cloud COS and processed by ADP.
128
+
129
+ **Note:** File upload requires Tencent Cloud credentials (`TENCENTCLOUD_SECRETID` and `TENCENTCLOUD_SECRETKEY`).
130
+
131
+ ```typescript
132
+ import { AdpAgent } from "@cloudbase/agent-adapter-adp";
133
+ import { randomUUID } from "crypto";
134
+ import * as fs from "fs";
135
+
136
+ const agent = new AdpAgent({
137
+ name: "my-adp-agent",
138
+ adpConfig: {
139
+ appKey: "your-app-key",
140
+ enableUpload: true, // Enable file upload
141
+ credential: {
142
+ secretId: "your-secret-id",
143
+ secretKey: "your-secret-key",
144
+ },
145
+ },
146
+ });
147
+
148
+ const observable = agent.run({
149
+ runId: randomUUID(),
150
+ threadId: randomUUID(),
151
+ messages: [
152
+ {
153
+ id: randomUUID(),
154
+ role: "user",
155
+ content: [
156
+ { type: "text", text: "What's in this image?" },
157
+ {
158
+ type: "binary",
159
+ mimeType: "image/png",
160
+ filename: "screenshot.png",
161
+ data: fs.readFileSync("./screenshot.png"),
162
+ },
163
+ ],
164
+ },
165
+ ],
166
+ });
167
+ ```
168
+
169
+ **Supported file types:**
170
+
171
+ - **Images**: `image/png`, `image/jpeg`, `image/gif`, `image/webp`, `image/bmp`
172
+ - **Documents**: `application/pdf`, `application/msword`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `text/markdown`, `text/plain`
173
+
124
174
  ## API Reference
125
175
 
126
176
  ### AdpAgent
@@ -146,6 +196,7 @@ interface AdpConfig {
146
196
  token?: string; // Session token (optional if TENCENTCLOUD_SESSIONTOKEN env is set)
147
197
  };
148
198
  historyCount?: number; // Number of history messages to retrieve (reserved)
199
+ enableUpload?: boolean; // Enable file upload for images and documents (default: false)
149
200
  request?: {
150
201
  baseUrl?: string; // Base URL for ADP API (default: https://wss.lke.cloud.tencent.com)
151
202
  endpoint?: string; // API endpoint (default: /v1/qbot/chat/sse)
@@ -187,6 +238,7 @@ The adapter emits the following AG-UI events:
187
238
  - `axios`: HTTP client for API requests
188
239
  - `rxjs`: Reactive extensions for JavaScript
189
240
  - `tencentcloud-sdk-nodejs-lke`: Tencent Cloud LKE SDK
241
+ - `cos-nodejs-sdk-v5`: Tencent Cloud COS SDK (for file upload)
190
242
 
191
243
  ## Related Resources
192
244
 
package/dist/index.d.mts CHANGED
@@ -61,6 +61,7 @@ type AdpConfig = {
61
61
  body?: Partial<AdpChatRequest>;
62
62
  };
63
63
  historyCount?: number;
64
+ enableUpload?: boolean;
64
65
  appKey?: string;
65
66
  credential?: {
66
67
  secretId?: string;
@@ -282,6 +283,7 @@ declare class AdpAgent extends AbstractAgent {
282
283
  private reqLkeClient;
283
284
  protected adpConfig: AdpConfig;
284
285
  private finalAppKey;
286
+ private finalCloudCredential;
285
287
  constructor(config: AgentConfig & {
286
288
  adpConfig: AdpConfig;
287
289
  });
package/dist/index.d.ts CHANGED
@@ -61,6 +61,7 @@ type AdpConfig = {
61
61
  body?: Partial<AdpChatRequest>;
62
62
  };
63
63
  historyCount?: number;
64
+ enableUpload?: boolean;
64
65
  appKey?: string;
65
66
  credential?: {
66
67
  secretId?: string;
@@ -282,6 +283,7 @@ declare class AdpAgent extends AbstractAgent {
282
283
  private reqLkeClient;
283
284
  protected adpConfig: AdpConfig;
284
285
  private finalAppKey;
286
+ private finalCloudCredential;
285
287
  constructor(config: AgentConfig & {
286
288
  adpConfig: AdpConfig;
287
289
  });
package/dist/index.js CHANGED
@@ -129,18 +129,20 @@ var AdpAgent = class extends import_client.AbstractAgent {
129
129
  constructor(config) {
130
130
  super(config);
131
131
  this.finalAppKey = "";
132
+ this.finalCloudCredential = {};
132
133
  this.adpConfig = config.adpConfig;
133
134
  this.finalAppKey = this.adpConfig.appKey || this.adpConfig.request?.body?.botAppKey || process.env.ADP_APP_KEY || "";
134
135
  this.reqAppClient = import_axios.default.create({
135
136
  baseURL: this.adpConfig.request?.baseUrl || "https://wss.lke.cloud.tencent.com"
136
137
  });
137
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
+ };
138
144
  this.reqLkeClient = new LkeClient({
139
- credential: {
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
- }
145
+ credential: this.finalCloudCredential
144
146
  });
145
147
  }
146
148
  generateRequestBody({
@@ -170,7 +172,8 @@ var AdpAgent = class extends import_client.AbstractAgent {
170
172
  }
171
173
  async _run(subscriber, input) {
172
174
  try {
173
- const { runId, threadId } = input;
175
+ const { runId, threadId: _threadId } = input;
176
+ const threadId = _threadId || (0, import_crypto.randomUUID)();
174
177
  subscriber.next({
175
178
  type: import_client.EventType.RUN_STARTED,
176
179
  runId,
@@ -436,47 +439,63 @@ var AdpAgent = class extends import_client.AbstractAgent {
436
439
  if (typeof message.content === "string") {
437
440
  content = message.content;
438
441
  } else {
439
- const imageMap = /* @__PURE__ */ new Map();
440
- const imagesToUpload = [];
441
- message.content.forEach((item) => {
442
- if (item.type === "binary") {
443
- if (Object.keys(IMAGE_MIME_TYPES).includes(item.mimeType)) {
444
- imagesToUpload.push(item);
445
- }
446
- }
447
- });
448
- if (imagesToUpload.length) {
449
- await this.uploadToCos(
450
- imagesToUpload,
451
- (data, _, file) => {
452
- imageMap.set(
453
- file.id || file.filename,
454
- `https://${data.Location}`
442
+ if (this.adpConfig.enableUpload) {
443
+ if (!this.finalCloudCredential.token) {
444
+ if (!this.finalCloudCredential.secretId) {
445
+ throw new AdpAgentError(
446
+ "TENCENTCLOUD_SECRETID is required, check your env variables or config passed with the adapter",
447
+ "MISSING_SECRET_ID"
455
448
  );
456
- },
457
- (fileName, error) => {
449
+ }
450
+ if (!this.finalCloudCredential.secretKey) {
458
451
  throw new AdpAgentError(
459
- `Upload image ${fileName} failed: ${error}`,
460
- "UPLOAD_IMAGE_FAILED"
452
+ "TENCENTCLOUD_SECRETKEY is required, check your env variables or config passed with the adapter",
453
+ "MISSING_SECRET_KEY"
461
454
  );
462
455
  }
463
- );
464
- }
465
- content = message.content.reduce((acc, cur) => {
466
- if (cur.type === "text") {
467
- return acc + `${cur.text} `;
468
- } else if (cur.type === "binary") {
469
- if (Object.keys(IMAGE_MIME_TYPES).includes(cur.mimeType)) {
470
- if (imageMap.has(cur.id || cur.filename)) {
471
- return acc + `![${cur.filename}](${imageMap.get(cur.id || cur.filename)}) `;
472
- } else {
473
- return acc;
456
+ }
457
+ const imageMap = /* @__PURE__ */ new Map();
458
+ const imagesToUpload = [];
459
+ message.content.forEach((item) => {
460
+ if (item.type === "binary") {
461
+ if (Object.keys(IMAGE_MIME_TYPES).includes(item.mimeType)) {
462
+ imagesToUpload.push(item);
474
463
  }
475
- } else return acc;
476
- } else {
477
- return acc;
464
+ }
465
+ });
466
+ if (imagesToUpload.length) {
467
+ await this.uploadToCos(
468
+ imagesToUpload,
469
+ (data, _, file) => {
470
+ imageMap.set(
471
+ file.id || file.filename,
472
+ `https://${data.Location}`
473
+ );
474
+ },
475
+ (fileName, error) => {
476
+ throw new AdpAgentError(
477
+ `Upload image ${fileName} failed: ${error}`,
478
+ "UPLOAD_IMAGE_FAILED"
479
+ );
480
+ }
481
+ );
478
482
  }
479
- }, "").trim();
483
+ content = message.content.reduce((acc, cur) => {
484
+ if (cur.type === "text") {
485
+ return acc + `${cur.text} `;
486
+ } else if (cur.type === "binary") {
487
+ if (Object.keys(IMAGE_MIME_TYPES).includes(cur.mimeType)) {
488
+ if (imageMap.has(cur.id || cur.filename)) {
489
+ return acc + `![${cur.filename}](${imageMap.get(cur.id || cur.filename)}) `;
490
+ } else {
491
+ return acc;
492
+ }
493
+ } else return acc;
494
+ } else {
495
+ return acc;
496
+ }
497
+ }, "").trim();
498
+ }
480
499
  }
481
500
  result = `${message.role}: ${content}
482
501
  ${result}`;
@@ -513,105 +532,121 @@ ${result}`;
513
532
  const fileInfos = [];
514
533
  const successedFiles = [];
515
534
  const failedFiles = [];
516
- if (documentFiles.length) {
517
- try {
518
- await this.uploadToCos(
519
- documentFiles,
520
- (data, cosParams, file) => {
521
- successedFiles.push({ data, cosParams, file });
522
- },
523
- (fileName, error) => {
524
- failedFiles.push({ fileName, error });
525
- }
526
- );
527
- } catch (e) {
535
+ if (this.adpConfig.enableUpload) {
536
+ if (!this.finalCloudCredential.token) {
537
+ if (!this.finalCloudCredential.secretId) {
538
+ throw new AdpAgentError(
539
+ "TENCENTCLOUD_SECRETID is required, check your env variables or config passed with the adapter",
540
+ "MISSING_SECRET_ID"
541
+ );
542
+ }
543
+ if (!this.finalCloudCredential.secretKey) {
544
+ throw new AdpAgentError(
545
+ "TENCENTCLOUD_SECRETKEY is required, check your env variables or config passed with the adapter",
546
+ "MISSING_SECRET_KEY"
547
+ );
548
+ }
528
549
  }
529
- }
530
- if (successedFiles.length) {
531
- successedFiles.forEach(async ({ data: cosData, cosParams, file }) => {
532
- const extName = MIME_TYPES[file.mimeType];
533
- const requestBody = {
534
- sessionId: threadId,
535
- botAppKey: this.finalAppKey,
536
- requestId: (0, import_crypto.randomUUID)(),
537
- cosBucket: cosParams.Bucket,
538
- fileType: extName,
539
- fileName: file.filename,
540
- cosUrl: cosParams.UploadPath,
541
- cosHash: cosData.headers?.["x-cos-hash-crc64ecma"] || "",
542
- eTag: cosData.ETag,
543
- size: file.data?.length.toString() || "0"
544
- };
545
- const response = await this.reqAppClient.post(
546
- this.adpConfig.request?.endpoint || "/v1/qbot/chat/docParse",
547
- camelToSnakeKeys(requestBody),
548
- { responseType: "stream" }
549
- );
550
- const sseStream = response.data;
551
- let buffer = "";
552
- for await (const chunk of sseStream) {
553
- buffer += chunk.toString();
554
- const parts = buffer.split("\n\n");
555
- buffer = parts.pop() || "";
556
- for (const part of parts) {
557
- if (!part.trim()) continue;
558
- const event = { data: "", event: "" };
559
- for (const line of part.split("\n")) {
560
- if (line.startsWith("data:")) {
561
- event.data += line.slice(5);
562
- } else if (line.startsWith("event:")) {
563
- event.event = line.slice(6);
564
- }
550
+ if (documentFiles.length) {
551
+ try {
552
+ await this.uploadToCos(
553
+ documentFiles,
554
+ (data, cosParams, file) => {
555
+ successedFiles.push({ data, cosParams, file });
556
+ },
557
+ (fileName, error) => {
558
+ failedFiles.push({ fileName, error });
565
559
  }
566
- if (event.data) {
567
- let data;
568
- try {
569
- data = JSON.parse(event.data);
570
- } catch (e) {
571
- throw new AdpAgentError(
572
- `ADP returned invalid data: ${event.data}`,
573
- "INVALID_DATA"
574
- );
560
+ );
561
+ } catch (e) {
562
+ }
563
+ }
564
+ if (successedFiles.length) {
565
+ successedFiles.forEach(async ({ data: cosData, cosParams, file }) => {
566
+ const extName = MIME_TYPES[file.mimeType];
567
+ const requestBody = {
568
+ sessionId: threadId,
569
+ botAppKey: this.finalAppKey,
570
+ requestId: (0, import_crypto.randomUUID)(),
571
+ cosBucket: cosParams.Bucket,
572
+ fileType: extName,
573
+ fileName: file.filename,
574
+ cosUrl: cosParams.UploadPath,
575
+ cosHash: cosData.headers?.["x-cos-hash-crc64ecma"] || "",
576
+ eTag: cosData.ETag,
577
+ size: file.data?.length.toString() || "0"
578
+ };
579
+ const response = await this.reqAppClient.post(
580
+ this.adpConfig.request?.endpoint || "/v1/qbot/chat/docParse",
581
+ camelToSnakeKeys(requestBody),
582
+ { responseType: "stream" }
583
+ );
584
+ const sseStream = response.data;
585
+ let buffer = "";
586
+ for await (const chunk of sseStream) {
587
+ buffer += chunk.toString();
588
+ const parts = buffer.split("\n\n");
589
+ buffer = parts.pop() || "";
590
+ for (const part of parts) {
591
+ if (!part.trim()) continue;
592
+ const event = { data: "", event: "" };
593
+ for (const line of part.split("\n")) {
594
+ if (line.startsWith("data:")) {
595
+ event.data += line.slice(5);
596
+ } else if (line.startsWith("event:")) {
597
+ event.event = line.slice(6);
598
+ }
575
599
  }
576
- switch (data.type) {
577
- case "parsing": {
578
- subscriber.next({
579
- type: import_client.EventType.RAW,
580
- rawEvent: {
581
- message: `Parsing document ${file.filename}: ${data.payload.process}%`,
582
- type: "info"
583
- }
584
- });
585
- if (data.payload.is_final) {
586
- if (data.payload.error_message) {
587
- subscriber.next({
588
- type: import_client.EventType.RAW,
589
- rawEvent: {
590
- message: `Parsing document ${file.filename} failed: ${data.payload.error_message}`,
591
- type: "error"
592
- }
593
- });
594
- } else {
595
- const fileNameNoExt = file.filename.split(".").splice(0, -1).join(".");
596
- fileInfos.push({
597
- docId: data.payload.doc_id,
598
- fileName: fileNameNoExt,
599
- fileType: extName,
600
- fileSize: file.data?.length.toString() || "0",
601
- fileUrl: `https://${cosData.Location}`
602
- });
600
+ if (event.data) {
601
+ let data;
602
+ try {
603
+ data = JSON.parse(event.data);
604
+ } catch (e) {
605
+ throw new AdpAgentError(
606
+ `ADP returned invalid data: ${event.data}`,
607
+ "INVALID_DATA"
608
+ );
609
+ }
610
+ switch (data.type) {
611
+ case "parsing": {
612
+ subscriber.next({
613
+ type: import_client.EventType.RAW,
614
+ rawEvent: {
615
+ message: `Parsing document ${file.filename}: ${data.payload.process}%`,
616
+ type: "info"
617
+ }
618
+ });
619
+ if (data.payload.is_final) {
620
+ if (data.payload.error_message) {
621
+ subscriber.next({
622
+ type: import_client.EventType.RAW,
623
+ rawEvent: {
624
+ message: `Parsing document ${file.filename} failed: ${data.payload.error_message}`,
625
+ type: "error"
626
+ }
627
+ });
628
+ } else {
629
+ const fileNameNoExt = file.filename.split(".").splice(0, -1).join(".");
630
+ fileInfos.push({
631
+ docId: data.payload.doc_id,
632
+ fileName: fileNameNoExt,
633
+ fileType: extName,
634
+ fileSize: file.data?.length.toString() || "0",
635
+ fileUrl: `https://${cosData.Location}`
636
+ });
637
+ }
603
638
  }
639
+ break;
640
+ }
641
+ default: {
642
+ break;
604
643
  }
605
- break;
606
- }
607
- default: {
608
- break;
609
644
  }
610
645
  }
611
646
  }
612
647
  }
613
- }
614
- });
648
+ });
649
+ }
615
650
  }
616
651
  return {
617
652
  messages: newMessages,