@baishuyun/chat-backend 0.0.17 → 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/CHANGELOG.md +13 -0
- package/config/default.ts +2 -1
- package/dist/config/default.js +2 -1
- package/dist/src/app/main.js +2 -0
- package/dist/src/config/cache.config.js +6 -0
- package/dist/src/controllers/agent/bots.controller.js +29 -0
- package/dist/src/controllers/common/connect.controll.js +7 -2
- package/dist/src/controllers/common/model.js +2 -2
- package/dist/src/controllers/form/build/build.controller.js +16 -19
- package/dist/src/controllers/form/build/model.js +7 -4
- package/dist/src/controllers/form/build/utils.js +8 -10
- package/dist/src/controllers/report/query/query.controller.js +0 -3
- package/dist/src/controllers/report/query/suggest.controller.js +0 -4
- package/dist/src/middleware/tokenExchange.js +23 -0
- package/dist/src/routes/agent/agent.route.js +8 -0
- package/dist/src/routes/common/common.route.js +2 -1
- package/dist/src/services/fetchCozeInfo.js +26 -0
- package/dist/src/types/coze.js +1 -0
- package/dist/src/utils/safeJsonParser.js +8 -0
- package/package.json +6 -5
- package/src/app/main.ts +2 -0
- package/src/config/cache.config.ts +9 -0
- package/src/controllers/agent/bots.controller.ts +39 -0
- package/src/controllers/common/connect.controll.ts +10 -2
- package/src/controllers/common/model.ts +2 -2
- package/src/controllers/form/build/build.controller.ts +17 -22
- package/src/controllers/form/build/model.ts +8 -4
- package/src/controllers/form/build/utils.ts +10 -14
- package/src/controllers/report/query/query.controller.ts +0 -3
- package/src/controllers/report/query/suggest.controller.ts +0 -4
- package/src/middleware/tokenExchange.ts +26 -0
- package/src/routes/agent/agent.route.ts +11 -0
- package/src/routes/common/common.route.ts +2 -1
- package/src/services/fetchCozeInfo.ts +33 -0
- package/src/types/coze.ts +13 -0
- package/src/utils/safeJsonParser.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @baishuyun/chat-backend
|
|
2
2
|
|
|
3
|
+
## 0.0.18
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [e5dd21a]
|
|
8
|
+
- Updated dependencies [a934bc0]
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- Updated dependencies [4acb2cc]
|
|
11
|
+
- Updated dependencies [35058a9]
|
|
12
|
+
- @baishuyun/coze-provider@0.1.0
|
|
13
|
+
- @baishuyun/agents@0.1.0
|
|
14
|
+
- @baishuyun/types@1.1.0
|
|
15
|
+
|
|
3
16
|
## 0.0.17
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/config/default.ts
CHANGED
|
@@ -19,7 +19,7 @@ export default {
|
|
|
19
19
|
|
|
20
20
|
apiAuthKey: process.env.COZE_API_KEY,
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
userId: process.env.BUILTIN_COZE_USER_ID,
|
|
23
23
|
|
|
24
24
|
common: {
|
|
25
25
|
baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
|
|
@@ -31,6 +31,7 @@ export default {
|
|
|
31
31
|
botId: process.env.BUILD_BOT_ID || '7579927677256073216',
|
|
32
32
|
baseUrl: `http://${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: {
|
package/dist/config/default.js
CHANGED
|
@@ -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
|
-
|
|
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',
|
package/dist/src/app/main.js
CHANGED
|
@@ -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,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
|
|
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
|
-
|
|
30
|
-
const
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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(
|
|
46
|
+
messages: convertToModelMessages([lastUserMsg]),
|
|
49
47
|
includeRawChunks: true,
|
|
50
48
|
headers: {
|
|
51
|
-
"x-user-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
|
|
14
|
-
const
|
|
15
|
-
|
|
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 {
|
|
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
|
|
10
|
-
const intent = await
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
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 {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@baishuyun/chat-backend",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
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
|
|
26
|
-
"@baishuyun/
|
|
27
|
-
"@baishuyun/
|
|
26
|
+
"@baishuyun/coze-provider": "0.1.0",
|
|
27
|
+
"@baishuyun/types": "1.1.0",
|
|
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
|
|
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 = `http://${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
|
|
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
|
});
|
|
@@ -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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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(
|
|
56
|
+
messages: convertToModelMessages([lastUserMsg]),
|
|
61
57
|
includeRawChunks: true,
|
|
62
58
|
headers: {
|
|
63
|
-
"x-user-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
|
|
20
|
-
const
|
|
21
|
-
|
|
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 {
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
24
|
+
logger.debug("agent intent response: " + JSON.stringify(intent, null, 2));
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
const result = intent.content;
|
|
27
|
+
if (!result || result.length === 0) {
|
|
32
28
|
return userOriginIntent;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
const lastContent =
|
|
31
|
+
const lastContent = result[result.length - 1] as {
|
|
36
32
|
type: "text";
|
|
37
33
|
text: string;
|
|
38
34
|
};
|
|
@@ -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 = `http://${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
|
+
};
|