@ray-js/t-agent-plugin-aistream 0.2.0-beta-17 → 0.2.0-beta.18

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.
@@ -1,3 +1,6 @@
1
+ import "core-js/modules/web.dom-collections.iterator.js";
2
+ import { AIStreamAppErrorCode, AIStreamErrorCode, AIStreamServerErrorCode } from '../AIStreamTypes';
3
+ import { tryCatch } from './misc';
1
4
  export class BaseError extends Error {
2
5
  constructor(message, code) {
3
6
  super(message);
@@ -16,27 +19,87 @@ export class TTTError extends BaseError {
16
19
  this.name = 'TTTError';
17
20
  }
18
21
  }
19
- export class AIStreamConnectionError extends BaseError {
22
+ export class AIStreamError extends BaseError {
20
23
  constructor(message, code) {
21
24
  super(message, code);
22
25
  this.message = message;
23
26
  this.code = code;
24
- this.name = 'AIStreamConnectionError';
27
+ this.name = 'AIStreamError';
25
28
  }
26
29
  }
27
- export class AIStreamSessionError extends BaseError {
28
- constructor(message, code) {
29
- super(message, code);
30
- this.message = message;
31
- this.code = code;
32
- this.name = 'AIStreamSessionError';
30
+
31
+ /**
32
+ * 移除错误对象的顶部栈帧
33
+ *
34
+ * @param error 需要处理的错误对象
35
+ * @returns 移除顶部栈帧后的错误对象
36
+ */
37
+ function removeTopStackFrame(error) {
38
+ if (!error.stack) {
39
+ return error;
40
+ }
41
+
42
+ // 按行分割错误栈信息
43
+ const stackLines = error.stack.split('\n');
44
+
45
+ // 检查是否有足够的行来移除
46
+ if (stackLines.length <= 1) {
47
+ return error;
33
48
  }
49
+
50
+ // 保留第一行错误信息,移除第二行(顶部栈帧)
51
+ const newStack = [stackLines[0]].concat(stackLines.slice(2)).join('\n');
52
+ error.stack = newStack;
53
+ return error;
34
54
  }
35
- export class AIStreamEventError extends BaseError {
36
- constructor(message, code) {
37
- super(message, code);
38
- this.message = message;
39
- this.code = code;
40
- this.name = 'AIStreamEventError';
55
+ export const transformErrorCode = code => {
56
+ switch (code) {
57
+ case AIStreamAppErrorCode.GENERIC_ERROR:
58
+ return AIStreamErrorCode.UNKNOWN_ERROR;
59
+ case AIStreamAppErrorCode.INVALID_PARAMS:
60
+ case AIStreamAppErrorCode.CONNECTION_INVALID:
61
+ case AIStreamAppErrorCode.SESSION_ID_INVALID:
62
+ case AIStreamAppErrorCode.EVENT_ID_INVALID:
63
+ case AIStreamAppErrorCode.INVALID_DATA_PACKET:
64
+ case AIStreamAppErrorCode.FILE_DATA_READ_ERROR:
65
+ case AIStreamAppErrorCode.DATA_CHANNEL_NOT_FOUND:
66
+ return AIStreamErrorCode.INVALID_PARAMS;
67
+ case AIStreamAppErrorCode.HTTP_REQUEST_FAILED:
68
+ return AIStreamErrorCode.SESSION_CREATE_FAILED;
69
+ case AIStreamAppErrorCode.CONNECTION_CLOSED_BY_REMOTE:
70
+ return AIStreamErrorCode.CONNECTION_CLOSED;
71
+ case AIStreamServerErrorCode.OK:
72
+ case AIStreamServerErrorCode.CLOSE_BY_CLIENT:
73
+ case AIStreamServerErrorCode.CLOSE_BY_EXPIRE:
74
+ case AIStreamServerErrorCode.CLOSE_BY_IO:
75
+ case AIStreamServerErrorCode.CLOSE_BY_KEEPALIVE:
76
+ case AIStreamServerErrorCode.CLOSE_BY_REUSE:
77
+ return AIStreamErrorCode.CONNECTION_CLOSED;
78
+ case AIStreamServerErrorCode.BAD_REQUEST:
79
+ case AIStreamServerErrorCode.NOT_FOUND:
80
+ case AIStreamServerErrorCode.UNAUTHENTICATED:
81
+ return AIStreamErrorCode.INVALID_PARAMS;
82
+ case AIStreamServerErrorCode.INTERNAL_SERVER_ERROR:
83
+ return AIStreamErrorCode.UNKNOWN_ERROR;
84
+ case AIStreamServerErrorCode.REQUEST_TIMEOUT:
85
+ case AIStreamServerErrorCode.GATEWAY_TIMEOUT:
86
+ return AIStreamErrorCode.TIMEOUT;
87
+ }
88
+ return AIStreamErrorCode.UNKNOWN_ERROR;
89
+ };
90
+
91
+ // 函数实现
92
+ export async function tryCatchTTT(fn) {
93
+ const [error, result] = await tryCatch(fn);
94
+ if (error) {
95
+ let {
96
+ code,
97
+ message
98
+ } = error;
99
+ message = "".concat(message, " (original_code: ").concat(code || '-', ")");
100
+ code = transformErrorCode(code);
101
+ const e = removeTopStackFrame(new AIStreamError(message, code));
102
+ return [e, null];
41
103
  }
104
+ return [null, result];
42
105
  }
@@ -7,10 +7,7 @@ export interface SendBlocksToAIStreamParams {
7
7
  session: AIStreamSession;
8
8
  attribute?: AIStreamChatAttribute;
9
9
  signal?: AbortSignal;
10
- }
11
- export declare class AIStreamSessionError extends Error {
12
- readonly code: number;
13
- constructor(message: string, code: number);
10
+ enableTts?: boolean;
14
11
  }
15
12
  export declare function sendBlocksToAIStream(params: SendBlocksToAIStreamParams): {
16
13
  response: StreamResponse;
@@ -3,10 +3,10 @@ import "core-js/modules/es.json.stringify.js";
3
3
  import "core-js/modules/web.dom-collections.iterator.js";
4
4
  import '../polyfill';
5
5
  import { ReadableStream } from 'web-streams-polyfill';
6
- import { AIStreamAttributePayloadType, AIStreamAttributeType, AIStreamChatSysWorkflow, ConnectState, FileFormat, ReceivedTextPacketEof, ReceivedTextPacketType, SessionState, StreamFlag } from '../AIStreamTypes';
6
+ import { AIStreamAttributePayloadType, AIStreamAttributeType, AIStreamErrorCode, ConnectState, FileFormat, ReceivedTextPacketEof, ReceivedTextPacketType, SessionState, StreamFlag } from '../AIStreamTypes';
7
7
  import { EmitterEvent, generateId, safeParseJSON, StreamResponse } from '@ray-js/t-agent';
8
8
  import { tryCatch } from './misc';
9
- import { AIStreamConnectionError } from './errors';
9
+ import { AIStreamError, transformErrorCode } from './errors';
10
10
  import logger from './logger';
11
11
  const mimeTypeToFormatMap = {
12
12
  'video/mp4': FileFormat.MP4,
@@ -14,13 +14,6 @@ const mimeTypeToFormatMap = {
14
14
  'application/json': FileFormat.JSON,
15
15
  'application/pdf': FileFormat.PDF
16
16
  };
17
- export class AIStreamSessionError extends Error {
18
- constructor(message, code) {
19
- super(message);
20
- this.name = 'AIStreamSessionError';
21
- this.code = code;
22
- }
23
- }
24
17
  export function sendBlocksToAIStream(params) {
25
18
  logger.debug('sendBlocksToAIStream start');
26
19
  const {
@@ -41,8 +34,7 @@ export function sendBlocksToAIStream(params) {
41
34
  }
42
35
  const attribute = _objectSpread({
43
36
  'processing.interrupt': 'false',
44
- 'asr.enableVad': 'false',
45
- 'sys.workflow': audioEmitter ? AIStreamChatSysWorkflow.ASR_LLM : AIStreamChatSysWorkflow.LLM
37
+ 'asr.enableVad': 'false'
46
38
  }, params.attribute);
47
39
  let canceled = false;
48
40
  let closed = false;
@@ -80,6 +72,20 @@ export function sendBlocksToAIStream(params) {
80
72
  }
81
73
  }
82
74
  };
75
+ const emitError = error => {
76
+ if (!error) {
77
+ error = new AIStreamError('Unknown error', AIStreamErrorCode.UNKNOWN_ERROR);
78
+ }
79
+ if (!(error instanceof AIStreamError)) {
80
+ error = new AIStreamError("AIStream Error: ".concat(error.message || 'Unknown error'), transformErrorCode(error.code));
81
+ }
82
+ enqueue({
83
+ type: 'error',
84
+ level: 'error',
85
+ error,
86
+ meta: {}
87
+ });
88
+ };
83
89
  let pendingCancel = false;
84
90
  if (audioEmitter) {
85
91
  audioEmitter.addEventListener('confirm', () => {
@@ -108,16 +114,10 @@ export function sendBlocksToAIStream(params) {
108
114
  }]
109
115
  }));
110
116
  if (error) {
111
- const e = new AIStreamSessionError(error.message, error.code || 0);
112
- enqueue({
113
- type: 'error',
114
- error: e,
115
- level: 'error',
116
- meta: {}
117
- });
117
+ emitError(error);
118
118
  if (audioEmitter) {
119
119
  audioEmitter.dispatchEvent(new EmitterEvent('error', {
120
- detail: e
120
+ detail: error
121
121
  }));
122
122
  }
123
123
  close();
@@ -167,10 +167,9 @@ export function sendBlocksToAIStream(params) {
167
167
  if (packet.data.text === '') {
168
168
  logger.debug('sendBlocksToAIStream Receive ASR EOF EMPTY');
169
169
  // 没识别出任何文本
170
+ const e = new AIStreamError('ASR data is empty', AIStreamErrorCode.ASR_EMPTY);
170
171
  audioEmitter.dispatchEvent(new EmitterEvent('error', {
171
- detail: {
172
- name: 'AsrEmptyError'
173
- }
172
+ detail: e
174
173
  }));
175
174
  } else {
176
175
  logger.debug('sendBlocksToAIStream Receive ASR EOF', packet.data.text);
@@ -225,13 +224,9 @@ export function sendBlocksToAIStream(params) {
225
224
  }
226
225
  } else if (data.type === 'sessionState') {
227
226
  if (data.body.sessionState === SessionState.CLOSED || data.body.sessionState === SessionState.CREATE_FAILED) {
228
- const e = new AIStreamSessionError('Session closed', data.body.code);
229
- enqueue({
230
- type: 'error',
231
- error: e,
232
- level: 'error',
233
- meta
234
- });
227
+ const msg = SessionState[data.body.sessionState];
228
+ const e = new AIStreamError("Session Error: ".concat(msg, ", (original_code: ").concat(data.body.code, ")"), transformErrorCode(data.body.code));
229
+ emitError(error);
235
230
  if (audioEmitter) {
236
231
  audioEmitter.dispatchEvent(new EmitterEvent('error', {
237
232
  detail: e
@@ -241,13 +236,8 @@ export function sendBlocksToAIStream(params) {
241
236
  }
242
237
  } else if (data.type === 'connectionState') {
243
238
  if (data.body.connectState === ConnectState.DISCONNECTED || data.body.connectState === ConnectState.CLOSED) {
244
- const e = new AIStreamConnectionError('Connection disconnected', data.body.code);
245
- enqueue({
246
- type: 'error',
247
- error: e,
248
- level: 'error',
249
- meta
250
- });
239
+ const e = new AIStreamError("Session disconnected, (original_code: ".concat(data.body.code, ")"), transformErrorCode(data.body.code));
240
+ emitError(error);
251
241
  if (audioEmitter) {
252
242
  audioEmitter.dispatchEvent(new EmitterEvent('error', {
253
243
  detail: e
@@ -269,14 +259,7 @@ export function sendBlocksToAIStream(params) {
269
259
  close();
270
260
  }
271
261
  });
272
- event.on('error', error => {
273
- enqueue({
274
- type: 'error',
275
- level: 'error',
276
- error: error instanceof Error ? error : new Error('Unknown error'),
277
- meta: {}
278
- });
279
- });
262
+ event.on('error', emitError);
280
263
  for (const block of blocks) {
281
264
  if (block.type === 'text') {
282
265
  event.write({
@@ -285,24 +268,24 @@ export function sendBlocksToAIStream(params) {
285
268
  type: 'text',
286
269
  message: block.text
287
270
  })
288
- });
271
+ }).catch(emitError);
289
272
  } else if (block.type === 'image_path') {
290
273
  event.write({
291
274
  type: 'image',
292
275
  path: block.image_path.path
293
- });
276
+ }).catch(emitError);
294
277
  } else if (block.type === 'file_path') {
295
278
  event.write({
296
279
  type: 'file',
297
280
  format: mimeTypeToFormatMap[block.file_url.mimeType] || 0,
298
281
  path: block.file_path.path
299
- });
282
+ }).catch(emitError);
300
283
  } else if (block.type === 'video_path') {
301
284
  event.write({
302
285
  type: 'file',
303
286
  format: FileFormat.MP4,
304
287
  path: block.file_path.path
305
- });
288
+ }).catch(emitError);
306
289
  }
307
290
  }
308
291
  if (audioEmitter) {
@@ -1,10 +1,11 @@
1
- import { ApiRequestByAtopParams, ApiRequestByHighwayParams, AudioBody, AuthorizeParams, AuthorizePolicyStatusParams, CanIUseRouterParams, CloseSessionParams, ConnectParams, ConnectStateBody, CreateSessionParams, DeleteRecordListParams, DisconnectParams, EventBody, EventChannelMessageParams, GetAppInfoParams, GetCurrentHomeInfoParams, GetMiniAppConfigParams, GetAccountInfoParams, ImageBody, InsertRecordParams, NavigateToMiniProgramParams, OpenInnerH5Params, OpenMiniWidgetParams, QueryAgentTokenParams, QueryRecordListParams, RecordAmplitudesBody, RegisterChannelParams, RouterParams, SendEventChatBreakParams, SendEventEndParams, SendEventPayloadEndParams, SendEventStartParams, SendImageDataParams, SendTextDataParams, SessionStateBody, StartRecordAndSendAudioDataParams, StopRecordAndSendAudioDataParams, TextBody, UpdateRecordParams, IsConnectedParams } from '../AIStreamTypes';
1
+ import { ApiRequestByAtopParams, ApiRequestByHighwayParams, AudioBody, AuthorizeParams, AuthorizePolicyStatusParams, CanIUseRouterParams, CloseSessionParams, ConnectParams, ConnectStateBody, CreateSessionParams, DeleteRecordListParams, DisconnectParams, EventBody, EventChannelMessageParams, GetAppInfoParams, GetCurrentHomeInfoParams, GetMiniAppConfigParams, GetAccountInfoParams, ImageBody, InsertRecordParams, NavigateToMiniProgramParams, OpenInnerH5Params, OpenMiniWidgetParams, QueryAgentTokenParams, QueryRecordListParams, RecordAmplitudesBody, RegisterChannelParams, RouterParams, SendEventChatBreakParams, SendEventEndParams, SendEventPayloadEndParams, SendEventStartParams, SendImageDataParams, SendTextDataParams, SessionStateBody, StartRecordAndSendAudioDataParams, StopRecordAndSendAudioDataParams, TextBody, UpdateRecordParams, IsConnectedParams, GetNetworkTypeParams, StartPlayAudioParams } from '../AIStreamTypes';
2
2
  export declare const getMiniAppConfig: (options?: Omit<GetMiniAppConfigParams, "success" | "fail"> | undefined) => Promise<{
3
3
  config: any;
4
4
  }>;
5
5
  export declare const getAccountInfo: (options?: Omit<GetAccountInfoParams, "success" | "fail"> | undefined) => Promise<{
6
6
  miniProgram: import("../AIStreamTypes").MiniProgramAccountInfo;
7
7
  }>;
8
+ export declare const systemInfo: any;
8
9
  export declare const isDevTools: () => boolean;
9
10
  export declare const apiRequestByAtop: (options?: Omit<ApiRequestByAtopParams, "success" | "fail"> | undefined) => Promise<{
10
11
  thing_json_?: any;
@@ -85,7 +86,11 @@ export declare const registerRecordAmplitudes: (options?: Omit<{
85
86
  count: number;
86
87
  }, "success" | "fail"> | undefined) => Promise<never>;
87
88
  export declare const unregisterVoiceAmplitudes: (options?: Omit<any, "success" | "fail"> | undefined) => Promise<unknown>;
89
+ export declare const startPlayAudio: (options?: Omit<StartPlayAudioParams, "success" | "fail"> | undefined) => Promise<null>;
88
90
  export declare const listenEventReceived: (listener: (params: EventBody) => void) => () => void;
91
+ export declare const listenAudioPlayEnd: (listener: (params: {
92
+ path: string;
93
+ }) => void) => () => void;
89
94
  export declare const listenAudioReceived: (listener: (params: AudioBody) => void) => () => void;
90
95
  export declare const listenTextReceived: (listener: (params: TextBody) => void) => () => void;
91
96
  export declare const listenImageReceived: (listener: (params: ImageBody) => void) => () => void;
@@ -101,3 +106,7 @@ export declare const updateRecord: (options?: Omit<UpdateRecordParams, "success"
101
106
  export declare const insertRecord: (options?: Omit<InsertRecordParams, "success" | "fail"> | undefined) => Promise<{
102
107
  id: number;
103
108
  }>;
109
+ export declare const getNetworkType: (options?: Omit<GetNetworkTypeParams, "success" | "fail"> | undefined) => Promise<{
110
+ networkType: import("../AIStreamTypes").NetworkType;
111
+ signalStrength: number;
112
+ }>;
package/dist/utils/ttt.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { listening, promisify } from './promisify';
2
2
  export const getMiniAppConfig = promisify(ty.getMiniAppConfig, true);
3
3
  export const getAccountInfo = promisify(ty.getAccountInfo);
4
+ export const systemInfo = ty.getSystemInfoSync();
4
5
  export const isDevTools = () => {
5
- return ty.getSystemInfoSync().brand === 'devtools';
6
+ return systemInfo.brand === 'devtools';
6
7
  };
7
8
  export const apiRequestByAtop = promisify(ty.apiRequestByAtop);
8
9
  export const apiRequestByHighway = promisify(ty.apiRequestByHighway);
9
10
  export const createNativeEventManager = options => {
10
- if (isDevTools) {
11
+ if (isDevTools()) {
11
12
  return {
12
13
  onListener: () => null,
13
14
  offerListener: () => null
@@ -55,10 +56,12 @@ export const sendImageData = promisify(ty.aistream.sendImageData, true);
55
56
  export const sendTextData = promisify(ty.aistream.sendTextData, true);
56
57
  export const registerRecordAmplitudes = promisify(ty.aistream.registerRecordAmplitudes, true);
57
58
  export const unregisterVoiceAmplitudes = promisify(ty.aistream.unregisterVoiceAmplitudes, true);
59
+ export const startPlayAudio = promisify(ty.aistream.startPlayAudio, true);
58
60
 
59
61
  // export const sendFileData = promisify<SendFileDataParams>(ty.aistream.sendFileData);
60
62
 
61
63
  export const listenEventReceived = listening(ty.aistream.onEventReceived, ty.aistream.offEventReceived, true);
64
+ export const listenAudioPlayEnd = listening(ty.aistream.onAudioPlayEnd, ty.aistream.onAudioPlayEnd, true);
62
65
  export const listenAudioReceived = listening(ty.aistream.onAudioReceived, ty.aistream.offAudioReceived, true);
63
66
 
64
67
  // export const listenVideoReceived = listening<VideoBody>(
@@ -82,4 +85,5 @@ export const listenRecordAmplitudes = listening(ty.aistream.onRecordAmplitudes,
82
85
  export const queryRecordList = promisify(ty.aistream.queryRecordList, true);
83
86
  export const deleteRecordList = promisify(ty.aistream.deleteRecordList, true);
84
87
  export const updateRecord = promisify(ty.aistream.updateRecord, true);
85
- export const insertRecord = promisify(ty.aistream.insertRecord, true);
88
+ export const insertRecord = promisify(ty.aistream.insertRecord, true);
89
+ export const getNetworkType = promisify(ty.getNetworkType, true);
@@ -26,6 +26,8 @@ export interface AIStreamOptions {
26
26
  earlyStart?: boolean;
27
27
  /** 自定义消息存储, 返回的实例需要实现 ChatHistoryLocalStore 接口, 返回null则不存储历史聊天记录 */
28
28
  createChatHistoryStore?: (agent: ChatAgent) => ChatHistoryLocalStore | null;
29
+ /** 是否开启音频合成 */
30
+ enableTts?: boolean;
29
31
  }
30
32
  export type AIStreamPlugin = GetChatPluginHandler<typeof withAIStream>;
31
33
  export interface AIStreamHooks {
@@ -1,5 +1,5 @@
1
- import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
1
  import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
2
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
3
3
  const _excluded = ["message"];
4
4
  import "core-js/modules/es.array.flat.js";
5
5
  import "core-js/modules/es.array.reverse.js";
@@ -98,7 +98,10 @@ export function withAIStream() {
98
98
  api: tokenOptions.api,
99
99
  apiVersion: tokenOptions.version,
100
100
  solutionCode: agentId,
101
- extParams: tokenOptions.extParams
101
+ extParams: _objectSpread(_objectSpread({}, tokenOptions.extParams), {}, {
102
+ needTts: !!options.enableTts,
103
+ deviceId: options.deviceId || undefined
104
+ })
102
105
  });
103
106
  await session.set('AIStream.streamSession', streamSession);
104
107
  if (options.earlyStart) {
@@ -344,8 +347,10 @@ export function withAIStream() {
344
347
  }
345
348
  if (message.bubble.text) {
346
349
  await message.persist();
347
- } else {
350
+ } else if (message.bubble.status === BubbleTileStatus.NORMAL) {
348
351
  await message.remove();
352
+ } else {
353
+ await message.persist();
349
354
  }
350
355
  return [userMsg, ...result.messages];
351
356
  };
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-17",
3
+ "version": "0.2.0-beta.18",
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": "32e3d3ae017d11825e0bfd4dc4441abe67bd8ef0"
38
+ "gitHead": "3826cdeac61109618670dcbbeafd4103113f04d7"
39
39
  }