@baishuyun/chat-backend 0.0.17 → 0.0.19

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/config/default.ts +7 -6
  3. package/dist/config/default.js +2 -1
  4. package/dist/src/app/main.js +2 -0
  5. package/dist/src/config/cache.config.js +6 -0
  6. package/dist/src/controllers/agent/bots.controller.js +29 -0
  7. package/dist/src/controllers/common/connect.controll.js +7 -2
  8. package/dist/src/controllers/common/model.js +2 -2
  9. package/dist/src/controllers/form/build/build.controller.js +16 -19
  10. package/dist/src/controllers/form/build/model.js +7 -4
  11. package/dist/src/controllers/form/build/utils.js +8 -10
  12. package/dist/src/controllers/report/query/query.controller.js +0 -3
  13. package/dist/src/controllers/report/query/suggest.controller.js +0 -4
  14. package/dist/src/middleware/tokenExchange.js +23 -0
  15. package/dist/src/routes/agent/agent.route.js +8 -0
  16. package/dist/src/routes/common/common.route.js +2 -1
  17. package/dist/src/services/fetchCozeInfo.js +26 -0
  18. package/dist/src/types/coze.js +1 -0
  19. package/dist/src/utils/safeJsonParser.js +8 -0
  20. package/package.json +6 -5
  21. package/src/app/main.ts +2 -0
  22. package/src/config/cache.config.ts +9 -0
  23. package/src/controllers/agent/bots.controller.ts +39 -0
  24. package/src/controllers/common/connect.controll.ts +10 -2
  25. package/src/controllers/common/model.ts +2 -2
  26. package/src/controllers/form/attachment-upload.controller.ts +16 -15
  27. package/src/controllers/form/build/build.controller.ts +17 -22
  28. package/src/controllers/form/build/model.ts +8 -4
  29. package/src/controllers/form/build/utils.ts +10 -14
  30. package/src/controllers/form/conversation/clear.controller.ts +12 -15
  31. package/src/controllers/form/fill/batch-fill.controller.ts +1 -1
  32. package/src/controllers/form/fill/fill.controller.ts +1 -1
  33. package/src/controllers/report/query/query.controller.ts +1 -4
  34. package/src/controllers/report/query/suggest.controller.ts +0 -4
  35. package/src/middleware/tokenExchange.ts +26 -0
  36. package/src/routes/agent/agent.route.ts +11 -0
  37. package/src/routes/common/common.route.ts +2 -1
  38. package/src/services/fetchCozeInfo.ts +33 -0
  39. package/src/types/coze.ts +13 -0
  40. package/src/utils/safeJsonParser.ts +7 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @baishuyun/chat-backend
2
2
 
3
+ ## 0.0.19
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @baishuyun/coze-provider@0.1.1
9
+
10
+ ## 0.0.18
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [e5dd21a]
15
+ - Updated dependencies [a934bc0]
16
+ - Updated dependencies
17
+ - Updated dependencies [4acb2cc]
18
+ - Updated dependencies [35058a9]
19
+ - @baishuyun/coze-provider@0.1.0
20
+ - @baishuyun/agents@0.1.0
21
+ - @baishuyun/types@1.1.0
22
+
3
23
  ## 0.0.17
4
24
 
5
25
  ### Patch Changes
package/config/default.ts CHANGED
@@ -17,25 +17,26 @@ export default {
17
17
  agent: {
18
18
  host: process.env.AGENT_HOST || '47.99.202.157',
19
19
 
20
- apiAuthKey: process.env.COZE_API_KEY,
20
+ apiAuthKey: process.env.BOT_API_KEY,
21
21
 
22
- deepseekApiKey: process.env.DS_API_KEY,
22
+ userId: process.env.BUILTIN_COZE_USER_ID,
23
23
 
24
24
  common: {
25
- baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
25
+ baseUrl: `https://${process.env.AGENT_HOST}/v3/`,
26
26
  apiKey: process.env.BOT_API_KEY, // load from env
27
27
  },
28
28
 
29
29
  form: {
30
30
  build: {
31
31
  botId: process.env.BUILD_BOT_ID || '7579927677256073216',
32
- baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
32
+ baseUrl: `https://${process.env.AGENT_HOST}/v3/`,
33
33
  apiKey: process.env.BOT_API_KEY, // load from env
34
+ intentBotId: process.env.FORM_BUILD_INTENT_BOT_ID || '7615799745042186240',
34
35
  },
35
36
 
36
37
  fill: {
37
38
  botId: process.env.FILL_BOT_ID || '7586483957357608960',
38
- baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
39
+ baseUrl: `https://${process.env.AGENT_HOST}/v3/`,
39
40
  apiKey: process.env.BOT_API_KEY, // load from env
40
41
  ocrApiKey: process.env.OCR_API_KEY || '', // load from env
41
42
  },
@@ -44,7 +45,7 @@ export default {
44
45
  report: {
45
46
  query: {
46
47
  botId: process.env.QUERY_BOT_ID || '7595888372090929152',
47
- baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
48
+ baseUrl: `https://${process.env.AGENT_HOST}/v3/`,
48
49
  apiKey: process.env.BOT_API_KEY, // load from env
49
50
  },
50
51
  },
@@ -15,7 +15,7 @@ export default {
15
15
  agent: {
16
16
  host: process.env.AGENT_HOST || '47.99.202.157',
17
17
  apiAuthKey: process.env.COZE_API_KEY,
18
- deepseekApiKey: process.env.DS_API_KEY,
18
+ userId: process.env.BUILTIN_COZE_USER_ID,
19
19
  common: {
20
20
  baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
21
21
  apiKey: process.env.BOT_API_KEY, // load from env
@@ -25,6 +25,7 @@ export default {
25
25
  botId: process.env.BUILD_BOT_ID || '7579927677256073216',
26
26
  baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
27
27
  apiKey: process.env.BOT_API_KEY, // load from env
28
+ intentBotId: process.env.FORM_BUILD_INTENT_BOT_ID || '7615799745042186240',
28
29
  },
29
30
  fill: {
30
31
  botId: process.env.FILL_BOT_ID || '7586483957357608960',
@@ -3,6 +3,7 @@
3
3
  */
4
4
  // 应用实例
5
5
  import app from '../config/hono.config.js';
6
+ import { createAgentRouter } from '../routes/agent/agent.route.js';
6
7
  import { createCommonRouter } from '../routes/common/common.route.js';
7
8
  // 子路由
8
9
  import { createFormRouter } from '../routes/form/form.route.js';
@@ -11,6 +12,7 @@ import { createReportRouter } from '../routes/report/report.route.js';
11
12
  app.route('web/api/form', createFormRouter());
12
13
  app.route('web/api/report', createReportRouter());
13
14
  app.route('web/api/common', createCommonRouter());
15
+ app.route('web/api/agent', createAgentRouter());
14
16
  // 基础健康检查
15
17
  app.get('/web/api/health', (c) => {
16
18
  return c.json({
@@ -0,0 +1,6 @@
1
+ import { LRUCache } from 'lru-cache';
2
+ const options = {
3
+ max: 5000,
4
+ ttl: 1000 * 60 * 55, // 假设 Token 1 小时有效,我们存 55 分钟
5
+ };
6
+ export const tokenCache = new LRUCache(options);
@@ -0,0 +1,29 @@
1
+ import config from 'config';
2
+ import { logger } from '../../logger/index.js';
3
+ import { safeJsonParser } from '../../utils/safeJsonParser.js';
4
+ export const listBots = async (c) => {
5
+ const agentHost = config.get('agent.host');
6
+ const apiBots = `http://${agentHost}/v1/bots`;
7
+ const cozeInfo = c.get('X-Coze-Info');
8
+ logger.debug(`Fetching bots from ${apiBots} with cozeToken: ${cozeInfo.cozeToken}`);
9
+ const result = await fetch(apiBots, {
10
+ method: 'GET',
11
+ headers: {
12
+ Authorization: `Bearer ${cozeInfo.cozeToken}`,
13
+ },
14
+ });
15
+ const resJson = await result.json();
16
+ logger.debug(JSON.stringify(resJson, null, 2));
17
+ if (!resJson.bot_list || !Array.isArray(resJson.bot_list)) {
18
+ return c.json([]);
19
+ }
20
+ const minifyedBots = resJson.bot_list.map((bot) => ({
21
+ id: bot.bot_id,
22
+ name: bot.name,
23
+ description: bot.description,
24
+ icon: bot.icon_url,
25
+ lastEditTime: bot.update_time,
26
+ publisherConfig: safeJsonParser(bot.pre_publish_ext),
27
+ }));
28
+ return c.json(minifyedBots);
29
+ };
@@ -9,12 +9,17 @@ export const connectToAgent = async (c) => {
9
9
  catch (_) {
10
10
  return c.json({ error: 'Invalid JSON' }, 400);
11
11
  }
12
- const botId = requestBody.botId;
12
+ const cozeInfo = c.get('X-Coze-Info');
13
13
  const messages = requestBody.messages;
14
+ const botId = c.req.header('X-Bot-Id') || '';
14
15
  const stream = streamText({
15
- model: createBaseModel(botId),
16
+ model: createBaseModel(botId, cozeInfo.cozeToken),
16
17
  messages: convertToModelMessages(messages),
17
18
  includeRawChunks: true,
19
+ headers: {
20
+ 'x-user-var': requestBody.userVar,
21
+ 'x-user-id': cozeInfo.userId,
22
+ },
18
23
  });
19
24
  return stream.toUIMessageStreamResponse();
20
25
  };
@@ -1,8 +1,8 @@
1
1
  import { createCoze } from '@baishuyun/coze-provider';
2
2
  import config from 'config';
3
- export const createBaseModel = (botId) => {
3
+ export const createBaseModel = (botId, token) => {
4
4
  const coze = createCoze({
5
- apiKey: config.get('agent.common.apiKey'),
5
+ apiKey: token || config.get('agent.common.apiKey'),
6
6
  baseURL: config.get('agent.common.baseUrl'),
7
7
  botId: botId,
8
8
  });
@@ -2,8 +2,6 @@ import { convertToModelMessages, streamText, generateText } from "ai";
2
2
  import {} from "hono";
3
3
  import { createModel } from "./model.js";
4
4
  import { createFieldsJsonTransformStream, SuggestionTransformStream, } from "@baishuyun/coze-provider";
5
- import { logger } from "../../../logger/index.js";
6
- import config from 'config';
7
5
  import { determineUserIntentByInput } from "./utils.js";
8
6
  /**
9
7
  * 搭建表单
@@ -26,30 +24,29 @@ export const buildForm = async (c) => {
26
24
  () => createFieldsJsonTransformStream(isBuildStage),
27
25
  () => new SuggestionTransformStream(isBuildStage),
28
26
  ]);
29
- logger.debug("intent: " + intent);
30
- const allMessages = [...requestBody.messages];
27
+ const allMsg = requestBody.messages || [];
28
+ const lastUserMsg = allMsg.length > 0 ? allMsg[allMsg.length - 1] : {
29
+ role: "user",
30
+ parts: [],
31
+ };
31
32
  if (isBuildStage && formName) {
32
- allMessages.push({
33
- role: "user",
34
- parts: [
35
- {
36
- type: "text",
37
- text: `确认搭建:${formName}`,
38
- },
39
- ],
33
+ lastUserMsg.parts = lastUserMsg.parts.map((p, index) => {
34
+ const isLastPart = index === lastUserMsg.parts.length - 1;
35
+ if (!isLastPart || p.type !== "text") {
36
+ return p;
37
+ }
38
+ return {
39
+ type: "text",
40
+ text: `【确认搭建:${formName}】 ${p.text}`
41
+ };
40
42
  });
41
43
  }
42
- // clear empty text parts to avoid unnecessary streaming
43
- allMessages.forEach((message) => {
44
- message.parts = message.parts.filter((part) => part.type === "text" && part.text.trim() !== "");
45
- });
46
44
  const stream = streamText({
47
45
  model,
48
- messages: convertToModelMessages(allMessages),
46
+ messages: convertToModelMessages([lastUserMsg]),
49
47
  includeRawChunks: true,
50
48
  headers: {
51
- "x-user-stage": requestBody.stage,
52
- "x-user-id": Date.now().toString(),
49
+ "x-user-stage": intent,
53
50
  "x-user-token": c.req.header("authorization") || "",
54
51
  },
55
52
  });
@@ -1,4 +1,3 @@
1
- import { createFormBuildIntentAgent } from "@baishuyun/agents";
2
1
  import { createCoze } from "@baishuyun/coze-provider";
3
2
  import config from "config";
4
3
  export const createModel = (extraStreamTransformers) => {
@@ -10,7 +9,11 @@ export const createModel = (extraStreamTransformers) => {
10
9
  });
11
10
  return coze.chat("chat");
12
11
  };
13
- export const createUserIntentAgent = () => {
14
- const dsApiKey = config.get("agent.deepseekApiKey");
15
- return createFormBuildIntentAgent(dsApiKey);
12
+ export const createBuildIntentAgent = () => {
13
+ const coze = createCoze({
14
+ apiKey: config.get("agent.form.build.apiKey"),
15
+ baseURL: config.get("agent.form.build.baseUrl"),
16
+ botId: config.get("agent.form.build.intentBotId"),
17
+ });
18
+ return coze.chat("chat");
16
19
  };
@@ -1,4 +1,5 @@
1
- import { createUserIntentAgent } from "./model.js";
1
+ import { logger } from "../../../logger/index.js";
2
+ import { createBuildIntentAgent } from "./model.js";
2
3
  export const determineUserIntentByInput = async (input, userOriginIntent) => {
3
4
  if (userOriginIntent === "design") {
4
5
  return "design";
@@ -6,9 +7,9 @@ export const determineUserIntentByInput = async (input, userOriginIntent) => {
6
7
  if (!input || input.trim() === "") {
7
8
  return userOriginIntent;
8
9
  }
9
- const agent = createUserIntentAgent();
10
- const intent = await agent.generate({
11
- messages: [{
10
+ const cozeAgent = createBuildIntentAgent();
11
+ const intent = await cozeAgent.doGenerate({
12
+ prompt: [{
12
13
  role: "user",
13
14
  content: [{
14
15
  type: "text",
@@ -16,15 +17,12 @@ export const determineUserIntentByInput = async (input, userOriginIntent) => {
16
17
  }]
17
18
  }]
18
19
  });
19
- const result = intent.response.messages;
20
+ logger.debug("agent intent response: " + JSON.stringify(intent, null, 2));
21
+ const result = intent.content;
20
22
  if (!result || result.length === 0) {
21
23
  return userOriginIntent;
22
24
  }
23
- const lastMessage = result[result.length - 1];
24
- if (!lastMessage.content || lastMessage.content.length === 0) {
25
- return userOriginIntent;
26
- }
27
- const lastContent = lastMessage.content[0];
25
+ const lastContent = result[result.length - 1];
28
26
  const intentText = lastContent.text === "build" ? "build" : lastContent.text === "design" ? "design" : userOriginIntent;
29
27
  return intentText;
30
28
  };
@@ -32,9 +32,6 @@ export const queryReport = async (c) => {
32
32
  model: createReportQueryModel(),
33
33
  messages: modelMessages,
34
34
  includeRawChunks: true,
35
- headers: {
36
- 'x-user-id': Date.now().toString(), // uid,
37
- },
38
35
  });
39
36
  return stream.toUIMessageStreamResponse({
40
37
  originalMessages: messages, // 建议添加,便于消息 ID 管理
@@ -10,7 +10,6 @@ export const queryReportSuggest = async (c) => {
10
10
  catch (_) {
11
11
  return c.json({ error: 'Invalid JSON' }, 400);
12
12
  }
13
- const uid = c.req.header('X-User-Id') || '';
14
13
  const messages = requestBody.messages;
15
14
  const forms = requestBody.forms || [];
16
15
  const modelMessages = convertToModelMessages([
@@ -29,9 +28,6 @@ export const queryReportSuggest = async (c) => {
29
28
  model: createReportQueryModel(),
30
29
  messages: modelMessages,
31
30
  includeRawChunks: true,
32
- headers: {
33
- 'x-user-id': uid,
34
- },
35
31
  });
36
32
  return stream.toUIMessageStreamResponse({
37
33
  originalMessages: messages, // 建议添加,便于消息 ID 管理
@@ -0,0 +1,23 @@
1
+ import { createMiddleware } from 'hono/factory';
2
+ import { tokenCache } from '../config/cache.config.js';
3
+ import { fetchCozeInfo } from '../services/fetchCozeInfo.js';
4
+ import { logger } from '../logger/index.js';
5
+ export const tokenExchange = () => createMiddleware(async (c, next) => {
6
+ const userAuth = c.req.header('X-Bs-Team-Id') || c.req.query('x-bs-team-id') || '';
7
+ if (!userAuth) {
8
+ return c.json({ error: 'Unauthorized' }, 401);
9
+ }
10
+ let cozeInfo = tokenCache.get(userAuth);
11
+ if (!cozeInfo) {
12
+ try {
13
+ cozeInfo = await fetchCozeInfo(userAuth);
14
+ tokenCache.set(userAuth, cozeInfo);
15
+ }
16
+ catch (error) {
17
+ logger.error(`Failed to exchange token for userAuth: ${userAuth}, error: ${error}`);
18
+ return c.json({ error: 'Failed to exchange token' }, 500);
19
+ }
20
+ }
21
+ c.set('X-Coze-Info', cozeInfo);
22
+ await next();
23
+ });
@@ -0,0 +1,8 @@
1
+ import { Hono } from 'hono';
2
+ import { listBots } from '../../controllers/agent/bots.controller.js';
3
+ import { tokenExchange } from '../../middleware/tokenExchange.js';
4
+ export const createAgentRouter = () => {
5
+ const agentRouter = new Hono();
6
+ agentRouter.get('/bots', tokenExchange(), listBots);
7
+ return agentRouter;
8
+ };
@@ -1,7 +1,8 @@
1
1
  import { Hono } from 'hono';
2
2
  import { connectToAgent } from '../../controllers/common/connect.controll.js';
3
+ import { tokenExchange } from '../../middleware/tokenExchange.js';
3
4
  export const createCommonRouter = () => {
4
5
  const commonRouter = new Hono();
5
- commonRouter.post('/connect', connectToAgent);
6
+ commonRouter.post('/connect', tokenExchange(), connectToAgent);
6
7
  return commonRouter;
7
8
  };
@@ -0,0 +1,26 @@
1
+ import config from 'config';
2
+ import { logger } from '../logger/index.js';
3
+ export const fetchCozeInfo = async (bsTeamId) => {
4
+ const agentHost = config.get('agent.host');
5
+ const apiUrl = `http://${agentHost}/api/by_teamid/tokens`;
6
+ const res = await fetch(apiUrl, {
7
+ method: 'POST',
8
+ headers: {
9
+ 'Content-Type': 'application/json',
10
+ },
11
+ body: JSON.stringify({ token: bsTeamId }),
12
+ });
13
+ const result = await res.json();
14
+ if (result.code !== 0) {
15
+ throw new Error('Failed to fetch Coze token');
16
+ }
17
+ const tokenList = result.data;
18
+ if (tokenList.length === 0) {
19
+ throw new Error('No Coze token returned');
20
+ }
21
+ const cozeToken = tokenList[0];
22
+ return {
23
+ cozeToken,
24
+ userId: `${result.team_uid}`,
25
+ };
26
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ export const safeJsonParser = (str) => {
2
+ try {
3
+ return JSON.parse(str);
4
+ }
5
+ catch (error) {
6
+ return null; // or you can choose to return an empty object {} or any default value
7
+ }
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baishuyun/chat-backend",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,12 +19,13 @@
19
19
  "config": "^4.1.1",
20
20
  "dotenv": "^17.2.3",
21
21
  "hono": "^4.10.6",
22
+ "lru-cache": "^11.2.7",
22
23
  "parse-sse": "^0.1.0",
23
24
  "pino": "^10.1.0",
24
25
  "zod": "^4.1.13",
25
- "@baishuyun/coze-provider": "0.0.17",
26
- "@baishuyun/agents": "0.0.17",
27
- "@baishuyun/types": "1.0.17"
26
+ "@baishuyun/types": "1.1.0",
27
+ "@baishuyun/coze-provider": "0.1.1",
28
+ "@baishuyun/agents": "0.1.0"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/config": "^3.3.5",
@@ -35,7 +36,7 @@
35
36
  "pm2": "^6.0.14",
36
37
  "tsx": "^4.7.1",
37
38
  "typescript": "^5.8.3",
38
- "@baishuyun/typescript-config": "0.0.17"
39
+ "@baishuyun/typescript-config": "0.1.0"
39
40
  },
40
41
  "scripts": {
41
42
  "dev": "cross-env NODE_ENV=development tsx watch src/index.ts",
package/src/app/main.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  // 应用实例
6
6
  import app from '../config/hono.config.js';
7
+ import { createAgentRouter } from '../routes/agent/agent.route.js';
7
8
  import { createCommonRouter } from '../routes/common/common.route.js';
8
9
 
9
10
  // 子路由
@@ -14,6 +15,7 @@ import { createReportRouter } from '../routes/report/report.route.js';
14
15
  app.route('web/api/form', createFormRouter());
15
16
  app.route('web/api/report', createReportRouter());
16
17
  app.route('web/api/common', createCommonRouter());
18
+ app.route('web/api/agent', createAgentRouter());
17
19
 
18
20
  // 基础健康检查
19
21
  app.get('/web/api/health', (c) => {
@@ -0,0 +1,9 @@
1
+ import { LRUCache } from 'lru-cache';
2
+ import type { ICozeInfoOfTokenExchange } from '../types/coze.js';
3
+
4
+ const options = {
5
+ max: 5000,
6
+ ttl: 1000 * 60 * 55, // 假设 Token 1 小时有效,我们存 55 分钟
7
+ };
8
+
9
+ export const tokenCache = new LRUCache<string, ICozeInfoOfTokenExchange>(options);
@@ -0,0 +1,39 @@
1
+ import type { Context } from 'hono';
2
+ import config from 'config';
3
+ import { logger } from '../../logger/index.js';
4
+ import { safeJsonParser } from '../../utils/safeJsonParser.js';
5
+ import type { ICozeInfoOfTokenExchange } from '../../types/coze.js';
6
+
7
+ export const listBots = async (c: Context) => {
8
+ const agentHost = config.get<string>('agent.host');
9
+ const apiBots = `https://${agentHost}/v1/bots`;
10
+ const cozeInfo = c.get('X-Coze-Info') as ICozeInfoOfTokenExchange;
11
+
12
+ logger.debug(`Fetching bots from ${apiBots} with cozeToken: ${cozeInfo.cozeToken}`);
13
+
14
+ const result = await fetch(apiBots, {
15
+ method: 'GET',
16
+ headers: {
17
+ Authorization: `Bearer ${cozeInfo.cozeToken}`,
18
+ },
19
+ });
20
+
21
+ const resJson = await result.json();
22
+
23
+ logger.debug(JSON.stringify(resJson, null, 2));
24
+
25
+ if (!resJson.bot_list || !Array.isArray(resJson.bot_list)) {
26
+ return c.json([]);
27
+ }
28
+
29
+ const minifyedBots = resJson.bot_list.map((bot: any) => ({
30
+ id: bot.bot_id,
31
+ name: bot.name,
32
+ description: bot.description,
33
+ icon: bot.icon_url,
34
+ lastEditTime: bot.update_time,
35
+ publisherConfig: safeJsonParser(bot.pre_publish_ext),
36
+ }));
37
+
38
+ return c.json(minifyedBots);
39
+ };
@@ -1,6 +1,7 @@
1
1
  import { convertToModelMessages, streamText } from 'ai';
2
2
  import type { Context } from 'hono';
3
3
  import { createBaseModel } from './model.js';
4
+ import type { ICozeInfoOfTokenExchange } from '../../types/coze.js';
4
5
 
5
6
  export const connectToAgent = async (c: Context) => {
6
7
  let requestBody;
@@ -11,13 +12,20 @@ export const connectToAgent = async (c: Context) => {
11
12
  return c.json({ error: 'Invalid JSON' }, 400);
12
13
  }
13
14
 
14
- const botId = requestBody.botId;
15
+ const cozeInfo = c.get('X-Coze-Info') as ICozeInfoOfTokenExchange;
16
+
15
17
  const messages = requestBody.messages;
16
18
 
19
+ const botId = c.req.header('X-Bot-Id') || '';
20
+
17
21
  const stream = streamText({
18
- model: createBaseModel(botId),
22
+ model: createBaseModel(botId, cozeInfo.cozeToken),
19
23
  messages: convertToModelMessages(messages),
20
24
  includeRawChunks: true,
25
+ headers: {
26
+ 'x-user-var': requestBody.userVar,
27
+ 'x-user-id': cozeInfo.userId,
28
+ },
21
29
  });
22
30
 
23
31
  return stream.toUIMessageStreamResponse();
@@ -1,9 +1,9 @@
1
1
  import { createCoze } from '@baishuyun/coze-provider';
2
2
  import config from 'config';
3
3
 
4
- export const createBaseModel = (botId: string) => {
4
+ export const createBaseModel = (botId: string, token?: string) => {
5
5
  const coze = createCoze({
6
- apiKey: config.get<string>('agent.common.apiKey'),
6
+ apiKey: token || config.get<string>('agent.common.apiKey'),
7
7
  baseURL: config.get<string>('agent.common.baseUrl'),
8
8
  botId: botId,
9
9
  });
@@ -1,27 +1,27 @@
1
- import { type Context } from "hono";
2
- import config from "config";
3
- import { logger } from "../../logger/index.js";
4
- import { parseImg } from "./fill/utils.js";
1
+ import { type Context } from 'hono';
2
+ import config from 'config';
3
+ import { logger } from '../../logger/index.js';
4
+ import { parseImg } from './fill/utils.js';
5
5
 
6
6
  export const uploadAttachment = async (c: Context) => {
7
7
  const formData = await c.req.formData();
8
- const file = formData.get("file"); // as FormData | null;
8
+ const file = formData.get('file'); // as FormData | null;
9
9
  // 校验文件是否存在
10
10
  if (!file || !(file instanceof Blob)) {
11
- return c.json({ error: "请上传有效的文件" }, 400);
11
+ return c.json({ error: '请上传有效的文件' }, 400);
12
12
  }
13
13
 
14
14
  const forwardFormData = new FormData();
15
- forwardFormData.append("file", file);
15
+ forwardFormData.append('file', file);
16
16
 
17
- const apiKey = config.get<string>("agent.apiAuthKey");
18
- const host = config.get<string>("agent.host");
19
- const api = `http://${host}/v1/files/upload`;
17
+ const apiKey = config.get<string>('agent.apiAuthKey');
18
+ const host = config.get<string>('agent.host');
19
+ const api = `https://${host}/v1/files/upload`;
20
20
 
21
21
  logger.debug(c.body);
22
22
 
23
23
  const response = await fetch(api, {
24
- method: "POST",
24
+ method: 'POST',
25
25
  body: formData,
26
26
  headers: {
27
27
  // Add any auth headers
@@ -30,9 +30,7 @@ export const uploadAttachment = async (c: Context) => {
30
30
  });
31
31
 
32
32
  if (!response.ok) {
33
- const errorData = await response
34
- .json()
35
- .catch(() => ({ message: "上传失败" }));
33
+ const errorData = await response.json().catch(() => ({ message: '上传失败' }));
36
34
  return c.json({
37
35
  error: `目标接口返回错误: ${errorData.message}`,
38
36
  status: response.status,
@@ -44,8 +42,11 @@ export const uploadAttachment = async (c: Context) => {
44
42
 
45
43
  const orcResult = await parseImg(file);
46
44
 
45
+ // replace file url protol to https
46
+ const secureUrl = url.replace('http://', 'https://');
47
+
47
48
  return c.json({
48
- url,
49
+ url: secureUrl,
49
50
  name: uri,
50
51
  contentType: file.type,
51
52
  parsedData: orcResult,
@@ -5,8 +5,6 @@ import {
5
5
  createFieldsJsonTransformStream,
6
6
  SuggestionTransformStream,
7
7
  } from "@baishuyun/coze-provider";
8
- import { logger } from "../../../logger/index.js";
9
- import config from 'config';
10
8
  import { determineUserIntentByInput } from "./utils.js";
11
9
 
12
10
  /**
@@ -33,35 +31,32 @@ export const buildForm = async (c: Context) => {
33
31
  () => new SuggestionTransformStream(isBuildStage),
34
32
  ]);
35
33
 
36
- logger.debug("intent: " + intent);
34
+ const allMsg = requestBody.messages || [];
35
+ const lastUserMsg = allMsg.length > 0 ? allMsg[allMsg.length - 1] : {
36
+ role: "user",
37
+ parts: [],
38
+ };
37
39
 
38
- const allMessages = [...requestBody.messages];
39
40
  if (isBuildStage && formName) {
40
- allMessages.push({
41
- role: "user",
42
- parts: [
43
- {
44
- type: "text",
45
- text: `确认搭建:${formName}`,
46
- },
47
- ],
41
+ lastUserMsg.parts = lastUserMsg.parts.map((p: any, index: number) => {
42
+ const isLastPart = index === lastUserMsg.parts.length - 1;
43
+ if (!isLastPart || p.type !== "text") {
44
+ return p;
45
+ }
46
+
47
+ return {
48
+ type: "text",
49
+ text: `【确认搭建:${formName}】 ${p.text}`
50
+ };
48
51
  });
49
52
  }
50
53
 
51
- // clear empty text parts to avoid unnecessary streaming
52
- allMessages.forEach((message) => {
53
- message.parts = message.parts.filter(
54
- (part: TextUIPart) => part.type === "text" && part.text.trim() !== ""
55
- );
56
- });
57
-
58
54
  const stream = streamText({
59
55
  model,
60
- messages: convertToModelMessages(allMessages),
56
+ messages: convertToModelMessages([lastUserMsg]),
61
57
  includeRawChunks: true,
62
58
  headers: {
63
- "x-user-stage": requestBody.stage,
64
- "x-user-id": Date.now().toString(),
59
+ "x-user-stage": intent as string,
65
60
  "x-user-token": c.req.header("authorization") || "",
66
61
  },
67
62
  });
@@ -1,5 +1,4 @@
1
1
  import type { LanguageModelV2StreamPart } from "@ai-sdk/provider";
2
- import { createFormBuildIntentAgent } from "@baishuyun/agents";
3
2
  import { createCoze } from "@baishuyun/coze-provider";
4
3
  import config from "config";
5
4
 
@@ -16,7 +15,12 @@ export const createModel = (
16
15
  return coze.chat("chat");
17
16
  };
18
17
 
19
- export const createUserIntentAgent = () => {
20
- const dsApiKey = config.get<string>("agent.deepseekApiKey");
21
- return createFormBuildIntentAgent(dsApiKey);
18
+ export const createBuildIntentAgent = () => {
19
+ const coze = createCoze({
20
+ apiKey: config.get<string>("agent.form.build.apiKey"),
21
+ baseURL: config.get<string>("agent.form.build.baseUrl"),
22
+ botId: config.get<string>("agent.form.build.intentBotId"),
23
+ });
24
+
25
+ return coze.chat("chat");
22
26
  }
@@ -1,4 +1,5 @@
1
- import { createUserIntentAgent } from "./model.js"
1
+ import { logger } from "../../../logger/index.js";
2
+ import { createBuildIntentAgent } from "./model.js"
2
3
 
3
4
  export const determineUserIntentByInput = async (input: string, userOriginIntent: "build" | "design" | null): Promise<"build" | "design" | null> => {
4
5
  if (userOriginIntent === "design") {
@@ -9,30 +10,25 @@ export const determineUserIntentByInput = async (input: string, userOriginIntent
9
10
  return userOriginIntent;
10
11
  }
11
12
 
12
- const agent = createUserIntentAgent();
13
-
14
- const intent = await agent.generate({
15
- messages: [{
13
+ const cozeAgent = createBuildIntentAgent();
14
+ const intent = await cozeAgent.doGenerate({
15
+ prompt: [{
16
16
  role: "user",
17
17
  content: [{
18
18
  type: "text",
19
19
  text: input,
20
20
  }]
21
21
  }]
22
- })
23
-
24
- const result = intent.response.messages;
25
- if (!result || result.length === 0) {
26
- return userOriginIntent;
27
- }
22
+ });
28
23
 
29
- const lastMessage = result[result.length - 1];
24
+ logger.debug("agent intent response: " + JSON.stringify(intent, null, 2));
30
25
 
31
- if (!lastMessage.content || lastMessage.content.length === 0) {
26
+ const result = intent.content;
27
+ if (!result || result.length === 0) {
32
28
  return userOriginIntent;
33
29
  }
34
30
 
35
- const lastContent = lastMessage.content[0] as {
31
+ const lastContent = result[result.length - 1] as {
36
32
  type: "text";
37
33
  text: string;
38
34
  };
@@ -1,31 +1,28 @@
1
- import type { Context } from "hono";
2
- import config from "config";
1
+ import type { Context } from 'hono';
2
+ import config from 'config';
3
3
 
4
4
  export const clearConversation = async (c: Context) => {
5
5
  let requestBody;
6
6
 
7
- const agentHost = config.get<string>("agent.host");
7
+ const agentHost = config.get<string>('agent.host');
8
8
 
9
- const baseUrl = `http://${agentHost}/v1`;
10
- const apiKey = config.get<string>("agent.apiAuthKey");
9
+ const baseUrl = `https://${agentHost}/v1`;
10
+ const apiKey = config.get<string>('agent.apiAuthKey');
11
11
 
12
12
  try {
13
13
  const json = await c.req.json();
14
14
  requestBody = json;
15
15
  } catch (_) {
16
- return c.json({ error: "Invalid JSON" }, 400);
16
+ return c.json({ error: 'Invalid JSON' }, 400);
17
17
  }
18
18
 
19
- const result = await fetch(
20
- `${baseUrl}/conversations/${requestBody.conversationId}/clear`,
21
- {
22
- method: "POST",
23
- headers: {
24
- // Add any auth headers
25
- Authorization: `Bearer ${apiKey}`,
26
- },
19
+ const result = await fetch(`${baseUrl}/conversations/${requestBody.conversationId}/clear`, {
20
+ method: 'POST',
21
+ headers: {
22
+ // Add any auth headers
23
+ Authorization: `Bearer ${apiKey}`,
27
24
  },
28
- );
25
+ });
29
26
 
30
27
  return c.json(result);
31
28
  };
@@ -36,7 +36,7 @@ const getModelMessagesFromUserMessages = ({
36
36
  parts: [
37
37
  {
38
38
  type: 'text',
39
- text: `formStructure: ${JSON.stringify(formStructure)}`,
39
+ text: `\n\nformStructure: ${JSON.stringify(formStructure)}\n\n`,
40
40
  },
41
41
  mode2part(mode),
42
42
  {
@@ -45,7 +45,7 @@ export const fillForm = async (c: Context) => {
45
45
  ...flattenParts,
46
46
  {
47
47
  type: 'text',
48
- text: `formStructure: ${JSON.stringify(formStructure)}`,
48
+ text: `\n\nformStructure: ${JSON.stringify(formStructure)}\n\n`,
49
49
  },
50
50
  extraPart,
51
51
  ],
@@ -13,7 +13,7 @@ export const queryReport = async (c: Context) => {
13
13
  return c.json({ error: 'Invalid JSON' }, 400);
14
14
  }
15
15
 
16
- const uid = c.req.header('X-User-Id') || '';
16
+ const uid = c.req.header('X-Bs-User-Id') || '';
17
17
  const messages: UIMessage[] = requestBody.messages;
18
18
 
19
19
  const extraText = buildExtraMsgParts({
@@ -39,9 +39,6 @@ export const queryReport = async (c: Context) => {
39
39
  model: createReportQueryModel(),
40
40
  messages: modelMessages,
41
41
  includeRawChunks: true,
42
- headers: {
43
- 'x-user-id': Date.now().toString(), // uid,
44
- },
45
42
  });
46
43
 
47
44
  return stream.toUIMessageStreamResponse({
@@ -12,7 +12,6 @@ export const queryReportSuggest = async (c: Context) => {
12
12
  return c.json({ error: 'Invalid JSON' }, 400);
13
13
  }
14
14
 
15
- const uid = c.req.header('X-User-Id') || '';
16
15
  const messages: UIMessage[] = requestBody.messages;
17
16
 
18
17
  const forms = requestBody.forms || [];
@@ -35,9 +34,6 @@ export const queryReportSuggest = async (c: Context) => {
35
34
  model: createReportQueryModel(),
36
35
  messages: modelMessages,
37
36
  includeRawChunks: true,
38
- headers: {
39
- 'x-user-id': uid,
40
- },
41
37
  });
42
38
 
43
39
  return stream.toUIMessageStreamResponse({
@@ -0,0 +1,26 @@
1
+ import { createMiddleware } from 'hono/factory';
2
+ import { tokenCache } from '../config/cache.config.js';
3
+ import { fetchCozeInfo } from '../services/fetchCozeInfo.js';
4
+ import { logger } from '../logger/index.js';
5
+
6
+ export const tokenExchange = () =>
7
+ createMiddleware(async (c, next) => {
8
+ const userAuth = c.req.header('X-Bs-Team-Id') || c.req.query('x-bs-team-id') || '';
9
+ if (!userAuth) {
10
+ return c.json({ error: 'Unauthorized' }, 401);
11
+ }
12
+
13
+ let cozeInfo = tokenCache.get(userAuth);
14
+ if (!cozeInfo) {
15
+ try {
16
+ cozeInfo = await fetchCozeInfo(userAuth);
17
+ tokenCache.set(userAuth, cozeInfo);
18
+ } catch (error) {
19
+ logger.error(`Failed to exchange token for userAuth: ${userAuth}, error: ${error}`);
20
+ return c.json({ error: 'Failed to exchange token' }, 500);
21
+ }
22
+ }
23
+
24
+ c.set('X-Coze-Info', cozeInfo);
25
+ await next();
26
+ });
@@ -0,0 +1,11 @@
1
+ import { Hono } from 'hono';
2
+ import { listBots } from '../../controllers/agent/bots.controller.js';
3
+ import { tokenExchange } from '../../middleware/tokenExchange.js';
4
+
5
+ export const createAgentRouter = () => {
6
+ const agentRouter = new Hono();
7
+
8
+ agentRouter.get('/bots', tokenExchange(), listBots);
9
+
10
+ return agentRouter;
11
+ };
@@ -1,10 +1,11 @@
1
1
  import { Hono } from 'hono';
2
2
  import { connectToAgent } from '../../controllers/common/connect.controll.js';
3
+ import { tokenExchange } from '../../middleware/tokenExchange.js';
3
4
 
4
5
  export const createCommonRouter = () => {
5
6
  const commonRouter = new Hono();
6
7
 
7
- commonRouter.post('/connect', connectToAgent);
8
+ commonRouter.post('/connect', tokenExchange(), connectToAgent);
8
9
 
9
10
  return commonRouter;
10
11
  };
@@ -0,0 +1,33 @@
1
+ import config from 'config';
2
+ import type { ICozeInfoOfTokenExchange, OpenSpaceData } from '../types/coze.js';
3
+ import { logger } from '../logger/index.js';
4
+
5
+ export const fetchCozeInfo = async (bsTeamId: string): Promise<ICozeInfoOfTokenExchange> => {
6
+ const agentHost = config.get<string>('agent.host');
7
+
8
+ const apiUrl = `https://${agentHost}/api/by_teamid/tokens`;
9
+ const res = await fetch(apiUrl, {
10
+ method: 'POST',
11
+ headers: {
12
+ 'Content-Type': 'application/json',
13
+ },
14
+ body: JSON.stringify({ token: bsTeamId }),
15
+ });
16
+
17
+ const result = await res.json();
18
+ if (result.code !== 0) {
19
+ throw new Error('Failed to fetch Coze token');
20
+ }
21
+
22
+ const tokenList = result.data as Array<string>;
23
+ if (tokenList.length === 0) {
24
+ throw new Error('No Coze token returned');
25
+ }
26
+
27
+ const cozeToken = tokenList[0];
28
+
29
+ return {
30
+ cozeToken,
31
+ userId: `${result.team_uid}`,
32
+ };
33
+ };
@@ -0,0 +1,13 @@
1
+ export interface ICozeInfoOfTokenExchange {
2
+ cozeToken: string;
3
+ userId: string;
4
+ }
5
+
6
+ export type OpenSpaceData = Array<IOpenSpace>;
7
+
8
+ export interface IOpenSpace {
9
+ id: string;
10
+ name: string;
11
+ icon_url: string;
12
+ owner_uid: string;
13
+ }
@@ -0,0 +1,7 @@
1
+ export const safeJsonParser = (str: string) => {
2
+ try {
3
+ return JSON.parse(str);
4
+ } catch (error) {
5
+ return null; // or you can choose to return an empty object {} or any default value
6
+ }
7
+ };