@volley/recognition-client-sdk 0.1.296 → 0.1.381

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.
@@ -150,34 +150,44 @@ export class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRecognition
150
150
  private state: RecognitionState;
151
151
  private isRecordingAudio: boolean = false;
152
152
  private stateChangeCallback: ((state: RecognitionState) => void) | undefined;
153
+ private expectedUuid: string;
154
+ private logger: IRecognitionClientConfig['logger'];
153
155
 
154
156
  constructor(config: SimplifiedVGFClientConfig) {
155
157
  const { onStateChange, initialState, ...clientConfig } = config;
156
158
  this.stateChangeCallback = onStateChange;
159
+ this.logger = clientConfig.logger;
157
160
 
158
161
  // Use provided initial state or create from config
159
162
  if (initialState) {
160
- // If previous session is in terminal state (ABORTED/FINALIZED), force new UUID
161
- // This prevents server from attaching to completed session which silently drops audio
162
- if (initialState.transcriptionStatus === TranscriptionStatus.ABORTED ||
163
- initialState.transcriptionStatus === TranscriptionStatus.FINALIZED) {
163
+ // Check if initial state has a valid UUID
164
+ const needsNewUuid = !initialState.audioUtteranceId ||
165
+ initialState.audioUtteranceId === '' ||
166
+ initialState.transcriptionStatus === TranscriptionStatus.ABORTED ||
167
+ initialState.transcriptionStatus === TranscriptionStatus.FINALIZED;
164
168
 
169
+ if (needsNewUuid) {
165
170
  // Generate new UUID for fresh session
166
171
  const newUUID = crypto.randomUUID();
167
172
 
168
173
  if (clientConfig.logger) {
169
- clientConfig.logger('info', `Terminal session detected (${initialState.transcriptionStatus}), generating new UUID: ${newUUID}`);
174
+ const reason = !initialState.audioUtteranceId ? 'Missing UUID' :
175
+ initialState.audioUtteranceId === '' ? 'Empty UUID' :
176
+ `Terminal session (${initialState.transcriptionStatus})`;
177
+ clientConfig.logger('info', `${reason} detected, generating new UUID: ${newUUID}`);
170
178
  }
171
179
 
172
- // Update state with new UUID and reset session-specific fields
180
+ // Update state with new UUID and reset session-specific fields if terminal
173
181
  this.state = {
174
182
  ...initialState,
175
183
  audioUtteranceId: newUUID,
176
- // Reset status fields for fresh session
177
- transcriptionStatus: TranscriptionStatus.NOT_STARTED,
178
- startRecordingStatus: RecordingStatus.READY,
179
- // Clear previous session's transcript
180
- finalTranscript: undefined
184
+ // Reset status fields for fresh session if terminal state
185
+ ...(initialState.transcriptionStatus === TranscriptionStatus.ABORTED ||
186
+ initialState.transcriptionStatus === TranscriptionStatus.FINALIZED ? {
187
+ transcriptionStatus: TranscriptionStatus.NOT_STARTED,
188
+ startRecordingStatus: RecordingStatus.READY,
189
+ finalTranscript: undefined
190
+ } : {})
181
191
  };
182
192
 
183
193
  // Use new UUID in client config
@@ -188,7 +198,7 @@ export class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRecognition
188
198
  onStateChange(this.state);
189
199
  }
190
200
  } else {
191
- // Non-terminal state - safe to reuse UUID (e.g., reconnecting to IN_PROGRESS session)
201
+ // Non-terminal state with valid UUID - safe to reuse (e.g., reconnecting to IN_PROGRESS session)
192
202
  this.state = initialState;
193
203
  // Override audioUtteranceId in config if state has one
194
204
  if (initialState.audioUtteranceId && !clientConfig.audioUtteranceId) {
@@ -203,6 +213,9 @@ export class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRecognition
203
213
  // Client is immediately ready to accept audio (will buffer if not connected)
204
214
  this.state = { ...this.state, startRecordingStatus: 'READY' };
205
215
 
216
+ // Track the expected UUID for this session
217
+ this.expectedUuid = this.state.audioUtteranceId;
218
+
206
219
  // If VGF state has promptSlotMap, configure gameContext to use it
207
220
  if (this.state.promptSlotMap) {
208
221
  // Set useContext=true in ASR config to enable context processing
@@ -229,6 +242,20 @@ export class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRecognition
229
242
 
230
243
  // These callbacks ONLY update the VGF state sink
231
244
  onTranscript: (result) => {
245
+ // Skip update if UUID doesn't match (stale callback from previous session)
246
+ if (result.audioUtteranceId && result.audioUtteranceId !== this.expectedUuid) {
247
+ if (this.logger) {
248
+ this.logger('warn',
249
+ `[VGF] Skipping transcript update: UUID mismatch (expected: ${this.expectedUuid}, got: ${result.audioUtteranceId})`
250
+ );
251
+ }
252
+ // Still call original callback if provided
253
+ if (clientConfig.onTranscript) {
254
+ clientConfig.onTranscript(result);
255
+ }
256
+ return;
257
+ }
258
+
232
259
  // Update VGF state based on transcript
233
260
  this.state = mapTranscriptionResultToState(this.state, result, this.isRecordingAudio);
234
261
  this.notifyStateChange();
@@ -240,6 +267,20 @@ export class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRecognition
240
267
  },
241
268
 
242
269
  onMetadata: (metadata) => {
270
+ // Skip update if UUID doesn't match (stale callback from previous session)
271
+ if (metadata.audioUtteranceId && metadata.audioUtteranceId !== this.expectedUuid) {
272
+ if (this.logger) {
273
+ this.logger('warn',
274
+ `[VGF] Skipping metadata update: UUID mismatch (expected: ${this.expectedUuid}, got: ${metadata.audioUtteranceId})`
275
+ );
276
+ }
277
+ // Still call original callback if provided
278
+ if (clientConfig.onMetadata) {
279
+ clientConfig.onMetadata(metadata);
280
+ }
281
+ return;
282
+ }
283
+
243
284
  this.state = mapMetadataToState(this.state, metadata);
244
285
  this.notifyStateChange();
245
286
 
@@ -256,6 +297,20 @@ export class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRecognition
256
297
  },
257
298
 
258
299
  onError: (error) => {
300
+ // Skip update if UUID doesn't match (stale callback from previous session)
301
+ if (error.audioUtteranceId && error.audioUtteranceId !== this.expectedUuid) {
302
+ if (this.logger) {
303
+ this.logger('warn',
304
+ `[VGF] Skipping error update: UUID mismatch (expected: ${this.expectedUuid}, got: ${error.audioUtteranceId})`
305
+ );
306
+ }
307
+ // Still call original callback if provided
308
+ if (clientConfig.onError) {
309
+ clientConfig.onError(error);
310
+ }
311
+ return;
312
+ }
313
+
259
314
  this.isRecordingAudio = false; // Reset on error
260
315
  this.state = mapErrorToState(this.state, error);
261
316
  this.notifyStateChange();
@@ -382,6 +437,7 @@ export class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRecognition
382
437
  }
383
438
 
384
439
  private notifyStateChange(): void {
440
+ // State has already been validated for correct UUID before this is called
385
441
  if (this.stateChangeCallback) {
386
442
  this.stateChangeCallback({ ...this.state });
387
443
  }
@@ -39,6 +39,9 @@ export const RecognitionVGFStateSchema = z.object({
39
39
 
40
40
  // Support for prompt slot mapping - passed to recognition context when present
41
41
  promptSlotMap: z.record(z.string(), z.array(z.string())).optional(), // Optional map of slot names to prompt values for recognition context
42
+
43
+ // Recognition action processing state - managed externally, SDK preserves but never modifies
44
+ recognitionActionProcessingState: z.string().optional(), // "NOT_STARTED", "IN_PROGRESS", "COMPLETED"
42
45
  })
43
46
 
44
47
  export type RecognitionState = z.infer<typeof RecognitionVGFStateSchema>
@@ -63,6 +66,14 @@ export const TranscriptionStatus = {
63
66
 
64
67
  export type TranscriptionStatusType = typeof TranscriptionStatus[keyof typeof TranscriptionStatus]
65
68
 
69
+ export const RecognitionActionProcessingState = {
70
+ NOT_STARTED: "NOT_STARTED",
71
+ IN_PROGRESS: "IN_PROGRESS",
72
+ COMPLETED: "COMPLETED",
73
+ } as const
74
+
75
+ export type RecognitionActionProcessingStateType = typeof RecognitionActionProcessingState[keyof typeof RecognitionActionProcessingState]
76
+
66
77
  // Helper function to create initial state
67
78
  export function createInitialRecognitionState(audioUtteranceId: string): RecognitionState {
68
79
  return {
@@ -70,6 +81,7 @@ export function createInitialRecognitionState(audioUtteranceId: string): Recogni
70
81
  startRecordingStatus: RecordingStatus.NOT_READY,
71
82
  transcriptionStatus: TranscriptionStatus.NOT_STARTED,
72
83
  pendingTranscript: "",
84
+ recognitionActionProcessingState: RecognitionActionProcessingState.NOT_STARTED,
73
85
  }
74
86
  }
75
87