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