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