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