@volley/recognition-client-sdk 0.1.782 → 0.1.800
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.
- package/dist/browser.bundled.d.ts +75 -4
- package/dist/index.bundled.d.ts +198 -87
- package/dist/index.js +191 -20
- package/dist/index.js.map +4 -4
- package/dist/recog-client-sdk.browser.js +95 -4
- package/dist/recog-client-sdk.browser.js.map +4 -4
- package/dist/recognition-client.d.ts +23 -0
- package/dist/recognition-client.d.ts.map +1 -1
- package/dist/recognition-client.types.d.ts +32 -0
- package/dist/recognition-client.types.d.ts.map +1 -1
- package/dist/simplified-vgf-recognition-client.d.ts +22 -85
- package/dist/simplified-vgf-recognition-client.d.ts.map +1 -1
- package/dist/utils/audio-resampler.d.ts +32 -0
- package/dist/utils/audio-resampler.d.ts.map +1 -0
- package/dist/vgf-recognition-mapper.d.ts +9 -17
- package/dist/vgf-recognition-mapper.d.ts.map +1 -1
- package/dist/vgf-recognition-state.d.ts +103 -0
- package/dist/vgf-recognition-state.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.spec.ts +2 -0
- package/src/recognition-client.ts +65 -7
- package/src/recognition-client.types.ts +37 -0
- package/src/simplified-vgf-recognition-client.spec.ts +0 -27
- package/src/simplified-vgf-recognition-client.ts +97 -127
- package/src/utils/audio-resampler.spec.ts +69 -0
- package/src/utils/audio-resampler.ts +79 -0
- package/src/vgf-recognition-mapper.spec.ts +143 -0
- package/src/vgf-recognition-mapper.ts +35 -45
- package/src/vgf-recognition-state.ts +19 -1
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
+
mapTranscriptionResultToState,
|
|
7
|
+
mapSessionConfiguredToState,
|
|
6
8
|
resetRecognitionVGFState
|
|
7
9
|
} from './vgf-recognition-mapper.js';
|
|
8
10
|
import {
|
|
@@ -11,6 +13,13 @@ import {
|
|
|
11
13
|
TranscriptionStatus,
|
|
12
14
|
RecognitionActionProcessingState
|
|
13
15
|
} from './vgf-recognition-state.js';
|
|
16
|
+
import {
|
|
17
|
+
ASRApiType,
|
|
18
|
+
DetectionTypeV1,
|
|
19
|
+
RecognitionResultTypeV1,
|
|
20
|
+
SessionConfiguredV1,
|
|
21
|
+
TranscriptionResultV1
|
|
22
|
+
} from '@recog/shared-types';
|
|
14
23
|
|
|
15
24
|
describe('resetRecognitionVGFState', () => {
|
|
16
25
|
it('should generate a new UUID', () => {
|
|
@@ -75,4 +84,138 @@ describe('resetRecognitionVGFState', () => {
|
|
|
75
84
|
expect(newState.startRecordingStatus).toBe(RecordingStatus.READY);
|
|
76
85
|
expect(newState.recognitionActionProcessingState).toBe(RecognitionActionProcessingState.NOT_STARTED);
|
|
77
86
|
});
|
|
87
|
+
|
|
88
|
+
it('should preserve prompt input fields and clear detections', () => {
|
|
89
|
+
const originalState: RecognitionState = {
|
|
90
|
+
audioUtteranceId: 'old-uuid-123',
|
|
91
|
+
pendingTranscript: '',
|
|
92
|
+
promptSTT: 'hello,world',
|
|
93
|
+
promptSTF: 'map to play()',
|
|
94
|
+
promptTTF: 'extract title,artist',
|
|
95
|
+
detections: [
|
|
96
|
+
{ type: DetectionTypeV1.SEARCH, query: 'one time', score: 0.92 }
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const newState = resetRecognitionVGFState(originalState);
|
|
101
|
+
|
|
102
|
+
expect(newState.promptSTT).toBe('hello,world');
|
|
103
|
+
expect(newState.promptSTF).toBe('map to play()');
|
|
104
|
+
expect(newState.promptTTF).toBe('extract title,artist');
|
|
105
|
+
expect(newState.detections).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('mapTranscriptionResultToState detections', () => {
|
|
110
|
+
const baseState: RecognitionState = {
|
|
111
|
+
audioUtteranceId: 'utt-1',
|
|
112
|
+
pendingTranscript: ''
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
it('should copy detections from a pending transcript result', () => {
|
|
116
|
+
const result: TranscriptionResultV1 = {
|
|
117
|
+
type: RecognitionResultTypeV1.TRANSCRIPTION,
|
|
118
|
+
audioUtteranceId: 'utt-1',
|
|
119
|
+
finalTranscript: '',
|
|
120
|
+
finalTranscriptRaw: '',
|
|
121
|
+
pendingTranscript: 'one',
|
|
122
|
+
is_finished: false,
|
|
123
|
+
detections: [
|
|
124
|
+
{ type: DetectionTypeV1.SEARCH, query: 'one time', score: 0.81, startMs: 100, endMs: 900 }
|
|
125
|
+
]
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const newState = mapTranscriptionResultToState(baseState, result, true);
|
|
129
|
+
|
|
130
|
+
expect(newState.detections).toEqual([
|
|
131
|
+
{ type: DetectionTypeV1.SEARCH, query: 'one time', score: 0.81, startMs: 100, endMs: 900 }
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should copy detections from a final transcript result', () => {
|
|
136
|
+
const result: TranscriptionResultV1 = {
|
|
137
|
+
type: RecognitionResultTypeV1.TRANSCRIPTION,
|
|
138
|
+
audioUtteranceId: 'utt-1',
|
|
139
|
+
finalTranscript: 'one time',
|
|
140
|
+
finalTranscriptRaw: 'one time',
|
|
141
|
+
is_finished: true,
|
|
142
|
+
detections: [
|
|
143
|
+
{ type: DetectionTypeV1.SEARCH, query: 'one time', score: 0.93 }
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const newState = mapTranscriptionResultToState(baseState, result, false);
|
|
148
|
+
|
|
149
|
+
expect(newState.detections).toHaveLength(1);
|
|
150
|
+
expect(newState.detections?.[0]?.query).toBe('one time');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should mirror SessionConfiguredV1 onto state and clear on reset', () => {
|
|
154
|
+
const sessionConfigured: SessionConfiguredV1 = {
|
|
155
|
+
type: RecognitionResultTypeV1.SESSION_CONFIGURED,
|
|
156
|
+
audioUtteranceId: 'utt-1',
|
|
157
|
+
provider: 'deepgram',
|
|
158
|
+
model: 'nova-3',
|
|
159
|
+
sampleRate: 16000,
|
|
160
|
+
encoding: 'linear16',
|
|
161
|
+
apiType: ASRApiType.STREAMING,
|
|
162
|
+
isFallback: false
|
|
163
|
+
};
|
|
164
|
+
const afterConfigured = mapSessionConfiguredToState(baseState, sessionConfigured);
|
|
165
|
+
expect(afterConfigured.sessionConfigured?.provider).toBe('deepgram');
|
|
166
|
+
expect(afterConfigured.sessionConfigured?.model).toBe('nova-3');
|
|
167
|
+
expect(afterConfigured.sessionConfigured?.isFallback).toBe(false);
|
|
168
|
+
|
|
169
|
+
const afterReset = resetRecognitionVGFState(afterConfigured);
|
|
170
|
+
expect(afterReset.sessionConfigured).toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should copy accumulatedAudioTimeMs on pending and final transcripts and clear on reset', () => {
|
|
174
|
+
const pending: TranscriptionResultV1 = {
|
|
175
|
+
type: RecognitionResultTypeV1.TRANSCRIPTION,
|
|
176
|
+
audioUtteranceId: 'utt-1',
|
|
177
|
+
finalTranscript: '',
|
|
178
|
+
finalTranscriptRaw: '',
|
|
179
|
+
pendingTranscript: 'hi',
|
|
180
|
+
is_finished: false,
|
|
181
|
+
accumulatedAudioTimeMs: 1234
|
|
182
|
+
};
|
|
183
|
+
const afterPending = mapTranscriptionResultToState(baseState, pending, true);
|
|
184
|
+
expect(afterPending.accumulatedAudioTimeMs).toBe(1234);
|
|
185
|
+
|
|
186
|
+
const final: TranscriptionResultV1 = {
|
|
187
|
+
type: RecognitionResultTypeV1.TRANSCRIPTION,
|
|
188
|
+
audioUtteranceId: 'utt-1',
|
|
189
|
+
finalTranscript: 'hi',
|
|
190
|
+
finalTranscriptRaw: 'hi',
|
|
191
|
+
is_finished: true,
|
|
192
|
+
accumulatedAudioTimeMs: 5678
|
|
193
|
+
};
|
|
194
|
+
const afterFinal = mapTranscriptionResultToState(afterPending, final, false);
|
|
195
|
+
expect(afterFinal.accumulatedAudioTimeMs).toBe(5678);
|
|
196
|
+
|
|
197
|
+
const afterReset = resetRecognitionVGFState(afterFinal);
|
|
198
|
+
expect(afterReset.accumulatedAudioTimeMs).toBeUndefined();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should leave existing detections untouched when the result omits them', () => {
|
|
202
|
+
const stateWithDetections: RecognitionState = {
|
|
203
|
+
...baseState,
|
|
204
|
+
detections: [{ type: DetectionTypeV1.SEARCH, query: 'prev', score: 0.5 }]
|
|
205
|
+
};
|
|
206
|
+
const result: TranscriptionResultV1 = {
|
|
207
|
+
type: RecognitionResultTypeV1.TRANSCRIPTION,
|
|
208
|
+
audioUtteranceId: 'utt-1',
|
|
209
|
+
finalTranscript: '',
|
|
210
|
+
finalTranscriptRaw: '',
|
|
211
|
+
pendingTranscript: 'hello',
|
|
212
|
+
is_finished: false
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const newState = mapTranscriptionResultToState(stateWithDetections, result, true);
|
|
216
|
+
|
|
217
|
+
expect(newState.detections).toEqual([
|
|
218
|
+
{ type: DetectionTypeV1.SEARCH, query: 'prev', score: 0.5 }
|
|
219
|
+
]);
|
|
220
|
+
});
|
|
78
221
|
});
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from './recognition-client.types.js';
|
|
20
20
|
import {
|
|
21
21
|
TranscriptionResultV1,
|
|
22
|
-
|
|
22
|
+
SessionConfiguredV1
|
|
23
23
|
} from '@recog/shared-types';
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -94,6 +94,9 @@ export function mapTranscriptionResultToState(
|
|
|
94
94
|
if (result.lastNonSilence !== undefined) {
|
|
95
95
|
newState.lastNonSilence = result.lastNonSilence;
|
|
96
96
|
}
|
|
97
|
+
if (result.accumulatedAudioTimeMs !== undefined) {
|
|
98
|
+
newState.accumulatedAudioTimeMs = result.accumulatedAudioTimeMs;
|
|
99
|
+
}
|
|
97
100
|
} else {
|
|
98
101
|
// Transcription is finished
|
|
99
102
|
newState.transcriptionStatus = TranscriptionStatus.FINALIZED;
|
|
@@ -110,21 +113,46 @@ export function mapTranscriptionResultToState(
|
|
|
110
113
|
if (result.lastNonSilence !== undefined) {
|
|
111
114
|
newState.lastNonSilence = result.lastNonSilence;
|
|
112
115
|
}
|
|
116
|
+
if (result.accumulatedAudioTimeMs !== undefined) {
|
|
117
|
+
newState.accumulatedAudioTimeMs = result.accumulatedAudioTimeMs;
|
|
118
|
+
}
|
|
113
119
|
|
|
114
120
|
// Clear pending when we have final
|
|
115
121
|
newState.pendingTranscript = "";
|
|
116
122
|
newState.pendingConfidence = undefined;
|
|
117
123
|
}
|
|
118
124
|
|
|
125
|
+
// Mirror provider-reported detections (currently Deepgram `search`; future entries
|
|
126
|
+
// may include keywords/keyterms/speech_contexts from other providers).
|
|
127
|
+
// Server pre-sorts by score descending, so [0] is the top hit — passed through as-is.
|
|
128
|
+
if (result.detections !== undefined) {
|
|
129
|
+
newState.detections = result.detections;
|
|
130
|
+
}
|
|
131
|
+
|
|
119
132
|
return newState;
|
|
120
133
|
}
|
|
121
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Mirrors the SessionConfiguredV1 message onto the VGF state.
|
|
137
|
+
* Carries the resolved provider/model/sampleRate/encoding/apiType/isFallback
|
|
138
|
+
* that the server actually chose (after circuit-breaker/fallback). Fires once
|
|
139
|
+
* per session, before audio streaming begins.
|
|
140
|
+
*/
|
|
141
|
+
export function mapSessionConfiguredToState(
|
|
142
|
+
currentState: RecognitionState,
|
|
143
|
+
sessionConfigured: SessionConfiguredV1
|
|
144
|
+
): RecognitionState {
|
|
145
|
+
return {
|
|
146
|
+
...currentState,
|
|
147
|
+
sessionConfigured
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
122
151
|
/**
|
|
123
152
|
* Maps error to state
|
|
124
153
|
*/
|
|
125
154
|
export function mapErrorToState(
|
|
126
|
-
currentState: RecognitionState
|
|
127
|
-
error: ErrorResultV1
|
|
155
|
+
currentState: RecognitionState
|
|
128
156
|
): RecognitionState {
|
|
129
157
|
return {
|
|
130
158
|
...currentState,
|
|
@@ -185,7 +213,10 @@ export function resetRecognitionVGFState(currentState: RecognitionState): Recogn
|
|
|
185
213
|
recognitionActionProcessingState: RecognitionActionProcessingState.NOT_STARTED,
|
|
186
214
|
finalTranscript: undefined,
|
|
187
215
|
voiceEnd: undefined,
|
|
188
|
-
lastNonSilence: undefined
|
|
216
|
+
lastNonSilence: undefined,
|
|
217
|
+
accumulatedAudioTimeMs: undefined,
|
|
218
|
+
detections: undefined,
|
|
219
|
+
sessionConfigured: undefined
|
|
189
220
|
};
|
|
190
221
|
}
|
|
191
222
|
|
|
@@ -199,47 +230,6 @@ export function updateStateOnReady(currentState: RecognitionState): RecognitionS
|
|
|
199
230
|
};
|
|
200
231
|
}
|
|
201
232
|
|
|
202
|
-
/**
|
|
203
|
-
* Parses function call from transcript (STEP 3 support)
|
|
204
|
-
* This is a placeholder - actual implementation would use NLP/LLM
|
|
205
|
-
*/
|
|
206
|
-
export function extractFunctionCallFromTranscript(
|
|
207
|
-
transcript: string,
|
|
208
|
-
gameContext?: any
|
|
209
|
-
): { metadata?: string; confidence?: number } | null {
|
|
210
|
-
// This would be replaced with actual function call extraction logic
|
|
211
|
-
// For example, using an LLM to parse intent from the transcript
|
|
212
|
-
// and map it to game actions
|
|
213
|
-
|
|
214
|
-
// Example stub implementation:
|
|
215
|
-
const lowerTranscript = transcript.toLowerCase();
|
|
216
|
-
|
|
217
|
-
// Simple pattern matching for demo
|
|
218
|
-
if (lowerTranscript.includes("play") && lowerTranscript.includes("artist")) {
|
|
219
|
-
return {
|
|
220
|
-
metadata: JSON.stringify({ action: "play", target: "artist" }),
|
|
221
|
-
confidence: 0.8
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Updates state with function call results (STEP 3)
|
|
230
|
-
*/
|
|
231
|
-
export function updateStateWithFunctionCall(
|
|
232
|
-
currentState: RecognitionState,
|
|
233
|
-
functionCall: { metadata?: string; confidence?: number }
|
|
234
|
-
): RecognitionState {
|
|
235
|
-
return {
|
|
236
|
-
...currentState,
|
|
237
|
-
functionCallMetadata: functionCall.metadata,
|
|
238
|
-
functionCallConfidence: functionCall.confidence,
|
|
239
|
-
finalFunctionCallTimestamp: new Date().toISOString()
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
233
|
// Helper function to generate UUID (simplified version)
|
|
244
234
|
function generateUUID(): string {
|
|
245
235
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
|
+
import { DetectionV1Schema, SessionConfiguredSchemaV1 } from "@recog/shared-types"
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* VGF-style state schema for game-side recognition state/results management.
|
|
@@ -25,9 +26,13 @@ export const RecognitionVGFStateSchema = z.object({
|
|
|
25
26
|
// Voice timing (ms from stream start, prefix-adjusted)
|
|
26
27
|
voiceEnd: z.number().optional(), // voice end time identified by ASR
|
|
27
28
|
lastNonSilence: z.number().optional(), // last non-silence sample time from PCM analysis
|
|
29
|
+
accumulatedAudioTimeMs: z.number().optional(), // total user audio time watermark (ms) — mirrors TranscriptionResultV1.accumulatedAudioTimeMs
|
|
28
30
|
|
|
29
31
|
// Tracking-only metadata
|
|
30
|
-
asrConfig: z.string().optional(), // Json format of the ASR config
|
|
32
|
+
asrConfig: z.string().optional(), // Json format of the *requested* ASR config (set once at construction).
|
|
33
|
+
// For the *resolved* truth — actual provider/model/sampleRate/encoding/apiType/isFallback chosen by the
|
|
34
|
+
// server after circuit-breaker/fallback — see `sessionConfigured` below.
|
|
35
|
+
sessionConfigured: SessionConfiguredSchemaV1.optional(), // Mirrors the SessionConfiguredV1 message; populated when the server emits it (before audio streams).
|
|
31
36
|
startRecordingTimestamp: z.string().optional(), // Start of recording. Immutable after set.
|
|
32
37
|
finalRecordingTimestamp: z.string().optional(), // End of recording. Immutable after set. Transcription may still be in progress.
|
|
33
38
|
finalTranscriptionTimestamp: z.string().optional(), // When the final transcript was produced. Immutable after set.
|
|
@@ -44,6 +49,19 @@ export const RecognitionVGFStateSchema = z.object({
|
|
|
44
49
|
// Support for prompt slot mapping - passed to recognition context when present
|
|
45
50
|
promptSlotMap: z.record(z.string(), z.array(z.string())).optional(), // Optional map of slot names to prompt values for recognition context
|
|
46
51
|
|
|
52
|
+
// Optional prompt inputs - when set, forwarded into GameContext at client creation.
|
|
53
|
+
// Mirror the GameContextV1 fields: STT (ASR keywords/keyterms), STF (speech->function), TTF (text->function).
|
|
54
|
+
promptSTT: z.string().optional(),
|
|
55
|
+
promptSTF: z.string().optional(),
|
|
56
|
+
promptTTF: z.string().optional(),
|
|
57
|
+
|
|
58
|
+
// Provider-reported phrase detections from the last transcript message.
|
|
59
|
+
// Mirrors TranscriptionResultV1.detections — a heterogeneous list keyed by DetectionTypeV1
|
|
60
|
+
// (today only 'search' from Deepgram; future entries may include keywords/keyterms/speech_contexts).
|
|
61
|
+
// Sorted by `score` descending by the server (see deepgram/message-handlers/v1/transform-transcript.ts
|
|
62
|
+
// and provider-to-recognition-transformer.ts), so [0] is the top hit — no client-side re-rank needed.
|
|
63
|
+
detections: z.array(DetectionV1Schema).optional(),
|
|
64
|
+
|
|
47
65
|
// Recognition action processing state - managed externally, SDK preserves but never modifies
|
|
48
66
|
recognitionActionProcessingState: z.string().optional(), // "NOT_STARTED", "IN_PROGRESS", "COMPLETED"
|
|
49
67
|
})
|