@opengeni/sdk 0.48.0 → 0.57.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.
Files changed (122) hide show
  1. package/README.md +147 -1
  2. package/dist/artifact-client.d.ts +20 -2
  3. package/dist/artifacts.d.ts +4 -0
  4. package/dist/artifacts.js +11 -0
  5. package/dist/artifacts.js.map +1 -0
  6. package/dist/{chunk-V5F253OG.js → chunk-GU6JF75T.js} +6 -3
  7. package/dist/chunk-GU6JF75T.js.map +1 -0
  8. package/dist/chunk-HJ3HHUT5.js +222 -0
  9. package/dist/chunk-HJ3HHUT5.js.map +1 -0
  10. package/dist/{chunk-KIHGPM7H.js → chunk-JDLDDRNE.js} +20 -14
  11. package/dist/chunk-JDLDDRNE.js.map +1 -0
  12. package/dist/chunk-JRPFWI6T.js +128 -0
  13. package/dist/chunk-JRPFWI6T.js.map +1 -0
  14. package/dist/{chunk-MBGBLVUV.js → chunk-PKQ377ED.js} +1725 -555
  15. package/dist/chunk-PKQ377ED.js.map +1 -0
  16. package/dist/chunk-ST5DDKJP.js +987 -0
  17. package/dist/chunk-ST5DDKJP.js.map +1 -0
  18. package/dist/chunk-VWE2QIVO.js +1162 -0
  19. package/dist/chunk-VWE2QIVO.js.map +1 -0
  20. package/dist/chunk-VYCTL62C.js +230 -0
  21. package/dist/chunk-VYCTL62C.js.map +1 -0
  22. package/dist/{chunk-DXDL7EEW.js → chunk-YKZME56C.js} +197 -113
  23. package/dist/chunk-YKZME56C.js.map +1 -0
  24. package/dist/client.d.ts +240 -16
  25. package/dist/codex-realtime-controller.d.ts +9 -6
  26. package/dist/codex-realtime-controller.js +2 -2
  27. package/dist/codex-realtime-lifecycle.d.ts +1 -1
  28. package/dist/codex-realtime-v3.d.ts +3 -3
  29. package/dist/codex-realtime.d.ts +1 -1
  30. package/dist/company-profile.d.ts +100 -0
  31. package/dist/core.d.ts +5 -5
  32. package/dist/core.js +10 -5
  33. package/dist/desktop.d.ts +9 -1
  34. package/dist/editable-artifact-resources.d.ts +96 -0
  35. package/dist/editable-artifacts/browser-session.d.ts +35 -0
  36. package/dist/editable-artifacts/controller.d.ts +60 -0
  37. package/dist/editable-artifacts/errors.d.ts +23 -0
  38. package/dist/editable-artifacts/http-live-transport.d.ts +46 -0
  39. package/dist/editable-artifacts/index.d.ts +19 -0
  40. package/dist/editable-artifacts/pool.d.ts +15 -0
  41. package/dist/editable-artifacts/session.d.ts +96 -0
  42. package/dist/editable-artifacts/storage.d.ts +72 -0
  43. package/dist/editable-artifacts/types.d.ts +416 -0
  44. package/dist/editable-artifacts/worker/browser-client.d.ts +80 -0
  45. package/dist/editable-artifacts/worker/browser-entry.d.ts +6 -0
  46. package/dist/editable-artifacts/worker/kernel-adapter.d.ts +45 -0
  47. package/dist/editable-artifacts/worker/rpc-protocol.d.ts +92 -0
  48. package/dist/editable-artifacts/worker/runtime.d.ts +66 -0
  49. package/dist/editable-artifacts/worker/wire-codec.d.ts +52 -0
  50. package/dist/editable-artifacts-worker.d.ts +8 -0
  51. package/dist/editable-artifacts-worker.js +1708 -0
  52. package/dist/editable-artifacts-worker.js.map +1 -0
  53. package/dist/editable-artifacts.d.ts +6 -0
  54. package/dist/editable-artifacts.js +5954 -0
  55. package/dist/editable-artifacts.js.map +1 -0
  56. package/dist/gateway-realtime-transport.d.ts +2 -1
  57. package/dist/gateway-realtime-transport.js +5 -3
  58. package/dist/index.d.ts +45 -35
  59. package/dist/index.js +134 -64
  60. package/dist/index.js.map +1 -1
  61. package/dist/interaction-revision-stream.d.ts +11 -0
  62. package/dist/interaction.d.ts +1484 -0
  63. package/dist/interaction.js +81 -0
  64. package/dist/interaction.js.map +1 -0
  65. package/dist/memory-slack-client.d.ts +13 -0
  66. package/dist/memory-slack-delivery.d.ts +99 -0
  67. package/dist/memory-slack.d.ts +4 -0
  68. package/dist/memory-slack.js +44 -0
  69. package/dist/memory-slack.js.map +1 -0
  70. package/dist/proxy.d.ts +3 -3
  71. package/dist/realtime.d.ts +14 -14
  72. package/dist/realtime.js +11 -7
  73. package/dist/realtime.js.map +1 -1
  74. package/dist/retained-artifacts.d.ts +12 -0
  75. package/dist/stream.d.ts +1 -1
  76. package/dist/terminal.d.ts +1 -1
  77. package/dist/types.d.ts +1347 -34
  78. package/dist/workspace-control-stream.d.ts +2 -2
  79. package/dist/workspace-live-stream.d.ts +14 -0
  80. package/dist/workspace-state.d.ts +1 -1
  81. package/package.json +29 -3
  82. package/src/artifact-client.ts +129 -0
  83. package/src/artifacts.ts +17 -0
  84. package/src/client.ts +3022 -910
  85. package/src/codex-realtime-controller.ts +25 -7
  86. package/src/codex-realtime-lifecycle.ts +1 -0
  87. package/src/company-profile.ts +92 -0
  88. package/src/desktop.ts +11 -1
  89. package/src/editable-artifact-resources.ts +119 -0
  90. package/src/editable-artifacts/browser-session.ts +176 -0
  91. package/src/editable-artifacts/controller.ts +2632 -0
  92. package/src/editable-artifacts/errors.ts +52 -0
  93. package/src/editable-artifacts/http-live-transport.ts +1449 -0
  94. package/src/editable-artifacts/index.ts +175 -0
  95. package/src/editable-artifacts/pool.ts +81 -0
  96. package/src/editable-artifacts/session.ts +778 -0
  97. package/src/editable-artifacts/storage.ts +1718 -0
  98. package/src/editable-artifacts/types.ts +499 -0
  99. package/src/editable-artifacts/worker/browser-client.ts +1167 -0
  100. package/src/editable-artifacts/worker/browser-entry.ts +57 -0
  101. package/src/editable-artifacts/worker/kernel-adapter.ts +1039 -0
  102. package/src/editable-artifacts/worker/rpc-protocol.ts +622 -0
  103. package/src/editable-artifacts/worker/runtime.ts +1204 -0
  104. package/src/editable-artifacts/worker/wire-codec.ts +852 -0
  105. package/src/editable-artifacts-worker.ts +8 -0
  106. package/src/editable-artifacts.ts +6 -0
  107. package/src/errors.ts +16 -2
  108. package/src/gateway-realtime-transport.ts +252 -109
  109. package/src/index.ts +215 -1
  110. package/src/interaction-revision-stream.ts +117 -0
  111. package/src/interaction.ts +2874 -0
  112. package/src/memory-slack-client.ts +71 -0
  113. package/src/memory-slack-delivery.ts +128 -0
  114. package/src/memory-slack.ts +19 -0
  115. package/src/realtime.ts +14 -4
  116. package/src/retained-artifacts.ts +326 -0
  117. package/src/types.ts +1636 -52
  118. package/src/workspace-live-stream.ts +137 -0
  119. package/dist/chunk-DXDL7EEW.js.map +0 -1
  120. package/dist/chunk-KIHGPM7H.js.map +0 -1
  121. package/dist/chunk-MBGBLVUV.js.map +0 -1
  122. package/dist/chunk-V5F253OG.js.map +0 -1
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Dedicated browser module-Worker entry for editable artifacts.
3
+ *
4
+ * Bundle this subpath as a Worker URL; importing it in the Worker installs the
5
+ * bounded RPC runtime. The ordinary SDK and editable-artifacts client entries
6
+ * do not load this code on the main thread.
7
+ */
8
+ export { installBrowserArtifactWorkerEntry } from "./editable-artifacts/worker/browser-entry";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Durable editable-artifact synchronization. Import from
3
+ * `@opengeni/sdk/editable-artifacts` so ordinary SDK/session/realtime consumers
4
+ * do not load the collaboration controller or its offline storage adapters.
5
+ */
6
+ export * from "./editable-artifacts/index";
package/src/errors.ts CHANGED
@@ -41,7 +41,9 @@ export class OpenGeniApiError extends Error {
41
41
  this.retryable = options.retryable ?? decoded?.retryable ?? retryableApiStatus(status);
42
42
  this.correlationId = correlationId;
43
43
  this.outcomeUnknown =
44
- options.outcomeUnknown ?? (gatewayFailure && !!options.mutation && !decoded);
44
+ options.outcomeUnknown ??
45
+ decoded?.outcomeUnknown ??
46
+ (gatewayFailure && !!options.mutation && !decoded);
45
47
  this.body = !fromResponse || decoded ? body : "";
46
48
  this.details = decoded?.details;
47
49
  }
@@ -52,6 +54,7 @@ function decodeApiErrorBody(body: string): {
52
54
  message: string | undefined;
53
55
  requestId: string | undefined;
54
56
  retryable: boolean | undefined;
57
+ outcomeUnknown: boolean | undefined;
55
58
  details: Record<string, unknown> | undefined;
56
59
  } | null {
57
60
  if (!body) return null;
@@ -67,13 +70,24 @@ function decodeApiErrorBody(body: string): {
67
70
  const message = boundedApiField(nested.message);
68
71
  const requestId = boundedCorrelationId(nested.requestId);
69
72
  const retryable = typeof nested.retryable === "boolean" ? nested.retryable : undefined;
73
+ const outcomeUnknown =
74
+ typeof nested.outcomeUnknown === "boolean" ? nested.outcomeUnknown : undefined;
70
75
  const details = boundedApiDetails(nested.details);
71
- if (!code && !message && !requestId && retryable === undefined && !details) return null;
76
+ if (
77
+ !code &&
78
+ !message &&
79
+ !requestId &&
80
+ retryable === undefined &&
81
+ outcomeUnknown === undefined &&
82
+ !details
83
+ )
84
+ return null;
72
85
  return {
73
86
  code,
74
87
  message,
75
88
  requestId,
76
89
  retryable,
90
+ outcomeUnknown,
77
91
  details,
78
92
  };
79
93
  } catch {
@@ -9,14 +9,33 @@ type GatewayServerEvent = Record<string, unknown> & { type: string };
9
9
 
10
10
  const AUDIO_SAMPLE_RATE = 24_000;
11
11
  const DELEGATION_TOOL = "delegate_to_session";
12
+ type RealtimeDialect = "gateway" | "xai";
12
13
 
13
14
  export function createGatewayRealtimeTransportStarter(): RealtimeControllerTransportStarter {
15
+ return createWebsocketRealtimeTransportStarter("gateway");
16
+ }
17
+
18
+ export function createXaiSubscriptionRealtimeTransportStarter(): RealtimeControllerTransportStarter {
19
+ return createWebsocketRealtimeTransportStarter("xai");
20
+ }
21
+
22
+ function createWebsocketRealtimeTransportStarter(
23
+ dialect: RealtimeDialect,
24
+ ): RealtimeControllerTransportStarter {
14
25
  return async (input) => {
15
26
  const client = input.client as CodexRealtimeControllerClient;
16
- if (!client.negotiateGatewayRealtime) {
17
- throw new Error("The OpenGeni client does not support AI Gateway realtime");
27
+ const negotiate =
28
+ dialect === "xai"
29
+ ? client.negotiateXaiSubscriptionRealtime?.bind(client)
30
+ : client.negotiateGatewayRealtime?.bind(client);
31
+ if (!negotiate) {
32
+ throw new Error(
33
+ dialect === "xai"
34
+ ? "The OpenGeni client does not support connected SuperGrok realtime"
35
+ : "The OpenGeni client does not support AI Gateway realtime",
36
+ );
18
37
  }
19
- const answer = await client.negotiateGatewayRealtime(
38
+ const answer = await negotiate(
20
39
  input.workspaceId,
21
40
  input.sessionId,
22
41
  {
@@ -32,16 +51,22 @@ export function createGatewayRealtimeTransportStarter(): RealtimeControllerTrans
32
51
  );
33
52
  throwIfAborted(input.signal);
34
53
 
35
- const websocket = new WebSocket(answer.url, [
36
- "ai-gateway-realtime.v1",
37
- `ai-gateway-auth.${answer.token}`,
38
- ]);
54
+ const websocket = new WebSocket(
55
+ answer.url,
56
+ dialect === "xai"
57
+ ? [`xai-client-secret.${answer.token}`]
58
+ : ["ai-gateway-realtime.v1", `ai-gateway-auth.${answer.token}`],
59
+ );
39
60
  const channel = new GatewayRealtimeDataChannel((payload) =>
40
- handleBridgeOutbound(websocket, payload),
61
+ handleBridgeOutbound(websocket, payload, dialect),
41
62
  );
42
63
  input.onEventsCreated(channel.asRtcDataChannel());
43
64
  const audio = new GatewayRealtimeAudio({
44
- onAudio: (encoded) => send(websocket, { type: "input-audio-append", audio: encoded }),
65
+ onAudio: (encoded) =>
66
+ send(websocket, {
67
+ type: dialect === "xai" ? "input_audio_buffer.append" : "input-audio-append",
68
+ audio: encoded,
69
+ }),
45
70
  onAudibleOutputState: input.onAudibleOutputState,
46
71
  });
47
72
  let stopped = false;
@@ -62,8 +87,9 @@ export function createGatewayRealtimeTransportStarter(): RealtimeControllerTrans
62
87
  return;
63
88
  }
64
89
  if (!isRecord(parsed) || typeof parsed.type !== "string") return;
65
- handleGatewayEvent({
90
+ handleRealtimeEvent({
66
91
  event: parsed as GatewayServerEvent,
92
+ dialect,
67
93
  channel,
68
94
  websocket,
69
95
  audio,
@@ -84,56 +110,6 @@ export function createGatewayRealtimeTransportStarter(): RealtimeControllerTrans
84
110
  websocket.addEventListener("close", onClose);
85
111
  websocket.addEventListener("error", onError);
86
112
 
87
- await waitForWebSocketOpen(websocket, input.signal);
88
- channel.open();
89
- send(websocket, {
90
- type: "session-update",
91
- config: {
92
- instructions: answer.instructions,
93
- outputModalities: ["audio"],
94
- inputAudioFormat: { type: "audio/pcm", rate: AUDIO_SAMPLE_RATE },
95
- outputAudioFormat: { type: "audio/pcm", rate: AUDIO_SAMPLE_RATE },
96
- inputAudioTranscription: {},
97
- outputAudioTranscription: {},
98
- turnDetection: {
99
- type: "server-vad",
100
- prefixPaddingMs: 300,
101
- silenceDurationMs: 500,
102
- },
103
- tools: [
104
- {
105
- type: "function",
106
- name: DELEGATION_TOOL,
107
- description:
108
- "Pass execution work, actions, and session tasks to the underlying session agent. Include the complete standalone request and relevant conversational context.",
109
- parameters: {
110
- type: "object",
111
- properties: {
112
- request: {
113
- type: "string",
114
- description: "Complete standalone task for the session agent",
115
- },
116
- },
117
- required: ["request"],
118
- additionalProperties: false,
119
- },
120
- },
121
- ],
122
- },
123
- });
124
- for (const item of answer.initialItems) {
125
- send(websocket, {
126
- type: "conversation-item-create",
127
- item: {
128
- type: "text-message",
129
- role: "user",
130
- text: `<session_initial_item role="${item.role}">\n${item.text}\n</session_initial_item>`,
131
- },
132
- });
133
- }
134
- await audio.startCapture(input.media);
135
- input.onConnectionHealth("connected");
136
-
137
113
  const stop = (): void => {
138
114
  if (stopped) return;
139
115
  stopped = true;
@@ -153,6 +129,41 @@ export function createGatewayRealtimeTransportStarter(): RealtimeControllerTrans
153
129
  };
154
130
  input.signal.addEventListener("abort", stop, { once: true });
155
131
 
132
+ try {
133
+ await waitForWebSocketOpen(websocket, input.signal);
134
+ channel.open();
135
+ send(
136
+ websocket,
137
+ dialect === "xai"
138
+ ? xaiSessionUpdate(answer.instructions)
139
+ : gatewaySessionUpdate(answer.instructions),
140
+ );
141
+ for (const item of answer.initialItems) {
142
+ const text = `<session_initial_item role="${item.role}">\n${item.text}\n</session_initial_item>`;
143
+ send(
144
+ websocket,
145
+ dialect === "xai"
146
+ ? {
147
+ type: "conversation.item.create",
148
+ item: {
149
+ type: "message",
150
+ role: "user",
151
+ content: [{ type: "input_text", text }],
152
+ },
153
+ }
154
+ : {
155
+ type: "conversation-item-create",
156
+ item: { type: "text-message", role: "user", text },
157
+ },
158
+ );
159
+ }
160
+ await audio.startCapture(input.media);
161
+ input.onConnectionHealth("connected");
162
+ } catch (error) {
163
+ stop();
164
+ throw error;
165
+ }
166
+
156
167
  return {
157
168
  peerConnection: null as unknown as RTCPeerConnection,
158
169
  events: channel.asRtcDataChannel(),
@@ -174,8 +185,73 @@ export function createGatewayRealtimeTransportStarter(): RealtimeControllerTrans
174
185
  };
175
186
  }
176
187
 
177
- function handleGatewayEvent(input: {
188
+ function delegationTool(): Record<string, unknown> {
189
+ return {
190
+ type: "function",
191
+ name: DELEGATION_TOOL,
192
+ description:
193
+ "Pass execution work, actions, and session tasks to the underlying session agent. Include the complete standalone request and relevant conversational context.",
194
+ parameters: {
195
+ type: "object",
196
+ properties: {
197
+ request: {
198
+ type: "string",
199
+ description: "Complete standalone task for the session agent",
200
+ },
201
+ },
202
+ required: ["request"],
203
+ additionalProperties: false,
204
+ },
205
+ };
206
+ }
207
+
208
+ function gatewaySessionUpdate(instructions: string): GatewayClientEvent {
209
+ return {
210
+ type: "session-update",
211
+ config: {
212
+ instructions,
213
+ outputModalities: ["audio"],
214
+ inputAudioFormat: { type: "audio/pcm", rate: AUDIO_SAMPLE_RATE },
215
+ outputAudioFormat: { type: "audio/pcm", rate: AUDIO_SAMPLE_RATE },
216
+ inputAudioTranscription: {},
217
+ outputAudioTranscription: {},
218
+ turnDetection: {
219
+ type: "server-vad",
220
+ prefixPaddingMs: 300,
221
+ silenceDurationMs: 500,
222
+ },
223
+ tools: [delegationTool()],
224
+ },
225
+ };
226
+ }
227
+
228
+ function xaiSessionUpdate(instructions: string): GatewayClientEvent {
229
+ return {
230
+ type: "session.update",
231
+ session: {
232
+ instructions,
233
+ voice: "eve",
234
+ reasoning: { effort: "high" },
235
+ turn_detection: {
236
+ type: "server_vad",
237
+ prefix_padding_ms: 300,
238
+ silence_duration_ms: 500,
239
+ },
240
+ audio: {
241
+ input: {
242
+ format: { type: "audio/pcm", rate: AUDIO_SAMPLE_RATE },
243
+ transcription: { model: "grok-transcribe" },
244
+ },
245
+ output: { format: { type: "audio/pcm", rate: AUDIO_SAMPLE_RATE } },
246
+ },
247
+ tools: [delegationTool()],
248
+ },
249
+ };
250
+ }
251
+
252
+ function handleRealtimeEvent(input: {
178
253
  event: GatewayServerEvent;
254
+ dialect: RealtimeDialect;
179
255
  channel: GatewayRealtimeDataChannel;
180
256
  websocket: WebSocket;
181
257
  audio: GatewayRealtimeAudio;
@@ -185,8 +261,10 @@ function handleGatewayEvent(input: {
185
261
  }): void {
186
262
  const event = input.event;
187
263
  const eventId = providerEventId(event);
188
- if (event.type === "session-created") {
189
- const sessionId = stringValue(event.sessionId) ?? `gateway-${crypto.randomUUID()}`;
264
+ if (event.type === "session-created" || event.type === "session.created") {
265
+ const session = isRecord(event.session) ? event.session : null;
266
+ const sessionId =
267
+ stringValue(event.sessionId) ?? stringValue(session?.id) ?? `realtime-${crypto.randomUUID()}`;
190
268
  input.channel.providerEvent({
191
269
  type: "session.started",
192
270
  event_id: eventId,
@@ -194,23 +272,36 @@ function handleGatewayEvent(input: {
194
272
  });
195
273
  return;
196
274
  }
197
- if (event.type === "speech-started") {
275
+ if (event.type === "speech-started" || event.type === "input_audio_buffer.speech_started") {
198
276
  const itemId = input.getCurrentOutputItemId();
199
277
  if (itemId && input.audio.isPlaying()) {
200
278
  const audioEndMs = input.audio.playbackOffsetMs();
201
279
  input.audio.stopPlayback();
202
- send(input.websocket, {
203
- type: "conversation-item-truncate",
204
- itemId,
205
- contentIndex: 0,
206
- audioEndMs: Math.max(0, Math.round(audioEndMs)),
207
- });
280
+ send(
281
+ input.websocket,
282
+ input.dialect === "xai"
283
+ ? {
284
+ type: "conversation.item.truncate",
285
+ item_id: itemId,
286
+ content_index: 0,
287
+ audio_end_ms: Math.max(0, Math.round(audioEndMs)),
288
+ }
289
+ : {
290
+ type: "conversation-item-truncate",
291
+ itemId,
292
+ contentIndex: 0,
293
+ audioEndMs: Math.max(0, Math.round(audioEndMs)),
294
+ },
295
+ );
208
296
  }
209
297
  return;
210
298
  }
211
- if (event.type === "input-transcription-completed") {
299
+ if (
300
+ event.type === "input-transcription-completed" ||
301
+ event.type === "conversation.item.input_audio_transcription.completed"
302
+ ) {
212
303
  const transcript = stringValue(event.transcript) ?? "";
213
- const itemId = stringValue(event.itemId) ?? crypto.randomUUID();
304
+ const itemId = stringValue(event.itemId) ?? stringValue(event.item_id) ?? crypto.randomUUID();
214
305
  if (transcript.trim()) {
215
306
  input.channel.providerEvent({
216
307
  type: "turn.done",
@@ -220,17 +311,27 @@ function handleGatewayEvent(input: {
220
311
  }
221
312
  return;
222
313
  }
223
- if (event.type === "audio-delta") {
314
+ if (
315
+ event.type === "audio-delta" ||
316
+ event.type === "response.output_audio.delta" ||
317
+ event.type === "response.audio.delta"
318
+ ) {
224
319
  const delta = stringValue(event.delta);
225
320
  if (!delta) return;
226
- const itemId = stringValue(event.itemId);
321
+ const itemId = stringValue(event.itemId) ?? stringValue(event.item_id);
227
322
  if (itemId) input.setCurrentOutputItemId(itemId);
228
323
  input.audio.play(delta);
229
324
  input.channel.providerEvent({ type: "output_audio.delta", event_id: eventId, audio: delta });
230
325
  return;
231
326
  }
232
- if (event.type === "audio-transcript-done" || event.type === "text-done") {
233
- const itemId = stringValue(event.itemId) ?? crypto.randomUUID();
327
+ if (
328
+ event.type === "audio-transcript-done" ||
329
+ event.type === "text-done" ||
330
+ event.type === "response.output_audio_transcript.done" ||
331
+ event.type === "response.audio_transcript.done" ||
332
+ event.type === "response.output_text.done"
333
+ ) {
334
+ const itemId = stringValue(event.itemId) ?? stringValue(event.item_id) ?? crypto.randomUUID();
234
335
  const transcript = stringValue(event.transcript) ?? stringValue(event.text) ?? "";
235
336
  if (transcript.trim() && !input.finalizedAssistantItems.has(itemId)) {
236
337
  input.finalizedAssistantItems.add(itemId);
@@ -242,21 +343,27 @@ function handleGatewayEvent(input: {
242
343
  }
243
344
  return;
244
345
  }
245
- if (event.type === "audio-done") return;
246
- if (event.type === "function-call-arguments-done") {
247
- const callId = stringValue(event.callId) ?? stringValue(event.itemId) ?? crypto.randomUUID();
346
+ if (event.type === "audio-done" || event.type === "response.output_audio.done") return;
347
+ if (
348
+ event.type === "function-call-arguments-done" ||
349
+ event.type === "response.function_call_arguments.done"
350
+ ) {
351
+ const callId =
352
+ stringValue(event.callId) ??
353
+ stringValue(event.call_id) ??
354
+ stringValue(event.itemId) ??
355
+ stringValue(event.item_id) ??
356
+ crypto.randomUUID();
248
357
  const name = stringValue(event.name);
249
358
  if (name !== DELEGATION_TOOL) {
250
- send(input.websocket, {
251
- type: "conversation-item-create",
252
- item: {
253
- type: "function-call-output",
254
- callId,
255
- name,
256
- output: JSON.stringify({ error: "Unsupported realtime tool" }),
257
- },
258
- });
259
- send(input.websocket, { type: "response-create" });
359
+ sendFunctionOutput(
360
+ input.websocket,
361
+ input.dialect,
362
+ callId,
363
+ name,
364
+ JSON.stringify({ error: "Unsupported realtime tool" }),
365
+ );
366
+ sendResponseCreate(input.websocket, input.dialect);
260
367
  return;
261
368
  }
262
369
  const request = delegationRequest(stringValue(event.arguments) ?? "");
@@ -273,10 +380,12 @@ function handleGatewayEvent(input: {
273
380
  return;
274
381
  }
275
382
  if (event.type === "error") {
383
+ const nested = isRecord(event.error) ? event.error : null;
276
384
  input.channel.providerEvent({
277
385
  type: "error",
278
386
  event_id: eventId,
279
- message: stringValue(event.message) ?? "AI Gateway realtime provider error",
387
+ message:
388
+ stringValue(event.message) ?? stringValue(nested?.message) ?? "Realtime provider error",
280
389
  });
281
390
  }
282
391
  }
@@ -335,6 +444,7 @@ class GatewayRealtimeDataChannel extends EventTarget {
335
444
  function handleBridgeOutbound(
336
445
  websocket: WebSocket,
337
446
  messages: Array<Record<string, unknown>>,
447
+ dialect: RealtimeDialect,
338
448
  ): void {
339
449
  const groups = new Map<
340
450
  string,
@@ -361,16 +471,14 @@ function handleBridgeOutbound(
361
471
  }
362
472
  for (const group of groups.values()) {
363
473
  if (group.type === "delegation.context.append" && group.id && group.channel === "speakable") {
364
- send(websocket, {
365
- type: "conversation-item-create",
366
- item: {
367
- type: "function-call-output",
368
- callId: group.id,
369
- name: DELEGATION_TOOL,
370
- output: JSON.stringify({ result: group.text }),
371
- },
372
- });
373
- send(websocket, { type: "response-create" });
474
+ sendFunctionOutput(
475
+ websocket,
476
+ dialect,
477
+ group.id,
478
+ DELEGATION_TOOL,
479
+ JSON.stringify({ result: group.text }),
480
+ );
481
+ sendResponseCreate(websocket, dialect);
374
482
  continue;
375
483
  }
376
484
  const wrapper =
@@ -379,18 +487,52 @@ function handleBridgeOutbound(
379
487
  ? "execution_progress"
380
488
  : "execution_result"
381
489
  : "session_update";
382
- send(websocket, {
383
- type: "conversation-item-create",
384
- item: {
385
- type: "text-message",
386
- role: "user",
387
- text: `<${wrapper}>\n${group.text}\n</${wrapper}>`,
388
- },
389
- });
390
- if (group.channel === "speakable") send(websocket, { type: "response-create" });
490
+ const text = `<${wrapper}>\n${group.text}\n</${wrapper}>`;
491
+ send(
492
+ websocket,
493
+ dialect === "xai"
494
+ ? {
495
+ type: "conversation.item.create",
496
+ item: {
497
+ type: "message",
498
+ role: "user",
499
+ content: [{ type: "input_text", text }],
500
+ },
501
+ }
502
+ : {
503
+ type: "conversation-item-create",
504
+ item: { type: "text-message", role: "user", text },
505
+ },
506
+ );
507
+ if (group.channel === "speakable") sendResponseCreate(websocket, dialect);
391
508
  }
392
509
  }
393
510
 
511
+ function sendFunctionOutput(
512
+ websocket: WebSocket,
513
+ dialect: RealtimeDialect,
514
+ callId: string,
515
+ name: string | null,
516
+ output: string,
517
+ ): void {
518
+ send(
519
+ websocket,
520
+ dialect === "xai"
521
+ ? {
522
+ type: "conversation.item.create",
523
+ item: { type: "function_call_output", call_id: callId, output },
524
+ }
525
+ : {
526
+ type: "conversation-item-create",
527
+ item: { type: "function-call-output", callId, name, output },
528
+ },
529
+ );
530
+ }
531
+
532
+ function sendResponseCreate(websocket: WebSocket, dialect: RealtimeDialect): void {
533
+ send(websocket, { type: dialect === "xai" ? "response.create" : "response-create" });
534
+ }
535
+
394
536
  class GatewayRealtimeAudio {
395
537
  private captureContext: AudioContext | null = null;
396
538
  private captureSource: MediaStreamAudioSourceNode | null = null;
@@ -560,6 +702,7 @@ function providerEventId(event: Record<string, unknown>): string {
560
702
  const raw = isRecord(event.raw) ? event.raw : null;
561
703
  return (
562
704
  stringValue(event.eventId) ??
705
+ stringValue(event.event_id) ??
563
706
  stringValue(raw?.event_id) ??
564
707
  stringValue(raw?.id) ??
565
708
  crypto.randomUUID()