@ray-js/t-agent-plugin-aistream 0.2.8-beta.2 → 0.2.8-beta.4

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.
@@ -45,6 +45,12 @@ export declare class AIStreamObserverPool {
45
45
  private observerMap;
46
46
  constructor();
47
47
  private cancels;
48
+ private createObserverMap;
49
+ private isSessionAwareType;
50
+ private getEntrySessionIds;
51
+ private collectObservers;
52
+ private addObserver;
53
+ private removeObserver;
48
54
  start(): void;
49
55
  dispose(): void;
50
56
  connect(observer: AIStreamObserver): void;
@@ -10,25 +10,102 @@ export class AIStreamObserverPool {
10
10
  constructor() {
11
11
  _defineProperty(this, "isStarted", false);
12
12
  _defineProperty(this, "cancels", []);
13
- this.observerMap = {
14
- connectionState: new Set(),
15
- sessionState: new Set(),
16
- sessionEvent: new Set(),
17
- event: new Set(),
18
- text: new Set(),
19
- audio: new Set(),
20
- video: new Set(),
21
- file: new Set(),
22
- image: new Set()
13
+ this.observerMap = this.createObserverMap();
14
+ }
15
+ createObserverMap() {
16
+ return {
17
+ connectionState: {
18
+ all: new Set(),
19
+ bySessionId: new Map()
20
+ },
21
+ sessionState: {
22
+ all: new Set(),
23
+ bySessionId: new Map()
24
+ },
25
+ sessionEvent: {
26
+ all: new Set(),
27
+ bySessionId: new Map()
28
+ },
29
+ event: {
30
+ all: new Set(),
31
+ bySessionId: new Map()
32
+ },
33
+ text: {
34
+ all: new Set(),
35
+ bySessionId: new Map()
36
+ },
37
+ audio: {
38
+ all: new Set(),
39
+ bySessionId: new Map()
40
+ },
41
+ video: {
42
+ all: new Set(),
43
+ bySessionId: new Map()
44
+ },
45
+ file: {
46
+ all: new Set(),
47
+ bySessionId: new Map()
48
+ },
49
+ image: {
50
+ all: new Set(),
51
+ bySessionId: new Map()
52
+ }
23
53
  };
24
54
  }
55
+ isSessionAwareType(type) {
56
+ return type !== 'connectionState';
57
+ }
58
+ getEntrySessionIds(entry) {
59
+ if (entry.type === 'event' || entry.type === 'sessionEvent' || entry.type === 'sessionState') {
60
+ return entry.body.sessionId ? [entry.body.sessionId] : [];
61
+ }
62
+ if (entry.type === 'text' || entry.type === 'audio' || entry.type === 'video' || entry.type === 'file' || entry.type === 'image') {
63
+ return entry.body.sessionIdList || [];
64
+ }
65
+ return [];
66
+ }
67
+ collectObservers(entry) {
68
+ const bucket = this.observerMap[entry.type];
69
+ const observers = new Set(bucket.all);
70
+ this.getEntrySessionIds(entry).forEach(sessionId => {
71
+ const sessionObservers = bucket.bySessionId.get(sessionId);
72
+ sessionObservers === null || sessionObservers === void 0 || sessionObservers.forEach(observer => {
73
+ observers.add(observer);
74
+ });
75
+ });
76
+ return observers;
77
+ }
78
+ addObserver(type, observer) {
79
+ const sessionId = observer.options.sessionId;
80
+ const bucket = this.observerMap[type];
81
+ if (!sessionId || !this.isSessionAwareType(type)) {
82
+ bucket.all.add(observer);
83
+ return;
84
+ }
85
+ let sessionObservers = bucket.bySessionId.get(sessionId);
86
+ if (!sessionObservers) {
87
+ sessionObservers = new Set();
88
+ bucket.bySessionId.set(sessionId, sessionObservers);
89
+ }
90
+ sessionObservers.add(observer);
91
+ }
92
+ removeObserver(type, observer) {
93
+ const bucket = this.observerMap[type];
94
+ bucket.all.delete(observer);
95
+ bucket.bySessionId.forEach((sessionObservers, sessionId) => {
96
+ sessionObservers.delete(observer);
97
+ if (sessionObservers.size === 0) {
98
+ bucket.bySessionId.delete(sessionId);
99
+ }
100
+ });
101
+ }
25
102
  start() {
26
103
  if (this.isStarted) {
27
104
  return;
28
105
  }
29
106
  const handle = entry => {
30
107
  var _entry$body;
31
- const observers = this.observerMap[entry.type];
108
+ const observers = this.collectObservers(entry);
32
109
  let state = '';
33
110
  if (entry.type === 'connectionState') {
34
111
  state = "".concat(ConnectState[entry.body.connectState], " ").concat(entry.body.code || '');
@@ -47,18 +124,19 @@ export class AIStreamObserverPool {
47
124
  sessionId
48
125
  } = observer.options;
49
126
  let matched = true;
127
+ const sessionIdList = entry.body.sessionIdList || [];
50
128
  if (dataChannels !== null && dataChannels !== void 0 && dataChannels.length && !dataChannels.includes(entry.body.dataChannel)) {
51
129
  matched = false;
52
130
  }
53
- if (sessionId && !entry.body.sessionIdList.includes(sessionId)) {
131
+ if (sessionId && !sessionIdList.includes(sessionId)) {
54
132
  matched = false;
55
133
  }
56
134
  if (matched) {
57
135
  observer.callback(entry, observer);
58
136
  }
59
- } else {
137
+ } else if (!((entry.type === 'event' || entry.type === 'sessionEvent' || entry.type === 'sessionState') && observer.options.sessionId && observer.options.sessionId !== entry.body.sessionId)) {
60
138
  observer.callback(entry, observer);
61
- }
139
+ } // noop
62
140
  });
63
141
  };
64
142
  this.cancels = [listenConnectStateChanged(body => handle({
@@ -98,36 +176,26 @@ export class AIStreamObserverPool {
98
176
  this.isStarted = false;
99
177
  this.cancels.forEach(cancel => cancel());
100
178
  this.cancels = [];
101
- this.observerMap = {
102
- connectionState: new Set(),
103
- sessionState: new Set(),
104
- sessionEvent: new Set(),
105
- event: new Set(),
106
- text: new Set(),
107
- audio: new Set(),
108
- video: new Set(),
109
- file: new Set(),
110
- image: new Set()
111
- };
179
+ this.observerMap = this.createObserverMap();
112
180
  }
113
181
  connect(observer) {
114
182
  if (!this.isStarted) {
115
183
  this.start();
116
184
  }
117
- types.forEach(key => {
118
- const type = key;
185
+ types.forEach(type => {
119
186
  if (observer.options[type]) {
120
- this.observerMap[type].add(observer);
187
+ this.addObserver(type, observer);
121
188
  }
122
189
  });
123
190
  }
124
191
  disconnect(observer) {
125
- types.forEach(key => {
126
- this.observerMap[key].delete(observer);
192
+ types.forEach(type => {
193
+ this.removeObserver(type, observer);
127
194
  });
128
195
  let empty = true;
129
- types.forEach(key => {
130
- if (this.observerMap[key].size > 0) {
196
+ types.forEach(type => {
197
+ const bucket = this.observerMap[type];
198
+ if (bucket.all.size > 0 || bucket.bySessionId.size > 0) {
131
199
  empty = false;
132
200
  }
133
201
  });
@@ -6,7 +6,6 @@ export interface SendBlocksToAIStreamParams {
6
6
  blocks: InputBlock[];
7
7
  session: AIStreamSession;
8
8
  signal?: AbortSignalObject;
9
- enableTts?: boolean;
10
9
  eventIdPrefix?: string;
11
10
  getUserData: () => Promise<AIStreamUserData>;
12
11
  }
@@ -219,15 +219,17 @@ export function sendBlocksToAIStream(params) {
219
219
  if (!imageId) {
220
220
  imageId = generateId();
221
221
  }
222
+ const attachment = {
223
+ streamFlag: data.body.streamFlag,
224
+ path: data.body.path,
225
+ width: data.body.width,
226
+ height: data.body.height
227
+ };
222
228
  enqueue({
223
229
  id: imageId,
224
230
  type: 'attachment',
225
231
  attachmentType: 'image',
226
- attachment: {
227
- path: data.body.path || null,
228
- width: data.body.width || null,
229
- height: data.body.height || null
230
- },
232
+ attachment: attachment,
231
233
  meta
232
234
  });
233
235
  if (data.body.streamFlag === StreamFlag.END) {
@@ -237,13 +239,20 @@ export function sendBlocksToAIStream(params) {
237
239
  if (!audioId) {
238
240
  audioId = generateId();
239
241
  }
242
+ const attachment = {
243
+ streamFlag: data.body.streamFlag,
244
+ path: data.body.path,
245
+ pts: data.body.pts,
246
+ codecType: data.body.codecType,
247
+ sampleRate: data.body.sampleRate,
248
+ channels: data.body.channels,
249
+ bitDepth: data.body.bitDepth
250
+ };
240
251
  enqueue({
241
252
  id: audioId,
242
253
  type: 'attachment',
243
254
  attachmentType: 'audio',
244
- attachment: {
245
- path: data.body.path || null
246
- },
255
+ attachment,
247
256
  meta
248
257
  });
249
258
  if (data.body.streamFlag === StreamFlag.END) {
@@ -380,6 +389,8 @@ export function sendBlocksToAIStream(params) {
380
389
  }
381
390
  });
382
391
  return {
392
+ // web-streams-polyfill's ReadableStream is runtime-compatible but its
393
+ // getReader() overloads differ from lib.dom's, so cast to satisfy the type.
383
394
  response: new StreamResponse(stream),
384
395
  metaPromise
385
396
  };
@@ -72,6 +72,7 @@ export declare const createSession: (options?: Omit<CreateSessionParams, "succes
72
72
  sessionId: string;
73
73
  sendDataChannels: string[];
74
74
  revDataChannels: string[];
75
+ baseCacheDir: string;
75
76
  }>;
76
77
  export declare const closeSession: (options?: Omit<CloseSessionParams, "success" | "fail"> | undefined) => Promise<null>;
77
78
  export declare const sendEventStart: (options?: Omit<SendEventStartParams, "success" | "fail"> | undefined) => Promise<{
package/dist/utils/ttt.js CHANGED
@@ -42,6 +42,8 @@ export const sendEventStart = promisify(ty.aistream.sendEventStart, true);
42
42
  export const sendEventPayloadEnd = promisify(ty.aistream.sendEventPayloadEnd, true);
43
43
  export const sendEventEnd = promisify(ty.aistream.sendEventEnd, true);
44
44
  export const sendEventChatBreak = promisify(ty.aistream.sendEventChatBreak, true);
45
+
46
+ // @ts-ignore
45
47
  export const sendEventToAIStream = promisify(ty.aistream.sendEvent, true);
46
48
  export const startRecordAndSendAudioData = promisify(ty.aistream.startRecordAndSendAudioData, true);
47
49
  export const stopRecordAndSendAudioData = promisify(ty.aistream.stopRecordAndSendAudioData, true);
@@ -1,4 +1,4 @@
1
- import { AbortSignalObject, ChatAgent, ChatCardObject, ChatMessage, ChatMessageStatus, ChatTile, GetChatPluginHandler, InputBlock } from '@ray-js/t-agent';
1
+ import { AbortSignalObject, AttachmentPart, ChatAgent, ChatCardObject, ChatMessage, ChatMessageStatus, ChatTile, GetChatPluginHandler, InputBlock } from '@ray-js/t-agent';
2
2
  import { TTTAction } from './utils';
3
3
  import { AIStreamUserData, ConnectClientType, ReceivedTextSkillPacketBody, SendAIStreamEventParams, SessionEventBody } from './AIStreamTypes';
4
4
  import { ChatHistoryStore, StoredMessageObject } from './ChatHistoryStore';
@@ -46,7 +46,13 @@ export interface AIStreamHooks {
46
46
  onTextCompose: (respMsg: ChatMessage, status: ChatMessageStatus, result: {
47
47
  text: string;
48
48
  }) => void;
49
- onSkillCompose: (skill: ReceivedTextSkillPacketBody[], respMsg: ChatMessage, result: {
49
+ onSkillCompose: (skill: ReceivedTextSkillPacketBody, respMsg: ChatMessage, result: {
50
+ messages: ChatMessage[];
51
+ }) => void;
52
+ onAttachmentCompose: (part: AttachmentPart, respMsg: ChatMessage, result: {
53
+ messages: ChatMessage[];
54
+ }) => void;
55
+ onAttachmentsEnd: (parts: AttachmentPart[], respMsg: ChatMessage, result: {
50
56
  messages: ChatMessage[];
51
57
  }) => void;
52
58
  onSkillsEnd: (skills: ReceivedTextSkillPacketBody[], respMsg: ChatMessage, result: {
@@ -94,6 +100,8 @@ export declare function withAIStream(options?: AIStreamOptions): (agent: ChatAge
94
100
  onCardsReceived: (fn: AIStreamHooks["onCardsReceived"]) => () => void;
95
101
  onUserDataRead: (fn: AIStreamHooks["onUserDataRead"]) => () => void;
96
102
  onSessionEventReceived: (fn: AIStreamHooks["onSessionEventReceived"]) => () => void;
103
+ onAttachmentCompose: (fn: AIStreamHooks["onAttachmentCompose"]) => () => void;
104
+ onAttachmentsEnd: (fn: AIStreamHooks["onAttachmentsEnd"]) => () => void;
97
105
  sendEvent: (params: SendAIStreamEventParams) => Promise<null>;
98
106
  onTTTAction: (fn: AIStreamHooks["onTTTAction"]) => () => void;
99
107
  getChatId: () => Promise<string>;
@@ -93,65 +93,79 @@ export function withAIStream() {
93
93
  clientType,
94
94
  deviceId
95
95
  });
96
- const streamSession = connection.createSession({
97
- ownerId: clientType === ConnectClientType.DEVICE ? deviceId : "".concat(homeId),
98
- api: tokenOptions.api,
99
- apiVersion: tokenOptions.version,
100
- solutionCode: agentId,
101
- extParams: _objectSpread({
102
- needTts: !!options.enableTts,
103
- deviceId
104
- }, tokenOptions.extParams),
105
- getSessionUserData: async () => {
106
- const result = {
107
- userData: {}
108
- };
109
- await hooks.callHook('onUserDataRead', 'create-session', {}, result);
110
- return result.userData;
111
- }
112
- });
113
- streamSession.on('sessionEvent', event => {
114
- hooks.callHook('onSessionEventReceived', event);
115
- });
116
- await session.set('AIStream.streamSession', streamSession);
117
- if (options.earlyStart) {
118
- // 故意异步,不阻塞消息列表加载
119
- streamSession.ensureSession().catch(error => {
120
- logger.error('earlyStart failed', error);
96
+ let streamSession = null;
97
+ try {
98
+ streamSession = connection.createSession({
99
+ ownerId: clientType === ConnectClientType.DEVICE ? deviceId : "".concat(homeId),
100
+ api: tokenOptions.api,
101
+ apiVersion: tokenOptions.version,
102
+ solutionCode: agentId,
103
+ extParams: _objectSpread({
104
+ needTts: !!options.enableTts,
105
+ deviceId
106
+ }, tokenOptions.extParams),
107
+ getSessionUserData: async () => {
108
+ const result = {
109
+ userData: {}
110
+ };
111
+ await hooks.callHook('onUserDataRead', 'create-session', {}, result);
112
+ return result.userData;
113
+ }
121
114
  });
122
- }
123
-
124
- /*
125
- * 2. 获取历史消息
126
- */
127
- let historyStore;
128
- if (typeof options.createChatHistoryStore === 'function') {
129
- historyStore = options.createChatHistoryStore(agent);
130
- } else {
131
- historyStore = new ChatHistoryLocalStore({
132
- bizCode: BizCode.CHAT,
133
- agentId,
134
- deviceId,
135
- homeId,
136
- indexId
115
+ streamSession.on('sessionEvent', event => {
116
+ hooks.callHook('onSessionEventReceived', event);
137
117
  });
138
- }
139
- if (historyStore) {
140
- await agent.session.set('AIStream.historyStore', historyStore);
141
- const {
142
- records
143
- } = await historyStore.queryAll({
144
- sort: 'desc',
145
- // id 倒序去获取最新的消息list,然后再reverse,保证最新的消息在最前面
146
- limit: options.historySize || 1000,
147
- offset: 0
118
+ await session.set('AIStream.connection', connection);
119
+ await session.set('AIStream.streamSession', streamSession);
120
+ if (options.earlyStart) {
121
+ // 故意异步,不阻塞消息列表加载
122
+ streamSession.ensureSession().catch(error => {
123
+ logger.error('earlyStart failed', error);
124
+ });
125
+ }
126
+
127
+ /*
128
+ * 2. 获取历史消息
129
+ */
130
+ let historyStore;
131
+ if (typeof options.createChatHistoryStore === 'function') {
132
+ historyStore = options.createChatHistoryStore(agent);
133
+ } else {
134
+ historyStore = new ChatHistoryLocalStore({
135
+ bizCode: BizCode.CHAT,
136
+ agentId,
137
+ deviceId,
138
+ homeId,
139
+ indexId
140
+ });
141
+ }
142
+ if (historyStore) {
143
+ await agent.session.set('AIStream.historyStore', historyStore);
144
+ const {
145
+ records
146
+ } = await historyStore.queryAll({
147
+ sort: 'desc',
148
+ // id 倒序去获取最新的消息list,然后再reverse,保证最新的消息在最前面
149
+ limit: options.historySize || 1000,
150
+ offset: 0
151
+ });
152
+ await session.set('AIStream.rawMessages', records.reverse());
153
+ session.isNewChat = records.length === 0;
154
+ } else {
155
+ session.isNewChat = true;
156
+ }
157
+ session.sessionId = indexId;
158
+ } catch (error) {
159
+ if (streamSession) {
160
+ await streamSession.close().catch(closeError => {
161
+ logger.error('withAIStream cleanup streamSession failed', closeError);
162
+ });
163
+ }
164
+ await connection.close().catch(closeError => {
165
+ logger.error('withAIStream cleanup connection failed', closeError);
148
166
  });
149
- await session.set('AIStream.rawMessages', records.reverse());
150
- session.isNewChat = records.length === 0;
151
- } else {
152
- session.isNewChat = true;
167
+ throw error;
153
168
  }
154
- session.sessionId = indexId;
155
169
  });
156
170
  const getHistoryStore = () => {
157
171
  const historyStore = session.get('AIStream.historyStore');
@@ -431,6 +445,7 @@ export function withAIStream() {
431
445
  });
432
446
  await hooks.callHook('onChatMessageSent', userMsg, message);
433
447
  const skills = [];
448
+ const attachments = [];
434
449
  logger.debug('withAIStream chat agent.flushStreamToShow');
435
450
  const result = {
436
451
  messages: await agent.flushStreamToShow(message, response, {
@@ -445,17 +460,28 @@ export function withAIStream() {
445
460
  const result = {
446
461
  messages: []
447
462
  };
463
+ await hooks.callHook('onAttachmentCompose', part, respMsg, result);
464
+ attachments.push(part);
448
465
  if (part.attachmentType === 'skill') {
449
- skills.push(part.attachment);
450
- await hooks.callHook('onSkillCompose', part.attachment, respMsg, result);
466
+ const skill = part.attachment;
467
+ skills.push(skill);
468
+ await hooks.callHook('onSkillCompose', skill, respMsg, result);
451
469
  }
452
470
  return result.messages;
453
471
  }
454
472
  })
455
473
  };
456
474
  logger.debug('withAIStream chat agent.flushStreamToShow end');
475
+ let changed = false;
457
476
  if (skills.length) {
477
+ changed = true;
458
478
  await hooks.callHook('onSkillsEnd', skills, message, result);
479
+ }
480
+ if (attachments.length) {
481
+ changed = true;
482
+ await hooks.callHook('onAttachmentsEnd', attachments, message, result);
483
+ }
484
+ if (changed) {
459
485
  await message.update();
460
486
  }
461
487
  let valid = false;
@@ -533,6 +559,7 @@ export function withAIStream() {
533
559
  });
534
560
  onAgentDispose(async () => {
535
561
  const streamSession = session.get('AIStream.streamSession');
562
+ const connection = session.get('AIStream.connection');
536
563
 
537
564
  // 比较低的概率 onAgentStart 还没跑完(卡在获取获取家庭信息)就调用了 dispose
538
565
  if (streamSession) {
@@ -541,6 +568,9 @@ export function withAIStream() {
541
568
  */
542
569
  await streamSession.close();
543
570
  }
571
+ if (connection) {
572
+ await connection.close();
573
+ }
544
574
  });
545
575
  const handleTTTAction = async (tile, tttAction) => {
546
576
  const result = {
@@ -664,6 +694,12 @@ export function withAIStream() {
664
694
  onSessionEventReceived: fn => {
665
695
  return hooks.hook('onSessionEventReceived', fn);
666
696
  },
697
+ onAttachmentCompose: fn => {
698
+ return hooks.hook('onAttachmentCompose', fn);
699
+ },
700
+ onAttachmentsEnd: fn => {
701
+ return hooks.hook('onAttachmentsEnd', fn);
702
+ },
667
703
  sendEvent: params => sendEventToAIStream(params),
668
704
  onTTTAction: fn => {
669
705
  return hooks.hook('onTTTAction', fn);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/t-agent-plugin-aistream",
3
- "version": "0.2.8-beta.2",
3
+ "version": "0.2.8-beta.4",
4
4
  "author": "Tuya.inc",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -26,7 +26,6 @@
26
26
  "test:coverage": "jest --runInBand --coverage"
27
27
  },
28
28
  "dependencies": {
29
- "dayjs": "^1.10.4",
30
29
  "url-parse": "^1.5.10",
31
30
  "web-streams-polyfill": "^4.0.0"
32
31
  },
@@ -37,5 +36,5 @@
37
36
  "devDependencies": {
38
37
  "@types/url-parse": "^1.4.11"
39
38
  },
40
- "gitHead": "450ca8d41536094b3bea57198fd7724ba6812438"
39
+ "gitHead": "556407c77bdeb9f879a8bb9e89184fc62e4c8bd0"
41
40
  }