@baishuyun/chat-backend 0.0.16 → 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/.env.example +4 -1
- package/CHANGELOG.md +22 -0
- package/config/default.ts +9 -0
- package/dist/config/default.js +7 -0
- package/dist/src/app/main.js +4 -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 +25 -0
- package/dist/src/controllers/common/model.js +10 -0
- package/dist/src/controllers/form/build/build.controller.js +28 -33
- package/dist/src/controllers/form/build/model.js +8 -0
- package/dist/src/controllers/form/build/utils.js +28 -0
- package/dist/src/controllers/form/fill/createFieldsFillingResultTransformStream.js +3 -6
- package/dist/src/controllers/form/fill/fill.controller.js +1 -13
- package/dist/src/controllers/form/fill/utils.js +1 -0
- package/dist/src/controllers/report/query/createQueryTransformStream.js +9 -0
- package/dist/src/controllers/report/query/query.controller.js +1 -16
- 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 +8 -0
- package/dist/src/services/fetchCozeInfo.js +26 -0
- package/dist/src/types/coze.js +1 -0
- package/dist/src/utils/createJsonStreamTransformer.js +3 -10
- package/dist/src/utils/safeJsonParser.js +8 -0
- package/package.json +6 -4
- package/src/app/main.ts +4 -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 +32 -0
- package/src/controllers/common/model.ts +12 -0
- package/src/controllers/form/build/build.controller.ts +31 -28
- package/src/controllers/form/build/model.ts +10 -0
- package/src/controllers/form/build/utils.ts +39 -0
- package/src/controllers/form/fill/utils.ts +1 -0
- 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 +11 -0
- package/src/services/fetchCozeInfo.ts +33 -0
- package/src/types/coze.ts +13 -0
- package/src/utils/safeJsonParser.ts +7 -0
package/.env.example
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
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
|
+
|
|
16
|
+
## 0.0.17
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @baishuyun/coze-provider@0.0.17
|
|
22
|
+
- @baishuyun/agents@0.0.17
|
|
23
|
+
- @baishuyun/types@1.0.17
|
|
24
|
+
|
|
3
25
|
## 0.0.16
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/config/default.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deepseek } from '@ai-sdk/deepseek';
|
|
1
2
|
import { asyncConfig } from 'config/async.js';
|
|
2
3
|
// load async configurations
|
|
3
4
|
const fetchRemoteConfig = async () => {
|
|
@@ -18,11 +19,19 @@ export default {
|
|
|
18
19
|
|
|
19
20
|
apiAuthKey: process.env.COZE_API_KEY,
|
|
20
21
|
|
|
22
|
+
userId: process.env.BUILTIN_COZE_USER_ID,
|
|
23
|
+
|
|
24
|
+
common: {
|
|
25
|
+
baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
|
|
26
|
+
apiKey: process.env.BOT_API_KEY, // load from env
|
|
27
|
+
},
|
|
28
|
+
|
|
21
29
|
form: {
|
|
22
30
|
build: {
|
|
23
31
|
botId: process.env.BUILD_BOT_ID || '7579927677256073216',
|
|
24
32
|
baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
|
|
25
33
|
apiKey: process.env.BOT_API_KEY, // load from env
|
|
34
|
+
intentBotId: process.env.FORM_BUILD_INTENT_BOT_ID || '7615799745042186240',
|
|
26
35
|
},
|
|
27
36
|
|
|
28
37
|
fill: {
|
package/dist/config/default.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deepseek } from '@ai-sdk/deepseek';
|
|
1
2
|
import { asyncConfig } from 'config/async.js';
|
|
2
3
|
// load async configurations
|
|
3
4
|
const fetchRemoteConfig = async () => {
|
|
@@ -14,11 +15,17 @@ export default {
|
|
|
14
15
|
agent: {
|
|
15
16
|
host: process.env.AGENT_HOST || '47.99.202.157',
|
|
16
17
|
apiAuthKey: process.env.COZE_API_KEY,
|
|
18
|
+
userId: process.env.BUILTIN_COZE_USER_ID,
|
|
19
|
+
common: {
|
|
20
|
+
baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
|
|
21
|
+
apiKey: process.env.BOT_API_KEY, // load from env
|
|
22
|
+
},
|
|
17
23
|
form: {
|
|
18
24
|
build: {
|
|
19
25
|
botId: process.env.BUILD_BOT_ID || '7579927677256073216',
|
|
20
26
|
baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
|
|
21
27
|
apiKey: process.env.BOT_API_KEY, // load from env
|
|
28
|
+
intentBotId: process.env.FORM_BUILD_INTENT_BOT_ID || '7615799745042186240',
|
|
22
29
|
},
|
|
23
30
|
fill: {
|
|
24
31
|
botId: process.env.FILL_BOT_ID || '7586483957357608960',
|
package/dist/src/app/main.js
CHANGED
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// 应用实例
|
|
5
5
|
import app from '../config/hono.config.js';
|
|
6
|
+
import { createAgentRouter } from '../routes/agent/agent.route.js';
|
|
7
|
+
import { createCommonRouter } from '../routes/common/common.route.js';
|
|
6
8
|
// 子路由
|
|
7
9
|
import { createFormRouter } from '../routes/form/form.route.js';
|
|
8
10
|
import { createReportRouter } from '../routes/report/report.route.js';
|
|
9
11
|
// 挂载子路由
|
|
10
12
|
app.route('web/api/form', createFormRouter());
|
|
11
13
|
app.route('web/api/report', createReportRouter());
|
|
14
|
+
app.route('web/api/common', createCommonRouter());
|
|
15
|
+
app.route('web/api/agent', createAgentRouter());
|
|
12
16
|
// 基础健康检查
|
|
13
17
|
app.get('/web/api/health', (c) => {
|
|
14
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
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { convertToModelMessages, streamText } from 'ai';
|
|
2
|
+
import { createBaseModel } from './model.js';
|
|
3
|
+
export const connectToAgent = async (c) => {
|
|
4
|
+
let requestBody;
|
|
5
|
+
try {
|
|
6
|
+
const json = await c.req.json();
|
|
7
|
+
requestBody = json;
|
|
8
|
+
}
|
|
9
|
+
catch (_) {
|
|
10
|
+
return c.json({ error: 'Invalid JSON' }, 400);
|
|
11
|
+
}
|
|
12
|
+
const cozeInfo = c.get('X-Coze-Info');
|
|
13
|
+
const messages = requestBody.messages;
|
|
14
|
+
const botId = c.req.header('X-Bot-Id') || '';
|
|
15
|
+
const stream = streamText({
|
|
16
|
+
model: createBaseModel(botId, cozeInfo.cozeToken),
|
|
17
|
+
messages: convertToModelMessages(messages),
|
|
18
|
+
includeRawChunks: true,
|
|
19
|
+
headers: {
|
|
20
|
+
'x-user-var': requestBody.userVar,
|
|
21
|
+
'x-user-id': cozeInfo.userId,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return stream.toUIMessageStreamResponse();
|
|
25
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createCoze } from '@baishuyun/coze-provider';
|
|
2
|
+
import config from 'config';
|
|
3
|
+
export const createBaseModel = (botId, token) => {
|
|
4
|
+
const coze = createCoze({
|
|
5
|
+
apiKey: token || config.get('agent.common.apiKey'),
|
|
6
|
+
baseURL: config.get('agent.common.baseUrl'),
|
|
7
|
+
botId: botId,
|
|
8
|
+
});
|
|
9
|
+
return coze.chat('chat');
|
|
10
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { convertToModelMessages, streamText } from "ai";
|
|
1
|
+
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 { determineUserIntentByInput } from "./utils.js";
|
|
5
6
|
/**
|
|
6
7
|
* 搭建表单
|
|
7
8
|
* @param c
|
|
@@ -16,44 +17,38 @@ export const buildForm = async (c) => {
|
|
|
16
17
|
catch (_) {
|
|
17
18
|
return c.json({ error: "Invalid JSON" }, 400);
|
|
18
19
|
}
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
const intent = await determineUserIntentByInput(requestBody.text, requestBody.stage);
|
|
21
|
+
const isBuildStage = intent === "build";
|
|
22
|
+
const formName = requestBody.name;
|
|
23
|
+
const model = createModel([
|
|
24
|
+
() => createFieldsJsonTransformStream(isBuildStage),
|
|
25
|
+
() => new SuggestionTransformStream(isBuildStage),
|
|
26
|
+
]);
|
|
27
|
+
const allMsg = requestBody.messages || [];
|
|
28
|
+
const lastUserMsg = allMsg.length > 0 ? allMsg[allMsg.length - 1] : {
|
|
29
|
+
role: "user",
|
|
30
|
+
parts: [],
|
|
31
|
+
};
|
|
32
|
+
if (isBuildStage && formName) {
|
|
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
|
+
};
|
|
31
42
|
});
|
|
32
43
|
}
|
|
33
44
|
const stream = streamText({
|
|
34
|
-
model
|
|
35
|
-
|
|
36
|
-
() => new SuggestionTransformStream(isBuildStage),
|
|
37
|
-
]),
|
|
38
|
-
messages: convertToModelMessages(allMessages),
|
|
45
|
+
model,
|
|
46
|
+
messages: convertToModelMessages([lastUserMsg]),
|
|
39
47
|
includeRawChunks: true,
|
|
40
48
|
headers: {
|
|
41
|
-
"x-user-stage":
|
|
42
|
-
"x-user-id": Date.now().toString(),
|
|
49
|
+
"x-user-stage": intent,
|
|
43
50
|
"x-user-token": c.req.header("authorization") || "",
|
|
44
51
|
},
|
|
45
52
|
});
|
|
46
|
-
|
|
47
|
-
// Add SSE keep-alive headers to prevent proxy timeouts during streaming
|
|
48
|
-
return new Response(response.body, {
|
|
49
|
-
status: response.status,
|
|
50
|
-
statusText: response.statusText,
|
|
51
|
-
headers: {
|
|
52
|
-
...Object.fromEntries(response.headers),
|
|
53
|
-
'Connection': 'keep-alive',
|
|
54
|
-
'Keep-Alive': 'timeout=300',
|
|
55
|
-
'X-Accel-Buffering': 'no',
|
|
56
|
-
'Cache-Control': 'no-cache, no-transform',
|
|
57
|
-
},
|
|
58
|
-
});
|
|
53
|
+
return stream.toUIMessageStreamResponse();
|
|
59
54
|
};
|
|
@@ -9,3 +9,11 @@ export const createModel = (extraStreamTransformers) => {
|
|
|
9
9
|
});
|
|
10
10
|
return coze.chat("chat");
|
|
11
11
|
};
|
|
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");
|
|
19
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { logger } from "../../../logger/index.js";
|
|
2
|
+
import { createBuildIntentAgent } from "./model.js";
|
|
3
|
+
export const determineUserIntentByInput = async (input, userOriginIntent) => {
|
|
4
|
+
if (userOriginIntent === "design") {
|
|
5
|
+
return "design";
|
|
6
|
+
}
|
|
7
|
+
if (!input || input.trim() === "") {
|
|
8
|
+
return userOriginIntent;
|
|
9
|
+
}
|
|
10
|
+
const cozeAgent = createBuildIntentAgent();
|
|
11
|
+
const intent = await cozeAgent.doGenerate({
|
|
12
|
+
prompt: [{
|
|
13
|
+
role: "user",
|
|
14
|
+
content: [{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: input,
|
|
17
|
+
}]
|
|
18
|
+
}]
|
|
19
|
+
});
|
|
20
|
+
logger.debug("agent intent response: " + JSON.stringify(intent, null, 2));
|
|
21
|
+
const result = intent.content;
|
|
22
|
+
if (!result || result.length === 0) {
|
|
23
|
+
return userOriginIntent;
|
|
24
|
+
}
|
|
25
|
+
const lastContent = result[result.length - 1];
|
|
26
|
+
const intentText = lastContent.text === "build" ? "build" : lastContent.text === "design" ? "design" : userOriginIntent;
|
|
27
|
+
return intentText;
|
|
28
|
+
};
|
|
@@ -54,14 +54,13 @@ export const createFieldsFillingResultTransformer = (enableJsonParser) => {
|
|
|
54
54
|
}
|
|
55
55
|
catch (e) {
|
|
56
56
|
logger.debug('exception in transform while processing filling chunk');
|
|
57
|
-
|
|
57
|
+
controller.error(e);
|
|
58
58
|
enqueueTextDelta('error', {
|
|
59
59
|
type: 'agent-error',
|
|
60
60
|
error: '解析异常,请刷新重试',
|
|
61
61
|
});
|
|
62
62
|
resolveParseCompleted?.();
|
|
63
|
-
//
|
|
64
|
-
controller.terminate();
|
|
63
|
+
controller.terminate(); // 信号通知下游可读流已关闭
|
|
65
64
|
}
|
|
66
65
|
},
|
|
67
66
|
start: (controller) => {
|
|
@@ -107,14 +106,12 @@ export const createFieldsFillingResultTransformer = (enableJsonParser) => {
|
|
|
107
106
|
console.error('JsonWidgetStream: JSON Parsing Error:', err);
|
|
108
107
|
errorLogged = true;
|
|
109
108
|
}
|
|
110
|
-
// Enqueue error message before closing to allow client to receive it
|
|
111
109
|
enqueueTextDelta('error', {
|
|
112
110
|
type: 'agent-error',
|
|
113
111
|
error: '操作超时,请刷新重试',
|
|
114
112
|
});
|
|
115
113
|
resolveParseCompleted?.();
|
|
116
|
-
//
|
|
117
|
-
controller.terminate();
|
|
114
|
+
controller.terminate(); // 信号通知下游可读流已关闭
|
|
118
115
|
};
|
|
119
116
|
parser.onEnd = () => {
|
|
120
117
|
enqueueTextDelta(' ', JSON.stringify({
|
|
@@ -61,19 +61,7 @@ export const fillForm = async (c) => {
|
|
|
61
61
|
'x-user-token': c.req.header('authorization') || '',
|
|
62
62
|
},
|
|
63
63
|
});
|
|
64
|
-
|
|
64
|
+
return stream.toUIMessageStreamResponse({
|
|
65
65
|
originalMessages: messages, // 建议添加,便于消息 ID 管理
|
|
66
66
|
});
|
|
67
|
-
// Add SSE keep-alive headers to prevent proxy timeouts during streaming
|
|
68
|
-
return new Response(response.body, {
|
|
69
|
-
status: response.status,
|
|
70
|
-
statusText: response.statusText,
|
|
71
|
-
headers: {
|
|
72
|
-
...Object.fromEntries(response.headers),
|
|
73
|
-
'Connection': 'keep-alive',
|
|
74
|
-
'Keep-Alive': 'timeout=300',
|
|
75
|
-
'X-Accel-Buffering': 'no',
|
|
76
|
-
'Cache-Control': 'no-cache, no-transform',
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
67
|
};
|
|
@@ -48,10 +48,18 @@ function handleParsedValue(ctx) {
|
|
|
48
48
|
const { parsedInfo, getResult, currentChunkId, deltaChunkEnqueuer: enqueueTextDelta, ctrl } = ctx;
|
|
49
49
|
const { value } = parsedInfo;
|
|
50
50
|
logger.debug('Parsed JSON value: ' + JSON.stringify(value));
|
|
51
|
+
// aggregate 类型 —— name 字段触发,独立处理后直接返回
|
|
52
|
+
if (isTargetElement('$.name', parsedInfo)) {
|
|
53
|
+
handleAggregateName(ctx);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
51
56
|
// Phase 0: 报表标题 —— 初始化结果并发送 text-start, 必须为起始字段
|
|
52
57
|
if (isTargetElement('$.title', parsedInfo)) {
|
|
53
58
|
handleTitle(ctx);
|
|
54
59
|
}
|
|
60
|
+
// Phase 3: aggregate 结果无需后续字段处理
|
|
61
|
+
if (getResult().isAggregate)
|
|
62
|
+
return;
|
|
55
63
|
// Phase 4: 按注册的 handler 分发字段处理
|
|
56
64
|
for (const { path, handler } of fieldHandlers) {
|
|
57
65
|
if (isTargetElement(path, parsedInfo)) {
|
|
@@ -70,6 +78,7 @@ function handleAggregateName(ctx) {
|
|
|
70
78
|
title: value,
|
|
71
79
|
source: 'aggregate',
|
|
72
80
|
type: 'data_table',
|
|
81
|
+
isAggregate: true,
|
|
73
82
|
});
|
|
74
83
|
logger.debug('Parsed aggregate table title: ' + value);
|
|
75
84
|
enqueueTextDelta(`${JSON.stringify(value)},`, { type: 'query-stream-parsed-info', result: JSON.stringify(getResult()) }, currentChunkId + 1, false);
|
|
@@ -32,23 +32,8 @@ 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 管理
|
|
41
38
|
});
|
|
42
|
-
// Add SSE keep-alive headers to prevent proxy timeouts during streaming
|
|
43
|
-
return new Response(response.body, {
|
|
44
|
-
status: response.status,
|
|
45
|
-
statusText: response.statusText,
|
|
46
|
-
headers: {
|
|
47
|
-
...Object.fromEntries(response.headers),
|
|
48
|
-
'Connection': 'keep-alive',
|
|
49
|
-
'Keep-Alive': 'timeout=300',
|
|
50
|
-
'X-Accel-Buffering': 'no',
|
|
51
|
-
'Cache-Control': 'no-cache, no-transform',
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
39
|
};
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { connectToAgent } from '../../controllers/common/connect.controll.js';
|
|
3
|
+
import { tokenExchange } from '../../middleware/tokenExchange.js';
|
|
4
|
+
export const createCommonRouter = () => {
|
|
5
|
+
const commonRouter = new Hono();
|
|
6
|
+
commonRouter.post('/connect', tokenExchange(), connectToAgent);
|
|
7
|
+
return commonRouter;
|
|
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 {};
|
|
@@ -83,8 +83,8 @@ class JsonStreamProcessor {
|
|
|
83
83
|
await this.parseCompleted;
|
|
84
84
|
logger.debug('Parser stopped gracefully');
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
if (!this.terminated)
|
|
87
|
+
controller.terminate();
|
|
88
88
|
}
|
|
89
89
|
catch (error) {
|
|
90
90
|
controller.error(`Cleanup error: ${error}`);
|
|
@@ -160,14 +160,7 @@ class JsonStreamProcessor {
|
|
|
160
160
|
terminateStream(controller) {
|
|
161
161
|
this.completeParsing();
|
|
162
162
|
this.terminated = true;
|
|
163
|
-
|
|
164
|
-
// This allows the stream to be properly handled by upstream consumers
|
|
165
|
-
try {
|
|
166
|
-
controller.error(new Error('Stream terminated due to error'));
|
|
167
|
-
}
|
|
168
|
-
catch (e) {
|
|
169
|
-
// Controller may already be closed, ignore
|
|
170
|
-
}
|
|
163
|
+
controller.terminate();
|
|
171
164
|
}
|
|
172
165
|
/** 向下游推送 agent-error 并终止流 */
|
|
173
166
|
enqueueError(controller, error) {
|
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,11 +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/types": "1.0
|
|
26
|
+
"@baishuyun/coze-provider": "0.1.0",
|
|
27
|
+
"@baishuyun/types": "1.1.0",
|
|
28
|
+
"@baishuyun/agents": "0.1.0"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
31
|
"@types/config": "^3.3.5",
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
"pm2": "^6.0.14",
|
|
35
37
|
"tsx": "^4.7.1",
|
|
36
38
|
"typescript": "^5.8.3",
|
|
37
|
-
"@baishuyun/typescript-config": "0.0
|
|
39
|
+
"@baishuyun/typescript-config": "0.1.0"
|
|
38
40
|
},
|
|
39
41
|
"scripts": {
|
|
40
42
|
"dev": "cross-env NODE_ENV=development tsx watch src/index.ts",
|
package/src/app/main.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
// 应用实例
|
|
6
6
|
import app from '../config/hono.config.js';
|
|
7
|
+
import { createAgentRouter } from '../routes/agent/agent.route.js';
|
|
8
|
+
import { createCommonRouter } from '../routes/common/common.route.js';
|
|
7
9
|
|
|
8
10
|
// 子路由
|
|
9
11
|
import { createFormRouter } from '../routes/form/form.route.js';
|
|
@@ -12,6 +14,8 @@ import { createReportRouter } from '../routes/report/report.route.js';
|
|
|
12
14
|
// 挂载子路由
|
|
13
15
|
app.route('web/api/form', createFormRouter());
|
|
14
16
|
app.route('web/api/report', createReportRouter());
|
|
17
|
+
app.route('web/api/common', createCommonRouter());
|
|
18
|
+
app.route('web/api/agent', createAgentRouter());
|
|
15
19
|
|
|
16
20
|
// 基础健康检查
|
|
17
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
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { convertToModelMessages, streamText } from 'ai';
|
|
2
|
+
import type { Context } from 'hono';
|
|
3
|
+
import { createBaseModel } from './model.js';
|
|
4
|
+
import type { ICozeInfoOfTokenExchange } from '../../types/coze.js';
|
|
5
|
+
|
|
6
|
+
export const connectToAgent = async (c: Context) => {
|
|
7
|
+
let requestBody;
|
|
8
|
+
try {
|
|
9
|
+
const json = await c.req.json();
|
|
10
|
+
requestBody = json;
|
|
11
|
+
} catch (_) {
|
|
12
|
+
return c.json({ error: 'Invalid JSON' }, 400);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const cozeInfo = c.get('X-Coze-Info') as ICozeInfoOfTokenExchange;
|
|
16
|
+
|
|
17
|
+
const messages = requestBody.messages;
|
|
18
|
+
|
|
19
|
+
const botId = c.req.header('X-Bot-Id') || '';
|
|
20
|
+
|
|
21
|
+
const stream = streamText({
|
|
22
|
+
model: createBaseModel(botId, cozeInfo.cozeToken),
|
|
23
|
+
messages: convertToModelMessages(messages),
|
|
24
|
+
includeRawChunks: true,
|
|
25
|
+
headers: {
|
|
26
|
+
'x-user-var': requestBody.userVar,
|
|
27
|
+
'x-user-id': cozeInfo.userId,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return stream.toUIMessageStreamResponse();
|
|
32
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createCoze } from '@baishuyun/coze-provider';
|
|
2
|
+
import config from 'config';
|
|
3
|
+
|
|
4
|
+
export const createBaseModel = (botId: string, token?: string) => {
|
|
5
|
+
const coze = createCoze({
|
|
6
|
+
apiKey: token || config.get<string>('agent.common.apiKey'),
|
|
7
|
+
baseURL: config.get<string>('agent.common.baseUrl'),
|
|
8
|
+
botId: botId,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return coze.chat('chat');
|
|
12
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { convertToModelMessages, streamText, type TextUIPart } from "ai";
|
|
1
|
+
import { convertToModelMessages, streamText, generateText, type TextUIPart } from "ai";
|
|
2
2
|
import { type Context } from "hono";
|
|
3
3
|
import { createModel } from "./model.js";
|
|
4
4
|
import {
|
|
5
5
|
createFieldsJsonTransformStream,
|
|
6
6
|
SuggestionTransformStream,
|
|
7
7
|
} from "@baishuyun/coze-provider";
|
|
8
|
+
import { determineUserIntentByInput } from "./utils.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* 搭建表单
|
|
@@ -21,39 +22,41 @@ export const buildForm = async (c: Context) => {
|
|
|
21
22
|
return c.json({ error: "Invalid JSON" }, 400);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
const intent = await determineUserIntentByInput(requestBody.text, requestBody.stage);
|
|
26
|
+
|
|
27
|
+
const isBuildStage = intent === "build";
|
|
28
|
+
const formName = requestBody.name;
|
|
29
|
+
const model = createModel([
|
|
30
|
+
() => createFieldsJsonTransformStream(isBuildStage),
|
|
31
|
+
() => new SuggestionTransformStream(isBuildStage),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const allMsg = requestBody.messages || [];
|
|
35
|
+
const lastUserMsg = allMsg.length > 0 ? allMsg[allMsg.length - 1] : {
|
|
36
|
+
role: "user",
|
|
37
|
+
parts: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (isBuildStage && formName) {
|
|
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
|
+
};
|
|
37
51
|
});
|
|
38
52
|
}
|
|
39
53
|
|
|
40
|
-
// clear empty text parts to avoid unnecessary streaming
|
|
41
|
-
allMessages.forEach((message) => {
|
|
42
|
-
message.parts = message.parts.filter(
|
|
43
|
-
(part: TextUIPart) => part.type === "text" && part.text.trim() !== ""
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
54
|
const stream = streamText({
|
|
48
|
-
model
|
|
49
|
-
|
|
50
|
-
() => new SuggestionTransformStream(isBuildStage),
|
|
51
|
-
]),
|
|
52
|
-
messages: convertToModelMessages(allMessages),
|
|
55
|
+
model,
|
|
56
|
+
messages: convertToModelMessages([lastUserMsg]),
|
|
53
57
|
includeRawChunks: true,
|
|
54
58
|
headers: {
|
|
55
|
-
"x-user-stage":
|
|
56
|
-
"x-user-id": Date.now().toString(),
|
|
59
|
+
"x-user-stage": intent as string,
|
|
57
60
|
"x-user-token": c.req.header("authorization") || "",
|
|
58
61
|
},
|
|
59
62
|
});
|
|
@@ -14,3 +14,13 @@ export const createModel = (
|
|
|
14
14
|
|
|
15
15
|
return coze.chat("chat");
|
|
16
16
|
};
|
|
17
|
+
|
|
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");
|
|
26
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { logger } from "../../../logger/index.js";
|
|
2
|
+
import { createBuildIntentAgent } from "./model.js"
|
|
3
|
+
|
|
4
|
+
export const determineUserIntentByInput = async (input: string, userOriginIntent: "build" | "design" | null): Promise<"build" | "design" | null> => {
|
|
5
|
+
if (userOriginIntent === "design") {
|
|
6
|
+
return "design";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!input || input.trim() === "") {
|
|
10
|
+
return userOriginIntent;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const cozeAgent = createBuildIntentAgent();
|
|
14
|
+
const intent = await cozeAgent.doGenerate({
|
|
15
|
+
prompt: [{
|
|
16
|
+
role: "user",
|
|
17
|
+
content: [{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: input,
|
|
20
|
+
}]
|
|
21
|
+
}]
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
logger.debug("agent intent response: " + JSON.stringify(intent, null, 2));
|
|
25
|
+
|
|
26
|
+
const result = intent.content;
|
|
27
|
+
if (!result || result.length === 0) {
|
|
28
|
+
return userOriginIntent;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const lastContent = result[result.length - 1] as {
|
|
32
|
+
type: "text";
|
|
33
|
+
text: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const intentText = lastContent.text === "build" ? "build" : lastContent.text === "design" ? "design" : userOriginIntent;
|
|
37
|
+
|
|
38
|
+
return intentText;
|
|
39
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { connectToAgent } from '../../controllers/common/connect.controll.js';
|
|
3
|
+
import { tokenExchange } from '../../middleware/tokenExchange.js';
|
|
4
|
+
|
|
5
|
+
export const createCommonRouter = () => {
|
|
6
|
+
const commonRouter = new Hono();
|
|
7
|
+
|
|
8
|
+
commonRouter.post('/connect', tokenExchange(), connectToAgent);
|
|
9
|
+
|
|
10
|
+
return commonRouter;
|
|
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
|
+
};
|