@yourgpt/llm-sdk 2.5.1-beta.5 → 2.6.0-beta.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/dist/adapters/index.d.mts +51 -0
- package/dist/adapters/index.d.ts +51 -0
- package/dist/adapters/index.js +248 -42
- package/dist/adapters/index.mjs +248 -42
- package/dist/index.js +123 -27
- package/dist/index.mjs +123 -27
- package/dist/providers/azure/index.js +8 -3
- package/dist/providers/azure/index.mjs +8 -3
- package/dist/providers/fireworks/index.js +248 -42
- package/dist/providers/fireworks/index.mjs +248 -42
- package/dist/providers/google/index.js +248 -42
- package/dist/providers/google/index.mjs +248 -42
- package/dist/providers/openai/index.js +248 -42
- package/dist/providers/openai/index.mjs +248 -42
- package/dist/providers/openrouter/index.js +248 -42
- package/dist/providers/openrouter/index.mjs +248 -42
- package/dist/providers/togetherai/index.js +248 -42
- package/dist/providers/togetherai/index.mjs +248 -42
- package/dist/providers/xai/index.js +248 -42
- package/dist/providers/xai/index.mjs +248 -42
- package/package.json +1 -1
|
@@ -33,10 +33,61 @@ declare class OpenAIAdapter implements LLMAdapter {
|
|
|
33
33
|
private static resolveProviderName;
|
|
34
34
|
private getClient;
|
|
35
35
|
private shouldUseResponsesApi;
|
|
36
|
+
/**
|
|
37
|
+
* Whether this model rejects function tools on /v1/chat/completions.
|
|
38
|
+
*
|
|
39
|
+
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
40
|
+
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
41
|
+
* restriction and may not implement /v1/responses at all.
|
|
42
|
+
*/
|
|
43
|
+
private requiresResponsesApiForTools;
|
|
44
|
+
/**
|
|
45
|
+
* Convert one message's content to Responses content parts.
|
|
46
|
+
*
|
|
47
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
48
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
49
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
50
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
51
|
+
* array-shaped has to be translated rather than forwarded.
|
|
52
|
+
*/
|
|
53
|
+
private toResponsesContent;
|
|
36
54
|
private buildResponsesInput;
|
|
55
|
+
/**
|
|
56
|
+
* Build the web_search tool for the Responses API, or undefined when web
|
|
57
|
+
* search is off.
|
|
58
|
+
*
|
|
59
|
+
* The Chat Completions path uses `web_search_preview`; the Responses API
|
|
60
|
+
* names the same built-in `web_search`. Without this, enabling web search
|
|
61
|
+
* alongside a gpt-5.4+ function tool silently did nothing, because routing
|
|
62
|
+
* to /v1/responses skipped the Chat Completions tool assembly entirely.
|
|
63
|
+
*/
|
|
64
|
+
private buildResponsesWebSearchTool;
|
|
65
|
+
/**
|
|
66
|
+
* Extract url_citation annotations from Responses output items.
|
|
67
|
+
*
|
|
68
|
+
* Annotations hang off the output_text content parts rather than arriving as
|
|
69
|
+
* their own stream events, so both the streaming and non-streaming paths read
|
|
70
|
+
* them from the same place.
|
|
71
|
+
*/
|
|
72
|
+
private collectResponsesCitations;
|
|
37
73
|
private buildResponsesTools;
|
|
38
74
|
private parseResponsesResult;
|
|
75
|
+
private buildResponsesPayload;
|
|
39
76
|
private completeWithResponses;
|
|
77
|
+
/**
|
|
78
|
+
* Stream from /v1/responses as real SSE.
|
|
79
|
+
*
|
|
80
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
81
|
+
* deltas, so the interesting ones are:
|
|
82
|
+
* response.output_text.delta — incremental assistant text
|
|
83
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
84
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
85
|
+
* response.completed — terminal, carries usage
|
|
86
|
+
*
|
|
87
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
88
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
89
|
+
*/
|
|
90
|
+
private streamWithResponses;
|
|
40
91
|
stream(request: ChatCompletionRequest): AsyncGenerator<StreamEvent>;
|
|
41
92
|
complete(request: ChatCompletionRequest): Promise<CompletionResult>;
|
|
42
93
|
}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -33,10 +33,61 @@ declare class OpenAIAdapter implements LLMAdapter {
|
|
|
33
33
|
private static resolveProviderName;
|
|
34
34
|
private getClient;
|
|
35
35
|
private shouldUseResponsesApi;
|
|
36
|
+
/**
|
|
37
|
+
* Whether this model rejects function tools on /v1/chat/completions.
|
|
38
|
+
*
|
|
39
|
+
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
40
|
+
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
41
|
+
* restriction and may not implement /v1/responses at all.
|
|
42
|
+
*/
|
|
43
|
+
private requiresResponsesApiForTools;
|
|
44
|
+
/**
|
|
45
|
+
* Convert one message's content to Responses content parts.
|
|
46
|
+
*
|
|
47
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
48
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
49
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
50
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
51
|
+
* array-shaped has to be translated rather than forwarded.
|
|
52
|
+
*/
|
|
53
|
+
private toResponsesContent;
|
|
36
54
|
private buildResponsesInput;
|
|
55
|
+
/**
|
|
56
|
+
* Build the web_search tool for the Responses API, or undefined when web
|
|
57
|
+
* search is off.
|
|
58
|
+
*
|
|
59
|
+
* The Chat Completions path uses `web_search_preview`; the Responses API
|
|
60
|
+
* names the same built-in `web_search`. Without this, enabling web search
|
|
61
|
+
* alongside a gpt-5.4+ function tool silently did nothing, because routing
|
|
62
|
+
* to /v1/responses skipped the Chat Completions tool assembly entirely.
|
|
63
|
+
*/
|
|
64
|
+
private buildResponsesWebSearchTool;
|
|
65
|
+
/**
|
|
66
|
+
* Extract url_citation annotations from Responses output items.
|
|
67
|
+
*
|
|
68
|
+
* Annotations hang off the output_text content parts rather than arriving as
|
|
69
|
+
* their own stream events, so both the streaming and non-streaming paths read
|
|
70
|
+
* them from the same place.
|
|
71
|
+
*/
|
|
72
|
+
private collectResponsesCitations;
|
|
37
73
|
private buildResponsesTools;
|
|
38
74
|
private parseResponsesResult;
|
|
75
|
+
private buildResponsesPayload;
|
|
39
76
|
private completeWithResponses;
|
|
77
|
+
/**
|
|
78
|
+
* Stream from /v1/responses as real SSE.
|
|
79
|
+
*
|
|
80
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
81
|
+
* deltas, so the interesting ones are:
|
|
82
|
+
* response.output_text.delta — incremental assistant text
|
|
83
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
84
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
85
|
+
* response.completed — terminal, carries usage
|
|
86
|
+
*
|
|
87
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
88
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
89
|
+
*/
|
|
90
|
+
private streamWithResponses;
|
|
40
91
|
stream(request: ChatCompletionRequest): AsyncGenerator<StreamEvent>;
|
|
41
92
|
complete(request: ChatCompletionRequest): Promise<CompletionResult>;
|
|
42
93
|
}
|
package/dist/adapters/index.js
CHANGED
|
@@ -104,14 +104,19 @@ function normalizeObjectJsonSchema(schema) {
|
|
|
104
104
|
(value) => typeof value === "string"
|
|
105
105
|
) : [];
|
|
106
106
|
normalized.required = Array.from(/* @__PURE__ */ new Set([...required, ...propertyKeys]));
|
|
107
|
-
|
|
108
|
-
normalized.additionalProperties = false;
|
|
109
|
-
}
|
|
107
|
+
normalized.additionalProperties = false;
|
|
110
108
|
} else if (type === "array" && normalized.items && typeof normalized.items === "object") {
|
|
111
109
|
normalized.items = normalizeObjectJsonSchema(
|
|
112
110
|
normalized.items
|
|
113
111
|
);
|
|
114
112
|
}
|
|
113
|
+
for (const keyword of ["anyOf", "oneOf", "allOf"]) {
|
|
114
|
+
const branches = normalized[keyword];
|
|
115
|
+
if (!Array.isArray(branches)) continue;
|
|
116
|
+
normalized[keyword] = branches.map(
|
|
117
|
+
(branch) => branch && typeof branch === "object" ? normalizeObjectJsonSchema(branch) : branch
|
|
118
|
+
);
|
|
119
|
+
}
|
|
115
120
|
return normalized;
|
|
116
121
|
}
|
|
117
122
|
function isOpenAIReasoningModel(modelId) {
|
|
@@ -507,18 +512,79 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
507
512
|
return this.client;
|
|
508
513
|
}
|
|
509
514
|
shouldUseResponsesApi(request) {
|
|
510
|
-
|
|
515
|
+
const hasTools = Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
|
|
516
|
+
if (!hasTools) return false;
|
|
517
|
+
if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false) {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
return this.requiresResponsesApiForTools(
|
|
521
|
+
request.config?.model || this.model
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Whether this model rejects function tools on /v1/chat/completions.
|
|
526
|
+
*
|
|
527
|
+
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
528
|
+
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
529
|
+
* restriction and may not implement /v1/responses at all.
|
|
530
|
+
*/
|
|
531
|
+
requiresResponsesApiForTools(modelId) {
|
|
532
|
+
if (!modelId) return false;
|
|
533
|
+
if (!/^gpt-5\.(?:[4-9]|\d{2,})/i.test(modelId)) return false;
|
|
534
|
+
const baseUrl = this.config.baseUrl;
|
|
535
|
+
if (!baseUrl) return true;
|
|
536
|
+
try {
|
|
537
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
538
|
+
} catch {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Convert one message's content to Responses content parts.
|
|
544
|
+
*
|
|
545
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
546
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
547
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
548
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
549
|
+
* array-shaped has to be translated rather than forwarded.
|
|
550
|
+
*/
|
|
551
|
+
toResponsesContent(content, role) {
|
|
552
|
+
const textType = role === "assistant" ? "output_text" : "input_text";
|
|
553
|
+
if (typeof content === "string") return content;
|
|
554
|
+
if (!Array.isArray(content)) {
|
|
555
|
+
return content == null ? "" : JSON.stringify(content);
|
|
556
|
+
}
|
|
557
|
+
return content.map((part) => {
|
|
558
|
+
const p = part;
|
|
559
|
+
if (p?.type === "text") return { type: textType, text: p.text ?? "" };
|
|
560
|
+
if (p?.type === "image_url") {
|
|
561
|
+
const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
|
|
562
|
+
return {
|
|
563
|
+
type: "input_image",
|
|
564
|
+
image_url: url,
|
|
565
|
+
...p.image_url?.detail ? { detail: p.image_url.detail } : {}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return p;
|
|
569
|
+
});
|
|
511
570
|
}
|
|
512
571
|
buildResponsesInput(request) {
|
|
513
572
|
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
|
|
514
573
|
const input = [];
|
|
515
574
|
for (const message of sourceMessages) {
|
|
516
575
|
if (message.role === "system") {
|
|
576
|
+
if (!request.systemPrompt && message.content) {
|
|
577
|
+
input.push({
|
|
578
|
+
type: "message",
|
|
579
|
+
role: "developer",
|
|
580
|
+
content: this.toResponsesContent(message.content, "developer")
|
|
581
|
+
});
|
|
582
|
+
}
|
|
517
583
|
continue;
|
|
518
584
|
}
|
|
519
585
|
if (message.role === "assistant") {
|
|
520
|
-
const content =
|
|
521
|
-
if (content) {
|
|
586
|
+
const content = this.toResponsesContent(message.content, "assistant");
|
|
587
|
+
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
522
588
|
input.push({
|
|
523
589
|
type: "message",
|
|
524
590
|
role: "assistant",
|
|
@@ -544,15 +610,67 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
544
610
|
});
|
|
545
611
|
continue;
|
|
546
612
|
}
|
|
613
|
+
const role = message.role === "developer" ? "developer" : "user";
|
|
547
614
|
input.push({
|
|
548
615
|
type: "message",
|
|
549
|
-
role
|
|
550
|
-
content:
|
|
616
|
+
role,
|
|
617
|
+
content: this.toResponsesContent(message.content, role)
|
|
551
618
|
});
|
|
552
619
|
}
|
|
553
620
|
return input;
|
|
554
621
|
}
|
|
555
|
-
|
|
622
|
+
/**
|
|
623
|
+
* Build the web_search tool for the Responses API, or undefined when web
|
|
624
|
+
* search is off.
|
|
625
|
+
*
|
|
626
|
+
* The Chat Completions path uses `web_search_preview`; the Responses API
|
|
627
|
+
* names the same built-in `web_search`. Without this, enabling web search
|
|
628
|
+
* alongside a gpt-5.4+ function tool silently did nothing, because routing
|
|
629
|
+
* to /v1/responses skipped the Chat Completions tool assembly entirely.
|
|
630
|
+
*/
|
|
631
|
+
buildResponsesWebSearchTool(request) {
|
|
632
|
+
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
633
|
+
if (!webSearchConfig) return void 0;
|
|
634
|
+
const tool = { type: "web_search" };
|
|
635
|
+
const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
|
|
636
|
+
if (wsConfig.userLocation) {
|
|
637
|
+
tool.search_context_size = "medium";
|
|
638
|
+
}
|
|
639
|
+
return tool;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Extract url_citation annotations from Responses output items.
|
|
643
|
+
*
|
|
644
|
+
* Annotations hang off the output_text content parts rather than arriving as
|
|
645
|
+
* their own stream events, so both the streaming and non-streaming paths read
|
|
646
|
+
* them from the same place.
|
|
647
|
+
*/
|
|
648
|
+
collectResponsesCitations(output, startIndex = 0) {
|
|
649
|
+
if (!Array.isArray(output)) return [];
|
|
650
|
+
const citations = [];
|
|
651
|
+
let index = startIndex;
|
|
652
|
+
for (const item of output) {
|
|
653
|
+
if (item?.type !== "message" || !Array.isArray(item.content)) continue;
|
|
654
|
+
for (const part of item.content) {
|
|
655
|
+
if (!Array.isArray(part?.annotations)) continue;
|
|
656
|
+
for (const annotation of part.annotations) {
|
|
657
|
+
const url = annotation?.url;
|
|
658
|
+
if (annotation?.type !== "url_citation" || !url) continue;
|
|
659
|
+
index++;
|
|
660
|
+
const domain = extractDomain(url);
|
|
661
|
+
citations.push({
|
|
662
|
+
index,
|
|
663
|
+
url,
|
|
664
|
+
title: annotation.title || domain,
|
|
665
|
+
domain,
|
|
666
|
+
favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return citations;
|
|
672
|
+
}
|
|
673
|
+
buildResponsesTools(tools, includeToolSearch, webSearchTool) {
|
|
556
674
|
const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
|
|
557
675
|
type: "function",
|
|
558
676
|
name: tool.name,
|
|
@@ -567,10 +685,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
567
685
|
strict: true,
|
|
568
686
|
defer_loading: tool.deferLoading === true
|
|
569
687
|
}));
|
|
570
|
-
|
|
688
|
+
const builtins = [];
|
|
689
|
+
if (includeToolSearch) builtins.push({ type: "tool_search" });
|
|
690
|
+
if (webSearchTool) builtins.push(webSearchTool);
|
|
691
|
+
return [...builtins, ...nativeTools];
|
|
571
692
|
}
|
|
572
693
|
parseResponsesResult(response) {
|
|
573
|
-
const
|
|
694
|
+
const textFromOutput = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "message").flatMap(
|
|
695
|
+
(item) => Array.isArray(item.content) ? item.content.filter(
|
|
696
|
+
(part) => part?.type === "output_text" && typeof part.text === "string"
|
|
697
|
+
).map((part) => part.text) : []
|
|
698
|
+
).join("") : "";
|
|
699
|
+
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
574
700
|
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
|
|
575
701
|
id: item.call_id ?? item.id ?? generateToolCallId(),
|
|
576
702
|
name: item.name,
|
|
@@ -593,59 +719,139 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
593
719
|
rawResponse: response
|
|
594
720
|
};
|
|
595
721
|
}
|
|
596
|
-
|
|
597
|
-
const client = await this.getClient();
|
|
722
|
+
buildResponsesPayload(request, stream) {
|
|
598
723
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
599
724
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
600
725
|
request.config?.responseFormat
|
|
601
726
|
);
|
|
727
|
+
const modelIdForPayload = request.config?.model || this.model;
|
|
602
728
|
const payload = {
|
|
603
|
-
model:
|
|
729
|
+
model: modelIdForPayload,
|
|
604
730
|
instructions: request.systemPrompt,
|
|
605
731
|
input: this.buildResponsesInput(request),
|
|
606
|
-
tools: this.buildResponsesTools(
|
|
732
|
+
tools: this.buildResponsesTools(
|
|
733
|
+
request.toolDefinitions ?? [],
|
|
734
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
735
|
+
this.buildResponsesWebSearchTool(request)
|
|
736
|
+
),
|
|
607
737
|
tool_choice: openaiToolOptions?.toolChoice === "required" ? "required" : openaiToolOptions?.toolChoice === "auto" ? "auto" : void 0,
|
|
608
738
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
609
|
-
|
|
739
|
+
// Reasoning models reject `temperature` outright — sending it would swap one
|
|
740
|
+
// 400 for another. isOpenAIReasoningModel already encodes that family test.
|
|
741
|
+
...isOpenAIReasoningModel(modelIdForPayload) ? {} : {
|
|
742
|
+
temperature: request.config?.temperature ?? this.config.temperature
|
|
743
|
+
},
|
|
610
744
|
max_output_tokens: request.config?.maxTokens ?? this.config.maxTokens,
|
|
611
745
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
612
|
-
stream
|
|
746
|
+
stream
|
|
613
747
|
};
|
|
748
|
+
return payload;
|
|
749
|
+
}
|
|
750
|
+
async completeWithResponses(request) {
|
|
751
|
+
const client = await this.getClient();
|
|
752
|
+
const payload = this.buildResponsesPayload(request, false);
|
|
614
753
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
615
754
|
const response = await client.responses.create(payload);
|
|
616
755
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
617
756
|
return this.parseResponsesResult(response);
|
|
618
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* Stream from /v1/responses as real SSE.
|
|
760
|
+
*
|
|
761
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
762
|
+
* deltas, so the interesting ones are:
|
|
763
|
+
* response.output_text.delta — incremental assistant text
|
|
764
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
765
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
766
|
+
* response.completed — terminal, carries usage
|
|
767
|
+
*
|
|
768
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
769
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
770
|
+
*/
|
|
771
|
+
async *streamWithResponses(request) {
|
|
772
|
+
const client = await this.getClient();
|
|
773
|
+
const payload = this.buildResponsesPayload(request, true);
|
|
774
|
+
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
775
|
+
const stream = await client.responses.create(payload);
|
|
776
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
777
|
+
let usage;
|
|
778
|
+
const citations = [];
|
|
779
|
+
function* flushToolCalls() {
|
|
780
|
+
for (const call of toolCalls.values()) {
|
|
781
|
+
yield { type: "action:start", id: call.id, name: call.name };
|
|
782
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
783
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
784
|
+
}
|
|
785
|
+
toolCalls.clear();
|
|
786
|
+
}
|
|
787
|
+
for await (const event of stream) {
|
|
788
|
+
if (request.signal?.aborted) break;
|
|
789
|
+
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
790
|
+
const index = event?.output_index ?? 0;
|
|
791
|
+
switch (event?.type) {
|
|
792
|
+
case "response.output_text.delta":
|
|
793
|
+
if (event.delta) {
|
|
794
|
+
yield { type: "message:delta", content: event.delta };
|
|
795
|
+
}
|
|
796
|
+
break;
|
|
797
|
+
case "response.output_item.added":
|
|
798
|
+
if (event.item?.type === "function_call") {
|
|
799
|
+
toolCalls.set(index, {
|
|
800
|
+
id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
|
|
801
|
+
name: event.item.name ?? "",
|
|
802
|
+
arguments: event.item.arguments ?? ""
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
break;
|
|
806
|
+
case "response.function_call_arguments.delta": {
|
|
807
|
+
const existing = toolCalls.get(index);
|
|
808
|
+
if (existing && event.delta) existing.arguments += event.delta;
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
case "response.function_call_arguments.done": {
|
|
812
|
+
const existing = toolCalls.get(index);
|
|
813
|
+
if (existing && typeof event.arguments === "string") {
|
|
814
|
+
existing.arguments = event.arguments;
|
|
815
|
+
}
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
case "response.completed":
|
|
819
|
+
case "response.incomplete": {
|
|
820
|
+
const found = this.collectResponsesCitations(
|
|
821
|
+
event.response?.output,
|
|
822
|
+
citations.length
|
|
823
|
+
);
|
|
824
|
+
if (found.length > 0) citations.push(...found);
|
|
825
|
+
const u = event.response?.usage;
|
|
826
|
+
if (u) {
|
|
827
|
+
usage = {
|
|
828
|
+
prompt_tokens: u.input_tokens ?? 0,
|
|
829
|
+
completion_tokens: u.output_tokens ?? 0,
|
|
830
|
+
total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
case "error":
|
|
836
|
+
case "response.failed":
|
|
837
|
+
throw new Error(
|
|
838
|
+
event.message ?? event.response?.error?.message ?? "Responses stream failed"
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
yield* flushToolCalls();
|
|
843
|
+
if (citations.length > 0) {
|
|
844
|
+
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
845
|
+
}
|
|
846
|
+
yield { type: "message:end" };
|
|
847
|
+
yield { type: "done", usage };
|
|
848
|
+
}
|
|
619
849
|
async *stream(request) {
|
|
620
850
|
if (this.shouldUseResponsesApi(request)) {
|
|
621
851
|
const messageId2 = generateMessageId();
|
|
622
852
|
yield { type: "message:start", id: messageId2 };
|
|
623
853
|
try {
|
|
624
|
-
|
|
625
|
-
if (result.content) {
|
|
626
|
-
yield { type: "message:delta", content: result.content };
|
|
627
|
-
}
|
|
628
|
-
for (const toolCall of result.toolCalls) {
|
|
629
|
-
yield {
|
|
630
|
-
type: "action:start",
|
|
631
|
-
id: toolCall.id,
|
|
632
|
-
name: toolCall.name
|
|
633
|
-
};
|
|
634
|
-
yield {
|
|
635
|
-
type: "action:args",
|
|
636
|
-
id: toolCall.id,
|
|
637
|
-
args: JSON.stringify(toolCall.args)
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
yield { type: "message:end" };
|
|
641
|
-
yield {
|
|
642
|
-
type: "done",
|
|
643
|
-
usage: result.usage ? {
|
|
644
|
-
prompt_tokens: result.usage.promptTokens,
|
|
645
|
-
completion_tokens: result.usage.completionTokens,
|
|
646
|
-
total_tokens: result.usage.totalTokens
|
|
647
|
-
} : void 0
|
|
648
|
-
};
|
|
854
|
+
yield* this.streamWithResponses(request);
|
|
649
855
|
return;
|
|
650
856
|
} catch (error) {
|
|
651
857
|
yield {
|