@ray-js/t-agent-plugin-aistream 0.2.7-beta.12 → 0.2.7-beta.14

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.
@@ -701,7 +701,8 @@ export declare enum AIStreamAppErrorCode {
701
701
  FILE_DATA_READ_ERROR = 39009,
702
702
  DATA_SEND_FAILED = 39010,
703
703
  CONNECTION_CLOSED_BY_REMOTE = 39012,
704
- AUDIO_UNEXPECTEDLY_RESET = 39014
704
+ AUDIO_UNEXPECTEDLY_RESET = 39014,
705
+ RECONNECT = 39016
705
706
  }
706
707
  export declare enum AIStreamServerErrorCode {
707
708
  OK = 200,
@@ -148,6 +148,7 @@ export let AIStreamAppErrorCode = /*#__PURE__*/function (AIStreamAppErrorCode) {
148
148
  AIStreamAppErrorCode[AIStreamAppErrorCode["DATA_SEND_FAILED"] = 39010] = "DATA_SEND_FAILED";
149
149
  AIStreamAppErrorCode[AIStreamAppErrorCode["CONNECTION_CLOSED_BY_REMOTE"] = 39012] = "CONNECTION_CLOSED_BY_REMOTE";
150
150
  AIStreamAppErrorCode[AIStreamAppErrorCode["AUDIO_UNEXPECTEDLY_RESET"] = 39014] = "AUDIO_UNEXPECTEDLY_RESET";
151
+ AIStreamAppErrorCode[AIStreamAppErrorCode["RECONNECT"] = 39016] = "RECONNECT";
151
152
  return AIStreamAppErrorCode;
152
153
  }({});
153
154
  export let AIStreamServerErrorCode = /*#__PURE__*/function (AIStreamServerErrorCode) {
@@ -6,7 +6,7 @@ import "core-js/modules/esnext.iterator.find.js";
6
6
  import "core-js/modules/esnext.iterator.for-each.js";
7
7
  import "core-js/modules/esnext.iterator.map.js";
8
8
  import "core-js/modules/web.dom-collections.iterator.js";
9
- import { AIStreamErrorCode, AIStreamServerErrorCode, BizTag, ConnectClientType, ConnectState, EventType, NetworkType, SessionState } from '../AIStreamTypes';
9
+ import { AIStreamAppErrorCode, AIStreamErrorCode, AIStreamServerErrorCode, BizTag, ConnectClientType, ConnectState, EventType, NetworkType, SessionState } from '../AIStreamTypes';
10
10
  import { closeSession, connect, createSession, disconnect, getCurrentHomeInfo, getNetworkType, isConnected, queryAgentToken, sendEventChatBreak, sendEventEnd, sendEventPayloadEnd, sendEventStart, sendImageData, sendTextData, startRecordAndSendAudioData, stopRecordAndSendAudioData } from './ttt';
11
11
  import { AIStreamObserver, AIStreamObserverPool } from './observer';
12
12
  import { isAbortError, safeParseJSON } from '@ray-js/t-agent';
@@ -262,7 +262,8 @@ export class AIStreamSession {
262
262
  if (options.getSessionUserData) {
263
263
  userData = await options.getSessionUserData();
264
264
  }
265
- {
265
+ let finErr;
266
+ for (let i = 0; i < 30; i++) {
266
267
  const [err, res] = await tryCatchTTT(() => createSession({
267
268
  bizTag: options.bizTag,
268
269
  agentToken,
@@ -270,13 +271,31 @@ export class AIStreamSession {
270
271
  userDataJson: JSON.stringify(userData)
271
272
  }));
272
273
  if (err) {
274
+ finErr = err;
275
+ if (err.code === AIStreamAppErrorCode.RECONNECT) {
276
+ // 200 毫秒后再试
277
+ await new Promise(resolve => {
278
+ setTimeout(resolve, 200);
279
+ });
280
+ continue;
281
+ } else {
282
+ this.promise = null;
283
+ throw err;
284
+ }
285
+ }
286
+ if (res) {
287
+ this.sessionId = res.sessionId;
288
+ this.sendDataChannels = res.sendDataChannels;
289
+ this.revDataChannels = res.revDataChannels;
273
290
  this.promise = null;
274
- throw err;
291
+ return;
275
292
  }
276
- this.sessionId = res.sessionId;
277
- this.sendDataChannels = res.sendDataChannels;
278
- this.revDataChannels = res.revDataChannels;
279
- this.promise = null;
293
+ }
294
+ this.promise = null;
295
+ if (finErr) {
296
+ throw finErr;
297
+ } else {
298
+ throw new AIStreamError('createSession reconnect timeout', AIStreamErrorCode.EVENT_ABORTED);
280
299
  }
281
300
  })();
282
301
  return this.promise;
@@ -42,6 +42,7 @@ export function sendBlocksToAIStream(params) {
42
42
  });
43
43
  const stream = new ReadableStream({
44
44
  async start(controller) {
45
+ var _cleanupPreEventListe2;
45
46
  const enqueue = part => {
46
47
  if (signal !== null && signal !== void 0 && signal.aborted || canceled || closed) {
47
48
  return;
@@ -84,22 +85,28 @@ export function sendBlocksToAIStream(params) {
84
85
  });
85
86
  };
86
87
  let pendingCancel = false;
88
+ // phase-1: 事件创建前监听 confirm/cancel,用于捕获"边界竞态"时的取消意图
89
+ // 使用具名函数以便在 startEvent 后统一解绑,避免监听器残留在 audioEmitter 上
90
+ let cleanupPreEventListeners;
87
91
  if (audioEmitter) {
88
- audioEmitter.addEventListener('confirm', () => {
89
- // 在确认发送时,如果事件还没创建,则取消发送
92
+ const onPreEventConfirm = () => {
90
93
  if (!event) {
91
94
  logger.debug('sendBlocksToAIStream audioEmitter confirm before event start');
92
- // event 留到后面再关
93
95
  pendingCancel = true;
94
96
  }
95
- });
96
- audioEmitter.addEventListener('cancel', () => {
97
+ };
98
+ const onPreEventCancel = () => {
97
99
  if (!event) {
98
100
  logger.debug('sendBlocksToAIStream audioEmitter cancel before event start');
99
- // event 留到后面再关
100
101
  pendingCancel = true;
101
102
  }
102
- });
103
+ };
104
+ audioEmitter.addEventListener('confirm', onPreEventConfirm);
105
+ audioEmitter.addEventListener('cancel', onPreEventCancel);
106
+ cleanupPreEventListeners = () => {
107
+ audioEmitter.removeEventListener('confirm', onPreEventConfirm);
108
+ audioEmitter.removeEventListener('cancel', onPreEventCancel);
109
+ };
103
110
  }
104
111
  const chatAttributes = {
105
112
  'processing.interrupt': 'false',
@@ -124,7 +131,10 @@ export function sendBlocksToAIStream(params) {
124
131
  }]
125
132
  }));
126
133
  if (error) {
134
+ var _cleanupPreEventListe;
127
135
  emitError(error);
136
+ // startEvent 失败,phase-1 监听已无用,立即解绑
137
+ (_cleanupPreEventListe = cleanupPreEventListeners) === null || _cleanupPreEventListe === void 0 || _cleanupPreEventListe();
128
138
  if (audioEmitter) {
129
139
  audioEmitter.dispatchEvent(new EmitterEvent('error', {
130
140
  detail: error
@@ -133,6 +143,9 @@ export function sendBlocksToAIStream(params) {
133
143
  close();
134
144
  return;
135
145
  }
146
+
147
+ // startEvent 已成功(event 已创建),phase-1 监听完成使命,解绑
148
+ (_cleanupPreEventListe2 = cleanupPreEventListeners) === null || _cleanupPreEventListe2 === void 0 || _cleanupPreEventListe2();
136
149
  if (signal !== null && signal !== void 0 && signal.aborted || pendingCancel) {
137
150
  logger.debug('sendBlocksToAIStream pendingCancel aborted');
138
151
  abort();
@@ -306,20 +319,40 @@ export function sendBlocksToAIStream(params) {
306
319
  const s = event.stream({
307
320
  type: 'audio'
308
321
  });
309
- audioEmitter.addEventListener('confirm', async () => {
322
+ let audioDone = false;
323
+
324
+ // cleanupPostEventListeners 先初始化为 noop,让两个 handler 可在闭包里安全引用
325
+ // 后面赋值为真正的清理函数
326
+ let cleanupPostEventListeners = () => {};
327
+ const onPostEventConfirm = async () => {
328
+ // 任一监听触发后,立即同时解绑两者,防止另一个因偶发二次触发产生副作用
329
+ cleanupPostEventListeners();
310
330
  if (!canceled) {
311
331
  await s.stop();
312
332
  }
313
- resolve();
314
- });
315
- audioEmitter.addEventListener('cancel', async () => {
333
+ if (!audioDone) {
334
+ audioDone = true;
335
+ resolve();
336
+ }
337
+ };
338
+ const onPostEventCancel = async () => {
339
+ cleanupPostEventListeners();
316
340
  if (!canceled) {
317
341
  await s.stop();
318
342
  }
319
343
  logger.debug('sendBlocksToAIStream audio cancel aborted');
320
344
  abort();
321
- resolve();
322
- });
345
+ if (!audioDone) {
346
+ audioDone = true;
347
+ resolve();
348
+ }
349
+ };
350
+ cleanupPostEventListeners = () => {
351
+ audioEmitter.removeEventListener('confirm', onPostEventConfirm);
352
+ audioEmitter.removeEventListener('cancel', onPostEventCancel);
353
+ };
354
+ audioEmitter.addEventListener('confirm', onPostEventConfirm);
355
+ audioEmitter.addEventListener('cancel', onPostEventCancel);
323
356
  s.start();
324
357
  });
325
358
  if (signal !== null && signal !== void 0 && signal.aborted) {
@@ -1,12 +1,19 @@
1
1
  import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
2
2
  import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
3
+ import _wrapAsyncGenerator from "@babel/runtime/helpers/esm/wrapAsyncGenerator";
4
+ import _awaitAsyncGenerator from "@babel/runtime/helpers/esm/awaitAsyncGenerator";
5
+ import _asyncGeneratorDelegate from "@babel/runtime/helpers/esm/asyncGeneratorDelegate";
3
6
  const _excluded = ["message"];
7
+ import "core-js/modules/es.symbol.description.js";
8
+ import "core-js/modules/es.symbol.async-iterator.js";
4
9
  import "core-js/modules/es.array.flat.js";
5
10
  import "core-js/modules/es.array.reverse.js";
6
11
  import "core-js/modules/es.array.unscopables.flat.js";
7
12
  import "core-js/modules/esnext.iterator.constructor.js";
8
13
  import "core-js/modules/esnext.iterator.map.js";
9
14
  import "core-js/modules/web.dom-collections.iterator.js";
15
+ function _asyncIterator(r) { var n, t, o, e = 2; for ("undefined" != typeof Symbol && (t = Symbol.asyncIterator, o = Symbol.iterator); e--;) { if (t && null != (n = r[t])) return n.call(r); if (o && null != (n = r[o])) return new AsyncFromSyncIterator(n.call(r)); t = "@@asyncIterator", o = "@@iterator"; } throw new TypeError("Object is not async iterable"); }
16
+ function AsyncFromSyncIterator(r) { function AsyncFromSyncIteratorContinuation(r) { if (Object(r) !== r) return Promise.reject(new TypeError(r + " is not an object.")); var n = r.done; return Promise.resolve(r.value).then(function (r) { return { value: r, done: n }; }); } return AsyncFromSyncIterator = function (r) { this.s = r, this.n = r.next; }, AsyncFromSyncIterator.prototype = { s: null, n: null, next: function () { return AsyncFromSyncIteratorContinuation(this.n.apply(this.s, arguments)); }, return: function (r) { var n = this.s.return; return void 0 === n ? Promise.resolve({ value: r, done: !0 }) : AsyncFromSyncIteratorContinuation(n.apply(this.s, arguments)); }, throw: function (r) { var n = this.s.return; return void 0 === n ? Promise.reject(r) : AsyncFromSyncIteratorContinuation(n.apply(this.s, arguments)); } }, new AsyncFromSyncIterator(r); }
10
17
  import { BubbleTileStatus, ChatMessageStatus, createHooks, EmitterEvent } from '@ray-js/t-agent';
11
18
  import { messageAppraise } from './utils/apis';
12
19
  import { getAccountInfo, getCurrentHomeInfo, runTTTAction, sendBlocksToAIStream } from './utils';
@@ -254,13 +261,37 @@ export function withAIStream() {
254
261
  return userData;
255
262
  }
256
263
  });
257
- signal === null || signal === void 0 || signal.addEventListener('abort', event => {
258
- logger.debug('withAIStream signal aborted, response.started:', result.response.started);
259
- if (result.response.started) {
260
- result.response.cancel(event.reason);
261
- }
262
- agent.hooks.callHook('onUserAbort', event.reason);
263
- });
264
+ if (signal) {
265
+ const onAbort = event => {
266
+ logger.debug('withAIStream signal aborted, response.started:', result.response.started);
267
+ if (result.response.started) {
268
+ result.response.cancel(event.reason);
269
+ }
270
+ agent.hooks.callHook('onUserAbort', event.reason);
271
+ };
272
+
273
+ // once:true 保证 abort 触发后自动解绑,避免监听器残留
274
+ signal.addEventListener('abort', onAbort, {
275
+ once: true
276
+ });
277
+
278
+ // 正常消费流时(未触发 abort)在 parts() 的 finally 里解绑,防止 signal 长期持有闭包
279
+ const _parts = result.response.parts.bind(result.response);
280
+ result.response.parts = () => {
281
+ const iterable = _parts();
282
+ return {
283
+ [Symbol.asyncIterator]() {
284
+ return _wrapAsyncGenerator(function* () {
285
+ try {
286
+ yield* _asyncGeneratorDelegate(_asyncIterator(iterable), _awaitAsyncGenerator);
287
+ } finally {
288
+ signal.removeEventListener('abort', onAbort);
289
+ }
290
+ })();
291
+ }
292
+ };
293
+ };
294
+ }
264
295
  signal === null || signal === void 0 || signal.throwIfAborted();
265
296
  return result;
266
297
  };
@@ -291,8 +322,18 @@ export function withAIStream() {
291
322
  if (audioEmitter) {
292
323
  let end = false;
293
324
  audioPromise = new Promise((resolve, reject) => {
294
- // 当确认发送时,展示 loading
295
325
  let userMsgShow = false;
326
+
327
+ // onAbort 先定义,让后续终态处理器可引用它来解绑
328
+ const onAbort = () => {
329
+ logger.debug('withAIStream chat agent.chat audioEmitter onAbort');
330
+ audioEmitter.dispatchEvent(new EmitterEvent('cancel'));
331
+ };
332
+ // signal 可选:仅在传入时注册
333
+ signal === null || signal === void 0 || signal.addEventListener('abort', onAbort);
334
+
335
+ // 当确认发送时,展示 loading
336
+
296
337
  audioEmitter.addEventListener('confirm', async () => {
297
338
  logger.debug('withAIStream chat agent.chat audioEmitter onConfirm');
298
339
  if (end) {
@@ -316,6 +357,8 @@ export function withAIStream() {
316
357
  return;
317
358
  }
318
359
  end = true;
360
+ // 终态:解绑 signal 上的 abort 监听,避免 signal 长期持有本轮闭包
361
+ signal === null || signal === void 0 || signal.removeEventListener('abort', onAbort);
319
362
  if (!event.detail.text && userMsgShow) {
320
363
  await userMsg.remove();
321
364
  reject(new Error('No text found in audio event'));
@@ -335,6 +378,8 @@ export function withAIStream() {
335
378
  audioEmitter.addEventListener('cancel', async () => {
336
379
  logger.debug('withAIStream chat agent.chat audioEmitter onCancel');
337
380
  end = true;
381
+ // 终态:解绑 signal 上的 abort 监听
382
+ signal === null || signal === void 0 || signal.removeEventListener('abort', onAbort);
338
383
  // 取消时,有可能 userMsg.persist 还在执行,所以这里不 await,先 reject
339
384
  reject(new Error('User cancel'));
340
385
  if (!response.started && userMsgShow) {
@@ -343,13 +388,11 @@ export function withAIStream() {
343
388
  }, {
344
389
  once: true
345
390
  });
346
- signal.addEventListener('abort', () => {
347
- logger.debug('withAIStream chat agent.chat audioEmitter onAbort');
348
- audioEmitter.dispatchEvent(new EmitterEvent('cancel'));
349
- });
350
391
  audioEmitter.addEventListener('error', async () => {
351
392
  logger.debug('withAIStream chat agent.chat audioEmitter onError');
352
393
  end = true;
394
+ // 终态:解绑 signal 上的 abort 监听
395
+ signal === null || signal === void 0 || signal.removeEventListener('abort', onAbort);
353
396
  reject(new Error('Audio emitter error'));
354
397
  if (userMsgShow && userMsg.status !== ChatMessageStatus.FINISH) {
355
398
  await userMsg.remove();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/t-agent-plugin-aistream",
3
- "version": "0.2.7-beta.12",
3
+ "version": "0.2.7-beta.14",
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": "71b387bb5578e4749293f146f3737d87c7c22fa7"
38
+ "gitHead": "350bf4a18c930208c4ad83c2c65d09acec6d5063"
39
39
  }