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