@ray-js/t-agent-plugin-aistream 0.2.0-beta-11 → 0.2.0-beta-13

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.
@@ -34,7 +34,8 @@ export class AIStreamConnection {
34
34
  _defineProperty(this, "promise", null);
35
35
  _defineProperty(this, "observer", null);
36
36
  _defineProperty(this, "onStateChanged", entry => {
37
- if (entry.type === 'connectionState' && entry.body.connectionId === this.connectionId) {
37
+ // 一个小程序只能启动一个 connection,所以这里不判断 id
38
+ if (entry.type === 'connectionState') {
38
39
  this.state = entry.body.connectState;
39
40
  this.activeSessions.forEach(session => {
40
41
  if (session.sessionId) {
@@ -347,7 +348,7 @@ export class AIStreamEvent {
347
348
  userData: chunk.userData
348
349
  });
349
350
  }
350
- await sendEventPayloadEnd({
351
+ sendEventPayloadEnd({
351
352
  eventId: this.eventId,
352
353
  sessionId: this.sessionId,
353
354
  dataChannel
@@ -380,7 +381,7 @@ export class AIStreamEvent {
380
381
  stream.started = true;
381
382
  if (source.type === 'audio') {
382
383
  if (source.amplitudeCount) {
383
- await registerRecordAmplitudes({
384
+ registerRecordAmplitudes({
384
385
  count: source.amplitudeCount
385
386
  });
386
387
  }
@@ -404,7 +405,7 @@ export class AIStreamEvent {
404
405
  }
405
406
  if (source.type === 'audio') {
406
407
  if (source.amplitudeCount) {
407
- await unregisterVoiceAmplitudes({
408
+ unregisterVoiceAmplitudes({
408
409
  count: source.amplitudeCount
409
410
  });
410
411
  }
@@ -421,7 +422,7 @@ export class AIStreamEvent {
421
422
  // userData: source.userData,
422
423
  // });
423
424
  }
424
- await sendEventPayloadEnd({
425
+ sendEventPayloadEnd({
425
426
  eventId: this.eventId,
426
427
  sessionId: this.sessionId,
427
428
  dataChannel
@@ -449,7 +450,8 @@ export class AIStreamEvent {
449
450
  return;
450
451
  }
451
452
  if (this.sessionId) {
452
- await sendEventChatBreak({
453
+ // 故意不等,直接发送中断事件
454
+ sendEventChatBreak({
453
455
  eventId: this.eventId,
454
456
  sessionId: this.sessionId,
455
457
  userData: options === null || options === void 0 ? void 0 : options.userData
@@ -153,14 +153,18 @@ mock.hooks.hook('closeSession', context => {
153
153
  map.delete(sessionId);
154
154
  context.result = true;
155
155
  });
156
- mock.hooks.hook('sendEventStart', context => {
156
+ mock.hooks.hook('sendEventStart', async context => {
157
157
  const {
158
158
  options
159
159
  } = context;
160
160
  const {
161
161
  sessionId
162
162
  } = options;
163
+
164
+ // throw new Error('sendEventStart is deprecated, use sendEventStartV2 instead');
165
+
163
166
  const session = getSession(sessionId);
167
+ await mock.sleep(200);
164
168
  if (session.currentEvent) {
165
169
  throw new Error('sendEventStart already in event');
166
170
  }
@@ -216,13 +220,14 @@ mock.hooks.hook('unregisterVoiceAmplitudes', context => {
216
220
  mock.data.delete('recordAmplitudesCount');
217
221
  context.result = true;
218
222
  });
219
- mock.hooks.hook('sendEventEnd', context => {
223
+ mock.hooks.hook('sendEventEnd', async context => {
220
224
  const session = getSession(context.options.sessionId, context.options.eventId);
221
225
  context.result = true;
222
226
  const event = session.currentEvent;
223
227
  if (event.eventId !== context.options.eventId) {
224
228
  throw new Error('sendEventEnd eventId mismatch');
225
229
  }
230
+ await mock.sleep(100);
226
231
  (async () => {
227
232
  var _ctx$responseSkills;
228
233
  await event.asr.promise;
@@ -249,7 +254,6 @@ mock.hooks.hook('sendEventEnd', context => {
249
254
  return;
250
255
  }
251
256
  event.replyEvent(EventType.EVENT_END);
252
- console.log('No data to send, ending event, session.currentEvent = null');
253
257
  session.currentEvent = null;
254
258
  return;
255
259
  }
@@ -319,28 +323,28 @@ mock.hooks.hook('sendEventEnd', context => {
319
323
  return;
320
324
  }
321
325
  event.replyEvent(EventType.EVENT_END);
322
- console.log('finish sendEventEnd session.currentEvent = null');
323
326
  session.currentEvent = null;
324
327
  })();
325
328
  });
326
- mock.hooks.hook('sendEventPayloadEnd', context => {
329
+ mock.hooks.hook('sendEventPayloadEnd', async context => {
327
330
  getSession(context.options.sessionId, context.options.eventId);
328
331
  context.result = true;
332
+ await mock.sleep(100);
329
333
  });
330
- mock.hooks.hook('sendEventChatBreak', context => {
334
+ mock.hooks.hook('sendEventChatBreak', async context => {
331
335
  const session = getSession(context.options.sessionId, context.options.eventId);
332
336
  session.currentEvent.controller.abort(new Error('sendEventChatBreak'));
333
- console.log('sendEventChatBreak currentEvent = null');
334
337
  session.currentEvent = null;
335
338
  context.result = true;
336
339
  });
337
- mock.hooks.hook('startRecordAndSendAudioData', context => {
340
+ mock.hooks.hook('startRecordAndSendAudioData', async context => {
338
341
  const session = getSession(context.options.sessionId);
339
342
  if (!session.currentEvent) {
340
343
  throw new Error('startRecordAndSendAudioData event not exists');
341
344
  }
342
345
  const event = session.currentEvent;
343
346
  context.result = true;
347
+ await mock.sleep(100);
344
348
  (async () => {
345
349
  const {
346
350
  finishController
@@ -378,7 +382,7 @@ mock.hooks.hook('startRecordAndSendAudioData', context => {
378
382
  }
379
383
 
380
384
  // 终止识别到出完整结果的延迟
381
- await mock.sleep(5000);
385
+ await mock.sleep(500);
382
386
  if (event.controller.signal.aborted) {
383
387
  return;
384
388
  }
@@ -437,13 +441,14 @@ mock.hooks.hook('startRecordAndSendAudioData', context => {
437
441
  }
438
442
  })();
439
443
  });
440
- mock.hooks.hook('stopRecordAndSendAudioData', context => {
444
+ mock.hooks.hook('stopRecordAndSendAudioData', async context => {
441
445
  const session = getSession(context.options.sessionId);
442
446
  if (!session.currentEvent) {
443
447
  throw new Error('stopRecordAndSendAudioData event not exists');
444
448
  }
445
449
  session.currentEvent.asr.finishController.abort(new Error('stopRecordAndSendAudioData'));
446
450
  context.result = true;
451
+ await mock.sleep(100);
447
452
  });
448
453
  mock.hooks.hook('startRecordAndSendVideoData', context => {
449
454
  context.result = true;
@@ -7,6 +7,7 @@ import { AIStreamAttributePayloadType, AIStreamAttributeType, AIStreamChatSysWor
7
7
  import { EmitterEvent, generateId, safeParseJSON, StreamResponse } from '@ray-js/t-agent';
8
8
  import { tryCatch } from './misc';
9
9
  import { AIStreamConnectionError } from './errors';
10
+ import logger from './logger';
10
11
  const mimeTypeToFormatMap = {
11
12
  'video/mp4': FileFormat.MP4,
12
13
  'text/json': FileFormat.JSON,
@@ -21,6 +22,7 @@ export class AIStreamSessionError extends Error {
21
22
  }
22
23
  }
23
24
  export function sendBlocksToAIStream(params) {
25
+ logger.debug('sendBlocksToAIStream start');
24
26
  const {
25
27
  session,
26
28
  blocks,
@@ -52,11 +54,59 @@ export function sendBlocksToAIStream(params) {
52
54
  const stream = new ReadableStream({
53
55
  async start(controller) {
54
56
  const enqueue = part => {
55
- if (canceled || closed) {
57
+ if (signal !== null && signal !== void 0 && signal.aborted || canceled || closed) {
56
58
  return;
57
59
  }
58
60
  controller.enqueue(part);
59
61
  };
62
+ let aborting = false;
63
+ const abort = async () => {
64
+ if (aborting || canceled || closed) {
65
+ return;
66
+ }
67
+ aborting = true;
68
+ canceled = true;
69
+ if (event) {
70
+ await event.abort();
71
+ }
72
+ aborting = false;
73
+ };
74
+ const close = () => {
75
+ if (!closed) {
76
+ logger.debug('sendBlocksToAIStream close');
77
+ closed = true;
78
+ if (!canceled && !(signal !== null && signal !== void 0 && signal.aborted)) {
79
+ controller.close();
80
+ }
81
+ }
82
+ };
83
+
84
+ // signal?.addEventListener('abort', async () => {
85
+ // logger.debug('sendBlocksToAIStream signal aborted');
86
+ // abortPromise = abort();
87
+ // await abortPromise;
88
+ // logger.debug('sendBlocksToAIStream signal aborted done');
89
+ // close();
90
+ // });
91
+
92
+ let pendingCancel = false;
93
+ if (audioEmitter) {
94
+ audioEmitter.addEventListener('confirm', () => {
95
+ // 在确认发送时,如果事件还没创建,则取消发送
96
+ if (!event) {
97
+ logger.debug('sendBlocksToAIStream audioEmitter confirm before event start');
98
+ // event 留到后面再关
99
+ pendingCancel = true;
100
+ }
101
+ });
102
+ audioEmitter.addEventListener('cancel', () => {
103
+ if (!event) {
104
+ logger.debug('sendBlocksToAIStream audioEmitter cancel before event start');
105
+ // event 留到后面再关
106
+ pendingCancel = true;
107
+ }
108
+ });
109
+ }
60
110
  let error;
61
111
  [error, event] = await tryCatch(() => session.startEvent({
62
112
  userData: [{
@@ -65,15 +115,26 @@ export function sendBlocksToAIStream(params) {
65
115
  value: JSON.stringify(attribute)
66
116
  }]
67
117
  }));
68
- if (signal !== null && signal !== void 0 && signal.aborted) {
69
- canceled = true;
70
- controller.close();
118
+ if (error) {
119
+ const e = new AIStreamSessionError(error.message, error.code || 0);
120
+ enqueue({
121
+ type: 'error',
122
+ error: e,
123
+ level: 'error',
124
+ meta: {}
125
+ });
126
+ if (audioEmitter) {
127
+ audioEmitter.dispatchEvent(new EmitterEvent('error', {
128
+ detail: e
129
+ }));
130
+ }
131
+ close();
71
132
  return;
72
133
  }
73
- if (error) {
74
- controller.error(error);
75
- closed = true;
76
- controller.close();
134
+ if (signal !== null && signal !== void 0 && signal.aborted || pendingCancel) {
135
+ logger.debug('sendBlocksToAIStream pendingCancel aborted');
136
+ await abort();
137
+ close();
77
138
  return;
78
139
  }
79
140
  const meta = {
@@ -91,6 +152,7 @@ export function sendBlocksToAIStream(params) {
91
152
  }
92
153
  const packet = safeParseJSON(data.body.text);
93
154
  if (packet.bizType === ReceivedTextPacketType.NLG) {
155
+ logger.debug('sendBlocksToAIStream Receive NLG', packet.data.content);
94
156
  const text = prevText + packet.data.content;
95
157
  enqueue({
96
158
  type: 'text',
@@ -100,6 +162,7 @@ export function sendBlocksToAIStream(params) {
100
162
  });
101
163
  prevText = text;
102
164
  } else if (packet.bizType === ReceivedTextPacketType.SKILL) {
165
+ logger.debug('sendBlocksToAIStream Receive SKILL', packet.data);
103
166
  enqueue({
104
167
  id: generateId(),
105
168
  type: 'attachment',
@@ -110,11 +173,15 @@ export function sendBlocksToAIStream(params) {
110
173
  } else if (packet.bizType === ReceivedTextPacketType.ASR && audioEmitter) {
111
174
  if (packet.eof === ReceivedTextPacketEof.END) {
112
175
  if (packet.data.text === '') {
176
+ logger.debug('sendBlocksToAIStream Receive ASR EOF EMPTY');
113
177
  // 没识别出任何文本
114
178
  audioEmitter.dispatchEvent(new EmitterEvent('error', {
115
- detail: {}
179
+ detail: {
180
+ name: 'AsrEmptyError'
181
+ }
116
182
  }));
117
183
  } else {
184
+ logger.debug('sendBlocksToAIStream Receive ASR EOF', packet.data.text);
118
185
  audioEmitter.dispatchEvent(new EmitterEvent('finish', {
119
186
  detail: {
120
187
  text: packet.data.text
@@ -122,6 +189,7 @@ export function sendBlocksToAIStream(params) {
122
189
  }));
123
190
  }
124
191
  } else {
192
+ logger.debug('sendBlocksToAIStream Receive ASR', packet.data.text);
125
193
  audioEmitter.dispatchEvent(new EmitterEvent('update', {
126
194
  detail: {
127
195
  text: packet.data.text
@@ -165,35 +233,48 @@ export function sendBlocksToAIStream(params) {
165
233
  }
166
234
  } else if (data.type === 'sessionState') {
167
235
  if (data.body.sessionState === SessionState.CLOSED || data.body.sessionState === SessionState.CREATE_FAILED) {
236
+ const e = new AIStreamSessionError('Session closed', data.body.code);
168
237
  enqueue({
169
238
  type: 'error',
170
- error: new AIStreamSessionError('Session closed', data.body.code),
239
+ error: e,
171
240
  level: 'error',
172
241
  meta
173
242
  });
243
+ if (audioEmitter) {
244
+ audioEmitter.dispatchEvent(new EmitterEvent('error', {
245
+ detail: e
246
+ }));
247
+ }
248
+ close();
174
249
  }
175
250
  } else if (data.type === 'connectionState') {
176
251
  if (data.body.connectState === ConnectState.DISCONNECTED || data.body.connectState === ConnectState.CLOSED) {
252
+ const e = new AIStreamConnectionError('Connection disconnected', data.body.code);
177
253
  enqueue({
178
254
  type: 'error',
179
- error: new AIStreamConnectionError('Connection disconnected', data.body.code),
255
+ error: e,
180
256
  level: 'error',
181
257
  meta
182
258
  });
259
+ if (audioEmitter) {
260
+ audioEmitter.dispatchEvent(new EmitterEvent('error', {
261
+ detail: e
262
+ }));
263
+ }
264
+ close();
183
265
  }
184
266
  }
185
267
  });
186
268
  event.on('close', () => {
187
- if (!canceled && !closed) {
188
- // 当取消后,不需要关闭控制器
189
- controller.close();
269
+ logger.debug('sendBlocksToAIStream event closed');
270
+ if (!canceled) {
271
+ close();
190
272
  }
191
273
  });
192
274
  event.on('finish', () => {
275
+ logger.debug('sendBlocksToAIStream event finished');
193
276
  if (!canceled) {
194
- closed = true;
195
- // 当取消后,不需要关闭控制器
196
- controller.close();
277
+ close();
197
278
  }
198
279
  });
199
280
  event.on('error', error => {
@@ -238,26 +319,40 @@ export function sendBlocksToAIStream(params) {
238
319
  type: 'audio',
239
320
  amplitudeCount
240
321
  });
241
- audioEmitter.addEventListener('confirm', () => {
242
- s.stop().then(resolve);
322
+ audioEmitter.addEventListener('confirm', async () => {
323
+ if (!canceled) {
324
+ await s.stop();
325
+ }
326
+ resolve();
243
327
  });
244
328
  audioEmitter.addEventListener('cancel', async () => {
245
- await s.stop();
246
329
  if (!canceled) {
247
- canceled = true;
248
- await event.abort();
330
+ await s.stop();
249
331
  }
332
+ logger.debug('sendBlocksToAIStream audio cancel aborted');
333
+ await abort();
250
334
  resolve();
251
335
  });
252
336
  s.start();
253
337
  });
338
+ if (signal !== null && signal !== void 0 && signal.aborted) {
339
+ logger.debug('sendBlocksToAIStream after audio aborted');
340
+ await abort();
341
+ close();
342
+ return;
343
+ }
254
344
  }
255
345
  await event.end();
346
+ if (signal !== null && signal !== void 0 && signal.aborted) {
347
+ await abort();
348
+ close();
349
+ }
256
350
  },
257
- async cancel() {
351
+ async cancel(reason) {
258
352
  if (!canceled) {
259
353
  var _event;
260
354
  canceled = true;
355
+ logger.debug('sendBlocksToAIStream stream canceled', reason);
261
356
  await ((_event = event) === null || _event === void 0 ? void 0 : _event.abort());
262
357
  }
263
358
  }
@@ -209,6 +209,7 @@ export function withAIStream() {
209
209
  return result;
210
210
  };
211
211
  const chat = async (blocks, signal, options) => {
212
+ logger.log('withAIStream chat agent.chat start');
212
213
  const {
213
214
  sendBy = 'user',
214
215
  responseBy = 'assistant',
@@ -287,7 +288,7 @@ export function withAIStream() {
287
288
  });
288
289
  const onError = async () => {
289
290
  end = true;
290
- if (userMsg.isShow) {
291
+ if (userMsg.isShow && userMsg.status !== ChatMessageStatus.FINISH) {
291
292
  await userMsg.remove();
292
293
  }
293
294
  audioEmitter.removeEventListener('error', onError);
@@ -303,6 +304,7 @@ export function withAIStream() {
303
304
  await userMsg.show();
304
305
  await userMsg.persist();
305
306
  }
307
+ logger.log('withAIStream chat agent.chat send');
306
308
  const {
307
309
  response,
308
310
  metaPromise
@@ -310,6 +312,7 @@ export function withAIStream() {
310
312
  if (audioPromise) {
311
313
  try {
312
314
  await audioPromise;
315
+ logger.log('withAIStream chat agent.chat audioPromise finish');
313
316
  } catch (e) {
314
317
  // 没有识别到文字
315
318
  return [];
@@ -320,6 +323,7 @@ export function withAIStream() {
320
323
  role: responseBy
321
324
  });
322
325
  const skills = [];
326
+ logger.debug('withAIStream chat agent.flushStreamToShow');
323
327
  const result = {
324
328
  messages: await agent.flushStreamToShow(message, response, {
325
329
  attachmentCompose: async (respMsg, part) => {
@@ -334,18 +338,15 @@ export function withAIStream() {
334
338
  }
335
339
  })
336
340
  };
341
+ logger.debug('withAIStream chat agent.flushStreamToShow end');
337
342
  if (skills.length) {
338
343
  await hooks.callHook('onSkillsEnd', skills, message, result);
339
344
  await message.update();
340
345
  }
341
- if (message.bubble.status === BubbleTileStatus.ABORTED) {
342
- if (message.bubble.text) {
343
- await message.persist();
344
- } else {
345
- await message.remove();
346
- }
347
- } else {
346
+ if (message.bubble.text) {
348
347
  await message.persist();
348
+ } else {
349
+ await message.remove();
349
350
  }
350
351
  return [userMsg, ...result.messages];
351
352
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/t-agent-plugin-aistream",
3
- "version": "0.2.0-beta-11",
3
+ "version": "0.2.0-beta-13",
4
4
  "author": "Tuya.inc",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -35,5 +35,5 @@
35
35
  "devDependencies": {
36
36
  "@types/url-parse": "^1.4.11"
37
37
  },
38
- "gitHead": "dd56b31b0095fb82d0fb5d4f58581d00ae2970ee"
38
+ "gitHead": "ad4d4b4637a4a5396bcf5adf21adcbf215561436"
39
39
  }