@kernl-sdk/xai 0.1.0
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/.turbo/turbo-build.log +5 -0
- package/dist/__tests__/realtime.integration.test.d.ts +2 -0
- package/dist/__tests__/realtime.integration.test.d.ts.map +1 -0
- package/dist/__tests__/realtime.integration.test.js +157 -0
- package/dist/__tests__/realtime.test.d.ts +2 -0
- package/dist/__tests__/realtime.test.d.ts.map +1 -0
- package/dist/__tests__/realtime.test.js +263 -0
- package/dist/connection.d.ts +47 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +138 -0
- package/dist/convert/event.d.ts +28 -0
- package/dist/convert/event.d.ts.map +1 -0
- package/dist/convert/event.js +314 -0
- package/dist/convert/types.d.ts +212 -0
- package/dist/convert/types.d.ts.map +1 -0
- package/dist/convert/types.js +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/model.d.ts +36 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +112 -0
- package/dist/protocol.d.ts +212 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +1 -0
- package/dist/realtime/connection.d.ts +47 -0
- package/dist/realtime/connection.d.ts.map +1 -0
- package/dist/realtime/connection.js +138 -0
- package/dist/realtime/convert/event.d.ts +28 -0
- package/dist/realtime/convert/event.d.ts.map +1 -0
- package/dist/realtime/convert/event.js +314 -0
- package/dist/realtime/model.d.ts +36 -0
- package/dist/realtime/model.d.ts.map +1 -0
- package/dist/realtime/model.js +111 -0
- package/dist/realtime/protocol.d.ts +212 -0
- package/dist/realtime/protocol.d.ts.map +1 -0
- package/dist/realtime/protocol.js +1 -0
- package/dist/realtime.d.ts +36 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +250 -0
- package/package.json +55 -0
- package/src/__tests__/realtime.integration.test.ts +203 -0
- package/src/__tests__/realtime.test.ts +350 -0
- package/src/index.ts +41 -0
- package/src/realtime/connection.ts +167 -0
- package/src/realtime/convert/event.ts +388 -0
- package/src/realtime/model.ts +162 -0
- package/src/realtime/protocol.ts +286 -0
- package/tsconfig.json +13 -0
package/dist/model.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { GrokRealtimeConnection } from "./connection.js";
|
|
2
|
+
const XAI_REALTIME_URL = "wss://api.x.ai/v1/realtime";
|
|
3
|
+
const XAI_CLIENT_SECRETS_URL = "https://api.x.ai/v1/realtime/client_secrets";
|
|
4
|
+
/**
|
|
5
|
+
* Grok (xAI) realtime model implementation.
|
|
6
|
+
*/
|
|
7
|
+
export class GrokRealtimeModel {
|
|
8
|
+
spec = "1.0";
|
|
9
|
+
provider = "xai";
|
|
10
|
+
modelId;
|
|
11
|
+
apiKey;
|
|
12
|
+
baseUrl;
|
|
13
|
+
constructor(modelId, options) {
|
|
14
|
+
this.modelId = modelId;
|
|
15
|
+
this.apiKey =
|
|
16
|
+
options?.apiKey ??
|
|
17
|
+
(typeof process !== "undefined" ? process.env?.XAI_API_KEY : null) ??
|
|
18
|
+
null;
|
|
19
|
+
this.baseUrl = options?.baseUrl ?? XAI_REALTIME_URL;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create ephemeral credential for client-side connections.
|
|
23
|
+
*
|
|
24
|
+
* Must be called server-side where API key is available.
|
|
25
|
+
*/
|
|
26
|
+
async authenticate() {
|
|
27
|
+
if (!this.apiKey) {
|
|
28
|
+
throw new Error("API key required for authenticate(). " +
|
|
29
|
+
"Call this server-side where XAI_API_KEY is available.");
|
|
30
|
+
}
|
|
31
|
+
const res = await fetch(XAI_CLIENT_SECRETS_URL, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
expires_after: { seconds: 300 },
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const text = await res.text();
|
|
43
|
+
throw new Error(`Failed to create credential: ${res.status} ${text}`);
|
|
44
|
+
}
|
|
45
|
+
const data = (await res.json());
|
|
46
|
+
return {
|
|
47
|
+
kind: "token",
|
|
48
|
+
token: data.value,
|
|
49
|
+
expiresAt: new Date(Date.now() + 300_000), // 5 min TTL
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Establish a WebSocket connection to the Grok realtime API.
|
|
54
|
+
*/
|
|
55
|
+
async connect(options) {
|
|
56
|
+
const credential = options?.credential;
|
|
57
|
+
if (credential && credential.kind !== "token") {
|
|
58
|
+
throw new Error(`Grok requires token credentials, got "${credential.kind}".`);
|
|
59
|
+
}
|
|
60
|
+
const authToken = credential?.token ?? this.apiKey;
|
|
61
|
+
if (!authToken) {
|
|
62
|
+
throw new Error("No API key or credential provided. " +
|
|
63
|
+
"Either set XAI_API_KEY or pass a credential from authenticate().");
|
|
64
|
+
}
|
|
65
|
+
// Use injectable WebSocket or globalThis.WebSocket
|
|
66
|
+
const WS = options?.websocket ?? globalThis.WebSocket;
|
|
67
|
+
if (!WS) {
|
|
68
|
+
throw new Error("No WebSocket available. In Node.js <22, use WebSocketTransport with the 'ws' package:\n" +
|
|
69
|
+
" import WebSocket from 'ws';\n" +
|
|
70
|
+
" import { WebSocketTransport } from 'kernl';\n" +
|
|
71
|
+
" new RealtimeSession(agent, { transport: new WebSocketTransport({ websocket: WebSocket }), ... })");
|
|
72
|
+
}
|
|
73
|
+
// Grok uses standard Authorization header via protocols
|
|
74
|
+
// Browser WebSocket doesn't support custom headers, so we pass token in subprotocol
|
|
75
|
+
const url = this.baseUrl;
|
|
76
|
+
// For browser: use subprotocol to pass auth (similar pattern to OpenAI)
|
|
77
|
+
// For Node.js with 'ws' package: headers can be passed directly
|
|
78
|
+
const protocols = [`token.${authToken}`];
|
|
79
|
+
const ws = new WS(url, protocols);
|
|
80
|
+
const connection = new GrokRealtimeConnection(ws);
|
|
81
|
+
await new Promise((resolve, reject) => {
|
|
82
|
+
if (options?.abort?.aborted) {
|
|
83
|
+
return reject(new Error("Connection aborted"));
|
|
84
|
+
}
|
|
85
|
+
const onOpen = () => {
|
|
86
|
+
cleanup();
|
|
87
|
+
resolve();
|
|
88
|
+
};
|
|
89
|
+
const onError = (event) => {
|
|
90
|
+
cleanup();
|
|
91
|
+
const err = event instanceof Error
|
|
92
|
+
? event
|
|
93
|
+
: new Error("WebSocket connection failed");
|
|
94
|
+
reject(err);
|
|
95
|
+
};
|
|
96
|
+
const onAbort = () => {
|
|
97
|
+
cleanup();
|
|
98
|
+
ws.close();
|
|
99
|
+
reject(new Error("Connection aborted"));
|
|
100
|
+
};
|
|
101
|
+
const cleanup = () => {
|
|
102
|
+
ws.removeEventListener("open", onOpen);
|
|
103
|
+
ws.removeEventListener("error", onError);
|
|
104
|
+
options?.abort?.removeEventListener("abort", onAbort);
|
|
105
|
+
};
|
|
106
|
+
ws.addEventListener("open", onOpen);
|
|
107
|
+
ws.addEventListener("error", onError);
|
|
108
|
+
options?.abort?.addEventListener("abort", onAbort);
|
|
109
|
+
});
|
|
110
|
+
return connection;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { JSONSchema7 } from "json-schema";
|
|
2
|
+
/**
|
|
3
|
+
* Grok (xAI) Realtime API wire types.
|
|
4
|
+
*
|
|
5
|
+
* Based on https://docs.x.ai/docs/guides/voice/agent
|
|
6
|
+
*/
|
|
7
|
+
export type GrokClientEvent = GrokSessionUpdate | GrokInputAudioBufferAppend | GrokInputAudioBufferCommit | GrokInputAudioBufferClear | GrokConversationItemCreate | GrokResponseCreate;
|
|
8
|
+
export interface GrokSessionUpdate {
|
|
9
|
+
type: "session.update";
|
|
10
|
+
session: GrokSessionConfig;
|
|
11
|
+
}
|
|
12
|
+
export interface GrokInputAudioBufferAppend {
|
|
13
|
+
type: "input_audio_buffer.append";
|
|
14
|
+
audio: string;
|
|
15
|
+
}
|
|
16
|
+
export interface GrokInputAudioBufferCommit {
|
|
17
|
+
type: "input_audio_buffer.commit";
|
|
18
|
+
}
|
|
19
|
+
export interface GrokInputAudioBufferClear {
|
|
20
|
+
type: "input_audio_buffer.clear";
|
|
21
|
+
}
|
|
22
|
+
export interface GrokConversationItemCreate {
|
|
23
|
+
type: "conversation.item.create";
|
|
24
|
+
item: GrokItem;
|
|
25
|
+
previous_item_id?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface GrokResponseCreate {
|
|
28
|
+
type: "response.create";
|
|
29
|
+
}
|
|
30
|
+
export type GrokServerEvent = GrokConversationCreated | GrokSessionUpdated | GrokInputAudioBufferCommitted | GrokInputAudioBufferCleared | GrokInputAudioBufferSpeechStarted | GrokInputAudioBufferSpeechStopped | GrokConversationItemAdded | GrokConversationItemInputAudioTranscriptionCompleted | GrokResponseCreated | GrokResponseOutputItemAdded | GrokResponseDone | GrokResponseOutputAudioDelta | GrokResponseOutputAudioDone | GrokResponseOutputAudioTranscriptDelta | GrokResponseOutputAudioTranscriptDone | GrokResponseFunctionCallArgumentsDone;
|
|
31
|
+
export interface GrokConversationCreated {
|
|
32
|
+
type: "conversation.created";
|
|
33
|
+
event_id: string;
|
|
34
|
+
conversation: {
|
|
35
|
+
id: string;
|
|
36
|
+
object: "realtime.conversation";
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface GrokSessionUpdated {
|
|
40
|
+
type: "session.updated";
|
|
41
|
+
event_id: string;
|
|
42
|
+
session: GrokSession;
|
|
43
|
+
}
|
|
44
|
+
export interface GrokInputAudioBufferCommitted {
|
|
45
|
+
type: "input_audio_buffer.committed";
|
|
46
|
+
event_id: string;
|
|
47
|
+
previous_item_id?: string;
|
|
48
|
+
item_id: string;
|
|
49
|
+
}
|
|
50
|
+
export interface GrokInputAudioBufferCleared {
|
|
51
|
+
type: "input_audio_buffer.cleared";
|
|
52
|
+
event_id: string;
|
|
53
|
+
}
|
|
54
|
+
export interface GrokInputAudioBufferSpeechStarted {
|
|
55
|
+
type: "input_audio_buffer.speech_started";
|
|
56
|
+
event_id: string;
|
|
57
|
+
item_id: string;
|
|
58
|
+
}
|
|
59
|
+
export interface GrokInputAudioBufferSpeechStopped {
|
|
60
|
+
type: "input_audio_buffer.speech_stopped";
|
|
61
|
+
event_id: string;
|
|
62
|
+
item_id: string;
|
|
63
|
+
}
|
|
64
|
+
export interface GrokConversationItemAdded {
|
|
65
|
+
type: "conversation.item.added";
|
|
66
|
+
event_id: string;
|
|
67
|
+
previous_item_id?: string;
|
|
68
|
+
item: GrokItemWithId;
|
|
69
|
+
}
|
|
70
|
+
export interface GrokConversationItemInputAudioTranscriptionCompleted {
|
|
71
|
+
type: "conversation.item.input_audio_transcription.completed";
|
|
72
|
+
event_id: string;
|
|
73
|
+
item_id: string;
|
|
74
|
+
transcript: string;
|
|
75
|
+
}
|
|
76
|
+
export interface GrokResponseCreated {
|
|
77
|
+
type: "response.created";
|
|
78
|
+
event_id: string;
|
|
79
|
+
response: {
|
|
80
|
+
id: string;
|
|
81
|
+
object: "realtime.response";
|
|
82
|
+
status: "in_progress";
|
|
83
|
+
output: unknown[];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export interface GrokResponseOutputItemAdded {
|
|
87
|
+
type: "response.output_item.added";
|
|
88
|
+
event_id: string;
|
|
89
|
+
response_id: string;
|
|
90
|
+
output_index: number;
|
|
91
|
+
item: GrokItemWithId;
|
|
92
|
+
}
|
|
93
|
+
export interface GrokResponseDone {
|
|
94
|
+
type: "response.done";
|
|
95
|
+
event_id: string;
|
|
96
|
+
response: {
|
|
97
|
+
id: string;
|
|
98
|
+
object: "realtime.response";
|
|
99
|
+
status: "completed" | "cancelled" | "failed";
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export interface GrokResponseOutputAudioDelta {
|
|
103
|
+
type: "response.output_audio.delta";
|
|
104
|
+
event_id: string;
|
|
105
|
+
response_id: string;
|
|
106
|
+
item_id: string;
|
|
107
|
+
output_index: number;
|
|
108
|
+
content_index: number;
|
|
109
|
+
delta: string;
|
|
110
|
+
}
|
|
111
|
+
export interface GrokResponseOutputAudioDone {
|
|
112
|
+
type: "response.output_audio.done";
|
|
113
|
+
event_id: string;
|
|
114
|
+
response_id: string;
|
|
115
|
+
item_id: string;
|
|
116
|
+
}
|
|
117
|
+
export interface GrokResponseOutputAudioTranscriptDelta {
|
|
118
|
+
type: "response.output_audio_transcript.delta";
|
|
119
|
+
event_id: string;
|
|
120
|
+
response_id: string;
|
|
121
|
+
item_id: string;
|
|
122
|
+
delta: string;
|
|
123
|
+
}
|
|
124
|
+
export interface GrokResponseOutputAudioTranscriptDone {
|
|
125
|
+
type: "response.output_audio_transcript.done";
|
|
126
|
+
event_id: string;
|
|
127
|
+
response_id: string;
|
|
128
|
+
item_id: string;
|
|
129
|
+
}
|
|
130
|
+
export interface GrokResponseFunctionCallArgumentsDone {
|
|
131
|
+
type: "response.function_call_arguments.done";
|
|
132
|
+
event_id: string;
|
|
133
|
+
response_id?: string;
|
|
134
|
+
item_id?: string;
|
|
135
|
+
call_id: string;
|
|
136
|
+
name: string;
|
|
137
|
+
arguments: string;
|
|
138
|
+
}
|
|
139
|
+
export interface GrokSession {
|
|
140
|
+
instructions?: string;
|
|
141
|
+
voice?: GrokVoice;
|
|
142
|
+
turn_detection?: GrokTurnDetection;
|
|
143
|
+
}
|
|
144
|
+
export interface GrokSessionConfig {
|
|
145
|
+
instructions?: string;
|
|
146
|
+
voice?: GrokVoice;
|
|
147
|
+
turn_detection?: GrokTurnDetection | null;
|
|
148
|
+
audio?: GrokAudioConfig;
|
|
149
|
+
tools?: GrokTool[];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Available Grok voices.
|
|
153
|
+
*/
|
|
154
|
+
export type GrokVoice = "Ara" | "Rex" | "Sal" | "Eve" | "Leo";
|
|
155
|
+
export interface GrokTurnDetection {
|
|
156
|
+
type: "server_vad" | null;
|
|
157
|
+
}
|
|
158
|
+
export interface GrokAudioConfig {
|
|
159
|
+
input?: {
|
|
160
|
+
format?: GrokAudioFormat;
|
|
161
|
+
};
|
|
162
|
+
output?: {
|
|
163
|
+
format?: GrokAudioFormat;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export interface GrokAudioFormat {
|
|
167
|
+
type: "audio/pcm" | "audio/pcmu" | "audio/pcma";
|
|
168
|
+
rate?: number;
|
|
169
|
+
}
|
|
170
|
+
export type GrokTool = GrokFunctionTool | GrokWebSearchTool | GrokXSearchTool | GrokFileSearchTool;
|
|
171
|
+
export interface GrokFunctionTool {
|
|
172
|
+
type: "function";
|
|
173
|
+
name: string;
|
|
174
|
+
description?: string;
|
|
175
|
+
parameters?: JSONSchema7;
|
|
176
|
+
}
|
|
177
|
+
export interface GrokWebSearchTool {
|
|
178
|
+
type: "web_search";
|
|
179
|
+
}
|
|
180
|
+
export interface GrokXSearchTool {
|
|
181
|
+
type: "x_search";
|
|
182
|
+
allowed_x_handles?: string[];
|
|
183
|
+
}
|
|
184
|
+
export interface GrokFileSearchTool {
|
|
185
|
+
type: "file_search";
|
|
186
|
+
vector_store_ids: string[];
|
|
187
|
+
max_num_results?: number;
|
|
188
|
+
}
|
|
189
|
+
export type GrokItem = GrokMessageItem | GrokFunctionCallOutputItem;
|
|
190
|
+
export interface GrokMessageItem {
|
|
191
|
+
type: "message";
|
|
192
|
+
role: "user" | "assistant";
|
|
193
|
+
content: GrokContentPart[];
|
|
194
|
+
}
|
|
195
|
+
export interface GrokFunctionCallOutputItem {
|
|
196
|
+
type: "function_call_output";
|
|
197
|
+
call_id: string;
|
|
198
|
+
output: string;
|
|
199
|
+
}
|
|
200
|
+
export type GrokItemWithId = GrokItem & {
|
|
201
|
+
id: string;
|
|
202
|
+
object: "realtime.item";
|
|
203
|
+
status: "completed" | "in_progress";
|
|
204
|
+
};
|
|
205
|
+
export type GrokContentPart = {
|
|
206
|
+
type: "input_text";
|
|
207
|
+
text: string;
|
|
208
|
+
} | {
|
|
209
|
+
type: "input_audio";
|
|
210
|
+
transcript?: string;
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Emitter } from "@kernl-sdk/shared";
|
|
2
|
+
import { type RealtimeConnection, type RealtimeConnectionEvents, type RealtimeClientEvent, type TransportStatus, type WebSocketLike } from "@kernl-sdk/protocol";
|
|
3
|
+
/**
|
|
4
|
+
* Grok realtime connection implementation.
|
|
5
|
+
*/
|
|
6
|
+
export declare class GrokRealtimeConnection extends Emitter<RealtimeConnectionEvents> implements RealtimeConnection {
|
|
7
|
+
private ws;
|
|
8
|
+
private _status;
|
|
9
|
+
private _muted;
|
|
10
|
+
private _sessionId;
|
|
11
|
+
private currid;
|
|
12
|
+
private faudtime;
|
|
13
|
+
private audlenms;
|
|
14
|
+
private responding;
|
|
15
|
+
constructor(socket: WebSocketLike);
|
|
16
|
+
get status(): TransportStatus;
|
|
17
|
+
get muted(): boolean;
|
|
18
|
+
get sessionId(): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Send a client event to the Grok realtime API.
|
|
21
|
+
*/
|
|
22
|
+
send(event: RealtimeClientEvent): void;
|
|
23
|
+
/**
|
|
24
|
+
* Close the WebSocket connection.
|
|
25
|
+
*/
|
|
26
|
+
close(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Mute audio input.
|
|
29
|
+
*/
|
|
30
|
+
mute(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Unmute audio input.
|
|
33
|
+
*/
|
|
34
|
+
unmute(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Interrupt the current response.
|
|
37
|
+
*
|
|
38
|
+
* Note: Grok doesn't support response.cancel or item.truncate,
|
|
39
|
+
* so we just reset local state and emit the interrupted event.
|
|
40
|
+
*/
|
|
41
|
+
interrupt(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Reset audio tracking state.
|
|
44
|
+
*/
|
|
45
|
+
private reset;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/realtime/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAK7B;;GAEG;AACH,qBAAa,sBACX,SAAQ,OAAO,CAAC,wBAAwB,CACxC,YAAW,kBAAkB;IAE7B,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAuB;IAGzC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAkB;gBAExB,MAAM,EAAE,aAAa;IA4DjC,IAAI,MAAM,IAAI,eAAe,CAE5B;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,SAAS,IAAI,MAAM,GAAG,IAAI,CAE7B;IAED;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAOtC;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;;;;OAKG;IACH,SAAS,IAAI,IAAI;IASjB;;OAEG;IACH,OAAO,CAAC,KAAK;CAKd"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Emitter } from "@kernl-sdk/shared";
|
|
2
|
+
import { WS_OPEN, } from "@kernl-sdk/protocol";
|
|
3
|
+
import { CLIENT_EVENT, SERVER_EVENT } from "./convert/event.js";
|
|
4
|
+
/**
|
|
5
|
+
* Grok realtime connection implementation.
|
|
6
|
+
*/
|
|
7
|
+
export class GrokRealtimeConnection extends Emitter {
|
|
8
|
+
ws;
|
|
9
|
+
_status = "connecting";
|
|
10
|
+
_muted = false;
|
|
11
|
+
_sessionId = null;
|
|
12
|
+
// audio state tracking for interruption
|
|
13
|
+
currid;
|
|
14
|
+
faudtime;
|
|
15
|
+
audlenms = 0;
|
|
16
|
+
responding = false;
|
|
17
|
+
constructor(socket) {
|
|
18
|
+
super();
|
|
19
|
+
this.ws = socket;
|
|
20
|
+
socket.addEventListener("message", (event) => {
|
|
21
|
+
try {
|
|
22
|
+
const data = event && typeof event === "object" && "data" in event
|
|
23
|
+
? event.data
|
|
24
|
+
: String(event);
|
|
25
|
+
const raw = JSON.parse(data);
|
|
26
|
+
// track audio state for interruption handling
|
|
27
|
+
if (raw.type === "response.output_audio.delta") {
|
|
28
|
+
this.currid = raw.item_id;
|
|
29
|
+
if (this.faudtime === undefined) {
|
|
30
|
+
this.faudtime = Date.now();
|
|
31
|
+
this.audlenms = 0;
|
|
32
|
+
}
|
|
33
|
+
// calculate audio length assuming 24kHz PCM16
|
|
34
|
+
const bytes = base64ByteLength(raw.delta);
|
|
35
|
+
this.audlenms += (bytes / 2 / 24000) * 1000;
|
|
36
|
+
}
|
|
37
|
+
else if (raw.type === "response.created") {
|
|
38
|
+
this.responding = true;
|
|
39
|
+
}
|
|
40
|
+
else if (raw.type === "response.done") {
|
|
41
|
+
this.responding = false;
|
|
42
|
+
this.reset();
|
|
43
|
+
}
|
|
44
|
+
else if (raw.type === "input_audio_buffer.speech_started") {
|
|
45
|
+
this.interrupt();
|
|
46
|
+
}
|
|
47
|
+
const event_ = SERVER_EVENT.decode(raw);
|
|
48
|
+
if (event_) {
|
|
49
|
+
if (event_.kind === "session.created") {
|
|
50
|
+
this._sessionId = event_.session.id;
|
|
51
|
+
}
|
|
52
|
+
this.emit("event", event_);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
socket.addEventListener("open", () => {
|
|
60
|
+
this._status = "connected";
|
|
61
|
+
this.emit("status", this._status);
|
|
62
|
+
});
|
|
63
|
+
socket.addEventListener("close", () => {
|
|
64
|
+
this._status = "closed";
|
|
65
|
+
this.reset();
|
|
66
|
+
this.emit("status", this._status);
|
|
67
|
+
});
|
|
68
|
+
socket.addEventListener("error", (event) => {
|
|
69
|
+
const err = event instanceof Error ? event : new Error("WebSocket error");
|
|
70
|
+
this.emit("error", err);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
get status() {
|
|
74
|
+
return this._status;
|
|
75
|
+
}
|
|
76
|
+
get muted() {
|
|
77
|
+
return this._muted;
|
|
78
|
+
}
|
|
79
|
+
get sessionId() {
|
|
80
|
+
return this._sessionId;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Send a client event to the Grok realtime API.
|
|
84
|
+
*/
|
|
85
|
+
send(event) {
|
|
86
|
+
const encoded = CLIENT_EVENT.encode(event);
|
|
87
|
+
if (encoded && this.ws.readyState === WS_OPEN) {
|
|
88
|
+
this.ws.send(JSON.stringify(encoded));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Close the WebSocket connection.
|
|
93
|
+
*/
|
|
94
|
+
close() {
|
|
95
|
+
this.reset();
|
|
96
|
+
this.ws.close();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Mute audio input.
|
|
100
|
+
*/
|
|
101
|
+
mute() {
|
|
102
|
+
this._muted = true;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Unmute audio input.
|
|
106
|
+
*/
|
|
107
|
+
unmute() {
|
|
108
|
+
this._muted = false;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Interrupt the current response.
|
|
112
|
+
*
|
|
113
|
+
* Note: Grok doesn't support response.cancel or item.truncate,
|
|
114
|
+
* so we just reset local state and emit the interrupted event.
|
|
115
|
+
*/
|
|
116
|
+
interrupt() {
|
|
117
|
+
if (this.responding) {
|
|
118
|
+
this.responding = false;
|
|
119
|
+
}
|
|
120
|
+
this.emit("interrupted");
|
|
121
|
+
this.reset();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reset audio tracking state.
|
|
125
|
+
*/
|
|
126
|
+
reset() {
|
|
127
|
+
this.currid = undefined;
|
|
128
|
+
this.faudtime = undefined;
|
|
129
|
+
this.audlenms = 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get byte length from base64 string without decoding.
|
|
134
|
+
*/
|
|
135
|
+
function base64ByteLength(b64) {
|
|
136
|
+
const padding = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
|
|
137
|
+
return (b64.length * 3) / 4 - padding;
|
|
138
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Codec } from "@kernl-sdk/shared/lib";
|
|
2
|
+
import type { RealtimeClientEvent, RealtimeServerEvent, RealtimeSessionConfig, TurnDetectionConfig, LanguageModelItem, AudioConfig } from "@kernl-sdk/protocol";
|
|
3
|
+
import type { GrokClientEvent, GrokServerEvent, GrokSessionConfig, GrokTurnDetection, GrokItem, GrokAudioConfig } from "../protocol.js";
|
|
4
|
+
/**
|
|
5
|
+
* Codec for turn detection config.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TURN_DETECTION: Codec<TurnDetectionConfig, GrokTurnDetection | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Codec for audio config.
|
|
10
|
+
*/
|
|
11
|
+
export declare const AUDIO_CONFIG: Codec<AudioConfig, GrokAudioConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Codec for session config.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SESSION_CONFIG: Codec<RealtimeSessionConfig, GrokSessionConfig>;
|
|
16
|
+
/**
|
|
17
|
+
* Codec for conversation items.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ITEM: Codec<LanguageModelItem, GrokItem>;
|
|
20
|
+
/**
|
|
21
|
+
* Codec for client events (kernl → Grok).
|
|
22
|
+
*/
|
|
23
|
+
export declare const CLIENT_EVENT: Codec<RealtimeClientEvent, GrokClientEvent | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Codec for server events (Grok → kernl).
|
|
26
|
+
*/
|
|
27
|
+
export declare const SERVER_EVENT: Codec<RealtimeServerEvent | null, GrokServerEvent>;
|
|
28
|
+
//# sourceMappingURL=event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/realtime/convert/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EAER,eAAe,EAGhB,MAAM,aAAa,CAAC;AAcrB;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAChC,mBAAmB,EACnB,iBAAiB,GAAG,IAAI,CAazB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,EAAE,eAAe,CAyC5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,qBAAqB,EAAE,iBAAiB,CAgC1E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,IAAI,EAAE,KAAK,CAAC,iBAAiB,EAAE,QAAQ,CA8DnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,mBAAmB,EAAE,eAAe,GAAG,IAAI,CAuDzE,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,mBAAmB,GAAG,IAAI,EAAE,eAAe,CAuHzE,CAAC"}
|