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