@ray-js/t-agent-plugin-aistream 0.2.0-beta-2 → 0.2.0-beta-3

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.
@@ -0,0 +1,197 @@
1
+ import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
2
+ import "core-js/modules/es.json.stringify.js";
3
+ import { authorize } from './ttt';
4
+ import { DEFAULT_TOKEN_API, DEFAULT_TOKEN_API_VERSION, globalAIStreamClient } from '../global';
5
+ import { ConnectClientType, AIStreamAttributePayloadType, AIStreamAttributeType, AIStreamChatSysWorkflow } from '../AIStreamTypes';
6
+ import logger from './logger';
7
+ class AsrAgent {
8
+ constructor(options) {
9
+ /** 数据链接,一个agent保持一个链接就可以 */
10
+ /** 当前请求的session,每次请求都需要是一个新的session */
11
+ /** 音频流监听 */
12
+ /** 录音时长定时器 */
13
+ _defineProperty(this, "recordDurationTimer", null);
14
+ this.options = options;
15
+ }
16
+
17
+ /** 获取录音权限 */
18
+ getRecordScope() {
19
+ return authorize({
20
+ scope: 'scope.record'
21
+ }).then(() => true, () => false);
22
+ }
23
+
24
+ /** 获取数据链接,一般只有一个链接就可以 */
25
+ getConnection(clientType, deviceId) {
26
+ if (this.streamConn) {
27
+ return this.streamConn;
28
+ }
29
+ // 创建 activeSession
30
+ this.streamConn = globalAIStreamClient.getConnection({
31
+ clientType: clientType || ConnectClientType.APP,
32
+ deviceId: deviceId
33
+ });
34
+ return this.streamConn;
35
+ }
36
+ createSession() {
37
+ var _this$activeSession;
38
+ // 每次新的请求,都需要重新创建一个Session
39
+ const {
40
+ clientType,
41
+ deviceId,
42
+ tokenApi,
43
+ tokenApiVersion,
44
+ agentId
45
+ } = this.options;
46
+ const streamConn = this.getConnection(clientType, deviceId);
47
+ // 如果有激活的,需要释放
48
+ if ((_this$activeSession = this.activeSession) !== null && _this$activeSession !== void 0 && _this$activeSession.sessionId) {
49
+ this.activeSession.close();
50
+ }
51
+ const activeSession = streamConn.createSession({
52
+ api: tokenApi || DEFAULT_TOKEN_API,
53
+ apiVersion: tokenApiVersion || DEFAULT_TOKEN_API_VERSION,
54
+ solutionCode: agentId
55
+ });
56
+ this.activeSession = activeSession;
57
+ return this.activeSession;
58
+ }
59
+
60
+ /** 开始录音时长监听 */
61
+ startRecordTimer() {
62
+ const {
63
+ maxDuration
64
+ } = this.options;
65
+ if (maxDuration) {
66
+ if (this.recordDurationTimer) {
67
+ clearTimeout(this.recordDurationTimer);
68
+ }
69
+ this.recordDurationTimer = setTimeout(() => {
70
+ this.stop(true);
71
+ }, maxDuration * 1000);
72
+ }
73
+ }
74
+ async start() {
75
+ const hasScope = await this.getRecordScope();
76
+ if (!hasScope) {
77
+ throw new Error('authorize failed');
78
+ }
79
+ const {
80
+ onMessage,
81
+ onFinish,
82
+ onError
83
+ } = this.options || {};
84
+ const activeSession = await this.createSession();
85
+ const attribute = {
86
+ 'processing.interrupt': 'false',
87
+ 'asr.enableVad': 'false',
88
+ 'sys.workflow': AIStreamChatSysWorkflow.ASR
89
+ };
90
+ const activeEvent = await activeSession.startEvent({
91
+ userData: [{
92
+ type: AIStreamAttributeType.AI_CHAT,
93
+ payloadType: AIStreamAttributePayloadType.STRING,
94
+ value: JSON.stringify(attribute)
95
+ }]
96
+ });
97
+ this.activeEvent = activeEvent;
98
+ const audioStream = activeEvent.stream({
99
+ type: 'audio'
100
+ });
101
+ this.audioStream = audioStream;
102
+ await audioStream.start();
103
+ this.startRecordTimer();
104
+ activeEvent.on('data', entry => {
105
+ if (entry.type === 'text') {
106
+ console.log('text', entry, JSON.parse(entry.body.text));
107
+ let data = {
108
+ text: ''
109
+ };
110
+ try {
111
+ data = JSON.parse(entry.body.text) || {};
112
+ } catch (error) {
113
+ logger.error('JSON.parse error', error);
114
+ }
115
+ typeof onMessage === 'function' && onMessage(data);
116
+ }
117
+ });
118
+ activeEvent.on('finish', () => {
119
+ this.stop();
120
+ typeof onFinish === 'function' && onFinish();
121
+ });
122
+ activeEvent.on('error', error => {
123
+ typeof onError === 'function' && onError(error);
124
+ });
125
+ }
126
+ async stop(isAbort) {
127
+ var _this$activeSession2;
128
+ if (this.recordDurationTimer) {
129
+ clearTimeout(this.recordDurationTimer);
130
+ this.recordDurationTimer = null;
131
+ }
132
+ if (this.audioStream) {
133
+ await this.audioStream.stop();
134
+ this.audioStream = null;
135
+ }
136
+ if (this.activeEvent) {
137
+ if (isAbort) {
138
+ await this.activeEvent.abort();
139
+ } else {
140
+ await this.activeEvent.end();
141
+ }
142
+ this.activeEvent = null;
143
+ }
144
+ if (isAbort) {
145
+ const {
146
+ onAbort
147
+ } = this.options || {};
148
+ typeof onAbort === 'function' && onAbort();
149
+ }
150
+ await ((_this$activeSession2 = this.activeSession) === null || _this$activeSession2 === void 0 ? void 0 : _this$activeSession2.close());
151
+ }
152
+ async abort() {
153
+ await this.stop(true);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * 创建一个AsrAgent实例,用于语音转文本
159
+ * @param options AsrAgentOptions
160
+ * @param options.agentId 必填 语音转文本的agentId
161
+ * @param options.tokenApi 语音转文本的tokenApi
162
+ * @param options.tokenApiVersion 语音转文本的tokenApiVersion
163
+ * @param options.clientType 语音转文本的clientType
164
+ * @param options.deviceId 语音转文本的deviceId
165
+ * @example
166
+ * const asrAgent = createAsrAgent({
167
+ * agentId: 'asr-agent',
168
+ * tokenApi: 'm.thing.aigc.basic.server.token',
169
+ * tokenApiVersion: '1.0',
170
+ * clientType: ConnectClientType.APP,
171
+ * deviceId: 'deviceId',
172
+ * maxDuration: 60,
173
+ * onMessage: (message) => {
174
+ * console.log('onMessage', message);
175
+ * },
176
+ * onFinish: () => {
177
+ * console.log('onFinish');
178
+ * },
179
+ * onError: (error) => {
180
+ * console.log('onError', error);
181
+ * },
182
+ * onAbort: () => {
183
+ * console.log('onAbort');
184
+ * },
185
+ * });
186
+ * // 开始录音
187
+ * asrAgent.start()
188
+ * // 停止录音
189
+ * asrAgent.stop()
190
+ * // 中断录音
191
+ * asrAgent.abort()
192
+ * @returns
193
+ */
194
+ export function createAsrAgent(options) {
195
+ const asrAgent = new AsrAgent(options);
196
+ return asrAgent;
197
+ }
@@ -1,3 +1,4 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
1
2
  import "core-js/modules/es.array.sort.js";
2
3
  import "core-js/modules/es.json.stringify.js";
3
4
  import "core-js/modules/es.regexp.exec.js";
@@ -149,21 +150,31 @@ mock.hooks.hook('unregisterVoiceAmplitudes', context => {
149
150
  mock.hooks.hook('sendEventEnd', context => {
150
151
  const session = getSession(context.options.sessionId, context.options.eventId);
151
152
  context.result = true;
153
+ const event = session.currentEvent;
152
154
  (async () => {
155
+ await event.asrStatus.promise;
153
156
  const ctx = {
154
- data: session.currentEvent.data,
157
+ data: event.data,
155
158
  responseText: ''
156
159
  };
157
- await session.currentEvent.asrStatus.promise;
158
160
  await mock.hooks.callHook('sendToAIStream', ctx);
159
161
  const text = ctx.responseText || '⚠️ No mock text response matched!';
160
162
  const words = splitString(text);
163
+ if (event.controller.signal.aborted) {
164
+ return;
165
+ }
161
166
  session.replyEvent(EventType.EVENT_START);
162
167
  await mock.sleep(500);
168
+ if (event.controller.signal.aborted) {
169
+ return;
170
+ }
163
171
  const bizId = generateId();
164
172
  session.replyText(StreamFlag.START);
165
173
  for (const word of words) {
166
174
  await mock.sleep(100);
175
+ if (event.controller.signal.aborted) {
176
+ return;
177
+ }
167
178
  session.replyText(StreamFlag.IN_PROGRESS, {
168
179
  bizType: ReceivedTextPacketType.NLG,
169
180
  eof: ReceivedTextPacketEof.CONTINUE,
@@ -175,6 +186,10 @@ mock.hooks.hook('sendEventEnd', context => {
175
186
  }
176
187
  });
177
188
  }
189
+ await mock.sleep(100);
190
+ if (event.controller.signal.aborted) {
191
+ return;
192
+ }
178
193
  session.replyText(StreamFlag.IN_PROGRESS, {
179
194
  bizType: ReceivedTextPacketType.NLG,
180
195
  eof: ReceivedTextPacketEof.END,
@@ -186,8 +201,14 @@ mock.hooks.hook('sendEventEnd', context => {
186
201
  }
187
202
  });
188
203
  await mock.sleep(10);
204
+ if (event.controller.signal.aborted) {
205
+ return;
206
+ }
189
207
  session.replyText(StreamFlag.END);
190
208
  await mock.sleep(500);
209
+ if (event.controller.signal.aborted) {
210
+ return;
211
+ }
191
212
  session.replyEvent(EventType.EVENT_END);
192
213
  session.currentEvent = null;
193
214
  })();
@@ -249,6 +270,12 @@ mock.hooks.hook('startRecordAndSendAudioData', context => {
249
270
  });
250
271
  await mock.sleep(100);
251
272
  session.replyText(StreamFlag.END);
273
+ if (session.currentEvent) {
274
+ session.currentEvent.data.push({
275
+ type: 'text',
276
+ text
277
+ });
278
+ }
252
279
  (_finishResolve = finishResolve) === null || _finishResolve === void 0 || _finishResolve();
253
280
  });
254
281
  await mock.sleep(100);
@@ -426,4 +453,24 @@ mock.hooks.hook('insertRecord', context => {
426
453
  context.result = {
427
454
  id
428
455
  };
456
+ });
457
+ mock.hooks.hook('updateRecord', context => {
458
+ const options = context.options;
459
+ const id = options.id;
460
+
461
+ // 默认倒序
462
+ const records = filterRecords({
463
+ id: [id]
464
+ });
465
+ if (!records.length) {
466
+ throw new Error('record not exists');
467
+ }
468
+ const newRecord = _objectSpread(_objectSpread({}, records[0]), options);
469
+ mock.setRecord({
470
+ id,
471
+ record: newRecord
472
+ });
473
+ context.result = {
474
+ newRecord
475
+ };
429
476
  });
@@ -9,3 +9,5 @@ export * from './AIStream';
9
9
  export * from './observer';
10
10
  export * from './sendMessage';
11
11
  export * from './version';
12
+ export * from './createAsrAgent';
13
+ export * from './actions';
@@ -8,4 +8,6 @@ export * from './url';
8
8
  export * from './AIStream';
9
9
  export * from './observer';
10
10
  export * from './sendMessage';
11
- export * from './version';
11
+ export * from './version';
12
+ export * from './createAsrAgent';
13
+ export * from './actions';
@@ -41,6 +41,7 @@ declare const mock: {
41
41
  getRecords: <T = any>() => Row<T>[];
42
42
  getRecord: <T_1 = any>(id: number) => Row<T_1> | undefined;
43
43
  setRecord: <T_2 = any>(entry: Row<T_2>) => void;
44
+ updateRecord: <T_3 = any>(entry: Row<T_3>) => void;
44
45
  deleteRecord: (id: number) => void;
45
46
  clearRecords: () => void;
46
47
  getId: () => number;
@@ -52,6 +52,14 @@ const mock = {
52
52
  data: JSON.stringify(mock.getRecords())
53
53
  });
54
54
  },
55
+ updateRecord: entry => {
56
+ mock.getRecords();
57
+ mock.database.set(entry.id, entry);
58
+ ty.setStorageSync({
59
+ key: 'AIStreamMockDatabase',
60
+ data: JSON.stringify(mock.getRecords())
61
+ });
62
+ },
55
63
  deleteRecord: id => {
56
64
  mock.getRecords();
57
65
  mock.database.delete(id);
@@ -11,6 +11,12 @@ interface AsyncTTTFnParams<P> {
11
11
  }) => void;
12
12
  [key: string]: any;
13
13
  }
14
+ export declare class TTTError extends Error {
15
+ message: string;
16
+ errorCode: string | number;
17
+ constructor(message: string, errorCode: string | number);
18
+ toString(): string;
19
+ }
14
20
  export declare const getEnableMock: () => boolean;
15
21
  export declare const setEnableMock: (enable: boolean) => boolean;
16
22
  export declare function promisify<T extends AsyncTTTFnParams<any>>(fn: (options: any) => void, enableMock?: boolean): (options?: Omit<T, 'success' | 'fail'>) => Promise<Parameters<NonNullable<T["success"]>>[0]>;
@@ -2,30 +2,40 @@ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
2
  import { isDevTools } from './ttt';
3
3
  import logger from './logger';
4
4
  import { mock } from './mock';
5
+ export class TTTError extends Error {
6
+ constructor(message, errorCode) {
7
+ super(message);
8
+ this.message = message;
9
+ this.errorCode = errorCode;
10
+ }
11
+ toString() {
12
+ return "TTTError: ".concat(this.message, ", errorCode: ").concat(this.errorCode);
13
+ }
14
+ }
5
15
  let callId = 100000;
6
16
  export const getEnableMock = () => {
7
17
  try {
8
18
  const result = ty.getStorageSync({
9
- key: 'AIAssistantEnableMock'
19
+ key: 'AIStreamEnableMock'
10
20
  });
11
21
  // @ts-ignore
12
22
  if (result && (result === 'true' || result.data === 'true')) {
13
23
  return true;
14
24
  }
15
25
  } catch (e) {
16
- console.error('获取AIAssistantEnableMock配置失败', e);
26
+ console.error('获取AIStreamEnableMock配置失败', e);
17
27
  }
18
28
  return isDevTools();
19
29
  };
20
30
  export const setEnableMock = enable => {
21
31
  try {
22
32
  ty.setStorageSync({
23
- key: 'AIAssistantEnableMock',
33
+ key: 'AIStreamEnableMock',
24
34
  data: String(enable)
25
35
  });
26
36
  return true;
27
37
  } catch (e) {
28
- console.error('设置AIAssistantEnableMock配置失败', e);
38
+ console.error('设置AIStreamEnableMock配置失败', e);
29
39
  return false;
30
40
  }
31
41
  };
@@ -44,6 +54,16 @@ export function promisify(fn) {
44
54
  };
45
55
  const reject = error => {
46
56
  logger.debug("TTT error #".concat(id, " %c").concat(fn.name), 'background: red; color: white', error);
57
+
58
+ // 这是 TTT 的错误
59
+ if (error.errorCode != null && error.errorMsg != null) {
60
+ var _error$innerError;
61
+ if (((_error$innerError = error.innerError) === null || _error$innerError === void 0 ? void 0 : _error$innerError.errorCode) != null && error.innerError.errorMsg != null) {
62
+ error = new TTTError(error.innerError.errorMsg, error.innerError.errorCode);
63
+ } else {
64
+ error = new TTTError(error.errorMsg, error.errorCode);
65
+ }
66
+ }
47
67
  _reject(error);
48
68
  };
49
69
  const context = {
@@ -1,33 +1,33 @@
1
1
  import { ChatAgent, ChatMessage, ComposeHandler, InputBlock } from '@ray-js/t-agent';
2
2
  import { ConnectClientType } from './AIStreamTypes';
3
- import { ChatHistoryStore, StoredMessageObject } from './ChatHistoryStore';
3
+ import { ChatHistoryLocalStore, StoredMessageObject } from './ChatHistoryStore';
4
4
  export interface AIStreamOptions {
5
5
  /** client 类型: 1-作为设备代理, 2-作为 App */
6
6
  clientType?: ConnectClientType;
7
7
  /** 代理的设备ID, clientType == 1 时必传 */
8
8
  deviceId?: string;
9
+ /** Agent ID */
9
10
  agentId?: string;
10
- channel?: string;
11
- /** 打开小程序的链接,取链接参数字段 aiPtChannel */
12
- aiPtChannel?: string;
13
- sessionId?: string;
14
- tokenApi?: string;
15
- tokenApiVersion?: string;
11
+ /** 获取 agent token 的参数 */
12
+ tokenOptions?: {
13
+ api: string;
14
+ version: string;
15
+ extParams?: Record<string, any>;
16
+ };
16
17
  /** 历史消息数量,默认1000,为0的话,则已分页的方式取全部数据 */
17
18
  historySize?: number;
18
19
  /** 是否支持多模态 */
19
20
  multiModal?: boolean;
21
+ /** 是否将输入块传递给智能体,默认为 true,设置为 false 时,需要你自己编写 onInputBlocksPush Hook 来处理输入块 */
20
22
  wireInput?: boolean;
21
23
  /** 索引ID,如果传了 createChatHistoryStore,则会覆盖 */
22
24
  indexId?: string;
23
25
  /** 家庭id,不填默认当前家庭 */
24
26
  homeId?: number;
25
- /** 自定义消息存储, 返回的实例需要实现 ChatHistoryStore 接口 */
26
- createChatHistoryStore?: (agent: ChatAgent) => ChatHistoryStore;
27
- messageOptions?: {
28
- device_id?: string;
29
- [key: string]: string;
30
- };
27
+ /** 是否在 onAgentStart 阶段就建立连接 */
28
+ earlyStart?: boolean;
29
+ /** 自定义消息存储, 返回的实例需要实现 ChatHistoryLocalStore 接口, 返回null则不存储历史聊天记录 */
30
+ createChatHistoryStore?: (agent: ChatAgent) => ChatHistoryLocalStore | null;
31
31
  }
32
32
  export interface AIStreamHooks {
33
33
  onMessageParse: (msgItem: StoredMessageObject, result: {
@@ -39,7 +39,7 @@ export interface AIStreamHooks {
39
39
  }
40
40
  export declare function withAIStream(options?: AIStreamOptions): (agent: ChatAgent) => {
41
41
  hooks: import("hookable").Hookable<any, string>;
42
- assistant: {
42
+ aiStream: {
43
43
  send: (blocks: InputBlock[], signal?: AbortSignal, extraOptions?: Record<string, any>) => {
44
44
  response: import("@ray-js/t-agent").StreamResponse;
45
45
  metaPromise: Promise<Record<string, any>>;
@@ -53,12 +53,12 @@ export declare function withAIStream(options?: AIStreamOptions): (agent: ChatAge
53
53
  options: AIStreamOptions;
54
54
  removeMessage: (message: ChatMessage) => Promise<void>;
55
55
  clearAllMessages: () => Promise<void>;
56
- feedback: ({ requestId, type, }: {
56
+ feedback: ({ requestId, type }: {
57
57
  requestId: string;
58
- type: 'like' | 'unlike';
58
+ type: string;
59
59
  }) => Promise<{
60
60
  thingjson?: any;
61
61
  data: string;
62
- } | null>;
62
+ }>;
63
63
  };
64
64
  };