@yourgpt/llm-sdk 2.6.0-beta.2 → 2.6.0-beta.3

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/README.md CHANGED
@@ -83,12 +83,28 @@ existing option; `responses.reasoningSummary` requests a reasoning summary.
83
83
 
84
84
  **Stateless replay:** use `store: false` and preserve the returned message objects,
85
85
  including `providerMetadata`. Each assistant's `providerMetadata.openai.response`
86
- contains the response ID, ordered native output items, raw usage, status and
87
- refusals. Completed encrypted reasoning, native search items, citations and
88
- function-call IDs survive the next turn. Automatic tool loops retain them too.
89
- Streams use completed items, never partially streamed reasoning. Models may
90
- return no reasoning items on simple requests; the SDK preserves what is returned.
91
- Do not edit opaque output items or carry them across unrelated conversations.
86
+ contains minimal ordered continuation items, including function-call IDs, assistant
87
+ phase and completed encrypted reasoning. It excludes the raw response envelope,
88
+ native usage, logprobs, reasoning summaries and output-text annotations. Native
89
+ tool state needed for replay is retained with diagnostic fields removed. Streams
90
+ use completed items, never partially streamed reasoning. Keep this state intact
91
+ and within its original conversation; do not treat it as trusted authorization.
92
+
93
+ The separate **server-side model result** `providerMetadata` still contains the
94
+ full response ID, status, native usage and output for diagnostics and stored
95
+ continuation. Do not forward that complete model result to a public client.
96
+ `runtime.chat()` retains top-level `usage` for billing and `events` for debugging;
97
+ after billing, omit both before serializing it:
98
+
99
+ ```ts
100
+ const result = await runtime.chat(request);
101
+ await recordBilling(result.usage);
102
+ const { usage, events, ...clientResult } = result;
103
+ return Response.json(clientResult);
104
+ ```
105
+
106
+ SSE response helpers omit usage by default. `runtime.stream(...).toResponse({
107
+ includeUsage: true })` explicitly enables it for a server-controlled raw API.
92
108
 
93
109
  `generateText().response.messages` now includes the final assistant turn as well
94
110
  as tool turns. `streamText().response` returns the same complete history after
@@ -101,7 +117,8 @@ storage adapter stores display/tool records and does **not** yet guarantee
101
117
  lossless Responses replay after a remote session reload.
102
118
 
103
119
  **Stored continuation:** explicitly use `store: true`, take the returned
104
- `providerMetadata.openai.response.id`, and pass it as
120
+ `generateText(...).providerMetadata.openai.response.id` (the server result, not
121
+ the saved message metadata), and pass it as
105
122
  `openai: { responses: { previousResponseId: id } }` on the next call. Send only new
106
123
  input after that response, and resend system instructions; do not also resend
107
124
  its entire history. The SDK does not automatically advance or persist response
@@ -222,7 +239,10 @@ or `{ role: "system", content: "..." }` messages. It combines them into
222
239
  Anthropic's top-level `system` field: the request's nonempty `systemPrompt` (or
223
240
  the runtime default) first, followed by system messages in order. System messages
224
241
  are removed from the conversation sent to Anthropic. This applies to `chat`,
225
- `generate`, `stream`, and `processChat`.
242
+ `generate`, `stream`, and `processChat`. If the same text is supplied through both
243
+ forms, the explicit prompt is sent once. Null, non-text values and unsupported
244
+ system blocks are ignored for proxy compatibility; valid text blocks are retained.
245
+ Repeated message-only instructions are preserved in order.
226
246
 
227
247
  ```ts
228
248
  const decision = await runtime.chat({
@@ -315,3 +335,17 @@ Tools can return native image content using `toModelOutput` or the existing `_ai
315
335
  `generateSpeech` / `transcribe` are separate server audio helpers, with `openaiSpeech` / `openaiTranscription` factories from `@yourgpt/llm-sdk/openai`. They support cancellation, validated encoded audio, and response metadata. Copilot's `VoiceRecorder` and `transcribeRecording` provide recorded microphone input and a reviewable transcript.
316
336
 
317
337
  See [image and audio contracts, examples, and limitations](./docs/multimodal-and-audio.md). Realtime voice calls and image generation are separate follow-ups.
338
+
339
+ ### Beta compatibility fixes
340
+
341
+ OpenAI-compatible Chat transports preserve raw structured tool-message content
342
+ instead of coercing arrays or objects to `[object Object]`. The SDK's explicit
343
+ `modelOutput` representation still uses provider-specific conversion. This does
344
+ not make every provider accept arbitrary object content; the provider validates
345
+ its own schema. Use its supported content-part format for live calls.
346
+
347
+ Malformed Responses function calls are rejected before tool execution with
348
+ `status: 502`, `code: "INVALID_PROVIDER_RESPONSE"`. The fallback chain can retry or
349
+ fall back according to its configured policy before any content has been emitted.
350
+ It does not switch models after streaming content or tool execution has started.
351
+ `generateText` also accepts model results that omit the optional `toolCalls` field.
@@ -526,21 +526,14 @@ function messageToOpenAIContent(message) {
526
526
  return blocks;
527
527
  }
528
528
  function extractAnthropicSystemPrompt(messages, systemPrompt) {
529
- const parts = systemPrompt ? [systemPrompt] : [];
529
+ const explicit = typeof systemPrompt === "string" ? systemPrompt : "";
530
+ const parts = explicit ? [explicit] : [];
530
531
  for (const message of messages) {
531
532
  if (message.role !== "system") continue;
532
- if (typeof message.content === "string") {
533
- if (message.content) parts.push(message.content);
534
- } else if (Array.isArray(message.content)) {
535
- for (const block of message.content) {
536
- if (block?.type !== "text" || typeof block.text !== "string") {
537
- throw new TypeError("Anthropic system messages must contain text.");
538
- }
539
- if (block.text) parts.push(block.text);
540
- }
541
- } else {
542
- throw new TypeError("Anthropic system messages must contain text.");
543
- }
533
+ const text = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.filter(
534
+ (block) => block?.type === "text" && typeof block.text === "string"
535
+ ).map((block) => block.text).filter(Boolean).join("\n") : "";
536
+ if (text && text !== explicit) parts.push(text);
544
537
  }
545
538
  return parts.join("\n");
546
539
  }
@@ -688,6 +681,54 @@ function responseMetadata(response) {
688
681
  }
689
682
  };
690
683
  }
684
+ function responseReplayMetadata(metadata) {
685
+ const response = metadata?.openai?.response;
686
+ if (!response) return metadata;
687
+ const output = response.output.map((item) => {
688
+ const fields = ["type", "id", "status"];
689
+ switch (item.type) {
690
+ case "reasoning":
691
+ fields.push("encrypted_content");
692
+ break;
693
+ case "message":
694
+ fields.push("role", "phase");
695
+ break;
696
+ case "function_call":
697
+ fields.push("call_id", "name", "namespace", "arguments");
698
+ break;
699
+ case "tool_search_call":
700
+ fields.push("call_id", "execution", "arguments");
701
+ break;
702
+ case "tool_search_output":
703
+ fields.push("call_id", "tools");
704
+ break;
705
+ case "web_search_call":
706
+ fields.push("action");
707
+ break;
708
+ default:
709
+ return stripItemDiagnostics(item);
710
+ }
711
+ const replay = Object.fromEntries(
712
+ fields.filter((key) => item[key] !== void 0).map((key) => [key, item[key]])
713
+ );
714
+ if (item.type === "reasoning") replay.summary = [];
715
+ if (item.type === "message")
716
+ replay.content = Array.isArray(item.content) ? item.content.flatMap(
717
+ (part) => part?.type === "output_text" && typeof part.text === "string" ? [{ type: "output_text", text: part.text, annotations: [] }] : part?.type === "refusal" && typeof part.refusal === "string" ? [{ type: "refusal", refusal: part.refusal }] : []
718
+ ) : [];
719
+ return stripItemDiagnostics(replay);
720
+ });
721
+ return { openai: { response: { output, refusals: [] } } };
722
+ }
723
+ function stripItemDiagnostics(item) {
724
+ const {
725
+ usage: _usage,
726
+ logprobs: _logprobs,
727
+ metadata: _metadata,
728
+ ...state
729
+ } = item;
730
+ return state;
731
+ }
691
732
  function getResponseReplay(metadata) {
692
733
  const response = metadata?.openai?.response;
693
734
  if (response === void 0) return void 0;
@@ -696,10 +737,17 @@ function getResponseReplay(metadata) {
696
737
  "Invalid OpenAI Responses replay metadata: output must contain typed items"
697
738
  );
698
739
  }
699
- return response.output;
740
+ return responseReplayMetadata(metadata)?.openai?.response?.output;
700
741
  }
701
742
 
702
743
  // src/core/provider-error.ts
744
+ function invalidProviderResponse(message) {
745
+ return Object.assign(new Error(message), {
746
+ name: "InvalidProviderResponseError",
747
+ status: 502,
748
+ code: "INVALID_PROVIDER_RESPONSE"
749
+ });
750
+ }
703
751
  function providerErrorEvent(error, code) {
704
752
  const details = error ?? {};
705
753
  const event = {
@@ -876,7 +924,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
876
924
  formatRawMessages(messages, preserveProviderMetadata = false) {
877
925
  return messages.map((message) => {
878
926
  const { attachments, providerMetadata, modelOutput, ...rest } = message;
879
- let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
927
+ let content = message.role === "tool" && !preserveProviderMetadata && modelOutput !== void 0 ? textToolOutput(modelOutput, "") : message.content;
880
928
  if (preserveProviderMetadata && modelOutput)
881
929
  rest.modelOutput = modelOutput;
882
930
  if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
@@ -979,17 +1027,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
979
1027
  const seenCallIds = /* @__PURE__ */ new Set();
980
1028
  const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
981
1029
  if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
982
- throw new Error("Responses contains an incomplete function call");
1030
+ throw invalidProviderResponse(
1031
+ "Responses contains an incomplete function call"
1032
+ );
983
1033
  }
984
1034
  if (seenCallIds.has(item.call_id))
985
- throw new Error("Responses contains duplicate function call IDs");
1035
+ throw invalidProviderResponse(
1036
+ "Responses contains duplicate function call IDs"
1037
+ );
986
1038
  seenCallIds.add(item.call_id);
987
- const args = JSON.parse(item.arguments);
1039
+ let args;
1040
+ try {
1041
+ args = JSON.parse(item.arguments);
1042
+ } catch {
1043
+ throw invalidProviderResponse(
1044
+ "Responses function arguments must be valid JSON"
1045
+ );
1046
+ }
988
1047
  if (!args || typeof args !== "object" || Array.isArray(args))
989
- throw new Error(
1048
+ throw invalidProviderResponse(
990
1049
  "Responses function arguments must be a JSON object"
991
1050
  );
992
- return { id: item.call_id, name: item.name, args };
1051
+ return {
1052
+ id: item.call_id,
1053
+ name: item.name,
1054
+ args
1055
+ };
993
1056
  }) : [];
994
1057
  return {
995
1058
  content,
@@ -1232,7 +1295,9 @@ var OpenAIAdapter = class _OpenAIAdapter {
1232
1295
  const uniqueCalls = /* @__PURE__ */ new Map();
1233
1296
  for (const call of toolCalls.values()) {
1234
1297
  if (!call.id || !call.name)
1235
- throw new Error("Responses stream contains an incomplete tool call");
1298
+ throw invalidProviderResponse(
1299
+ "Responses stream contains an incomplete tool call"
1300
+ );
1236
1301
  uniqueCalls.set(call.id, call);
1237
1302
  }
1238
1303
  toolCalls.clear();
@@ -524,21 +524,14 @@ function messageToOpenAIContent(message) {
524
524
  return blocks;
525
525
  }
526
526
  function extractAnthropicSystemPrompt(messages, systemPrompt) {
527
- const parts = systemPrompt ? [systemPrompt] : [];
527
+ const explicit = typeof systemPrompt === "string" ? systemPrompt : "";
528
+ const parts = explicit ? [explicit] : [];
528
529
  for (const message of messages) {
529
530
  if (message.role !== "system") continue;
530
- if (typeof message.content === "string") {
531
- if (message.content) parts.push(message.content);
532
- } else if (Array.isArray(message.content)) {
533
- for (const block of message.content) {
534
- if (block?.type !== "text" || typeof block.text !== "string") {
535
- throw new TypeError("Anthropic system messages must contain text.");
536
- }
537
- if (block.text) parts.push(block.text);
538
- }
539
- } else {
540
- throw new TypeError("Anthropic system messages must contain text.");
541
- }
531
+ const text = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.filter(
532
+ (block) => block?.type === "text" && typeof block.text === "string"
533
+ ).map((block) => block.text).filter(Boolean).join("\n") : "";
534
+ if (text && text !== explicit) parts.push(text);
542
535
  }
543
536
  return parts.join("\n");
544
537
  }
@@ -686,6 +679,54 @@ function responseMetadata(response) {
686
679
  }
687
680
  };
688
681
  }
682
+ function responseReplayMetadata(metadata) {
683
+ const response = metadata?.openai?.response;
684
+ if (!response) return metadata;
685
+ const output = response.output.map((item) => {
686
+ const fields = ["type", "id", "status"];
687
+ switch (item.type) {
688
+ case "reasoning":
689
+ fields.push("encrypted_content");
690
+ break;
691
+ case "message":
692
+ fields.push("role", "phase");
693
+ break;
694
+ case "function_call":
695
+ fields.push("call_id", "name", "namespace", "arguments");
696
+ break;
697
+ case "tool_search_call":
698
+ fields.push("call_id", "execution", "arguments");
699
+ break;
700
+ case "tool_search_output":
701
+ fields.push("call_id", "tools");
702
+ break;
703
+ case "web_search_call":
704
+ fields.push("action");
705
+ break;
706
+ default:
707
+ return stripItemDiagnostics(item);
708
+ }
709
+ const replay = Object.fromEntries(
710
+ fields.filter((key) => item[key] !== void 0).map((key) => [key, item[key]])
711
+ );
712
+ if (item.type === "reasoning") replay.summary = [];
713
+ if (item.type === "message")
714
+ replay.content = Array.isArray(item.content) ? item.content.flatMap(
715
+ (part) => part?.type === "output_text" && typeof part.text === "string" ? [{ type: "output_text", text: part.text, annotations: [] }] : part?.type === "refusal" && typeof part.refusal === "string" ? [{ type: "refusal", refusal: part.refusal }] : []
716
+ ) : [];
717
+ return stripItemDiagnostics(replay);
718
+ });
719
+ return { openai: { response: { output, refusals: [] } } };
720
+ }
721
+ function stripItemDiagnostics(item) {
722
+ const {
723
+ usage: _usage,
724
+ logprobs: _logprobs,
725
+ metadata: _metadata,
726
+ ...state
727
+ } = item;
728
+ return state;
729
+ }
689
730
  function getResponseReplay(metadata) {
690
731
  const response = metadata?.openai?.response;
691
732
  if (response === void 0) return void 0;
@@ -694,10 +735,17 @@ function getResponseReplay(metadata) {
694
735
  "Invalid OpenAI Responses replay metadata: output must contain typed items"
695
736
  );
696
737
  }
697
- return response.output;
738
+ return responseReplayMetadata(metadata)?.openai?.response?.output;
698
739
  }
699
740
 
700
741
  // src/core/provider-error.ts
742
+ function invalidProviderResponse(message) {
743
+ return Object.assign(new Error(message), {
744
+ name: "InvalidProviderResponseError",
745
+ status: 502,
746
+ code: "INVALID_PROVIDER_RESPONSE"
747
+ });
748
+ }
701
749
  function providerErrorEvent(error, code) {
702
750
  const details = error ?? {};
703
751
  const event = {
@@ -874,7 +922,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
874
922
  formatRawMessages(messages, preserveProviderMetadata = false) {
875
923
  return messages.map((message) => {
876
924
  const { attachments, providerMetadata, modelOutput, ...rest } = message;
877
- let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
925
+ let content = message.role === "tool" && !preserveProviderMetadata && modelOutput !== void 0 ? textToolOutput(modelOutput, "") : message.content;
878
926
  if (preserveProviderMetadata && modelOutput)
879
927
  rest.modelOutput = modelOutput;
880
928
  if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
@@ -977,17 +1025,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
977
1025
  const seenCallIds = /* @__PURE__ */ new Set();
978
1026
  const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
979
1027
  if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
980
- throw new Error("Responses contains an incomplete function call");
1028
+ throw invalidProviderResponse(
1029
+ "Responses contains an incomplete function call"
1030
+ );
981
1031
  }
982
1032
  if (seenCallIds.has(item.call_id))
983
- throw new Error("Responses contains duplicate function call IDs");
1033
+ throw invalidProviderResponse(
1034
+ "Responses contains duplicate function call IDs"
1035
+ );
984
1036
  seenCallIds.add(item.call_id);
985
- const args = JSON.parse(item.arguments);
1037
+ let args;
1038
+ try {
1039
+ args = JSON.parse(item.arguments);
1040
+ } catch {
1041
+ throw invalidProviderResponse(
1042
+ "Responses function arguments must be valid JSON"
1043
+ );
1044
+ }
986
1045
  if (!args || typeof args !== "object" || Array.isArray(args))
987
- throw new Error(
1046
+ throw invalidProviderResponse(
988
1047
  "Responses function arguments must be a JSON object"
989
1048
  );
990
- return { id: item.call_id, name: item.name, args };
1049
+ return {
1050
+ id: item.call_id,
1051
+ name: item.name,
1052
+ args
1053
+ };
991
1054
  }) : [];
992
1055
  return {
993
1056
  content,
@@ -1230,7 +1293,9 @@ var OpenAIAdapter = class _OpenAIAdapter {
1230
1293
  const uniqueCalls = /* @__PURE__ */ new Map();
1231
1294
  for (const call of toolCalls.values()) {
1232
1295
  if (!call.id || !call.name)
1233
- throw new Error("Responses stream contains an incomplete tool call");
1296
+ throw invalidProviderResponse(
1297
+ "Responses stream contains an incomplete tool call"
1298
+ );
1234
1299
  uniqueCalls.set(call.id, call);
1235
1300
  }
1236
1301
  toolCalls.clear();
package/dist/index.d.mts CHANGED
@@ -1166,7 +1166,9 @@ declare function formatSSEData(event: StreamEvent): string;
1166
1166
  /**
1167
1167
  * Create a ReadableStream from an async generator of events
1168
1168
  */
1169
- declare function createEventStream(generator: AsyncGenerator<StreamEvent>): ReadableStream<Uint8Array>;
1169
+ declare function createEventStream(generator: AsyncGenerator<StreamEvent>, options?: {
1170
+ includeUsage?: boolean;
1171
+ }): ReadableStream<Uint8Array>;
1170
1172
  /**
1171
1173
  * Create SSE Response object
1172
1174
  */
package/dist/index.d.ts CHANGED
@@ -1166,7 +1166,9 @@ declare function formatSSEData(event: StreamEvent): string;
1166
1166
  /**
1167
1167
  * Create a ReadableStream from an async generator of events
1168
1168
  */
1169
- declare function createEventStream(generator: AsyncGenerator<StreamEvent>): ReadableStream<Uint8Array>;
1169
+ declare function createEventStream(generator: AsyncGenerator<StreamEvent>, options?: {
1170
+ includeUsage?: boolean;
1171
+ }): ReadableStream<Uint8Array>;
1170
1172
  /**
1171
1173
  * Create SSE Response object
1172
1174
  */
package/dist/index.js CHANGED
@@ -3,6 +3,56 @@
3
3
  var hono = require('hono');
4
4
  var cors = require('hono/cors');
5
5
 
6
+ // src/core/openai-responses.ts
7
+ function responseReplayMetadata(metadata) {
8
+ const response = metadata?.openai?.response;
9
+ if (!response) return metadata;
10
+ const output = response.output.map((item) => {
11
+ const fields = ["type", "id", "status"];
12
+ switch (item.type) {
13
+ case "reasoning":
14
+ fields.push("encrypted_content");
15
+ break;
16
+ case "message":
17
+ fields.push("role", "phase");
18
+ break;
19
+ case "function_call":
20
+ fields.push("call_id", "name", "namespace", "arguments");
21
+ break;
22
+ case "tool_search_call":
23
+ fields.push("call_id", "execution", "arguments");
24
+ break;
25
+ case "tool_search_output":
26
+ fields.push("call_id", "tools");
27
+ break;
28
+ case "web_search_call":
29
+ fields.push("action");
30
+ break;
31
+ default:
32
+ return stripItemDiagnostics(item);
33
+ }
34
+ const replay = Object.fromEntries(
35
+ fields.filter((key) => item[key] !== void 0).map((key) => [key, item[key]])
36
+ );
37
+ if (item.type === "reasoning") replay.summary = [];
38
+ if (item.type === "message")
39
+ replay.content = Array.isArray(item.content) ? item.content.flatMap(
40
+ (part) => part?.type === "output_text" && typeof part.text === "string" ? [{ type: "output_text", text: part.text, annotations: [] }] : part?.type === "refusal" && typeof part.refusal === "string" ? [{ type: "refusal", refusal: part.refusal }] : []
41
+ ) : [];
42
+ return stripItemDiagnostics(replay);
43
+ });
44
+ return { openai: { response: { output, refusals: [] } } };
45
+ }
46
+ function stripItemDiagnostics(item) {
47
+ const {
48
+ usage: _usage,
49
+ logprobs: _logprobs,
50
+ metadata: _metadata,
51
+ ...state
52
+ } = item;
53
+ return state;
54
+ }
55
+
6
56
  // src/core/tool-output.ts
7
57
  var imageTypes = /* @__PURE__ */ new Set([
8
58
  "image/png",
@@ -240,10 +290,11 @@ async function generateText(params) {
240
290
  signal
241
291
  });
242
292
  throwIfAborted(signal);
293
+ const toolCalls = result.toolCalls ?? [];
243
294
  const stepToolResults = [];
244
- if (result.toolCalls && result.toolCalls.length > 0 && tools) {
245
- allToolCalls.push(...result.toolCalls);
246
- for (const call of result.toolCalls) {
295
+ if (toolCalls.length > 0 && tools) {
296
+ allToolCalls.push(...toolCalls);
297
+ for (const call of toolCalls) {
247
298
  throwIfAborted(signal);
248
299
  const toolDef = tools[call.name];
249
300
  if (!toolDef) {
@@ -288,7 +339,7 @@ async function generateText(params) {
288
339
  }
289
340
  steps.push({
290
341
  text: result.text,
291
- toolCalls: result.toolCalls,
342
+ toolCalls,
292
343
  toolResults: stepToolResults,
293
344
  finishReason: result.finishReason,
294
345
  usage: result.usage,
@@ -297,11 +348,11 @@ async function generateText(params) {
297
348
  const assistantMessage = {
298
349
  role: "assistant",
299
350
  content: result.text || null,
300
- ...result.toolCalls.length ? { toolCalls: result.toolCalls } : {},
301
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {}
351
+ ...toolCalls.length ? { toolCalls } : {},
352
+ ...result.providerMetadata ? { providerMetadata: responseReplayMetadata(result.providerMetadata) } : {}
302
353
  };
303
354
  messages = [...messages, assistantMessage];
304
- if (!result.toolCalls || result.toolCalls.length === 0) break;
355
+ if (toolCalls.length === 0) break;
305
356
  for (const tr of stepToolResults) {
306
357
  const toolMessage = {
307
358
  role: "tool",
@@ -452,7 +503,7 @@ async function streamText(params) {
452
503
  role: "assistant",
453
504
  content: stepText || null,
454
505
  ...toolCalls.length ? { toolCalls } : {},
455
- ...stepProviderMetadata ? { providerMetadata: stepProviderMetadata } : {}
506
+ ...stepProviderMetadata ? { providerMetadata: responseReplayMetadata(stepProviderMetadata) } : {}
456
507
  }
457
508
  ];
458
509
  if (toolCalls.length === 0 || !tools) break;
@@ -784,17 +835,32 @@ function createTextStreamHeaders() {
784
835
  };
785
836
  }
786
837
  function formatSSEData(event) {
787
- return `data: ${JSON.stringify(event)}
838
+ const safeEvent = { ...event };
839
+ if ("providerMetadata" in safeEvent)
840
+ safeEvent.providerMetadata = responseReplayMetadata(
841
+ safeEvent.providerMetadata
842
+ );
843
+ if (safeEvent.type === "done" && safeEvent.messages)
844
+ safeEvent.messages = safeEvent.messages.map((message) => ({
845
+ ...message,
846
+ ...message.providerMetadata ? { providerMetadata: responseReplayMetadata(message.providerMetadata) } : {}
847
+ }));
848
+ return `data: ${JSON.stringify(safeEvent)}
788
849
 
789
850
  `;
790
851
  }
791
- function createEventStream(generator) {
852
+ function createEventStream(generator, options) {
792
853
  const encoder = new TextEncoder();
793
854
  return new ReadableStream({
794
855
  async start(controller) {
795
856
  try {
796
857
  for await (const event of generator) {
797
- const data = formatSSEData(event);
858
+ let clientEvent = event;
859
+ if (event.type === "done" && !options?.includeUsage) {
860
+ const { usage: _usage, ...withoutUsage } = event;
861
+ clientEvent = withoutUsage;
862
+ }
863
+ const data = formatSSEData(clientEvent);
798
864
  controller.enqueue(encoder.encode(data));
799
865
  }
800
866
  } catch (error) {
@@ -935,7 +1001,9 @@ var StreamResult = class {
935
1001
  ...createSSEHeaders(),
936
1002
  ...options?.headers
937
1003
  };
938
- return new Response(createEventStream(this.generator), { headers });
1004
+ return new Response(createEventStream(this.generator, options), {
1005
+ headers
1006
+ });
939
1007
  }
940
1008
  /**
941
1009
  * Alias for toResponse() - returns SSE Response
@@ -2378,7 +2446,10 @@ var Runtime = class {
2378
2446
  yield event;
2379
2447
  break;
2380
2448
  case "message:end":
2381
- yield event;
2449
+ yield {
2450
+ ...event,
2451
+ providerMetadata: responseReplayMetadata(event.providerMetadata)
2452
+ };
2382
2453
  break;
2383
2454
  case "message:delta":
2384
2455
  accumulatedText += event.content;
@@ -2500,7 +2571,9 @@ var Runtime = class {
2500
2571
  return;
2501
2572
  // Exit on error
2502
2573
  case "done":
2503
- adapterProviderMetadata = event.providerMetadata;
2574
+ adapterProviderMetadata = responseReplayMetadata(
2575
+ event.providerMetadata
2576
+ );
2504
2577
  if (event.usage) {
2505
2578
  adapterUsage = event.usage;
2506
2579
  }
@@ -2771,7 +2844,7 @@ var Runtime = class {
2771
2844
  }
2772
2845
  yield {
2773
2846
  type: "message:end",
2774
- providerMetadata: result.providerMetadata
2847
+ providerMetadata: responseReplayMetadata(result.providerMetadata)
2775
2848
  };
2776
2849
  if (result.toolCalls.length > 0) {
2777
2850
  const serverToolCalls = [];
@@ -2872,7 +2945,11 @@ var Runtime = class {
2872
2945
  const assistantMessage = {
2873
2946
  role: "assistant",
2874
2947
  content: result.content || null,
2875
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {},
2948
+ ...result.providerMetadata ? {
2949
+ providerMetadata: responseReplayMetadata(
2950
+ result.providerMetadata
2951
+ )
2952
+ } : {},
2876
2953
  tool_calls: result.toolCalls.map((tc) => ({
2877
2954
  id: tc.id,
2878
2955
  type: "function",
@@ -2902,7 +2979,11 @@ var Runtime = class {
2902
2979
  const assistantWithToolCalls = {
2903
2980
  role: "assistant",
2904
2981
  content: result.content || null,
2905
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {},
2982
+ ...result.providerMetadata ? {
2983
+ providerMetadata: responseReplayMetadata(
2984
+ result.providerMetadata
2985
+ )
2986
+ } : {},
2906
2987
  tool_calls: result.toolCalls.map((tc) => ({
2907
2988
  id: tc.id,
2908
2989
  type: "function",
@@ -2934,7 +3015,11 @@ var Runtime = class {
2934
3015
  const assistantMessage = {
2935
3016
  role: "assistant",
2936
3017
  content: result.content || null,
2937
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {},
3018
+ ...result.providerMetadata ? {
3019
+ providerMetadata: responseReplayMetadata(
3020
+ result.providerMetadata
3021
+ )
3022
+ } : {},
2938
3023
  tool_calls: clientToolCalls.map((tc) => ({
2939
3024
  id: tc.id,
2940
3025
  type: "function",
@@ -2964,7 +3049,11 @@ var Runtime = class {
2964
3049
  newMessages.push({
2965
3050
  role: "assistant",
2966
3051
  content: result.content,
2967
- ...result.providerMetadata ? { providerMetadata: result.providerMetadata } : {}
3052
+ ...result.providerMetadata ? {
3053
+ providerMetadata: responseReplayMetadata(
3054
+ result.providerMetadata
3055
+ )
3056
+ } : {}
2968
3057
  });
2969
3058
  }
2970
3059
  if (debug) {