@yourgpt/llm-sdk 2.5.1-beta.6 → 2.6.0-beta.1
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 +77 -0
- package/dist/adapters/index.d.mts +54 -5
- package/dist/adapters/index.d.ts +54 -5
- package/dist/adapters/index.js +466 -169
- package/dist/adapters/index.mjs +466 -169
- package/dist/{base-C58Dsr9p.d.ts → base-BdTpUAWI.d.ts} +2 -1
- package/dist/{base-tNgbBaSo.d.mts → base-CkeT7u-O.d.mts} +2 -1
- package/dist/fallback/index.d.mts +4 -4
- package/dist/fallback/index.d.ts +4 -4
- package/dist/fallback/index.js +75 -39
- package/dist/fallback/index.mjs +75 -39
- package/dist/index.d.mts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +89 -30
- package/dist/index.mjs +89 -30
- package/dist/providers/anthropic/index.d.mts +3 -3
- package/dist/providers/anthropic/index.d.ts +3 -3
- package/dist/providers/azure/index.d.mts +3 -3
- package/dist/providers/azure/index.d.ts +3 -3
- package/dist/providers/azure/index.js +8 -3
- package/dist/providers/azure/index.mjs +8 -3
- package/dist/providers/fireworks/index.d.mts +3 -3
- package/dist/providers/fireworks/index.d.ts +3 -3
- package/dist/providers/fireworks/index.js +466 -169
- package/dist/providers/fireworks/index.mjs +466 -169
- package/dist/providers/google/index.d.mts +3 -3
- package/dist/providers/google/index.d.ts +3 -3
- package/dist/providers/google/index.js +466 -169
- package/dist/providers/google/index.mjs +466 -169
- package/dist/providers/ollama/index.d.mts +4 -4
- package/dist/providers/ollama/index.d.ts +4 -4
- package/dist/providers/openai/index.d.mts +6 -23
- package/dist/providers/openai/index.d.ts +6 -23
- package/dist/providers/openai/index.js +912 -500
- package/dist/providers/openai/index.mjs +912 -500
- package/dist/providers/openrouter/index.d.mts +3 -3
- package/dist/providers/openrouter/index.d.ts +3 -3
- package/dist/providers/openrouter/index.js +466 -169
- package/dist/providers/openrouter/index.mjs +466 -169
- package/dist/providers/togetherai/index.d.mts +3 -3
- package/dist/providers/togetherai/index.d.ts +3 -3
- package/dist/providers/togetherai/index.js +466 -169
- package/dist/providers/togetherai/index.mjs +466 -169
- package/dist/providers/xai/index.d.mts +3 -3
- package/dist/providers/xai/index.d.ts +3 -3
- package/dist/providers/xai/index.js +466 -169
- package/dist/providers/xai/index.mjs +466 -169
- package/dist/{types-BkQCSiIt.d.mts → types-BW3csYo5.d.mts} +22 -1
- package/dist/{types-BkQCSiIt.d.ts → types-BW3csYo5.d.ts} +22 -1
- package/dist/{types-B6dhnguR.d.mts → types-BWRYIvl_.d.mts} +11 -4
- package/dist/{types-CCxPmkmK.d.ts → types-CpXcT-tY.d.ts} +11 -4
- package/dist/{types-BQ31QIsA.d.ts → types-DA54bSmX.d.ts} +3 -11
- package/dist/{types-BSSiJW2o.d.mts → types-NOswlhm9.d.mts} +3 -11
- package/dist/yourgpt/index.d.mts +1 -1
- package/dist/yourgpt/index.d.ts +1 -1
- package/package.json +115 -48
- package/LICENSE +0 -21
package/dist/adapters/index.mjs
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
// src/core/openai-parameters.ts
|
|
2
|
+
function capabilities(model) {
|
|
3
|
+
const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
|
|
4
|
+
const major = Number(gpt?.[1] ?? 0);
|
|
5
|
+
const minor = Number(gpt?.[2] ?? 0);
|
|
6
|
+
const chat = gpt?.[3]?.startsWith("chat") ?? false;
|
|
7
|
+
const reasoning = /^o\d+(?:-|$)/i.test(model) || major >= 5 && !chat;
|
|
8
|
+
const supportsNone = major === 5 && minor >= 1 && !chat || /^gpt-6-(?:sol|luna)(?:-\d{4}-\d{2}-\d{2})?$/i.test(model);
|
|
9
|
+
return { major, minor, reasoning, supportsNone };
|
|
10
|
+
}
|
|
11
|
+
function requiresOpenAIResponses(model, baseUrl, options = {}) {
|
|
12
|
+
if (options.parameterPolicy === "passthrough") return false;
|
|
13
|
+
const c = capabilities(model);
|
|
14
|
+
if (!c.reasoning || options.reasoningEffort === "none") return false;
|
|
15
|
+
if (!(c.major >= 6 || c.major === 5 && c.minor >= 4)) return false;
|
|
16
|
+
if (!baseUrl) return true;
|
|
17
|
+
try {
|
|
18
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function buildOpenAIParameters(model, maxTokens, temperature, options = {}, api = "chat") {
|
|
24
|
+
const c = capabilities(model ?? "");
|
|
25
|
+
const normalize = options.parameterPolicy !== "passthrough";
|
|
26
|
+
const effort = options.reasoningEffort;
|
|
27
|
+
if (normalize && c.reasoning && effort === "none" && !c.supportsNone) {
|
|
28
|
+
throw new Error(`${model} does not support reasoningEffort: none`);
|
|
29
|
+
}
|
|
30
|
+
if (normalize && c.major >= 6 && effort === "minimal") {
|
|
31
|
+
throw new Error(`${model} does not support reasoningEffort: minimal`);
|
|
32
|
+
}
|
|
33
|
+
const result = {
|
|
34
|
+
[api === "responses" ? "max_output_tokens" : normalize && c.reasoning ? "max_completion_tokens" : "max_tokens"]: maxTokens
|
|
35
|
+
};
|
|
36
|
+
if (effort !== void 0) {
|
|
37
|
+
if (api === "responses") result.reasoning = { effort };
|
|
38
|
+
else result.reasoning_effort = effort;
|
|
39
|
+
}
|
|
40
|
+
if (temperature !== void 0) {
|
|
41
|
+
if (!normalize || !c.reasoning || effort === "none" && c.supportsNone) {
|
|
42
|
+
result.temperature = temperature;
|
|
43
|
+
} else {
|
|
44
|
+
(options.onWarning ?? console.warn)(
|
|
45
|
+
`[llm-sdk] ${model}: omitted temperature because reasoning is enabled.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
1
52
|
// src/adapters/base.ts
|
|
2
53
|
function stringifyForDebug(value) {
|
|
3
54
|
return JSON.stringify(
|
|
@@ -102,25 +153,20 @@ function normalizeObjectJsonSchema(schema) {
|
|
|
102
153
|
(value) => typeof value === "string"
|
|
103
154
|
) : [];
|
|
104
155
|
normalized.required = Array.from(/* @__PURE__ */ new Set([...required, ...propertyKeys]));
|
|
105
|
-
|
|
106
|
-
normalized.additionalProperties = false;
|
|
107
|
-
}
|
|
156
|
+
normalized.additionalProperties = false;
|
|
108
157
|
} else if (type === "array" && normalized.items && typeof normalized.items === "object") {
|
|
109
158
|
normalized.items = normalizeObjectJsonSchema(
|
|
110
159
|
normalized.items
|
|
111
160
|
);
|
|
112
161
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
function buildOpenAITokenParams(modelId, maxTokens, temperature) {
|
|
120
|
-
if (isOpenAIReasoningModel(modelId)) {
|
|
121
|
-
return { max_completion_tokens: maxTokens };
|
|
162
|
+
for (const keyword of ["anyOf", "oneOf", "allOf"]) {
|
|
163
|
+
const branches = normalized[keyword];
|
|
164
|
+
if (!Array.isArray(branches)) continue;
|
|
165
|
+
normalized[keyword] = branches.map(
|
|
166
|
+
(branch) => branch && typeof branch === "object" ? normalizeObjectJsonSchema(branch) : branch
|
|
167
|
+
);
|
|
122
168
|
}
|
|
123
|
-
return
|
|
169
|
+
return normalized;
|
|
124
170
|
}
|
|
125
171
|
function stripSchemaKeys(schema, keysToDrop, options = {}) {
|
|
126
172
|
if (Array.isArray(schema)) {
|
|
@@ -468,6 +514,28 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
468
514
|
return formatted;
|
|
469
515
|
}
|
|
470
516
|
|
|
517
|
+
// src/core/provider-error.ts
|
|
518
|
+
function providerErrorEvent(error, code) {
|
|
519
|
+
const details = error ?? {};
|
|
520
|
+
const event = {
|
|
521
|
+
type: "error",
|
|
522
|
+
message: error instanceof Error ? error.message : String(error),
|
|
523
|
+
code,
|
|
524
|
+
...typeof details.status === "number" ? { status: details.status } : {},
|
|
525
|
+
...typeof details.code === "string" ? { providerCode: details.code } : {},
|
|
526
|
+
...typeof details.param === "string" ? { param: details.param } : {},
|
|
527
|
+
...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
|
|
528
|
+
};
|
|
529
|
+
Object.defineProperty(event, "cause", { value: error, enumerable: false });
|
|
530
|
+
return event;
|
|
531
|
+
}
|
|
532
|
+
function throwIfAborted(signal) {
|
|
533
|
+
if (!signal?.aborted) return;
|
|
534
|
+
const error = new Error("The operation was aborted");
|
|
535
|
+
error.name = "AbortError";
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
|
|
471
539
|
// src/core/utils.ts
|
|
472
540
|
function generateId(prefix = "id") {
|
|
473
541
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -499,24 +567,84 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
499
567
|
const { default: OpenAI } = await import('openai');
|
|
500
568
|
this.client = new OpenAI({
|
|
501
569
|
apiKey: this.config.apiKey,
|
|
502
|
-
baseURL: this.config.baseUrl
|
|
570
|
+
baseURL: this.config.baseUrl,
|
|
571
|
+
organization: this.config.organization,
|
|
572
|
+
defaultHeaders: this.config.headers
|
|
503
573
|
});
|
|
504
574
|
}
|
|
505
575
|
return this.client;
|
|
506
576
|
}
|
|
577
|
+
parameterOptions(request) {
|
|
578
|
+
return {
|
|
579
|
+
...this.config,
|
|
580
|
+
reasoningEffort: request.config?.reasoningEffort ?? this.config.reasoningEffort
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
toolDefinitions(request) {
|
|
584
|
+
if (request.toolDefinitions?.length) return request.toolDefinitions;
|
|
585
|
+
return formatTools(request.actions ?? []).map((tool) => ({
|
|
586
|
+
name: tool.function.name,
|
|
587
|
+
description: tool.function.description ?? "",
|
|
588
|
+
location: "server",
|
|
589
|
+
inputSchema: tool.function.parameters
|
|
590
|
+
}));
|
|
591
|
+
}
|
|
507
592
|
shouldUseResponsesApi(request) {
|
|
508
|
-
|
|
593
|
+
if (this.toolDefinitions(request).length === 0) return false;
|
|
594
|
+
if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
|
|
595
|
+
return true;
|
|
596
|
+
return requiresOpenAIResponses(
|
|
597
|
+
request.config?.model || this.model,
|
|
598
|
+
this.config.baseUrl,
|
|
599
|
+
this.parameterOptions(request)
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Convert one message's content to Responses content parts.
|
|
604
|
+
*
|
|
605
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
606
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
607
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
608
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
609
|
+
* array-shaped has to be translated rather than forwarded.
|
|
610
|
+
*/
|
|
611
|
+
toResponsesContent(content, role) {
|
|
612
|
+
const textType = role === "assistant" ? "output_text" : "input_text";
|
|
613
|
+
if (typeof content === "string") return content;
|
|
614
|
+
if (!Array.isArray(content)) {
|
|
615
|
+
return content == null ? "" : JSON.stringify(content);
|
|
616
|
+
}
|
|
617
|
+
return content.map((part) => {
|
|
618
|
+
const p = part;
|
|
619
|
+
if (p?.type === "text") return { type: textType, text: p.text ?? "" };
|
|
620
|
+
if (p?.type === "image_url") {
|
|
621
|
+
const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
|
|
622
|
+
return {
|
|
623
|
+
type: "input_image",
|
|
624
|
+
image_url: url,
|
|
625
|
+
...p.image_url?.detail ? { detail: p.image_url.detail } : {}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
return p;
|
|
629
|
+
});
|
|
509
630
|
}
|
|
510
631
|
buildResponsesInput(request) {
|
|
511
|
-
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
|
|
632
|
+
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
|
|
512
633
|
const input = [];
|
|
513
634
|
for (const message of sourceMessages) {
|
|
514
635
|
if (message.role === "system") {
|
|
636
|
+
if (!request.systemPrompt && message.content) {
|
|
637
|
+
input.push({
|
|
638
|
+
type: "message",
|
|
639
|
+
role: "developer",
|
|
640
|
+
content: this.toResponsesContent(message.content, "developer")
|
|
641
|
+
});
|
|
642
|
+
}
|
|
515
643
|
continue;
|
|
516
644
|
}
|
|
517
645
|
if (message.role === "assistant") {
|
|
518
|
-
const content =
|
|
519
|
-
if (content) {
|
|
646
|
+
const content = this.toResponsesContent(message.content, "assistant");
|
|
647
|
+
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
520
648
|
input.push({
|
|
521
649
|
type: "message",
|
|
522
650
|
role: "assistant",
|
|
@@ -542,15 +670,87 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
542
670
|
});
|
|
543
671
|
continue;
|
|
544
672
|
}
|
|
673
|
+
const role = message.role === "developer" ? "developer" : "user";
|
|
545
674
|
input.push({
|
|
546
675
|
type: "message",
|
|
547
|
-
role
|
|
548
|
-
content:
|
|
676
|
+
role,
|
|
677
|
+
content: this.toResponsesContent(message.content, role)
|
|
549
678
|
});
|
|
550
679
|
}
|
|
551
680
|
return input;
|
|
552
681
|
}
|
|
553
|
-
|
|
682
|
+
/** Preserve Copilot screenshot attachments on every OpenAI transport. */
|
|
683
|
+
formatRawMessages(messages) {
|
|
684
|
+
return messages.map((message) => {
|
|
685
|
+
const { attachments, ...rest } = message;
|
|
686
|
+
let content = message.content;
|
|
687
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
|
|
688
|
+
content = null;
|
|
689
|
+
}
|
|
690
|
+
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
691
|
+
const images = attachments.filter((attachment) => attachment && typeof attachment === "object").map(
|
|
692
|
+
(attachment) => attachmentToOpenAIImage(attachment)
|
|
693
|
+
).filter((image) => image !== null);
|
|
694
|
+
if (images.length > 0) {
|
|
695
|
+
const parts = Array.isArray(content) ? content : content ? [{ type: "text", text: content }] : [];
|
|
696
|
+
content = [...parts, ...images];
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return { ...rest, content };
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Build the web_search tool for the Responses API, or undefined when web
|
|
704
|
+
* search is off.
|
|
705
|
+
*
|
|
706
|
+
* The Chat Completions path uses `web_search_preview`; the Responses API
|
|
707
|
+
* names the same built-in `web_search`. Without this, enabling web search
|
|
708
|
+
* alongside a gpt-5.4+ function tool silently did nothing, because routing
|
|
709
|
+
* to /v1/responses skipped the Chat Completions tool assembly entirely.
|
|
710
|
+
*/
|
|
711
|
+
buildResponsesWebSearchTool(request) {
|
|
712
|
+
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
713
|
+
if (!webSearchConfig) return void 0;
|
|
714
|
+
const tool = { type: "web_search" };
|
|
715
|
+
const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
|
|
716
|
+
if (wsConfig.userLocation) {
|
|
717
|
+
tool.search_context_size = "medium";
|
|
718
|
+
}
|
|
719
|
+
return tool;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Extract url_citation annotations from Responses output items.
|
|
723
|
+
*
|
|
724
|
+
* Annotations hang off the output_text content parts rather than arriving as
|
|
725
|
+
* their own stream events, so both the streaming and non-streaming paths read
|
|
726
|
+
* them from the same place.
|
|
727
|
+
*/
|
|
728
|
+
collectResponsesCitations(output, startIndex = 0) {
|
|
729
|
+
if (!Array.isArray(output)) return [];
|
|
730
|
+
const citations = [];
|
|
731
|
+
let index = startIndex;
|
|
732
|
+
for (const item of output) {
|
|
733
|
+
if (item?.type !== "message" || !Array.isArray(item.content)) continue;
|
|
734
|
+
for (const part of item.content) {
|
|
735
|
+
if (!Array.isArray(part?.annotations)) continue;
|
|
736
|
+
for (const annotation of part.annotations) {
|
|
737
|
+
const url = annotation?.url;
|
|
738
|
+
if (annotation?.type !== "url_citation" || !url) continue;
|
|
739
|
+
index++;
|
|
740
|
+
const domain = extractDomain(url);
|
|
741
|
+
citations.push({
|
|
742
|
+
index,
|
|
743
|
+
url,
|
|
744
|
+
title: annotation.title || domain,
|
|
745
|
+
domain,
|
|
746
|
+
favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return citations;
|
|
752
|
+
}
|
|
753
|
+
buildResponsesTools(tools, includeToolSearch, webSearchTool) {
|
|
554
754
|
const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
|
|
555
755
|
type: "function",
|
|
556
756
|
name: tool.name,
|
|
@@ -565,20 +765,22 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
565
765
|
strict: true,
|
|
566
766
|
defer_loading: tool.deferLoading === true
|
|
567
767
|
}));
|
|
568
|
-
|
|
768
|
+
const builtins = [];
|
|
769
|
+
if (includeToolSearch) builtins.push({ type: "tool_search" });
|
|
770
|
+
if (webSearchTool) builtins.push(webSearchTool);
|
|
771
|
+
return [...builtins, ...nativeTools];
|
|
569
772
|
}
|
|
570
773
|
parseResponsesResult(response) {
|
|
571
|
-
const
|
|
774
|
+
const textFromOutput = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "message").flatMap(
|
|
775
|
+
(item) => Array.isArray(item.content) ? item.content.filter(
|
|
776
|
+
(part) => part?.type === "output_text" && typeof part.text === "string"
|
|
777
|
+
).map((part) => part.text) : []
|
|
778
|
+
).join("") : "";
|
|
779
|
+
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
572
780
|
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
|
|
573
781
|
id: item.call_id ?? item.id ?? generateToolCallId(),
|
|
574
782
|
name: item.name,
|
|
575
|
-
args: (
|
|
576
|
-
try {
|
|
577
|
-
return JSON.parse(item.arguments ?? "{}");
|
|
578
|
-
} catch {
|
|
579
|
-
return {};
|
|
580
|
-
}
|
|
581
|
-
})()
|
|
783
|
+
args: JSON.parse(item.arguments ?? "{}")
|
|
582
784
|
})) : [];
|
|
583
785
|
return {
|
|
584
786
|
content,
|
|
@@ -591,102 +793,194 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
591
793
|
rawResponse: response
|
|
592
794
|
};
|
|
593
795
|
}
|
|
594
|
-
|
|
595
|
-
const client = await this.getClient();
|
|
796
|
+
buildResponsesPayload(request, stream) {
|
|
596
797
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
597
798
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
598
799
|
request.config?.responseFormat
|
|
599
800
|
);
|
|
801
|
+
const modelIdForPayload = request.config?.model || this.model;
|
|
600
802
|
const payload = {
|
|
601
|
-
model:
|
|
803
|
+
model: modelIdForPayload,
|
|
602
804
|
instructions: request.systemPrompt,
|
|
603
805
|
input: this.buildResponsesInput(request),
|
|
604
|
-
tools: this.buildResponsesTools(
|
|
605
|
-
|
|
806
|
+
tools: this.buildResponsesTools(
|
|
807
|
+
this.toolDefinitions(request),
|
|
808
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
809
|
+
this.buildResponsesWebSearchTool(request)
|
|
810
|
+
),
|
|
811
|
+
tool_choice: openaiToolOptions?.toolChoice,
|
|
606
812
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
607
|
-
|
|
608
|
-
|
|
813
|
+
...buildOpenAIParameters(
|
|
814
|
+
modelIdForPayload,
|
|
815
|
+
request.config?.maxTokens ?? this.config.maxTokens,
|
|
816
|
+
request.config?.temperature ?? this.config.temperature,
|
|
817
|
+
this.parameterOptions(request),
|
|
818
|
+
"responses"
|
|
819
|
+
),
|
|
609
820
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
610
|
-
stream
|
|
821
|
+
stream
|
|
611
822
|
};
|
|
823
|
+
return payload;
|
|
824
|
+
}
|
|
825
|
+
async completeWithResponses(request) {
|
|
826
|
+
const client = await this.getClient();
|
|
827
|
+
throwIfAborted(request.signal);
|
|
828
|
+
const payload = this.buildResponsesPayload(request, false);
|
|
612
829
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
613
|
-
|
|
830
|
+
if (!client.responses?.create)
|
|
831
|
+
throw new Error(
|
|
832
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
833
|
+
);
|
|
834
|
+
const response = await client.responses.create(payload, {
|
|
835
|
+
signal: request.signal
|
|
836
|
+
});
|
|
837
|
+
throwIfAborted(request.signal);
|
|
614
838
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
839
|
+
if (response.status && response.status !== "completed") {
|
|
840
|
+
throw Object.assign(
|
|
841
|
+
new Error(
|
|
842
|
+
response.error?.message ?? `Responses request ${response.status}`
|
|
843
|
+
),
|
|
844
|
+
response.error
|
|
845
|
+
);
|
|
846
|
+
}
|
|
615
847
|
return this.parseResponsesResult(response);
|
|
616
848
|
}
|
|
849
|
+
/**
|
|
850
|
+
* Stream from /v1/responses as real SSE.
|
|
851
|
+
*
|
|
852
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
853
|
+
* deltas, so the interesting ones are:
|
|
854
|
+
* response.output_text.delta — incremental assistant text
|
|
855
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
856
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
857
|
+
* response.completed — terminal, carries usage
|
|
858
|
+
*
|
|
859
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
860
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
861
|
+
*/
|
|
862
|
+
async *streamWithResponses(request) {
|
|
863
|
+
const client = await this.getClient();
|
|
864
|
+
throwIfAborted(request.signal);
|
|
865
|
+
const payload = this.buildResponsesPayload(request, true);
|
|
866
|
+
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
867
|
+
if (!client.responses?.create)
|
|
868
|
+
throw new Error(
|
|
869
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
870
|
+
);
|
|
871
|
+
const stream = await client.responses.create(payload, {
|
|
872
|
+
signal: request.signal
|
|
873
|
+
});
|
|
874
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
875
|
+
let usage;
|
|
876
|
+
const citations = [];
|
|
877
|
+
let completed = false;
|
|
878
|
+
function* flushToolCalls() {
|
|
879
|
+
for (const call of toolCalls.values()) {
|
|
880
|
+
if (!call.name)
|
|
881
|
+
throw new Error("Responses tool call is missing its name");
|
|
882
|
+
JSON.parse(call.arguments || "{}");
|
|
883
|
+
}
|
|
884
|
+
for (const call of toolCalls.values()) {
|
|
885
|
+
yield { type: "action:start", id: call.id, name: call.name };
|
|
886
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
887
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
888
|
+
}
|
|
889
|
+
toolCalls.clear();
|
|
890
|
+
}
|
|
891
|
+
for await (const event of stream) {
|
|
892
|
+
throwIfAborted(request.signal);
|
|
893
|
+
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
894
|
+
const index = event?.output_index ?? 0;
|
|
895
|
+
switch (event?.type) {
|
|
896
|
+
case "response.output_text.delta":
|
|
897
|
+
if (event.delta) {
|
|
898
|
+
yield { type: "message:delta", content: event.delta };
|
|
899
|
+
}
|
|
900
|
+
break;
|
|
901
|
+
case "response.output_item.added":
|
|
902
|
+
if (event.item?.type === "function_call") {
|
|
903
|
+
toolCalls.set(index, {
|
|
904
|
+
id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
|
|
905
|
+
name: event.item.name ?? "",
|
|
906
|
+
arguments: event.item.arguments ?? ""
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
break;
|
|
910
|
+
case "response.function_call_arguments.delta": {
|
|
911
|
+
const existing = toolCalls.get(index);
|
|
912
|
+
if (existing && event.delta) existing.arguments += event.delta;
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
case "response.function_call_arguments.done": {
|
|
916
|
+
const existing = toolCalls.get(index);
|
|
917
|
+
if (existing && typeof event.arguments === "string") {
|
|
918
|
+
existing.arguments = event.arguments;
|
|
919
|
+
}
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
case "response.completed": {
|
|
923
|
+
completed = true;
|
|
924
|
+
const found = this.collectResponsesCitations(
|
|
925
|
+
event.response?.output,
|
|
926
|
+
citations.length
|
|
927
|
+
);
|
|
928
|
+
if (found.length > 0) citations.push(...found);
|
|
929
|
+
const u = event.response?.usage;
|
|
930
|
+
if (u) {
|
|
931
|
+
usage = {
|
|
932
|
+
prompt_tokens: u.input_tokens ?? 0,
|
|
933
|
+
completion_tokens: u.output_tokens ?? 0,
|
|
934
|
+
total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
case "response.incomplete":
|
|
940
|
+
throw new Error(
|
|
941
|
+
`Responses stream incomplete: ${event.response?.incomplete_details?.reason ?? "unknown reason"}`
|
|
942
|
+
);
|
|
943
|
+
case "error":
|
|
944
|
+
case "response.failed": {
|
|
945
|
+
const details = event.response?.error ?? event;
|
|
946
|
+
throw Object.assign(
|
|
947
|
+
new Error(details.message ?? "Responses stream failed"),
|
|
948
|
+
{
|
|
949
|
+
code: details.code,
|
|
950
|
+
param: details.param,
|
|
951
|
+
status: details.status
|
|
952
|
+
}
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
throwIfAborted(request.signal);
|
|
958
|
+
if (!completed)
|
|
959
|
+
throw new Error("Responses stream ended without a completed response");
|
|
960
|
+
yield* flushToolCalls();
|
|
961
|
+
if (citations.length > 0) {
|
|
962
|
+
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
963
|
+
}
|
|
964
|
+
yield { type: "message:end" };
|
|
965
|
+
yield { type: "done", usage };
|
|
966
|
+
}
|
|
617
967
|
async *stream(request) {
|
|
968
|
+
throwIfAborted(request.signal);
|
|
618
969
|
if (this.shouldUseResponsesApi(request)) {
|
|
619
970
|
const messageId2 = generateMessageId();
|
|
620
971
|
yield { type: "message:start", id: messageId2 };
|
|
621
972
|
try {
|
|
622
|
-
|
|
623
|
-
if (result.content) {
|
|
624
|
-
yield { type: "message:delta", content: result.content };
|
|
625
|
-
}
|
|
626
|
-
for (const toolCall of result.toolCalls) {
|
|
627
|
-
yield {
|
|
628
|
-
type: "action:start",
|
|
629
|
-
id: toolCall.id,
|
|
630
|
-
name: toolCall.name
|
|
631
|
-
};
|
|
632
|
-
yield {
|
|
633
|
-
type: "action:args",
|
|
634
|
-
id: toolCall.id,
|
|
635
|
-
args: JSON.stringify(toolCall.args)
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
yield { type: "message:end" };
|
|
639
|
-
yield {
|
|
640
|
-
type: "done",
|
|
641
|
-
usage: result.usage ? {
|
|
642
|
-
prompt_tokens: result.usage.promptTokens,
|
|
643
|
-
completion_tokens: result.usage.completionTokens,
|
|
644
|
-
total_tokens: result.usage.totalTokens
|
|
645
|
-
} : void 0
|
|
646
|
-
};
|
|
973
|
+
yield* this.streamWithResponses(request);
|
|
647
974
|
return;
|
|
648
975
|
} catch (error) {
|
|
649
|
-
yield
|
|
650
|
-
type: "error",
|
|
651
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
652
|
-
code: "OPENAI_RESPONSES_ERROR"
|
|
653
|
-
};
|
|
976
|
+
yield providerErrorEvent(error, "OPENAI_RESPONSES_ERROR");
|
|
654
977
|
return;
|
|
655
978
|
}
|
|
656
979
|
}
|
|
657
980
|
const client = await this.getClient();
|
|
658
981
|
let messages;
|
|
659
982
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
660
|
-
const processedMessages = request.rawMessages
|
|
661
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
662
|
-
return { ...msg, content: null };
|
|
663
|
-
}
|
|
664
|
-
const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
|
|
665
|
-
if (hasAttachments) {
|
|
666
|
-
const content = [];
|
|
667
|
-
if (msg.content) {
|
|
668
|
-
content.push({ type: "text", text: msg.content });
|
|
669
|
-
}
|
|
670
|
-
for (const attachment of msg.attachments) {
|
|
671
|
-
if (attachment.type === "image") {
|
|
672
|
-
let imageUrl;
|
|
673
|
-
if (attachment.url) {
|
|
674
|
-
imageUrl = attachment.url;
|
|
675
|
-
} else if (attachment.data) {
|
|
676
|
-
imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
|
|
677
|
-
} else {
|
|
678
|
-
continue;
|
|
679
|
-
}
|
|
680
|
-
content.push({
|
|
681
|
-
type: "image_url",
|
|
682
|
-
image_url: { url: imageUrl, detail: "auto" }
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
return { ...msg, content, attachments: void 0 };
|
|
687
|
-
}
|
|
688
|
-
return msg;
|
|
689
|
-
});
|
|
983
|
+
const processedMessages = this.formatRawMessages(request.rawMessages);
|
|
690
984
|
if (request.systemPrompt) {
|
|
691
985
|
const hasSystem = processedMessages.some((m) => m.role === "system");
|
|
692
986
|
if (!hasSystem) {
|
|
@@ -706,7 +1000,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
706
1000
|
request.systemPrompt
|
|
707
1001
|
);
|
|
708
1002
|
}
|
|
709
|
-
const tools = request.
|
|
1003
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1004
|
+
type: "function",
|
|
1005
|
+
function: {
|
|
1006
|
+
name: tool.name,
|
|
1007
|
+
description: tool.description,
|
|
1008
|
+
parameters: normalizeObjectJsonSchema(
|
|
1009
|
+
tool.inputSchema
|
|
1010
|
+
)
|
|
1011
|
+
}
|
|
1012
|
+
})) : formatTools(request.actions ?? []);
|
|
710
1013
|
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
711
1014
|
if (webSearchConfig) {
|
|
712
1015
|
const webSearchTool = {
|
|
@@ -735,26 +1038,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
735
1038
|
tools: tools.length > 0 ? tools : void 0,
|
|
736
1039
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
737
1040
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
738
|
-
...
|
|
1041
|
+
...buildOpenAIParameters(
|
|
739
1042
|
modelIdForPayload,
|
|
740
1043
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
741
|
-
request.config?.temperature ?? this.config.temperature
|
|
1044
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1045
|
+
this.parameterOptions(request)
|
|
742
1046
|
),
|
|
743
1047
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
744
1048
|
stream: true,
|
|
745
1049
|
stream_options: { include_usage: true }
|
|
746
1050
|
};
|
|
747
1051
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
748
|
-
const stream = await client.chat.completions.create(payload
|
|
749
|
-
|
|
1052
|
+
const stream = await client.chat.completions.create(payload, {
|
|
1053
|
+
signal: request.signal
|
|
1054
|
+
});
|
|
1055
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1056
|
+
let finishReason;
|
|
750
1057
|
const collectedCitations = [];
|
|
751
1058
|
let citationIndex = 0;
|
|
752
1059
|
let usage;
|
|
753
1060
|
for await (const chunk of stream) {
|
|
754
1061
|
logProviderPayload("openai", "stream chunk", chunk, request.debug);
|
|
755
|
-
|
|
756
|
-
break;
|
|
757
|
-
}
|
|
1062
|
+
throwIfAborted(request.signal);
|
|
758
1063
|
const delta = chunk.choices[0]?.delta;
|
|
759
1064
|
const choice = chunk.choices[0];
|
|
760
1065
|
if (delta?.content) {
|
|
@@ -777,38 +1082,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
777
1082
|
}
|
|
778
1083
|
}
|
|
779
1084
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
const tcExtraContent = toolCall.extra_content;
|
|
796
|
-
currentToolCall = {
|
|
797
|
-
id: toolCall.id,
|
|
798
|
-
name: toolCall.function?.name || "",
|
|
799
|
-
arguments: toolCall.function?.arguments || "",
|
|
800
|
-
...tcExtraContent ? { extra_content: tcExtraContent } : {}
|
|
801
|
-
};
|
|
802
|
-
yield {
|
|
803
|
-
type: "action:start",
|
|
804
|
-
id: currentToolCall.id,
|
|
805
|
-
name: currentToolCall.name,
|
|
806
|
-
...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
|
|
807
|
-
};
|
|
808
|
-
} else if (currentToolCall && toolCall.function?.arguments) {
|
|
809
|
-
currentToolCall.arguments += toolCall.function.arguments;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
1085
|
+
for (const part of delta?.tool_calls ?? []) {
|
|
1086
|
+
const index = part.index ?? 0;
|
|
1087
|
+
const call = toolCalls.get(index) ?? {
|
|
1088
|
+
id: "",
|
|
1089
|
+
name: "",
|
|
1090
|
+
arguments: ""
|
|
1091
|
+
};
|
|
1092
|
+
if (part.id) call.id = part.id;
|
|
1093
|
+
if (part.function?.name) call.name += part.function.name;
|
|
1094
|
+
if (part.function?.arguments)
|
|
1095
|
+
call.arguments += part.function.arguments;
|
|
1096
|
+
if (part.extra_content) call.extra_content = part.extra_content;
|
|
1097
|
+
toolCalls.set(index, call);
|
|
812
1098
|
}
|
|
813
1099
|
if (chunk.usage) {
|
|
814
1100
|
usage = {
|
|
@@ -817,21 +1103,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
817
1103
|
total_tokens: chunk.usage.total_tokens
|
|
818
1104
|
};
|
|
819
1105
|
}
|
|
820
|
-
if (choice?.finish_reason)
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1106
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1107
|
+
}
|
|
1108
|
+
throwIfAborted(request.signal);
|
|
1109
|
+
if (!finishReason)
|
|
1110
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
1111
|
+
if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
|
|
1112
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
1113
|
+
}
|
|
1114
|
+
for (const call of toolCalls.values()) {
|
|
1115
|
+
if (!call.id || !call.name)
|
|
1116
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1117
|
+
JSON.parse(call.arguments || "{}");
|
|
1118
|
+
}
|
|
1119
|
+
for (const call of toolCalls.values()) {
|
|
1120
|
+
yield {
|
|
1121
|
+
type: "action:start",
|
|
1122
|
+
id: call.id,
|
|
1123
|
+
name: call.name,
|
|
1124
|
+
...call.extra_content ? { extra_content: call.extra_content } : {}
|
|
1125
|
+
};
|
|
1126
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
1127
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
835
1128
|
}
|
|
836
1129
|
if (collectedCitations.length > 0) {
|
|
837
1130
|
const uniqueCitations = deduplicateCitations(collectedCitations);
|
|
@@ -840,26 +1133,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
840
1133
|
yield { type: "message:end" };
|
|
841
1134
|
yield { type: "done", usage };
|
|
842
1135
|
} catch (error) {
|
|
843
|
-
yield {
|
|
844
|
-
type: "error",
|
|
845
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
846
|
-
code: `${this.provider.toUpperCase()}_ERROR`
|
|
847
|
-
};
|
|
1136
|
+
yield providerErrorEvent(error, `${this.provider.toUpperCase()}_ERROR`);
|
|
848
1137
|
}
|
|
849
1138
|
}
|
|
850
1139
|
async complete(request) {
|
|
1140
|
+
throwIfAborted(request.signal);
|
|
851
1141
|
if (this.shouldUseResponsesApi(request)) {
|
|
852
1142
|
return this.completeWithResponses(request);
|
|
853
1143
|
}
|
|
854
1144
|
const client = await this.getClient();
|
|
855
1145
|
let messages;
|
|
856
1146
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
857
|
-
const sanitized = request.rawMessages
|
|
858
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
859
|
-
return { ...msg, content: null };
|
|
860
|
-
}
|
|
861
|
-
return msg;
|
|
862
|
-
});
|
|
1147
|
+
const sanitized = this.formatRawMessages(request.rawMessages);
|
|
863
1148
|
if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
|
|
864
1149
|
messages = [
|
|
865
1150
|
{ role: "system", content: request.systemPrompt },
|
|
@@ -874,7 +1159,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
874
1159
|
request.systemPrompt
|
|
875
1160
|
);
|
|
876
1161
|
}
|
|
877
|
-
const tools = request.
|
|
1162
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1163
|
+
type: "function",
|
|
1164
|
+
function: {
|
|
1165
|
+
name: tool.name,
|
|
1166
|
+
description: tool.description,
|
|
1167
|
+
parameters: normalizeObjectJsonSchema(
|
|
1168
|
+
tool.inputSchema
|
|
1169
|
+
)
|
|
1170
|
+
}
|
|
1171
|
+
})) : formatTools(request.actions ?? []);
|
|
878
1172
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
879
1173
|
const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
|
|
880
1174
|
type: "function",
|
|
@@ -889,16 +1183,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
889
1183
|
tools: tools.length > 0 ? tools : void 0,
|
|
890
1184
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
891
1185
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
892
|
-
...
|
|
1186
|
+
...buildOpenAIParameters(
|
|
893
1187
|
modelIdForCompletePayload,
|
|
894
1188
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
895
|
-
request.config?.temperature ?? this.config.temperature
|
|
1189
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1190
|
+
this.parameterOptions(request)
|
|
896
1191
|
),
|
|
897
1192
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
898
1193
|
stream: false
|
|
899
1194
|
};
|
|
900
1195
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
901
|
-
const response = await client.chat.completions.create(payload
|
|
1196
|
+
const response = await client.chat.completions.create(payload, {
|
|
1197
|
+
signal: request.signal
|
|
1198
|
+
});
|
|
902
1199
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
903
1200
|
const choice = response.choices?.[0];
|
|
904
1201
|
const message = choice?.message;
|