@baishuyun/chat-backend 0.0.19 → 0.0.21

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 (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/config/default.ts +8 -0
  3. package/dist/config/default.js +12 -5
  4. package/dist/src/app/main.js +9 -0
  5. package/dist/src/config/hono.config.js +2 -1
  6. package/dist/src/const/error_code.js +3 -0
  7. package/dist/src/controllers/agent/bots.controller.js +1 -1
  8. package/dist/src/controllers/common/connect.controll.js +4 -2
  9. package/dist/src/controllers/common/model.js +2 -0
  10. package/dist/src/controllers/common/transformer-factory/agent-calling-listener.js +44 -0
  11. package/dist/src/controllers/form/attachment-upload.controller.js +15 -15
  12. package/dist/src/controllers/form/build/build.controller.js +3 -2
  13. package/dist/src/controllers/form/conversation/clear.controller.js +6 -6
  14. package/dist/src/controllers/form/fill/batch-fill.controller.js +3 -4
  15. package/dist/src/controllers/form/fill/createBatchFillingTransformStream.js +1 -17
  16. package/dist/src/controllers/form/fill/fill.controller.js +2 -1
  17. package/dist/src/controllers/form/fill/utils.js +16 -0
  18. package/dist/src/controllers/report/query/query.controller.js +1 -1
  19. package/dist/src/middleware/botRouter.js +34 -0
  20. package/dist/src/routes/common/common.route.js +2 -1
  21. package/dist/src/services/asr/asr-websocket.js +183 -0
  22. package/dist/src/services/asr/volc-protocol.js +143 -0
  23. package/dist/src/services/fetchCozeInfo.js +1 -1
  24. package/dist/src/utils/createFakeUIMessageStreamResponse.js +25 -0
  25. package/dist/src/utils/createJsonStreamTransformer.js +17 -2
  26. package/dist/src/utils/createSpecialPartMeta.js +5 -0
  27. package/package.json +7 -5
  28. package/src/app/main.ts +12 -0
  29. package/src/config/hono.config.ts +2 -1
  30. package/src/const/error_code.ts +3 -0
  31. package/src/controllers/common/connect.controll.ts +5 -2
  32. package/src/controllers/common/model.ts +2 -0
  33. package/src/controllers/common/transformer-factory/agent-calling-listener.ts +69 -0
  34. package/src/controllers/form/build/build.controller.ts +5 -2
  35. package/src/controllers/form/fill/batch-fill.controller.ts +3 -5
  36. package/src/controllers/form/fill/createBatchFillingTransformStream.ts +1 -20
  37. package/src/controllers/form/fill/fill.controller.ts +2 -0
  38. package/src/controllers/form/fill/utils.ts +18 -0
  39. package/src/middleware/botRouter.ts +41 -0
  40. package/src/routes/common/common.route.ts +2 -1
  41. package/src/services/asr/asr-websocket.ts +231 -0
  42. package/src/services/asr/volc-protocol.ts +220 -0
  43. package/src/utils/createFakeUIMessageStreamResponse.ts +27 -0
  44. package/src/utils/createJsonStreamTransformer.ts +21 -2
  45. package/src/utils/createSpecialPartMeta.ts +7 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * 火山引擎 ASR v3 大模型语音识别 WebSocket 二进制协议
3
+ * 协议文档: https://www.volcengine.com/docs/6561/1354869
4
+ *
5
+ * v3 协议要点:
6
+ * - 4 字节 header
7
+ * - payloadSize 为 4 字节 uint32 大端
8
+ * - FullServerResponse 包含 4 字节 sequence 字段
9
+ * - ErrorResponse 包含 errorCode(4B) + errorMsgSize(4B) + errorMsg
10
+ * - 鉴权在 HTTP Header 中,不在 payload
11
+ */
12
+ const PROTOCOL_VERSION = 0x01;
13
+ const HEADER_SIZE_VALUE = 0x01; // actual header size = 1 * 4 = 4 bytes
14
+ /** 消息类型 */
15
+ export const MSG_TYPE_FULL_CLIENT_REQUEST = 0x01;
16
+ export const MSG_TYPE_AUDIO_ONLY_REQUEST = 0x02;
17
+ export const MSG_TYPE_FULL_SERVER_RESPONSE = 0x09;
18
+ export const MSG_TYPE_ERROR_RESPONSE = 0x0f;
19
+ /** 序列化方式 */
20
+ const SERIALIZATION_JSON = 0x01;
21
+ const SERIALIZATION_NONE = 0x00;
22
+ /** 压缩方式 */
23
+ const COMPRESSION_NONE = 0x00;
24
+ /**
25
+ * 构建 4 字节协议头
26
+ *
27
+ * byte0: version(4bit) | header_size(4bit)
28
+ * byte1: msg_type(4bit) | flags(4bit)
29
+ * byte2: serialization(4bit) | compression(4bit)
30
+ * byte3: reserved
31
+ */
32
+ function build4ByteHeader(msgType, flags, serialization) {
33
+ const header = Buffer.alloc(4);
34
+ header[0] = (PROTOCOL_VERSION << 4) | HEADER_SIZE_VALUE;
35
+ header[1] = (msgType << 4) | (flags & 0x0f);
36
+ header[2] = (serialization << 4) | COMPRESSION_NONE;
37
+ header[3] = 0x00;
38
+ return header;
39
+ }
40
+ /** 构建完整消息:header + payloadSize(4B) + payload */
41
+ function buildMessage(msgType, flags, serialization, payload) {
42
+ const header = build4ByteHeader(msgType, flags, serialization);
43
+ const payloadSize = Buffer.alloc(4);
44
+ payloadSize.writeUInt32BE(payload.length, 0);
45
+ return Buffer.concat([header, payloadSize, payload]);
46
+ }
47
+ /**
48
+ * 解析 4 字节协议头
49
+ */
50
+ export function parseHeader(buffer) {
51
+ const version = buffer[0] >> 4;
52
+ const headerSizeValue = buffer[0] & 0x0f;
53
+ const headerSize = headerSizeValue * 4;
54
+ const msgType = buffer[1] >> 4;
55
+ const flags = buffer[1] & 0x0f;
56
+ const serialization = buffer[2] >> 4;
57
+ const compression = buffer[2] & 0x0f;
58
+ return {
59
+ msgType,
60
+ flags,
61
+ bodyOffset: headerSize,
62
+ };
63
+ }
64
+ /**
65
+ * 读取 payload size(4 字节 uint32 大端)
66
+ */
67
+ export function readPayloadSize(buffer, offset) {
68
+ return buffer.readUInt32BE(offset);
69
+ }
70
+ /**
71
+ * 创建 FullClientRequest(初始化请求)
72
+ *
73
+ * Payload JSON 格式:
74
+ * {
75
+ * user: { uid: string },
76
+ * audio: { format, rate, bits, channel, codec, language },
77
+ * request: { model_name, enable_itn, enable_punc, ... }
78
+ * }
79
+ */
80
+ export function createFullClientRequest() {
81
+ const payload = Buffer.from(JSON.stringify({
82
+ user: {
83
+ uid: '0',
84
+ },
85
+ audio: {
86
+ format: 'pcm',
87
+ rate: 16000,
88
+ bits: 16,
89
+ channel: 1,
90
+ codec: 'raw',
91
+ language: 'zh-CN',
92
+ },
93
+ request: {
94
+ model_name: 'bigmodel',
95
+ enable_itn: true,
96
+ enable_punc: true,
97
+ enable_ddc: false,
98
+ },
99
+ }), 'utf-8');
100
+ return buildMessage(MSG_TYPE_FULL_CLIENT_REQUEST, 0b0000, SERIALIZATION_JSON, payload);
101
+ }
102
+ /**
103
+ * 创建 AudioOnlyRequest(音频数据请求)
104
+ *
105
+ * flags:
106
+ * - 0b0000: 普通音频包
107
+ * - 0b0010: 最后一包音频(负包)
108
+ */
109
+ export function createAudioOnlyRequest(audioData, isLast = false) {
110
+ const flags = isLast ? 0b0010 : 0b0000;
111
+ return buildMessage(MSG_TYPE_AUDIO_ONLY_REQUEST, flags, SERIALIZATION_NONE, audioData);
112
+ }
113
+ /**
114
+ * 解析 JSON payload
115
+ */
116
+ export function parseJsonPayload(payloadBuffer) {
117
+ try {
118
+ return JSON.parse(payloadBuffer.toString('utf-8'));
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * 解析 ErrorResponse
126
+ *
127
+ * 格式:header(4B) + errorCode(4B) + errorMsgSize(4B) + errorMsg
128
+ */
129
+ export function parseErrorPayload(buffer, bodyOffset) {
130
+ const errorCodeOffset = bodyOffset;
131
+ const errorMsgSizeOffset = errorCodeOffset + 4;
132
+ const errorMsgOffset = errorMsgSizeOffset + 4;
133
+ if (buffer.length < errorMsgOffset) {
134
+ return null;
135
+ }
136
+ const code = buffer.readUInt32BE(errorCodeOffset);
137
+ const msgSize = buffer.readUInt32BE(errorMsgSizeOffset);
138
+ if (buffer.length < errorMsgOffset + msgSize) {
139
+ return null;
140
+ }
141
+ const message = buffer.slice(errorMsgOffset, errorMsgOffset + msgSize).toString('utf-8');
142
+ return { code, message };
143
+ }
@@ -2,7 +2,7 @@ import config from 'config';
2
2
  import { logger } from '../logger/index.js';
3
3
  export const fetchCozeInfo = async (bsTeamId) => {
4
4
  const agentHost = config.get('agent.host');
5
- const apiUrl = `http://${agentHost}/api/by_teamid/tokens`;
5
+ const apiUrl = `https://${agentHost}/api/by_teamid/tokens`;
6
6
  const res = await fetch(apiUrl, {
7
7
  method: 'POST',
8
8
  headers: {
@@ -0,0 +1,25 @@
1
+ import { createUIMessageStream, createUIMessageStreamResponse, generateId } from 'ai';
2
+ import { createSpecialPartMeta } from './createSpecialPartMeta.js';
3
+ /**
4
+ * 创建一个与 `streamText().toUIMessageStreamResponse()` 格式一致的假流式响应,
5
+ * 用于在无法调用真实模型时向客户端推送一条指定的文本消息。
6
+ */
7
+ export const createFakeUIMessageStreamResponse = (text) => {
8
+ const stream = createUIMessageStream({
9
+ execute: ({ writer }) => {
10
+ const messageId = generateId();
11
+ const textId = generateId();
12
+ writer.write({ type: 'start', messageId });
13
+ writer.write({ type: 'text-start', id: textId });
14
+ writer.write({
15
+ type: 'text-delta',
16
+ id: textId,
17
+ delta: text,
18
+ providerMetadata: createSpecialPartMeta('error-bot-id-required'),
19
+ });
20
+ writer.write({ type: 'text-end', id: textId });
21
+ writer.write({ type: 'finish', finishReason: 'stop' });
22
+ },
23
+ });
24
+ return createUIMessageStreamResponse({ stream });
25
+ };
@@ -35,6 +35,7 @@ class JsonStreamProcessor {
35
35
  parseCompleted = null;
36
36
  resolveParseCompleted = null;
37
37
  terminated = false;
38
+ passthroughMode = false; // 当启用且遇到非 JSON chunk 时,切换到透传模式
38
39
  constructor(options) {
39
40
  this.options = options;
40
41
  }
@@ -51,6 +52,10 @@ class JsonStreamProcessor {
51
52
  logger.warn('JSON parser not initialized or stream already terminated');
52
53
  return;
53
54
  }
55
+ if (this.passthroughMode) {
56
+ controller.enqueue(chunk);
57
+ return;
58
+ }
54
59
  try {
55
60
  // 1) error chunk → 通知下游后终止
56
61
  if (chunk.type === 'error') {
@@ -71,6 +76,11 @@ class JsonStreamProcessor {
71
76
  }
72
77
  }
73
78
  catch (error) {
79
+ if (this.options.bypassParseError) {
80
+ this.passthroughMode = true;
81
+ controller.enqueue(chunk);
82
+ return;
83
+ }
74
84
  this.enqueueError(controller, error);
75
85
  }
76
86
  }
@@ -120,6 +130,10 @@ class JsonStreamProcessor {
120
130
  });
121
131
  };
122
132
  this.parser.onError = (err) => {
133
+ if (this.options.bypassParseError) {
134
+ this.passthroughMode = true;
135
+ return;
136
+ }
123
137
  if (!this.terminated) {
124
138
  const msg = this.options.onParseError
125
139
  ? this.options.onParseError(err)
@@ -130,8 +144,9 @@ class JsonStreamProcessor {
130
144
  }
131
145
  };
132
146
  this.parser.onEnd = () => {
133
- logger.debug('Parsing completed');
134
- enqueue(' ', JSON.stringify({ appendConfirm: 'save-fields' }));
147
+ if (!this.options.bypassParseError) {
148
+ enqueue(' ', JSON.stringify({ appendConfirm: 'save-fields' }));
149
+ }
135
150
  this.completeParsing();
136
151
  };
137
152
  }
@@ -0,0 +1,5 @@
1
+ export const createSpecialPartMeta = (type, content) => {
2
+ return {
3
+ raw: { data: { specialPart: true, type: 'error-bot-id-required' } },
4
+ };
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baishuyun/chat-backend",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,21 +22,23 @@
22
22
  "lru-cache": "^11.2.7",
23
23
  "parse-sse": "^0.1.0",
24
24
  "pino": "^10.1.0",
25
+ "ws": "^8.18.0",
25
26
  "zod": "^4.1.13",
26
- "@baishuyun/types": "1.1.0",
27
- "@baishuyun/coze-provider": "0.1.1",
28
- "@baishuyun/agents": "0.1.0"
27
+ "@baishuyun/coze-provider": "1.0.1",
28
+ "@baishuyun/agents": "1.0.1",
29
+ "@baishuyun/types": "2.0.1"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@types/config": "^3.3.5",
32
33
  "@types/dotenv": "^8.2.3",
33
34
  "@types/node": "^20.11.17",
35
+ "@types/ws": "^8.5.13",
34
36
  "cross-env": "^10.1.0",
35
37
  "pino-pretty": "^13.1.3",
36
38
  "pm2": "^6.0.14",
37
39
  "tsx": "^4.7.1",
38
40
  "typescript": "^5.8.3",
39
- "@baishuyun/typescript-config": "0.1.0"
41
+ "@baishuyun/typescript-config": "1.0.1"
40
42
  },
41
43
  "scripts": {
42
44
  "dev": "cross-env NODE_ENV=development tsx watch src/index.ts",
package/src/app/main.ts CHANGED
@@ -26,3 +26,15 @@ app.get('/web/api/health', (c) => {
26
26
  service: 'hono-app',
27
27
  });
28
28
  });
29
+
30
+ // 挂载 ASR WebSocket Server
31
+ import { WebSocketServer } from 'ws';
32
+ import { server } from '../config/hono.config.js';
33
+ import { handleASRConnection } from '../services/asr/asr-websocket.js';
34
+
35
+ const wss = new WebSocketServer({
36
+ server: server as any,
37
+ path: '/web/api/asr',
38
+ });
39
+
40
+ wss.on('connection', handleASRConnection);
@@ -47,7 +47,7 @@ app.onError((err, c) => {
47
47
  return c.json({ message: 'Internal Server Error' }, 500);
48
48
  });
49
49
 
50
- serve(
50
+ const server = serve(
51
51
  {
52
52
  fetch: app.fetch,
53
53
  hostname: config.get<string>('app.host') || '',
@@ -58,4 +58,5 @@ serve(
58
58
  }
59
59
  );
60
60
 
61
+ export { server };
61
62
  export default app;
@@ -0,0 +1,3 @@
1
+ export const ERROR_CODE = {
2
+ BOT_ID_MISSING: 10001,
3
+ };
@@ -2,6 +2,7 @@ import { convertToModelMessages, streamText } from 'ai';
2
2
  import type { Context } from 'hono';
3
3
  import { createBaseModel } from './model.js';
4
4
  import type { ICozeInfoOfTokenExchange } from '../../types/coze.js';
5
+ import { logger } from '../../logger/index.js';
5
6
 
6
7
  export const connectToAgent = async (c: Context) => {
7
8
  let requestBody;
@@ -18,13 +19,15 @@ export const connectToAgent = async (c: Context) => {
18
19
 
19
20
  const botId = c.req.header('X-Bot-Id') || '';
20
21
 
22
+ logger.debug('enter common controller');
23
+
21
24
  const stream = streamText({
22
- model: createBaseModel(botId, cozeInfo.cozeToken),
25
+ model: createBaseModel(botId, cozeInfo?.cozeToken),
23
26
  messages: convertToModelMessages(messages),
24
27
  includeRawChunks: true,
25
28
  headers: {
26
29
  'x-user-var': requestBody.userVar,
27
- 'x-user-id': cozeInfo.userId,
30
+ 'x-user-id': cozeInfo?.userId,
28
31
  },
29
32
  });
30
33
 
@@ -1,11 +1,13 @@
1
1
  import { createCoze } from '@baishuyun/coze-provider';
2
2
  import config from 'config';
3
+ import { createAgentCallingListener } from './transformer-factory/agent-calling-listener.js';
3
4
 
4
5
  export const createBaseModel = (botId: string, token?: string) => {
5
6
  const coze = createCoze({
6
7
  apiKey: token || config.get<string>('agent.common.apiKey'),
7
8
  baseURL: config.get<string>('agent.common.baseUrl'),
8
9
  botId: botId,
10
+ extraStreamTransformers: [createAgentCallingListener],
9
11
  });
10
12
 
11
13
  return coze.chat('chat');
@@ -0,0 +1,69 @@
1
+ import {
2
+ createJsonStreamTransformer,
3
+ type IParserCtx,
4
+ } from '../../../utils/createJsonStreamTransformer.js';
5
+ import { JSONParser } from '@streamparser/json';
6
+ import { isTargetElement } from '../../report/query/utils.js';
7
+
8
+ function createJSONParser(): JSONParser {
9
+ return new JSONParser({
10
+ stringBufferSize: undefined,
11
+ numberBufferSize: undefined,
12
+ separator: '',
13
+ paths: ['$.type', '$.prompt', '$.agent'],
14
+ keepStack: true,
15
+ });
16
+ }
17
+
18
+ export interface IAgentCallingCtx {
19
+ type: string;
20
+ prompt: string;
21
+ agent: string;
22
+ }
23
+
24
+ type PathHandler = {
25
+ path: string;
26
+ handler: (ctx: IParserCtx<IAgentCallingCtx>, value: unknown) => void;
27
+ };
28
+
29
+ export const fieldHandlers: PathHandler[] = [
30
+ {
31
+ path: '$.type',
32
+ handler: (ctx, value) => ctx.setPartialResult({ type: value as string }),
33
+ },
34
+ {
35
+ path: '$.prompt',
36
+ handler: (ctx, value) => ctx.setPartialResult({ prompt: value as string }),
37
+ },
38
+ {
39
+ path: '$.agent',
40
+ handler: (ctx, value) => ctx.setPartialResult({ agent: value as string }),
41
+ },
42
+ ];
43
+
44
+ function handleParsedValue(ctx: IParserCtx<IAgentCallingCtx>): void {
45
+ const { parsedInfo, getResult, currentChunkId, deltaChunkEnqueuer: enqueueTextDelta, ctrl } = ctx;
46
+ const { value } = parsedInfo;
47
+
48
+ for (const { path, handler } of fieldHandlers) {
49
+ if (isTargetElement(path, parsedInfo)) {
50
+ handler(ctx, value);
51
+ break;
52
+ }
53
+ }
54
+
55
+ enqueueTextDelta(
56
+ `${JSON.stringify(value)},`,
57
+ { type: 'agent-calling', result: JSON.stringify(getResult()) },
58
+ currentChunkId,
59
+ true
60
+ );
61
+ }
62
+
63
+ export const createAgentCallingListener = () => {
64
+ return createJsonStreamTransformer({
65
+ bypassParseError: true,
66
+ createJSONParser,
67
+ onParseValue: handleParsedValue,
68
+ });
69
+ };
@@ -32,6 +32,7 @@ export const buildForm = async (c: Context) => {
32
32
  ]);
33
33
 
34
34
  const allMsg = requestBody.messages || [];
35
+
35
36
  const lastUserMsg = allMsg.length > 0 ? allMsg[allMsg.length - 1] : {
36
37
  role: "user",
37
38
  parts: [],
@@ -46,14 +47,16 @@ export const buildForm = async (c: Context) => {
46
47
 
47
48
  return {
48
49
  type: "text",
49
- text: `【确认搭建:${formName}】 ${p.text}`
50
+ text: `【确认搭建:${formName}】 ${p.text}`
50
51
  };
51
52
  });
52
53
  }
53
54
 
55
+ const lastSecondMsg = allMsg.length > 1 ? allMsg[allMsg.length - 2] : null;
56
+
54
57
  const stream = streamText({
55
58
  model,
56
- messages: convertToModelMessages([lastUserMsg]),
59
+ messages: convertToModelMessages([lastSecondMsg, lastUserMsg].filter(Boolean)),
57
60
  includeRawChunks: true,
58
61
  headers: {
59
62
  "x-user-stage": intent as string,
@@ -11,7 +11,7 @@ import {
11
11
  } from 'ai';
12
12
  import { logger } from '../../../logger/index.js';
13
13
  import { createBatchFillingModel } from './model.js';
14
- import { mode2part } from './utils.js';
14
+ import { extraGeneratePromptFromRandomContent, mode2part } from './utils.js';
15
15
  import type { FormFillingMode } from '@baishuyun/types';
16
16
 
17
17
  const getModelMessagesFromUserMessages = ({
@@ -36,7 +36,7 @@ const getModelMessagesFromUserMessages = ({
36
36
  parts: [
37
37
  {
38
38
  type: 'text',
39
- text: `\n\nformStructure: ${JSON.stringify(formStructure)}\n\n`,
39
+ text: `\n\n${extraGeneratePromptFromRandomContent()}\n\nformStructure: ${JSON.stringify(formStructure)}\n\n`,
40
40
  },
41
41
  mode2part(mode),
42
42
  {
@@ -56,7 +56,7 @@ const getModelMessagesFromUserMessages = ({
56
56
 
57
57
  lastUserMsg.parts.push({
58
58
  type: 'text',
59
- text: `formStructure: ${JSON.stringify(formStructure)}`,
59
+ text: `\n\n${extraGeneratePromptFromRandomContent()}\n\nformStructure: ${JSON.stringify(formStructure)}\n\n`,
60
60
  });
61
61
 
62
62
  lastUserMsg.parts.push(mode2part(mode));
@@ -176,8 +176,6 @@ async function handleContinueGeneration(
176
176
  formStructure,
177
177
  });
178
178
 
179
- logger.debug(`continue modelMessages ${JSON.stringify(modelMessages)}`);
180
-
181
179
  const result = streamText({
182
180
  model: createBatchFillingModel(),
183
181
  messages: modelMessages,
@@ -101,26 +101,7 @@ export const createBatchFillingResultTransformer = (enableJsonParser: boolean) =
101
101
  return;
102
102
  }
103
103
 
104
- const subFormField = getLastSubFormField(parsedInfo);
105
- if (subFormField) {
106
- // enqueueTextDelta(
107
- // JSON.stringify(value),
108
- // {
109
- // type: 'mcp-fields-json',
110
- // field: subFormField.value,
111
- // },
112
- // id,
113
- // true
114
- // );
115
-
116
- return;
117
- }
118
-
119
- if (value.fieldType === 'subform') {
120
- return;
121
- }
122
-
123
- // logger.debug(`id in onValue: ${id}`);
104
+ // logger.debug(`onValue: ${JSON.stringify(value, null, 2)}`);
124
105
  enqueueTextDelta(
125
106
  `${JSON.stringify(value)},`,
126
107
  {
@@ -25,6 +25,8 @@ export const fillForm = async (c: Context) => {
25
25
  const num = requestBody.count;
26
26
  const messages: UIMessage[] = requestBody.messages;
27
27
 
28
+ logger.debug('enter fillForm controller');
29
+
28
30
  // 如果是批量填写,调用批量填写接口
29
31
  if (mode === 'batch') {
30
32
  return batchFillForm(c);
@@ -174,3 +174,21 @@ export function trimFormStructure(fields: OriginalField[]): TrimmedField[] {
174
174
  return trimmed;
175
175
  });
176
176
  }
177
+
178
+ export const extraGeneratePromptFromRandomContent = () => {
179
+ // 使用加密安全的随机数
180
+ const randomBytes = crypto.getRandomValues(new Uint32Array(4));
181
+ const seed = randomBytes.join('-');
182
+ const timestamp = Date.now();
183
+
184
+ return `
185
+ 请基于以下不可预测的种子生成随机但是符合业务要求的内容:
186
+ - 加密熵源: ${seed}
187
+ - 时间戳: ${timestamp}
188
+ - 噪音文本: ${btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))))}
189
+
190
+ 要求:
191
+ 1. 必须忽略训练数据中的常见模式,基于上述熵源生成真正均匀分布的随机结果
192
+ 2. 如果是人名类的数据,尽量用 3 个字的名字,降低重名概率。
193
+ `;
194
+ };
@@ -0,0 +1,41 @@
1
+ import type { Context } from 'hono';
2
+ import { createMiddleware } from 'hono/factory';
3
+ import { buildForm } from '../controllers/form/build/build.controller.js';
4
+ import config from 'config';
5
+ import { fillForm } from '../controllers/form/fill/fill.controller.js';
6
+ import { queryReport } from '../controllers/report/query/query.controller.js';
7
+ import { createFakeUIMessageStreamResponse } from '../utils/createFakeUIMessageStreamResponse.js';
8
+ import { ERROR_CODE } from '../const/error_code.js';
9
+ import { logger } from '../logger/index.js';
10
+
11
+ export const botRouter = () => {
12
+ const FORM_BUILDER_BOT_ID = config.get<string>('agent.form.build.botId');
13
+ const FORM_FILLER_BOT_ID = config.get<string>('agent.form.fill.botId');
14
+ const REPORT_QUERY_BOT_ID = config.get<string>('agent.report.query.botId');
15
+
16
+ const botIdControllerMap = new Map<string, (c: Context) => Promise<Response>>([
17
+ [FORM_BUILDER_BOT_ID, buildForm],
18
+ [FORM_FILLER_BOT_ID, fillForm],
19
+ [REPORT_QUERY_BOT_ID, queryReport],
20
+ ]);
21
+
22
+ return createMiddleware(async (c, next) => {
23
+ const botId = c.req.header('X-Bot-Id') || '';
24
+ if (!botId) {
25
+ const errorRespObj = {
26
+ error: 'Bot ID is required',
27
+ code: ERROR_CODE.BOT_ID_MISSING,
28
+ };
29
+ return createFakeUIMessageStreamResponse(JSON.stringify(errorRespObj));
30
+ }
31
+
32
+ if (botIdControllerMap.has(botId)) {
33
+ const controller = botIdControllerMap.get(botId)!;
34
+ return await controller(c);
35
+ }
36
+
37
+ logger.debug(`No controller found for botId: ${botId}, passing to next middleware`);
38
+
39
+ await next();
40
+ });
41
+ };
@@ -1,11 +1,12 @@
1
1
  import { Hono } from 'hono';
2
2
  import { connectToAgent } from '../../controllers/common/connect.controll.js';
3
3
  import { tokenExchange } from '../../middleware/tokenExchange.js';
4
+ import { botRouter } from '../../middleware/botRouter.js';
4
5
 
5
6
  export const createCommonRouter = () => {
6
7
  const commonRouter = new Hono();
7
8
 
8
- commonRouter.post('/connect', tokenExchange(), connectToAgent);
9
+ commonRouter.post('/connect', /*tokenExchange(),*/ botRouter(), connectToAgent);
9
10
 
10
11
  return commonRouter;
11
12
  };