@cloudbase/agent-adapter-yuanqi 1.0.1-alpha.12 → 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/dist/index.js CHANGED
@@ -30,14 +30,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ ChatHistoryEntity: () => ChatHistoryEntity,
33
34
  YuanqiAgent: () => YuanqiAgent,
34
- camelToSnakeKeys: () => camelToSnakeKeys,
35
- convertMessagesToOpenAI: () => convertMessagesToOpenAI
35
+ YuanqiAgentError: () => YuanqiAgentError,
36
+ convertMessagesToOpenAI: () => convertMessagesToOpenAI,
37
+ createChatHistory: () => createChatHistory,
38
+ describeChatHistory: () => describeChatHistory,
39
+ processYuanqiStream: () => processYuanqiStream,
40
+ queryForLLM: () => queryForLLM,
41
+ transDataToChatEntity: () => transDataToChatEntity,
42
+ updateChatHistoryByRecordId: () => updateChatHistoryByRecordId
36
43
  });
37
44
  module.exports = __toCommonJS(index_exports);
38
45
 
39
46
  // src/agent.ts
40
47
  var import_client2 = require("@ag-ui/client");
48
+ var import_node_sdk = __toESM(require("@cloudbase/node-sdk"));
49
+ var import_manager_node = __toESM(require("@cloudbase/manager-node"));
41
50
  var import_openai = __toESM(require("openai"));
42
51
  var import_crypto = require("crypto");
43
52
 
@@ -62,6 +71,14 @@ function camelToSnakeKeys(obj) {
62
71
  }
63
72
  return obj;
64
73
  }
74
+ function genRandomStr(length) {
75
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
76
+ let result = "";
77
+ for (let i = 0; i < length; i++) {
78
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
79
+ }
80
+ return result;
81
+ }
65
82
 
66
83
  // src/stream.ts
67
84
  var import_client = require("@ag-ui/client");
@@ -82,6 +99,7 @@ async function* processYuanqiStream(stream, context) {
82
99
  if (!delta) continue;
83
100
  if (delta.content) {
84
101
  if (reasoningState.hasStarted) {
102
+ reasoningState.hasStarted = false;
85
103
  yield {
86
104
  type: import_client.EventType.THINKING_TEXT_MESSAGE_END,
87
105
  threadId,
@@ -196,15 +214,213 @@ async function* processYuanqiStream(stream, context) {
196
214
 
197
215
  // src/agent.ts
198
216
  var import_rxjs = require("rxjs");
217
+
218
+ // src/constant.ts
219
+ var CHAT_HISTORY_DATA_SOURCE = "ai_bot_chat_history_5hobd2b";
220
+
221
+ // src/chat_history.ts
222
+ function genRecordId() {
223
+ return "record-" + genRandomStr(8);
224
+ }
225
+ async function createChatHistory({
226
+ tcbClient,
227
+ chatHistoryEntity
228
+ }) {
229
+ try {
230
+ const recordId = chatHistoryEntity.recordId || genRecordId();
231
+ const data = {
232
+ record_id: recordId,
233
+ bot_id: chatHistoryEntity.botId,
234
+ role: chatHistoryEntity.role,
235
+ content: chatHistoryEntity.content,
236
+ sender: chatHistoryEntity.sender,
237
+ conversation: chatHistoryEntity.conversation,
238
+ type: chatHistoryEntity.type,
239
+ image: chatHistoryEntity.image,
240
+ trigger_src: chatHistoryEntity.triggerSrc,
241
+ origin_msg: chatHistoryEntity.originMsg,
242
+ reply_to: chatHistoryEntity.replyTo,
243
+ reply: chatHistoryEntity.reply,
244
+ trace_id: chatHistoryEntity.traceId,
245
+ need_async_reply: chatHistoryEntity.needAsyncReply,
246
+ async_reply: chatHistoryEntity.asyncReply,
247
+ createdAt: Date.now(),
248
+ updatedAt: Date.now()
249
+ };
250
+ const db = tcbClient.database();
251
+ const collection = db.collection(CHAT_HISTORY_DATA_SOURCE);
252
+ const result = await collection.add(data);
253
+ return recordId;
254
+ } catch (error) {
255
+ console.error("Failed to create chat history record, error:", error);
256
+ return void 0;
257
+ }
258
+ }
259
+ async function updateChatHistoryByRecordId({
260
+ tcbClient,
261
+ recordId,
262
+ chatHistoryEntity
263
+ }) {
264
+ try {
265
+ const db = tcbClient.database();
266
+ const _ = db.command;
267
+ const collection = db.collection(CHAT_HISTORY_DATA_SOURCE);
268
+ const result = await collection.where({ record_id: _.eq(recordId) }).update({
269
+ content: chatHistoryEntity.content,
270
+ image: chatHistoryEntity.image,
271
+ async_reply: chatHistoryEntity.asyncReply,
272
+ recommend_questions: chatHistoryEntity.recommendQuestions,
273
+ status: chatHistoryEntity.status,
274
+ origin_msg: chatHistoryEntity.originMsg,
275
+ updatedAt: Date.now()
276
+ });
277
+ return chatHistoryEntity.recordId;
278
+ } catch (error) {
279
+ console.error("Failed to update chat history, error:", error);
280
+ return void 0;
281
+ }
282
+ }
283
+ async function describeChatHistory({
284
+ tcbClient,
285
+ botId,
286
+ sort,
287
+ pageSize = 10,
288
+ pageNumber = 1,
289
+ conversation,
290
+ startCreatedAt,
291
+ triggerSrc
292
+ }) {
293
+ if (!sort || sort.length === 0) {
294
+ sort = "desc";
295
+ }
296
+ try {
297
+ const db = tcbClient.database();
298
+ const _ = db.command;
299
+ const collection = db.collection(CHAT_HISTORY_DATA_SOURCE);
300
+ const whereConditions = {
301
+ bot_id: _.eq(botId)
302
+ };
303
+ if (conversation) {
304
+ whereConditions.conversation = _.eq(conversation);
305
+ }
306
+ if (startCreatedAt !== void 0) {
307
+ whereConditions.createdAt = _.gt(startCreatedAt);
308
+ }
309
+ if (triggerSrc) {
310
+ whereConditions.trigger_src = _.eq(triggerSrc);
311
+ }
312
+ const skip = (pageNumber - 1) * pageSize;
313
+ const result = await collection.where(whereConditions).orderBy("createdAt", sort).skip(skip).limit(pageSize).get();
314
+ const countResult = await collection.where(whereConditions).count();
315
+ const total = countResult.total || 0;
316
+ const records = result?.data || [];
317
+ const entityList = records.map(
318
+ (item) => transDataToChatEntity(item)
319
+ );
320
+ return [entityList, total];
321
+ } catch (error) {
322
+ console.error("Failed to query chat history, error:", error);
323
+ return [[], 0];
324
+ }
325
+ }
326
+ function transDataToChatEntity(item) {
327
+ if (!item) {
328
+ return new ChatHistoryEntity();
329
+ }
330
+ const chatEntity = new ChatHistoryEntity();
331
+ chatEntity.botId = item.bot_id;
332
+ chatEntity.recordId = item.record_id;
333
+ chatEntity.role = item.role;
334
+ chatEntity.status = item.status;
335
+ chatEntity.content = item.content;
336
+ chatEntity.sender = item.sender;
337
+ chatEntity.conversation = item.conversation;
338
+ chatEntity.type = item.type;
339
+ chatEntity.triggerSrc = item.trigger_src;
340
+ chatEntity.originMsg = item.origin_msg;
341
+ chatEntity.replyTo = item.reply_to;
342
+ chatEntity.reply = item.reply;
343
+ chatEntity.traceId = item.trace_id;
344
+ chatEntity.needAsyncReply = item.need_async_reply;
345
+ chatEntity.asyncReply = item.async_reply;
346
+ chatEntity.createdAt = item.createdAt;
347
+ chatEntity.updatedAt = item.updatedAt;
348
+ return chatEntity;
349
+ }
350
+ async function queryForLLM({
351
+ tcbClient,
352
+ botId,
353
+ pageSize = 10,
354
+ startCreatedAt,
355
+ triggerSrc
356
+ }) {
357
+ if (startCreatedAt === void 0) {
358
+ startCreatedAt = Date.now() - 24 * 60 * 60 * 1e3;
359
+ }
360
+ const recordEntityList = [];
361
+ const [recordList] = await describeChatHistory({
362
+ tcbClient,
363
+ botId,
364
+ sort: "desc",
365
+ pageSize,
366
+ startCreatedAt,
367
+ triggerSrc
368
+ });
369
+ recordEntityList.push(...recordList.reverse());
370
+ const entityMap = /* @__PURE__ */ new Map();
371
+ recordEntityList.filter((item) => {
372
+ if (item.needAsyncReply === true) {
373
+ return !!item.asyncReply;
374
+ } else {
375
+ return !!item.content;
376
+ }
377
+ }).forEach((item) => {
378
+ entityMap.set(item.recordId, item);
379
+ });
380
+ const result = [];
381
+ recordEntityList.forEach((item) => {
382
+ const { role, content, reply } = item;
383
+ if (role === "user" && content?.length !== 0) {
384
+ if (entityMap.has(reply)) {
385
+ result.push({ role, content });
386
+ result.push({
387
+ role: entityMap.get(reply).role,
388
+ content: entityMap.get(reply).content
389
+ });
390
+ }
391
+ }
392
+ });
393
+ if (result.length % 2 === 1) {
394
+ result.splice(-1, 1);
395
+ }
396
+ return result;
397
+ }
398
+ var ChatHistoryEntity = class {
399
+ };
400
+
401
+ // src/agent.ts
402
+ var YuanqiAgentError = class extends Error {
403
+ constructor(message, code) {
404
+ super(message);
405
+ this.name = "YuanqiAgentError";
406
+ if (code) this.code = code;
407
+ }
408
+ };
199
409
  var YuanqiAgent = class extends import_client2.AbstractAgent {
200
410
  constructor(config) {
201
411
  super(config);
412
+ this.finalCloudCredential = {};
202
413
  this.yuanqiConfig = config.yuanqiConfig;
203
414
  this.model = new import_openai.default({
204
415
  apiKey: "",
205
416
  baseURL: this.yuanqiConfig.request?.baseUrl || "https://yuanqi.tencent.com/openapi/v1/agent"
206
417
  });
207
418
  this.finalAppId = this.yuanqiConfig.appId || this.yuanqiConfig.request?.body?.assistantId || process.env.YUANQI_APP_ID || "";
419
+ this.finalCloudCredential = {
420
+ secretId: this.yuanqiConfig.credential?.secretId || process.env.TENCENTCLOUD_SECRETID,
421
+ secretKey: this.yuanqiConfig.credential?.secretKey || process.env.TENCENTCLOUD_SECRETKEY,
422
+ token: this.yuanqiConfig.credential?.token || process.env.TENCENTCLOUD_SESSIONTOKEN
423
+ };
208
424
  }
209
425
  generateRequestBody({
210
426
  messages,
@@ -228,26 +444,49 @@ var YuanqiAgent = class extends import_client2.AbstractAgent {
228
444
  }
229
445
  async _run(subscriber, input) {
230
446
  try {
231
- const { messages, runId, threadId, forwardedProps } = input;
447
+ const { messages, runId, threadId: _threadId } = input;
448
+ const openai = this.model;
449
+ const threadId = _threadId || (0, import_crypto.randomUUID)();
232
450
  subscriber.next({
233
451
  type: import_client2.EventType.RUN_STARTED,
234
452
  threadId,
235
453
  runId
236
454
  });
237
455
  if (!this.finalAppId) {
238
- throw new Error(
239
- "YUANQI_APP_ID is required, check your env variables or config passed with the adapter"
456
+ throw new YuanqiAgentError(
457
+ "YUANQI_APP_ID is required, check your env variables or config passed with the adapter",
458
+ "MISSING_YUANQI_APP_ID"
240
459
  );
241
460
  }
242
461
  if (!this.yuanqiConfig.appKey && !process.env.YUANQI_APP_KEY) {
243
- throw new Error(
244
- "YUANQI_APP_KEY is required, check your env variables or config passed with the adapter"
462
+ throw new YuanqiAgentError(
463
+ "YUANQI_APP_KEY is required, check your env variables or config passed with the adapter",
464
+ "MISSING_YUANQI_APP_KEY"
245
465
  );
246
466
  }
247
- const openai = this.model;
248
- const openaiMessages = convertMessagesToOpenAI(messages);
467
+ const trimmedCount = messages.length - 1;
468
+ if (trimmedCount > 0) {
469
+ subscriber.next({
470
+ type: import_client2.EventType.RAW,
471
+ rawEvent: {
472
+ message: `Yuanqi handles message history itself, so that a total of ${trimmedCount} messages before the last user message will be trimmed.`,
473
+ type: "warn"
474
+ }
475
+ });
476
+ }
477
+ const latestUserMessage = messages.filter((m) => m.role === "user").pop();
478
+ if (!latestUserMessage) {
479
+ throw new YuanqiAgentError(
480
+ "No user message found, please send a message first.",
481
+ "MESSAGE_FORMAT_ERROR"
482
+ );
483
+ }
484
+ const allMessages = await this.getChatHistory(
485
+ subscriber,
486
+ latestUserMessage
487
+ );
249
488
  const body = this.generateRequestBody({
250
- messages: openaiMessages,
489
+ messages: allMessages,
251
490
  input
252
491
  });
253
492
  const stream = await openai.chat.completions.create(
@@ -264,26 +503,185 @@ var YuanqiAgent = class extends import_client2.AbstractAgent {
264
503
  }
265
504
  }
266
505
  );
267
- const messageId = `msg_${Date.now()}`;
268
- const context = { threadId, runId, messageId };
506
+ const userRecordId = `record-${(0, import_crypto.randomUUID)().slice(0, 8)}`;
507
+ const assistantRecordId = `record-${(0, import_crypto.randomUUID)().slice(0, 8)}`;
508
+ const context = { threadId, runId, messageId: userRecordId };
509
+ let fullAssistantContent = "";
269
510
  for await (const event of processYuanqiStream(stream, context)) {
270
511
  subscriber.next(event);
512
+ if (event.type === import_client2.EventType.TEXT_MESSAGE_CONTENT && event.delta) {
513
+ fullAssistantContent += event.delta;
514
+ }
271
515
  }
272
- } catch (error) {
273
- if (error instanceof Error) {
274
- console.error(`Error ${error.name}: ${error.message}`);
275
- } else {
276
- console.error(JSON.stringify(error));
516
+ const userContent = typeof latestUserMessage?.content === "string" ? latestUserMessage.content : latestUserMessage?.content?.filter((c) => c.type === "text").map((c) => c.text).join("") || "";
517
+ await this.saveChatHistory(
518
+ subscriber,
519
+ input,
520
+ userRecordId,
521
+ assistantRecordId,
522
+ userContent,
523
+ fullAssistantContent
524
+ );
525
+ } catch (e) {
526
+ console.error(JSON.stringify(e));
527
+ let code = "UNKNOWN_ERROR";
528
+ let message = JSON.stringify(e);
529
+ if (e instanceof YuanqiAgentError) {
530
+ code = e.code || "AGENT_ERROR";
531
+ message = e.message;
532
+ } else if (e instanceof Error) {
533
+ code = e.name || "ERROR";
534
+ message = e.message;
277
535
  }
278
536
  subscriber.next({
279
537
  type: import_client2.EventType.RUN_ERROR,
280
- message: error instanceof Error ? error.message : String(error),
281
- code: error instanceof Error ? error.name : "UNKNOWN_ERROR"
538
+ code,
539
+ message: `Sorry, an error occurred while running the agent: Error code ${code}, ${message}`
282
540
  });
541
+ } finally {
283
542
  subscriber.complete();
284
543
  }
285
544
  }
545
+ // Can be override by subclasses
546
+ async getChatHistory(subscriber, latestUserMessage) {
547
+ const envId = this.yuanqiConfig.envId || getCloudbaseEnvId();
548
+ const botId = `bot-yuanqi-${this.finalAppId}`;
549
+ const isDBReady = await this.checkIsDatabaseReady(envId);
550
+ if (!isDBReady) {
551
+ subscriber.next({
552
+ type: import_client2.EventType.RAW,
553
+ rawEvent: {
554
+ message: `Chat history database is not ready, skip history loading.`,
555
+ type: "warn"
556
+ }
557
+ });
558
+ return [];
559
+ }
560
+ const tcbClient = import_node_sdk.default.init({
561
+ env: envId,
562
+ secretId: this.finalCloudCredential.secretId,
563
+ secretKey: this.finalCloudCredential.secretKey,
564
+ sessionToken: this.finalCloudCredential.token
565
+ });
566
+ let historyMessages = [];
567
+ const historyCount = this.yuanqiConfig.historyCount ?? 10;
568
+ const historyRecords = await queryForLLM({
569
+ tcbClient,
570
+ botId,
571
+ pageSize: historyCount
572
+ });
573
+ historyMessages = historyRecords.map((record) => ({
574
+ role: record.role,
575
+ content: [{ type: "text", text: record.content }]
576
+ }));
577
+ const allMessages = historyMessages.concat(
578
+ convertMessagesToOpenAI([latestUserMessage])
579
+ );
580
+ return allMessages;
581
+ }
582
+ // Can be override by subclasses
583
+ async saveChatHistory(subscriber, input, userRecordId, assistantRecordId, userContent, assistantContent) {
584
+ const envId = this.yuanqiConfig.envId || getCloudbaseEnvId();
585
+ const botId = `bot-yuanqi-${this.finalAppId}`;
586
+ const { threadId, runId } = input;
587
+ const isDBReady = await this.checkIsDatabaseReady(envId);
588
+ if (!isDBReady) {
589
+ subscriber.next({
590
+ type: import_client2.EventType.RAW,
591
+ rawEvent: {
592
+ message: `Chat history database is not ready, skip history saving.`,
593
+ type: "warn"
594
+ }
595
+ });
596
+ return;
597
+ }
598
+ const tcbClient = import_node_sdk.default.init({
599
+ env: envId,
600
+ secretId: this.finalCloudCredential.secretId,
601
+ secretKey: this.finalCloudCredential.secretKey,
602
+ sessionToken: this.finalCloudCredential.token
603
+ });
604
+ const userEntity = new ChatHistoryEntity();
605
+ userEntity.recordId = userRecordId;
606
+ userEntity.botId = botId;
607
+ userEntity.role = "user";
608
+ userEntity.content = userContent;
609
+ userEntity.conversation = threadId;
610
+ userEntity.reply = assistantRecordId;
611
+ userEntity.triggerSrc = "";
612
+ userEntity.traceId = (0, import_crypto.randomUUID)();
613
+ await createChatHistory({ tcbClient, chatHistoryEntity: userEntity });
614
+ const assistantEntity = new ChatHistoryEntity();
615
+ assistantEntity.recordId = assistantRecordId;
616
+ assistantEntity.botId = botId;
617
+ assistantEntity.role = "assistant";
618
+ assistantEntity.content = assistantContent;
619
+ assistantEntity.conversation = threadId;
620
+ assistantEntity.replyTo = userRecordId;
621
+ assistantEntity.triggerSrc = "";
622
+ assistantEntity.traceId = runId;
623
+ assistantEntity.status = "done";
624
+ await createChatHistory({
625
+ tcbClient,
626
+ chatHistoryEntity: assistantEntity
627
+ });
628
+ }
629
+ async checkIsDatabaseReady(envId) {
630
+ if (!envId) {
631
+ throw new YuanqiAgentError(
632
+ "CLOUDBASE_ENV_ID is required, check your env variables or config passed with the adapter",
633
+ "MISSING_CLOUDBASE_ENV_ID"
634
+ );
635
+ }
636
+ if (!this.finalCloudCredential.token) {
637
+ if (!this.finalCloudCredential.secretId) {
638
+ throw new YuanqiAgentError(
639
+ "TENCENTCLOUD_SECRETID is required, check your env variables or config passed with the adapter",
640
+ "MISSING_SECRET_ID"
641
+ );
642
+ }
643
+ if (!this.finalCloudCredential.secretKey) {
644
+ throw new YuanqiAgentError(
645
+ "TENCENTCLOUD_SECRETKEY is required, check your env variables or config passed with the adapter",
646
+ "MISSING_SECRET_KEY"
647
+ );
648
+ }
649
+ }
650
+ try {
651
+ const managedTcbClient = import_manager_node.default.init({
652
+ envId,
653
+ secretId: this.finalCloudCredential.secretId,
654
+ secretKey: this.finalCloudCredential.secretKey
655
+ });
656
+ const checkDBRes = await managedTcbClient.database.checkCollectionExists(
657
+ CHAT_HISTORY_DATA_SOURCE
658
+ );
659
+ if (checkDBRes && checkDBRes.Exists) {
660
+ return true;
661
+ } else if (checkDBRes && !checkDBRes.Exists) {
662
+ await managedTcbClient.database.createCollection(
663
+ CHAT_HISTORY_DATA_SOURCE
664
+ );
665
+ return true;
666
+ }
667
+ } catch (dbError) {
668
+ console.error(
669
+ "Failed to check/create chat history collection:",
670
+ JSON.stringify(dbError)
671
+ );
672
+ return false;
673
+ }
674
+ }
286
675
  };
676
+ function getCloudbaseEnvId() {
677
+ if (!!process.env.CBR_ENV_ID) {
678
+ return process.env.CBR_ENV_ID;
679
+ } else if (!!process.env.SCF_NAMESPACE) {
680
+ return process.env.SCF_NAMESPACE;
681
+ } else {
682
+ return process.env.CLOUDBASE_ENV_ID || "";
683
+ }
684
+ }
287
685
  function convertMessagesToOpenAI(messages, systemPrompt) {
288
686
  const openaiMessages = [];
289
687
  if (systemPrompt) {
@@ -296,12 +694,21 @@ function convertMessagesToOpenAI(messages, systemPrompt) {
296
694
  if (msg.role === "user") {
297
695
  openaiMessages.push({
298
696
  role: "user",
299
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
697
+ content: typeof msg.content === "string" ? [{ type: "text", text: msg.content }] : msg.content.map((item) => {
698
+ if (item.type === "text") {
699
+ return { type: "text", text: item.text };
700
+ } else {
701
+ return {
702
+ type: "image_url",
703
+ image_url: { url: item.url || "" }
704
+ };
705
+ }
706
+ })
300
707
  });
301
708
  } else if (msg.role === "assistant") {
302
709
  openaiMessages.push({
303
710
  role: "assistant",
304
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content) || "",
711
+ content: msg.content ? [{ type: "text", text: msg.content }] : [],
305
712
  tool_calls: msg.toolCalls?.map((tc) => ({
306
713
  id: tc.id,
307
714
  type: "function",
@@ -315,7 +722,7 @@ function convertMessagesToOpenAI(messages, systemPrompt) {
315
722
  openaiMessages.push({
316
723
  role: "tool",
317
724
  tool_call_id: msg.toolCallId,
318
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
725
+ content: msg.content ? [{ type: "text", text: msg.content }] : []
319
726
  });
320
727
  }
321
728
  }
@@ -323,8 +730,15 @@ function convertMessagesToOpenAI(messages, systemPrompt) {
323
730
  }
324
731
  // Annotate the CommonJS export names for ESM import in node:
325
732
  0 && (module.exports = {
733
+ ChatHistoryEntity,
326
734
  YuanqiAgent,
327
- camelToSnakeKeys,
328
- convertMessagesToOpenAI
735
+ YuanqiAgentError,
736
+ convertMessagesToOpenAI,
737
+ createChatHistory,
738
+ describeChatHistory,
739
+ processYuanqiStream,
740
+ queryForLLM,
741
+ transDataToChatEntity,
742
+ updateChatHistoryByRecordId
329
743
  });
330
744
  //# sourceMappingURL=index.js.map