@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 +42 -8
- package/dist/adapters/index.js +86 -21
- package/dist/adapters/index.mjs +86 -21
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +108 -19
- package/dist/index.mjs +108 -19
- package/dist/providers/anthropic/index.js +6 -13
- package/dist/providers/anthropic/index.mjs +6 -13
- package/dist/providers/fireworks/index.js +80 -8
- package/dist/providers/fireworks/index.mjs +80 -8
- package/dist/providers/google/index.js +80 -8
- package/dist/providers/google/index.mjs +80 -8
- package/dist/providers/openai/index.js +80 -8
- package/dist/providers/openai/index.mjs +80 -8
- package/dist/providers/openrouter/index.js +80 -8
- package/dist/providers/openrouter/index.mjs +80 -8
- package/dist/providers/togetherai/index.js +80 -8
- package/dist/providers/togetherai/index.mjs +80 -8
- package/dist/providers/xai/index.js +80 -8
- package/dist/providers/xai/index.mjs +80 -8
- package/package.json +1 -1
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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
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.
|
package/dist/adapters/index.js
CHANGED
|
@@ -526,21 +526,14 @@ function messageToOpenAIContent(message) {
|
|
|
526
526
|
return blocks;
|
|
527
527
|
}
|
|
528
528
|
function extractAnthropicSystemPrompt(messages, systemPrompt) {
|
|
529
|
-
const
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
|
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,
|
|
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
|
|
1030
|
+
throw invalidProviderResponse(
|
|
1031
|
+
"Responses contains an incomplete function call"
|
|
1032
|
+
);
|
|
983
1033
|
}
|
|
984
1034
|
if (seenCallIds.has(item.call_id))
|
|
985
|
-
throw
|
|
1035
|
+
throw invalidProviderResponse(
|
|
1036
|
+
"Responses contains duplicate function call IDs"
|
|
1037
|
+
);
|
|
986
1038
|
seenCallIds.add(item.call_id);
|
|
987
|
-
|
|
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
|
|
1048
|
+
throw invalidProviderResponse(
|
|
990
1049
|
"Responses function arguments must be a JSON object"
|
|
991
1050
|
);
|
|
992
|
-
return {
|
|
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
|
|
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();
|
package/dist/adapters/index.mjs
CHANGED
|
@@ -524,21 +524,14 @@ function messageToOpenAIContent(message) {
|
|
|
524
524
|
return blocks;
|
|
525
525
|
}
|
|
526
526
|
function extractAnthropicSystemPrompt(messages, systemPrompt) {
|
|
527
|
-
const
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
|
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,
|
|
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
|
|
1028
|
+
throw invalidProviderResponse(
|
|
1029
|
+
"Responses contains an incomplete function call"
|
|
1030
|
+
);
|
|
981
1031
|
}
|
|
982
1032
|
if (seenCallIds.has(item.call_id))
|
|
983
|
-
throw
|
|
1033
|
+
throw invalidProviderResponse(
|
|
1034
|
+
"Responses contains duplicate function call IDs"
|
|
1035
|
+
);
|
|
984
1036
|
seenCallIds.add(item.call_id);
|
|
985
|
-
|
|
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
|
|
1046
|
+
throw invalidProviderResponse(
|
|
988
1047
|
"Responses function arguments must be a JSON object"
|
|
989
1048
|
);
|
|
990
|
-
return {
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
245
|
-
allToolCalls.push(...
|
|
246
|
-
for (const call of
|
|
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
|
|
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
|
-
...
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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), {
|
|
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
|
|
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 =
|
|
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 ? {
|
|
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 ? {
|
|
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 ? {
|
|
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 ? {
|
|
3052
|
+
...result.providerMetadata ? {
|
|
3053
|
+
providerMetadata: responseReplayMetadata(
|
|
3054
|
+
result.providerMetadata
|
|
3055
|
+
)
|
|
3056
|
+
} : {}
|
|
2968
3057
|
});
|
|
2969
3058
|
}
|
|
2970
3059
|
if (debug) {
|