@yourgpt/llm-sdk 2.5.1-beta.5 → 2.6.0-beta.0
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/dist/adapters/index.d.mts +51 -0
- package/dist/adapters/index.d.ts +51 -0
- package/dist/adapters/index.js +248 -42
- package/dist/adapters/index.mjs +248 -42
- package/dist/index.js +123 -27
- package/dist/index.mjs +123 -27
- package/dist/providers/azure/index.js +8 -3
- package/dist/providers/azure/index.mjs +8 -3
- package/dist/providers/fireworks/index.js +248 -42
- package/dist/providers/fireworks/index.mjs +248 -42
- package/dist/providers/google/index.js +248 -42
- package/dist/providers/google/index.mjs +248 -42
- package/dist/providers/openai/index.js +248 -42
- package/dist/providers/openai/index.mjs +248 -42
- package/dist/providers/openrouter/index.js +248 -42
- package/dist/providers/openrouter/index.mjs +248 -42
- package/dist/providers/togetherai/index.js +248 -42
- package/dist/providers/togetherai/index.mjs +248 -42
- package/dist/providers/xai/index.js +248 -42
- package/dist/providers/xai/index.mjs +248 -42
- package/package.json +1 -1
|
@@ -91,14 +91,19 @@ function normalizeObjectJsonSchema(schema) {
|
|
|
91
91
|
(value) => typeof value === "string"
|
|
92
92
|
) : [];
|
|
93
93
|
normalized.required = Array.from(/* @__PURE__ */ new Set([...required, ...propertyKeys]));
|
|
94
|
-
|
|
95
|
-
normalized.additionalProperties = false;
|
|
96
|
-
}
|
|
94
|
+
normalized.additionalProperties = false;
|
|
97
95
|
} else if (type === "array" && normalized.items && typeof normalized.items === "object") {
|
|
98
96
|
normalized.items = normalizeObjectJsonSchema(
|
|
99
97
|
normalized.items
|
|
100
98
|
);
|
|
101
99
|
}
|
|
100
|
+
for (const keyword of ["anyOf", "oneOf", "allOf"]) {
|
|
101
|
+
const branches = normalized[keyword];
|
|
102
|
+
if (!Array.isArray(branches)) continue;
|
|
103
|
+
normalized[keyword] = branches.map(
|
|
104
|
+
(branch) => branch && typeof branch === "object" ? normalizeObjectJsonSchema(branch) : branch
|
|
105
|
+
);
|
|
106
|
+
}
|
|
102
107
|
return normalized;
|
|
103
108
|
}
|
|
104
109
|
function isOpenAIReasoningModel(modelId) {
|
|
@@ -527,18 +532,79 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
527
532
|
return this.client;
|
|
528
533
|
}
|
|
529
534
|
shouldUseResponsesApi(request) {
|
|
530
|
-
|
|
535
|
+
const hasTools = Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
|
|
536
|
+
if (!hasTools) return false;
|
|
537
|
+
if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false) {
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
return this.requiresResponsesApiForTools(
|
|
541
|
+
request.config?.model || this.model
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Whether this model rejects function tools on /v1/chat/completions.
|
|
546
|
+
*
|
|
547
|
+
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
548
|
+
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
549
|
+
* restriction and may not implement /v1/responses at all.
|
|
550
|
+
*/
|
|
551
|
+
requiresResponsesApiForTools(modelId) {
|
|
552
|
+
if (!modelId) return false;
|
|
553
|
+
if (!/^gpt-5\.(?:[4-9]|\d{2,})/i.test(modelId)) return false;
|
|
554
|
+
const baseUrl = this.config.baseUrl;
|
|
555
|
+
if (!baseUrl) return true;
|
|
556
|
+
try {
|
|
557
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
558
|
+
} catch {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Convert one message's content to Responses content parts.
|
|
564
|
+
*
|
|
565
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
566
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
567
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
568
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
569
|
+
* array-shaped has to be translated rather than forwarded.
|
|
570
|
+
*/
|
|
571
|
+
toResponsesContent(content, role) {
|
|
572
|
+
const textType = role === "assistant" ? "output_text" : "input_text";
|
|
573
|
+
if (typeof content === "string") return content;
|
|
574
|
+
if (!Array.isArray(content)) {
|
|
575
|
+
return content == null ? "" : JSON.stringify(content);
|
|
576
|
+
}
|
|
577
|
+
return content.map((part) => {
|
|
578
|
+
const p = part;
|
|
579
|
+
if (p?.type === "text") return { type: textType, text: p.text ?? "" };
|
|
580
|
+
if (p?.type === "image_url") {
|
|
581
|
+
const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
|
|
582
|
+
return {
|
|
583
|
+
type: "input_image",
|
|
584
|
+
image_url: url,
|
|
585
|
+
...p.image_url?.detail ? { detail: p.image_url.detail } : {}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
return p;
|
|
589
|
+
});
|
|
531
590
|
}
|
|
532
591
|
buildResponsesInput(request) {
|
|
533
592
|
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
|
|
534
593
|
const input = [];
|
|
535
594
|
for (const message of sourceMessages) {
|
|
536
595
|
if (message.role === "system") {
|
|
596
|
+
if (!request.systemPrompt && message.content) {
|
|
597
|
+
input.push({
|
|
598
|
+
type: "message",
|
|
599
|
+
role: "developer",
|
|
600
|
+
content: this.toResponsesContent(message.content, "developer")
|
|
601
|
+
});
|
|
602
|
+
}
|
|
537
603
|
continue;
|
|
538
604
|
}
|
|
539
605
|
if (message.role === "assistant") {
|
|
540
|
-
const content =
|
|
541
|
-
if (content) {
|
|
606
|
+
const content = this.toResponsesContent(message.content, "assistant");
|
|
607
|
+
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
542
608
|
input.push({
|
|
543
609
|
type: "message",
|
|
544
610
|
role: "assistant",
|
|
@@ -564,15 +630,67 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
564
630
|
});
|
|
565
631
|
continue;
|
|
566
632
|
}
|
|
633
|
+
const role = message.role === "developer" ? "developer" : "user";
|
|
567
634
|
input.push({
|
|
568
635
|
type: "message",
|
|
569
|
-
role
|
|
570
|
-
content:
|
|
636
|
+
role,
|
|
637
|
+
content: this.toResponsesContent(message.content, role)
|
|
571
638
|
});
|
|
572
639
|
}
|
|
573
640
|
return input;
|
|
574
641
|
}
|
|
575
|
-
|
|
642
|
+
/**
|
|
643
|
+
* Build the web_search tool for the Responses API, or undefined when web
|
|
644
|
+
* search is off.
|
|
645
|
+
*
|
|
646
|
+
* The Chat Completions path uses `web_search_preview`; the Responses API
|
|
647
|
+
* names the same built-in `web_search`. Without this, enabling web search
|
|
648
|
+
* alongside a gpt-5.4+ function tool silently did nothing, because routing
|
|
649
|
+
* to /v1/responses skipped the Chat Completions tool assembly entirely.
|
|
650
|
+
*/
|
|
651
|
+
buildResponsesWebSearchTool(request) {
|
|
652
|
+
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
653
|
+
if (!webSearchConfig) return void 0;
|
|
654
|
+
const tool = { type: "web_search" };
|
|
655
|
+
const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
|
|
656
|
+
if (wsConfig.userLocation) {
|
|
657
|
+
tool.search_context_size = "medium";
|
|
658
|
+
}
|
|
659
|
+
return tool;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Extract url_citation annotations from Responses output items.
|
|
663
|
+
*
|
|
664
|
+
* Annotations hang off the output_text content parts rather than arriving as
|
|
665
|
+
* their own stream events, so both the streaming and non-streaming paths read
|
|
666
|
+
* them from the same place.
|
|
667
|
+
*/
|
|
668
|
+
collectResponsesCitations(output, startIndex = 0) {
|
|
669
|
+
if (!Array.isArray(output)) return [];
|
|
670
|
+
const citations = [];
|
|
671
|
+
let index = startIndex;
|
|
672
|
+
for (const item of output) {
|
|
673
|
+
if (item?.type !== "message" || !Array.isArray(item.content)) continue;
|
|
674
|
+
for (const part of item.content) {
|
|
675
|
+
if (!Array.isArray(part?.annotations)) continue;
|
|
676
|
+
for (const annotation of part.annotations) {
|
|
677
|
+
const url = annotation?.url;
|
|
678
|
+
if (annotation?.type !== "url_citation" || !url) continue;
|
|
679
|
+
index++;
|
|
680
|
+
const domain = extractDomain(url);
|
|
681
|
+
citations.push({
|
|
682
|
+
index,
|
|
683
|
+
url,
|
|
684
|
+
title: annotation.title || domain,
|
|
685
|
+
domain,
|
|
686
|
+
favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return citations;
|
|
692
|
+
}
|
|
693
|
+
buildResponsesTools(tools, includeToolSearch, webSearchTool) {
|
|
576
694
|
const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
|
|
577
695
|
type: "function",
|
|
578
696
|
name: tool.name,
|
|
@@ -587,10 +705,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
587
705
|
strict: true,
|
|
588
706
|
defer_loading: tool.deferLoading === true
|
|
589
707
|
}));
|
|
590
|
-
|
|
708
|
+
const builtins = [];
|
|
709
|
+
if (includeToolSearch) builtins.push({ type: "tool_search" });
|
|
710
|
+
if (webSearchTool) builtins.push(webSearchTool);
|
|
711
|
+
return [...builtins, ...nativeTools];
|
|
591
712
|
}
|
|
592
713
|
parseResponsesResult(response) {
|
|
593
|
-
const
|
|
714
|
+
const textFromOutput = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "message").flatMap(
|
|
715
|
+
(item) => Array.isArray(item.content) ? item.content.filter(
|
|
716
|
+
(part) => part?.type === "output_text" && typeof part.text === "string"
|
|
717
|
+
).map((part) => part.text) : []
|
|
718
|
+
).join("") : "";
|
|
719
|
+
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
594
720
|
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
|
|
595
721
|
id: item.call_id ?? item.id ?? generateToolCallId(),
|
|
596
722
|
name: item.name,
|
|
@@ -613,59 +739,139 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
613
739
|
rawResponse: response
|
|
614
740
|
};
|
|
615
741
|
}
|
|
616
|
-
|
|
617
|
-
const client = await this.getClient();
|
|
742
|
+
buildResponsesPayload(request, stream) {
|
|
618
743
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
619
744
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
620
745
|
request.config?.responseFormat
|
|
621
746
|
);
|
|
747
|
+
const modelIdForPayload = request.config?.model || this.model;
|
|
622
748
|
const payload = {
|
|
623
|
-
model:
|
|
749
|
+
model: modelIdForPayload,
|
|
624
750
|
instructions: request.systemPrompt,
|
|
625
751
|
input: this.buildResponsesInput(request),
|
|
626
|
-
tools: this.buildResponsesTools(
|
|
752
|
+
tools: this.buildResponsesTools(
|
|
753
|
+
request.toolDefinitions ?? [],
|
|
754
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
755
|
+
this.buildResponsesWebSearchTool(request)
|
|
756
|
+
),
|
|
627
757
|
tool_choice: openaiToolOptions?.toolChoice === "required" ? "required" : openaiToolOptions?.toolChoice === "auto" ? "auto" : void 0,
|
|
628
758
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
629
|
-
|
|
759
|
+
// Reasoning models reject `temperature` outright — sending it would swap one
|
|
760
|
+
// 400 for another. isOpenAIReasoningModel already encodes that family test.
|
|
761
|
+
...isOpenAIReasoningModel(modelIdForPayload) ? {} : {
|
|
762
|
+
temperature: request.config?.temperature ?? this.config.temperature
|
|
763
|
+
},
|
|
630
764
|
max_output_tokens: request.config?.maxTokens ?? this.config.maxTokens,
|
|
631
765
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
632
|
-
stream
|
|
766
|
+
stream
|
|
633
767
|
};
|
|
768
|
+
return payload;
|
|
769
|
+
}
|
|
770
|
+
async completeWithResponses(request) {
|
|
771
|
+
const client = await this.getClient();
|
|
772
|
+
const payload = this.buildResponsesPayload(request, false);
|
|
634
773
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
635
774
|
const response = await client.responses.create(payload);
|
|
636
775
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
637
776
|
return this.parseResponsesResult(response);
|
|
638
777
|
}
|
|
778
|
+
/**
|
|
779
|
+
* Stream from /v1/responses as real SSE.
|
|
780
|
+
*
|
|
781
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
782
|
+
* deltas, so the interesting ones are:
|
|
783
|
+
* response.output_text.delta — incremental assistant text
|
|
784
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
785
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
786
|
+
* response.completed — terminal, carries usage
|
|
787
|
+
*
|
|
788
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
789
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
790
|
+
*/
|
|
791
|
+
async *streamWithResponses(request) {
|
|
792
|
+
const client = await this.getClient();
|
|
793
|
+
const payload = this.buildResponsesPayload(request, true);
|
|
794
|
+
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
795
|
+
const stream = await client.responses.create(payload);
|
|
796
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
797
|
+
let usage;
|
|
798
|
+
const citations = [];
|
|
799
|
+
function* flushToolCalls() {
|
|
800
|
+
for (const call of toolCalls.values()) {
|
|
801
|
+
yield { type: "action:start", id: call.id, name: call.name };
|
|
802
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
803
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
804
|
+
}
|
|
805
|
+
toolCalls.clear();
|
|
806
|
+
}
|
|
807
|
+
for await (const event of stream) {
|
|
808
|
+
if (request.signal?.aborted) break;
|
|
809
|
+
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
810
|
+
const index = event?.output_index ?? 0;
|
|
811
|
+
switch (event?.type) {
|
|
812
|
+
case "response.output_text.delta":
|
|
813
|
+
if (event.delta) {
|
|
814
|
+
yield { type: "message:delta", content: event.delta };
|
|
815
|
+
}
|
|
816
|
+
break;
|
|
817
|
+
case "response.output_item.added":
|
|
818
|
+
if (event.item?.type === "function_call") {
|
|
819
|
+
toolCalls.set(index, {
|
|
820
|
+
id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
|
|
821
|
+
name: event.item.name ?? "",
|
|
822
|
+
arguments: event.item.arguments ?? ""
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
break;
|
|
826
|
+
case "response.function_call_arguments.delta": {
|
|
827
|
+
const existing = toolCalls.get(index);
|
|
828
|
+
if (existing && event.delta) existing.arguments += event.delta;
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
case "response.function_call_arguments.done": {
|
|
832
|
+
const existing = toolCalls.get(index);
|
|
833
|
+
if (existing && typeof event.arguments === "string") {
|
|
834
|
+
existing.arguments = event.arguments;
|
|
835
|
+
}
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
case "response.completed":
|
|
839
|
+
case "response.incomplete": {
|
|
840
|
+
const found = this.collectResponsesCitations(
|
|
841
|
+
event.response?.output,
|
|
842
|
+
citations.length
|
|
843
|
+
);
|
|
844
|
+
if (found.length > 0) citations.push(...found);
|
|
845
|
+
const u = event.response?.usage;
|
|
846
|
+
if (u) {
|
|
847
|
+
usage = {
|
|
848
|
+
prompt_tokens: u.input_tokens ?? 0,
|
|
849
|
+
completion_tokens: u.output_tokens ?? 0,
|
|
850
|
+
total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
case "error":
|
|
856
|
+
case "response.failed":
|
|
857
|
+
throw new Error(
|
|
858
|
+
event.message ?? event.response?.error?.message ?? "Responses stream failed"
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
yield* flushToolCalls();
|
|
863
|
+
if (citations.length > 0) {
|
|
864
|
+
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
865
|
+
}
|
|
866
|
+
yield { type: "message:end" };
|
|
867
|
+
yield { type: "done", usage };
|
|
868
|
+
}
|
|
639
869
|
async *stream(request) {
|
|
640
870
|
if (this.shouldUseResponsesApi(request)) {
|
|
641
871
|
const messageId2 = generateMessageId();
|
|
642
872
|
yield { type: "message:start", id: messageId2 };
|
|
643
873
|
try {
|
|
644
|
-
|
|
645
|
-
if (result.content) {
|
|
646
|
-
yield { type: "message:delta", content: result.content };
|
|
647
|
-
}
|
|
648
|
-
for (const toolCall of result.toolCalls) {
|
|
649
|
-
yield {
|
|
650
|
-
type: "action:start",
|
|
651
|
-
id: toolCall.id,
|
|
652
|
-
name: toolCall.name
|
|
653
|
-
};
|
|
654
|
-
yield {
|
|
655
|
-
type: "action:args",
|
|
656
|
-
id: toolCall.id,
|
|
657
|
-
args: JSON.stringify(toolCall.args)
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
yield { type: "message:end" };
|
|
661
|
-
yield {
|
|
662
|
-
type: "done",
|
|
663
|
-
usage: result.usage ? {
|
|
664
|
-
prompt_tokens: result.usage.promptTokens,
|
|
665
|
-
completion_tokens: result.usage.completionTokens,
|
|
666
|
-
total_tokens: result.usage.totalTokens
|
|
667
|
-
} : void 0
|
|
668
|
-
};
|
|
874
|
+
yield* this.streamWithResponses(request);
|
|
669
875
|
return;
|
|
670
876
|
} catch (error) {
|
|
671
877
|
yield {
|