@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.mjs CHANGED
@@ -3,6 +3,8 @@ import {
3
3
  AbstractAgent,
4
4
  EventType as EventType2
5
5
  } from "@ag-ui/client";
6
+ import tcb from "@cloudbase/node-sdk";
7
+ import managedTcb from "@cloudbase/manager-node";
6
8
  import OpenAI from "openai";
7
9
  import { randomUUID } from "crypto";
8
10
 
@@ -27,6 +29,14 @@ function camelToSnakeKeys(obj) {
27
29
  }
28
30
  return obj;
29
31
  }
32
+ function genRandomStr(length) {
33
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
34
+ let result = "";
35
+ for (let i = 0; i < length; i++) {
36
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
37
+ }
38
+ return result;
39
+ }
30
40
 
31
41
  // src/stream.ts
32
42
  import { EventType } from "@ag-ui/client";
@@ -47,6 +57,7 @@ async function* processYuanqiStream(stream, context) {
47
57
  if (!delta) continue;
48
58
  if (delta.content) {
49
59
  if (reasoningState.hasStarted) {
60
+ reasoningState.hasStarted = false;
50
61
  yield {
51
62
  type: EventType.THINKING_TEXT_MESSAGE_END,
52
63
  threadId,
@@ -161,15 +172,213 @@ async function* processYuanqiStream(stream, context) {
161
172
 
162
173
  // src/agent.ts
163
174
  import { Observable } from "rxjs";
175
+
176
+ // src/constant.ts
177
+ var CHAT_HISTORY_DATA_SOURCE = "ai_bot_chat_history_5hobd2b";
178
+
179
+ // src/chat_history.ts
180
+ function genRecordId() {
181
+ return "record-" + genRandomStr(8);
182
+ }
183
+ async function createChatHistory({
184
+ tcbClient,
185
+ chatHistoryEntity
186
+ }) {
187
+ try {
188
+ const recordId = chatHistoryEntity.recordId || genRecordId();
189
+ const data = {
190
+ record_id: recordId,
191
+ bot_id: chatHistoryEntity.botId,
192
+ role: chatHistoryEntity.role,
193
+ content: chatHistoryEntity.content,
194
+ sender: chatHistoryEntity.sender,
195
+ conversation: chatHistoryEntity.conversation,
196
+ type: chatHistoryEntity.type,
197
+ image: chatHistoryEntity.image,
198
+ trigger_src: chatHistoryEntity.triggerSrc,
199
+ origin_msg: chatHistoryEntity.originMsg,
200
+ reply_to: chatHistoryEntity.replyTo,
201
+ reply: chatHistoryEntity.reply,
202
+ trace_id: chatHistoryEntity.traceId,
203
+ need_async_reply: chatHistoryEntity.needAsyncReply,
204
+ async_reply: chatHistoryEntity.asyncReply,
205
+ createdAt: Date.now(),
206
+ updatedAt: Date.now()
207
+ };
208
+ const db = tcbClient.database();
209
+ const collection = db.collection(CHAT_HISTORY_DATA_SOURCE);
210
+ const result = await collection.add(data);
211
+ return recordId;
212
+ } catch (error) {
213
+ console.error("Failed to create chat history record, error:", error);
214
+ return void 0;
215
+ }
216
+ }
217
+ async function updateChatHistoryByRecordId({
218
+ tcbClient,
219
+ recordId,
220
+ chatHistoryEntity
221
+ }) {
222
+ try {
223
+ const db = tcbClient.database();
224
+ const _ = db.command;
225
+ const collection = db.collection(CHAT_HISTORY_DATA_SOURCE);
226
+ const result = await collection.where({ record_id: _.eq(recordId) }).update({
227
+ content: chatHistoryEntity.content,
228
+ image: chatHistoryEntity.image,
229
+ async_reply: chatHistoryEntity.asyncReply,
230
+ recommend_questions: chatHistoryEntity.recommendQuestions,
231
+ status: chatHistoryEntity.status,
232
+ origin_msg: chatHistoryEntity.originMsg,
233
+ updatedAt: Date.now()
234
+ });
235
+ return chatHistoryEntity.recordId;
236
+ } catch (error) {
237
+ console.error("Failed to update chat history, error:", error);
238
+ return void 0;
239
+ }
240
+ }
241
+ async function describeChatHistory({
242
+ tcbClient,
243
+ botId,
244
+ sort,
245
+ pageSize = 10,
246
+ pageNumber = 1,
247
+ conversation,
248
+ startCreatedAt,
249
+ triggerSrc
250
+ }) {
251
+ if (!sort || sort.length === 0) {
252
+ sort = "desc";
253
+ }
254
+ try {
255
+ const db = tcbClient.database();
256
+ const _ = db.command;
257
+ const collection = db.collection(CHAT_HISTORY_DATA_SOURCE);
258
+ const whereConditions = {
259
+ bot_id: _.eq(botId)
260
+ };
261
+ if (conversation) {
262
+ whereConditions.conversation = _.eq(conversation);
263
+ }
264
+ if (startCreatedAt !== void 0) {
265
+ whereConditions.createdAt = _.gt(startCreatedAt);
266
+ }
267
+ if (triggerSrc) {
268
+ whereConditions.trigger_src = _.eq(triggerSrc);
269
+ }
270
+ const skip = (pageNumber - 1) * pageSize;
271
+ const result = await collection.where(whereConditions).orderBy("createdAt", sort).skip(skip).limit(pageSize).get();
272
+ const countResult = await collection.where(whereConditions).count();
273
+ const total = countResult.total || 0;
274
+ const records = result?.data || [];
275
+ const entityList = records.map(
276
+ (item) => transDataToChatEntity(item)
277
+ );
278
+ return [entityList, total];
279
+ } catch (error) {
280
+ console.error("Failed to query chat history, error:", error);
281
+ return [[], 0];
282
+ }
283
+ }
284
+ function transDataToChatEntity(item) {
285
+ if (!item) {
286
+ return new ChatHistoryEntity();
287
+ }
288
+ const chatEntity = new ChatHistoryEntity();
289
+ chatEntity.botId = item.bot_id;
290
+ chatEntity.recordId = item.record_id;
291
+ chatEntity.role = item.role;
292
+ chatEntity.status = item.status;
293
+ chatEntity.content = item.content;
294
+ chatEntity.sender = item.sender;
295
+ chatEntity.conversation = item.conversation;
296
+ chatEntity.type = item.type;
297
+ chatEntity.triggerSrc = item.trigger_src;
298
+ chatEntity.originMsg = item.origin_msg;
299
+ chatEntity.replyTo = item.reply_to;
300
+ chatEntity.reply = item.reply;
301
+ chatEntity.traceId = item.trace_id;
302
+ chatEntity.needAsyncReply = item.need_async_reply;
303
+ chatEntity.asyncReply = item.async_reply;
304
+ chatEntity.createdAt = item.createdAt;
305
+ chatEntity.updatedAt = item.updatedAt;
306
+ return chatEntity;
307
+ }
308
+ async function queryForLLM({
309
+ tcbClient,
310
+ botId,
311
+ pageSize = 10,
312
+ startCreatedAt,
313
+ triggerSrc
314
+ }) {
315
+ if (startCreatedAt === void 0) {
316
+ startCreatedAt = Date.now() - 24 * 60 * 60 * 1e3;
317
+ }
318
+ const recordEntityList = [];
319
+ const [recordList] = await describeChatHistory({
320
+ tcbClient,
321
+ botId,
322
+ sort: "desc",
323
+ pageSize,
324
+ startCreatedAt,
325
+ triggerSrc
326
+ });
327
+ recordEntityList.push(...recordList.reverse());
328
+ const entityMap = /* @__PURE__ */ new Map();
329
+ recordEntityList.filter((item) => {
330
+ if (item.needAsyncReply === true) {
331
+ return !!item.asyncReply;
332
+ } else {
333
+ return !!item.content;
334
+ }
335
+ }).forEach((item) => {
336
+ entityMap.set(item.recordId, item);
337
+ });
338
+ const result = [];
339
+ recordEntityList.forEach((item) => {
340
+ const { role, content, reply } = item;
341
+ if (role === "user" && content?.length !== 0) {
342
+ if (entityMap.has(reply)) {
343
+ result.push({ role, content });
344
+ result.push({
345
+ role: entityMap.get(reply).role,
346
+ content: entityMap.get(reply).content
347
+ });
348
+ }
349
+ }
350
+ });
351
+ if (result.length % 2 === 1) {
352
+ result.splice(-1, 1);
353
+ }
354
+ return result;
355
+ }
356
+ var ChatHistoryEntity = class {
357
+ };
358
+
359
+ // src/agent.ts
360
+ var YuanqiAgentError = class extends Error {
361
+ constructor(message, code) {
362
+ super(message);
363
+ this.name = "YuanqiAgentError";
364
+ if (code) this.code = code;
365
+ }
366
+ };
164
367
  var YuanqiAgent = class extends AbstractAgent {
165
368
  constructor(config) {
166
369
  super(config);
370
+ this.finalCloudCredential = {};
167
371
  this.yuanqiConfig = config.yuanqiConfig;
168
372
  this.model = new OpenAI({
169
373
  apiKey: "",
170
374
  baseURL: this.yuanqiConfig.request?.baseUrl || "https://yuanqi.tencent.com/openapi/v1/agent"
171
375
  });
172
376
  this.finalAppId = this.yuanqiConfig.appId || this.yuanqiConfig.request?.body?.assistantId || process.env.YUANQI_APP_ID || "";
377
+ this.finalCloudCredential = {
378
+ secretId: this.yuanqiConfig.credential?.secretId || process.env.TENCENTCLOUD_SECRETID,
379
+ secretKey: this.yuanqiConfig.credential?.secretKey || process.env.TENCENTCLOUD_SECRETKEY,
380
+ token: this.yuanqiConfig.credential?.token || process.env.TENCENTCLOUD_SESSIONTOKEN
381
+ };
173
382
  }
174
383
  generateRequestBody({
175
384
  messages,
@@ -193,26 +402,49 @@ var YuanqiAgent = class extends AbstractAgent {
193
402
  }
194
403
  async _run(subscriber, input) {
195
404
  try {
196
- const { messages, runId, threadId, forwardedProps } = input;
405
+ const { messages, runId, threadId: _threadId } = input;
406
+ const openai = this.model;
407
+ const threadId = _threadId || randomUUID();
197
408
  subscriber.next({
198
409
  type: EventType2.RUN_STARTED,
199
410
  threadId,
200
411
  runId
201
412
  });
202
413
  if (!this.finalAppId) {
203
- throw new Error(
204
- "YUANQI_APP_ID is required, check your env variables or config passed with the adapter"
414
+ throw new YuanqiAgentError(
415
+ "YUANQI_APP_ID is required, check your env variables or config passed with the adapter",
416
+ "MISSING_YUANQI_APP_ID"
205
417
  );
206
418
  }
207
419
  if (!this.yuanqiConfig.appKey && !process.env.YUANQI_APP_KEY) {
208
- throw new Error(
209
- "YUANQI_APP_KEY is required, check your env variables or config passed with the adapter"
420
+ throw new YuanqiAgentError(
421
+ "YUANQI_APP_KEY is required, check your env variables or config passed with the adapter",
422
+ "MISSING_YUANQI_APP_KEY"
210
423
  );
211
424
  }
212
- const openai = this.model;
213
- const openaiMessages = convertMessagesToOpenAI(messages);
425
+ const trimmedCount = messages.length - 1;
426
+ if (trimmedCount > 0) {
427
+ subscriber.next({
428
+ type: EventType2.RAW,
429
+ rawEvent: {
430
+ message: `Yuanqi handles message history itself, so that a total of ${trimmedCount} messages before the last user message will be trimmed.`,
431
+ type: "warn"
432
+ }
433
+ });
434
+ }
435
+ const latestUserMessage = messages.filter((m) => m.role === "user").pop();
436
+ if (!latestUserMessage) {
437
+ throw new YuanqiAgentError(
438
+ "No user message found, please send a message first.",
439
+ "MESSAGE_FORMAT_ERROR"
440
+ );
441
+ }
442
+ const allMessages = await this.getChatHistory(
443
+ subscriber,
444
+ latestUserMessage
445
+ );
214
446
  const body = this.generateRequestBody({
215
- messages: openaiMessages,
447
+ messages: allMessages,
216
448
  input
217
449
  });
218
450
  const stream = await openai.chat.completions.create(
@@ -229,26 +461,185 @@ var YuanqiAgent = class extends AbstractAgent {
229
461
  }
230
462
  }
231
463
  );
232
- const messageId = `msg_${Date.now()}`;
233
- const context = { threadId, runId, messageId };
464
+ const userRecordId = `record-${randomUUID().slice(0, 8)}`;
465
+ const assistantRecordId = `record-${randomUUID().slice(0, 8)}`;
466
+ const context = { threadId, runId, messageId: userRecordId };
467
+ let fullAssistantContent = "";
234
468
  for await (const event of processYuanqiStream(stream, context)) {
235
469
  subscriber.next(event);
470
+ if (event.type === EventType2.TEXT_MESSAGE_CONTENT && event.delta) {
471
+ fullAssistantContent += event.delta;
472
+ }
236
473
  }
237
- } catch (error) {
238
- if (error instanceof Error) {
239
- console.error(`Error ${error.name}: ${error.message}`);
240
- } else {
241
- console.error(JSON.stringify(error));
474
+ const userContent = typeof latestUserMessage?.content === "string" ? latestUserMessage.content : latestUserMessage?.content?.filter((c) => c.type === "text").map((c) => c.text).join("") || "";
475
+ await this.saveChatHistory(
476
+ subscriber,
477
+ input,
478
+ userRecordId,
479
+ assistantRecordId,
480
+ userContent,
481
+ fullAssistantContent
482
+ );
483
+ } catch (e) {
484
+ console.error(JSON.stringify(e));
485
+ let code = "UNKNOWN_ERROR";
486
+ let message = JSON.stringify(e);
487
+ if (e instanceof YuanqiAgentError) {
488
+ code = e.code || "AGENT_ERROR";
489
+ message = e.message;
490
+ } else if (e instanceof Error) {
491
+ code = e.name || "ERROR";
492
+ message = e.message;
242
493
  }
243
494
  subscriber.next({
244
495
  type: EventType2.RUN_ERROR,
245
- message: error instanceof Error ? error.message : String(error),
246
- code: error instanceof Error ? error.name : "UNKNOWN_ERROR"
496
+ code,
497
+ message: `Sorry, an error occurred while running the agent: Error code ${code}, ${message}`
247
498
  });
499
+ } finally {
248
500
  subscriber.complete();
249
501
  }
250
502
  }
503
+ // Can be override by subclasses
504
+ async getChatHistory(subscriber, latestUserMessage) {
505
+ const envId = this.yuanqiConfig.envId || getCloudbaseEnvId();
506
+ const botId = `bot-yuanqi-${this.finalAppId}`;
507
+ const isDBReady = await this.checkIsDatabaseReady(envId);
508
+ if (!isDBReady) {
509
+ subscriber.next({
510
+ type: EventType2.RAW,
511
+ rawEvent: {
512
+ message: `Chat history database is not ready, skip history loading.`,
513
+ type: "warn"
514
+ }
515
+ });
516
+ return [];
517
+ }
518
+ const tcbClient = tcb.init({
519
+ env: envId,
520
+ secretId: this.finalCloudCredential.secretId,
521
+ secretKey: this.finalCloudCredential.secretKey,
522
+ sessionToken: this.finalCloudCredential.token
523
+ });
524
+ let historyMessages = [];
525
+ const historyCount = this.yuanqiConfig.historyCount ?? 10;
526
+ const historyRecords = await queryForLLM({
527
+ tcbClient,
528
+ botId,
529
+ pageSize: historyCount
530
+ });
531
+ historyMessages = historyRecords.map((record) => ({
532
+ role: record.role,
533
+ content: [{ type: "text", text: record.content }]
534
+ }));
535
+ const allMessages = historyMessages.concat(
536
+ convertMessagesToOpenAI([latestUserMessage])
537
+ );
538
+ return allMessages;
539
+ }
540
+ // Can be override by subclasses
541
+ async saveChatHistory(subscriber, input, userRecordId, assistantRecordId, userContent, assistantContent) {
542
+ const envId = this.yuanqiConfig.envId || getCloudbaseEnvId();
543
+ const botId = `bot-yuanqi-${this.finalAppId}`;
544
+ const { threadId, runId } = input;
545
+ const isDBReady = await this.checkIsDatabaseReady(envId);
546
+ if (!isDBReady) {
547
+ subscriber.next({
548
+ type: EventType2.RAW,
549
+ rawEvent: {
550
+ message: `Chat history database is not ready, skip history saving.`,
551
+ type: "warn"
552
+ }
553
+ });
554
+ return;
555
+ }
556
+ const tcbClient = tcb.init({
557
+ env: envId,
558
+ secretId: this.finalCloudCredential.secretId,
559
+ secretKey: this.finalCloudCredential.secretKey,
560
+ sessionToken: this.finalCloudCredential.token
561
+ });
562
+ const userEntity = new ChatHistoryEntity();
563
+ userEntity.recordId = userRecordId;
564
+ userEntity.botId = botId;
565
+ userEntity.role = "user";
566
+ userEntity.content = userContent;
567
+ userEntity.conversation = threadId;
568
+ userEntity.reply = assistantRecordId;
569
+ userEntity.triggerSrc = "";
570
+ userEntity.traceId = randomUUID();
571
+ await createChatHistory({ tcbClient, chatHistoryEntity: userEntity });
572
+ const assistantEntity = new ChatHistoryEntity();
573
+ assistantEntity.recordId = assistantRecordId;
574
+ assistantEntity.botId = botId;
575
+ assistantEntity.role = "assistant";
576
+ assistantEntity.content = assistantContent;
577
+ assistantEntity.conversation = threadId;
578
+ assistantEntity.replyTo = userRecordId;
579
+ assistantEntity.triggerSrc = "";
580
+ assistantEntity.traceId = runId;
581
+ assistantEntity.status = "done";
582
+ await createChatHistory({
583
+ tcbClient,
584
+ chatHistoryEntity: assistantEntity
585
+ });
586
+ }
587
+ async checkIsDatabaseReady(envId) {
588
+ if (!envId) {
589
+ throw new YuanqiAgentError(
590
+ "CLOUDBASE_ENV_ID is required, check your env variables or config passed with the adapter",
591
+ "MISSING_CLOUDBASE_ENV_ID"
592
+ );
593
+ }
594
+ if (!this.finalCloudCredential.token) {
595
+ if (!this.finalCloudCredential.secretId) {
596
+ throw new YuanqiAgentError(
597
+ "TENCENTCLOUD_SECRETID is required, check your env variables or config passed with the adapter",
598
+ "MISSING_SECRET_ID"
599
+ );
600
+ }
601
+ if (!this.finalCloudCredential.secretKey) {
602
+ throw new YuanqiAgentError(
603
+ "TENCENTCLOUD_SECRETKEY is required, check your env variables or config passed with the adapter",
604
+ "MISSING_SECRET_KEY"
605
+ );
606
+ }
607
+ }
608
+ try {
609
+ const managedTcbClient = managedTcb.init({
610
+ envId,
611
+ secretId: this.finalCloudCredential.secretId,
612
+ secretKey: this.finalCloudCredential.secretKey
613
+ });
614
+ const checkDBRes = await managedTcbClient.database.checkCollectionExists(
615
+ CHAT_HISTORY_DATA_SOURCE
616
+ );
617
+ if (checkDBRes && checkDBRes.Exists) {
618
+ return true;
619
+ } else if (checkDBRes && !checkDBRes.Exists) {
620
+ await managedTcbClient.database.createCollection(
621
+ CHAT_HISTORY_DATA_SOURCE
622
+ );
623
+ return true;
624
+ }
625
+ } catch (dbError) {
626
+ console.error(
627
+ "Failed to check/create chat history collection:",
628
+ JSON.stringify(dbError)
629
+ );
630
+ return false;
631
+ }
632
+ }
251
633
  };
634
+ function getCloudbaseEnvId() {
635
+ if (!!process.env.CBR_ENV_ID) {
636
+ return process.env.CBR_ENV_ID;
637
+ } else if (!!process.env.SCF_NAMESPACE) {
638
+ return process.env.SCF_NAMESPACE;
639
+ } else {
640
+ return process.env.CLOUDBASE_ENV_ID || "";
641
+ }
642
+ }
252
643
  function convertMessagesToOpenAI(messages, systemPrompt) {
253
644
  const openaiMessages = [];
254
645
  if (systemPrompt) {
@@ -261,12 +652,21 @@ function convertMessagesToOpenAI(messages, systemPrompt) {
261
652
  if (msg.role === "user") {
262
653
  openaiMessages.push({
263
654
  role: "user",
264
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
655
+ content: typeof msg.content === "string" ? [{ type: "text", text: msg.content }] : msg.content.map((item) => {
656
+ if (item.type === "text") {
657
+ return { type: "text", text: item.text };
658
+ } else {
659
+ return {
660
+ type: "image_url",
661
+ image_url: { url: item.url || "" }
662
+ };
663
+ }
664
+ })
265
665
  });
266
666
  } else if (msg.role === "assistant") {
267
667
  openaiMessages.push({
268
668
  role: "assistant",
269
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content) || "",
669
+ content: msg.content ? [{ type: "text", text: msg.content }] : [],
270
670
  tool_calls: msg.toolCalls?.map((tc) => ({
271
671
  id: tc.id,
272
672
  type: "function",
@@ -280,15 +680,22 @@ function convertMessagesToOpenAI(messages, systemPrompt) {
280
680
  openaiMessages.push({
281
681
  role: "tool",
282
682
  tool_call_id: msg.toolCallId,
283
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
683
+ content: msg.content ? [{ type: "text", text: msg.content }] : []
284
684
  });
285
685
  }
286
686
  }
287
687
  return openaiMessages;
288
688
  }
289
689
  export {
690
+ ChatHistoryEntity,
290
691
  YuanqiAgent,
291
- camelToSnakeKeys,
292
- convertMessagesToOpenAI
692
+ YuanqiAgentError,
693
+ convertMessagesToOpenAI,
694
+ createChatHistory,
695
+ describeChatHistory,
696
+ processYuanqiStream,
697
+ queryForLLM,
698
+ transDataToChatEntity,
699
+ updateChatHistoryByRecordId
293
700
  };
294
701
  //# sourceMappingURL=index.mjs.map