@tanstack/ai-grok 0.8.4 → 0.8.6
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/esm/adapters/image.js +26 -14
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/text.js +2 -2
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/transcription.js +7 -3
- package/dist/esm/adapters/transcription.js.map +1 -1
- package/dist/esm/adapters/tts.js +4 -1
- package/dist/esm/adapters/tts.js.map +1 -1
- package/dist/esm/model-meta.d.ts +21 -1
- package/dist/esm/model-meta.js +5 -1
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/realtime/adapter.js +19 -12
- package/dist/esm/realtime/adapter.js.map +1 -1
- package/dist/esm/utils/audio.js +1 -0
- package/dist/esm/utils/audio.js.map +1 -1
- package/package.json +5 -5
- package/src/adapters/image.ts +25 -16
- package/src/adapters/text.ts +2 -2
- package/src/adapters/transcription.ts +3 -2
- package/src/adapters/tts.ts +1 -2
- package/src/model-meta.ts +23 -0
- package/src/realtime/adapter.ts +28 -21
- package/src/utils/audio.ts +1 -0
package/src/realtime/adapter.ts
CHANGED
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
RealtimeMessage,
|
|
8
8
|
RealtimeMode,
|
|
9
9
|
RealtimeSessionConfig,
|
|
10
|
-
RealtimeStatus,
|
|
11
10
|
RealtimeToken,
|
|
12
11
|
} from '@tanstack/ai'
|
|
13
12
|
import type { InternalLogger } from '@tanstack/ai/adapter-internals'
|
|
@@ -129,7 +128,11 @@ async function createWebRTCConnection(
|
|
|
129
128
|
|
|
130
129
|
let audioElement: HTMLAudioElement | null = null
|
|
131
130
|
|
|
132
|
-
|
|
131
|
+
// Captured into a const so closures see a non-nullable reference (teardown
|
|
132
|
+
// re-points the outer `dataChannel` to null, but in-flight closures still
|
|
133
|
+
// need to close their own channel).
|
|
134
|
+
const channel = pc.createDataChannel('oai-events')
|
|
135
|
+
let dataChannel: RTCDataChannel | null = channel
|
|
133
136
|
|
|
134
137
|
let currentMode: RealtimeMode = 'idle'
|
|
135
138
|
let currentMessageId: string | null = null
|
|
@@ -171,8 +174,6 @@ async function createWebRTCConnection(
|
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
dataChannel = pc.createDataChannel('oai-events')
|
|
175
|
-
|
|
176
177
|
let dataChannelOpened = false
|
|
177
178
|
let rejectDataChannelReady: ((reason: unknown) => void) | null = null
|
|
178
179
|
let dataChannelReadyTimeout: ReturnType<typeof setTimeout> | null = null
|
|
@@ -198,7 +199,7 @@ async function createWebRTCConnection(
|
|
|
198
199
|
}
|
|
199
200
|
}, 15000)
|
|
200
201
|
|
|
201
|
-
|
|
202
|
+
channel.onopen = () => {
|
|
202
203
|
dataChannelOpened = true
|
|
203
204
|
if (dataChannelReadyTimeout !== null) {
|
|
204
205
|
clearTimeout(dataChannelReadyTimeout)
|
|
@@ -208,12 +209,12 @@ async function createWebRTCConnection(
|
|
|
208
209
|
// don't attempt a redundant reject on an already-settled promise.
|
|
209
210
|
rejectDataChannelReady = null
|
|
210
211
|
flushPendingEvents()
|
|
211
|
-
emit('status_change', { status: 'connected'
|
|
212
|
+
emit('status_change', { status: 'connected' })
|
|
212
213
|
resolve()
|
|
213
214
|
}
|
|
214
215
|
})
|
|
215
216
|
|
|
216
|
-
|
|
217
|
+
channel.onmessage = (event) => {
|
|
217
218
|
try {
|
|
218
219
|
const message = JSON.parse(event.data)
|
|
219
220
|
const messageRecord: Record<string, unknown> =
|
|
@@ -235,7 +236,7 @@ async function createWebRTCConnection(
|
|
|
235
236
|
}
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
|
|
239
|
+
channel.onerror = (error) => {
|
|
239
240
|
// Closing the peer connection cascades into `onerror`/`onclose` on the
|
|
240
241
|
// data channel. Once teardown has started, re-surfacing those as
|
|
241
242
|
// `emit('error')` is noise that confuses consumers (they just called
|
|
@@ -250,6 +251,7 @@ async function createWebRTCConnection(
|
|
|
250
251
|
// doesn't end up as "[object Event]".
|
|
251
252
|
// `onerror` always fires with an Event (often an RTCErrorEvent), so we
|
|
252
253
|
// can read it via the untyped helpers without first proving object-ness.
|
|
254
|
+
// eslint-disable-next-line no-restricted-syntax -- RTCErrorEvent is a typed DOM class that does not structurally overlap Record<string, unknown>; we duck-type it via readObject/readString
|
|
253
255
|
const errorRecord = error as unknown as Record<string, unknown>
|
|
254
256
|
const rtcError = readObject(errorRecord, 'error')
|
|
255
257
|
const msg =
|
|
@@ -261,7 +263,7 @@ async function createWebRTCConnection(
|
|
|
261
263
|
emit('error', { error: dcErr })
|
|
262
264
|
}
|
|
263
265
|
|
|
264
|
-
|
|
266
|
+
channel.onclose = () => {
|
|
265
267
|
// Same rationale as `onerror` above: `pc.close()` during teardown
|
|
266
268
|
// cascades to the data channel's `onclose`. If we've already started
|
|
267
269
|
// teardown, there's nothing to do here.
|
|
@@ -294,10 +296,7 @@ async function createWebRTCConnection(
|
|
|
294
296
|
// guard listeners would see two `idle` events per disconnect.
|
|
295
297
|
if (!isTornDown) {
|
|
296
298
|
emit('status_change', {
|
|
297
|
-
status:
|
|
298
|
-
state === 'failed'
|
|
299
|
-
? ('error' as RealtimeStatus)
|
|
300
|
-
: ('idle' as RealtimeStatus),
|
|
299
|
+
status: state === 'failed' ? 'error' : 'idle',
|
|
301
300
|
})
|
|
302
301
|
}
|
|
303
302
|
if (!dataChannelOpened) {
|
|
@@ -730,7 +729,9 @@ async function createWebRTCConnection(
|
|
|
730
729
|
currentMode = 'listening'
|
|
731
730
|
emit('mode_change', { mode: 'listening' })
|
|
732
731
|
}
|
|
733
|
-
emit('interrupted', {
|
|
732
|
+
emit('interrupted', {
|
|
733
|
+
...(currentMessageId !== null && { messageId: currentMessageId }),
|
|
734
|
+
})
|
|
734
735
|
break
|
|
735
736
|
|
|
736
737
|
case 'error': {
|
|
@@ -759,10 +760,12 @@ async function createWebRTCConnection(
|
|
|
759
760
|
break
|
|
760
761
|
}
|
|
761
762
|
|
|
763
|
+
case undefined:
|
|
762
764
|
default:
|
|
763
765
|
// The xAI realtime protocol is a moving target; log unhandled event
|
|
764
766
|
// types at provider level so they're visible during debugging without
|
|
765
|
-
// emitting a user-visible error.
|
|
767
|
+
// emitting a user-visible error. `undefined` shares the bucket because
|
|
768
|
+
// a malformed event without a `type` field is just as unhandleable.
|
|
766
769
|
logger.provider('grok.realtime unhandled server event', {
|
|
767
770
|
type: event.type,
|
|
768
771
|
})
|
|
@@ -928,7 +931,7 @@ async function createWebRTCConnection(
|
|
|
928
931
|
`provider=grok direction=out type=${readString(event, 'type') ?? '<unknown>'}`,
|
|
929
932
|
{ frame: event },
|
|
930
933
|
)
|
|
931
|
-
|
|
934
|
+
channel.send(JSON.stringify(event))
|
|
932
935
|
}
|
|
933
936
|
pendingEvents.length = 0
|
|
934
937
|
} catch (error) {
|
|
@@ -954,7 +957,7 @@ async function createWebRTCConnection(
|
|
|
954
957
|
// every cleanup site stays in sync (input analyser, output analyser,
|
|
955
958
|
// output source, audio element, etc.).
|
|
956
959
|
await teardownConnection()
|
|
957
|
-
emit('status_change', { status: 'idle'
|
|
960
|
+
emit('status_change', { status: 'idle' })
|
|
958
961
|
},
|
|
959
962
|
|
|
960
963
|
async startAudioCapture() {
|
|
@@ -1117,17 +1120,21 @@ async function createWebRTCConnection(
|
|
|
1117
1120
|
sendEvent({ type: 'response.cancel' })
|
|
1118
1121
|
currentMode = 'listening'
|
|
1119
1122
|
emit('mode_change', { mode: 'listening' })
|
|
1120
|
-
emit('interrupted', {
|
|
1123
|
+
emit('interrupted', {
|
|
1124
|
+
...(currentMessageId !== null && { messageId: currentMessageId }),
|
|
1125
|
+
})
|
|
1121
1126
|
},
|
|
1122
1127
|
|
|
1123
1128
|
on<TEvent extends RealtimeEvent>(
|
|
1124
1129
|
event: TEvent,
|
|
1125
1130
|
handler: RealtimeEventHandler<TEvent>,
|
|
1126
1131
|
): () => void {
|
|
1127
|
-
|
|
1128
|
-
|
|
1132
|
+
let handlers = eventHandlers.get(event)
|
|
1133
|
+
if (!handlers) {
|
|
1134
|
+
handlers = new Set()
|
|
1135
|
+
eventHandlers.set(event, handlers)
|
|
1129
1136
|
}
|
|
1130
|
-
|
|
1137
|
+
handlers.add(handler)
|
|
1131
1138
|
|
|
1132
1139
|
return () => {
|
|
1133
1140
|
eventHandlers.get(event)?.delete(handler)
|
package/src/utils/audio.ts
CHANGED
|
@@ -194,6 +194,7 @@ export function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
|
194
194
|
const end = Math.min(i + chunkSize, bytes.length)
|
|
195
195
|
binary += String.fromCharCode.apply(
|
|
196
196
|
null,
|
|
197
|
+
// eslint-disable-next-line no-restricted-syntax -- TS lib types String.fromCharCode.apply as Array<number> but runtime accepts any ArrayLike
|
|
197
198
|
bytes.subarray(i, end) as unknown as Array<number>,
|
|
198
199
|
)
|
|
199
200
|
}
|