@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) {
|
|
@@ -554,18 +559,79 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
554
559
|
return this.client;
|
|
555
560
|
}
|
|
556
561
|
shouldUseResponsesApi(request) {
|
|
557
|
-
|
|
562
|
+
const hasTools = Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
|
|
563
|
+
if (!hasTools) return false;
|
|
564
|
+
if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false) {
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
return this.requiresResponsesApiForTools(
|
|
568
|
+
request.config?.model || this.model
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Whether this model rejects function tools on /v1/chat/completions.
|
|
573
|
+
*
|
|
574
|
+
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
575
|
+
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
576
|
+
* restriction and may not implement /v1/responses at all.
|
|
577
|
+
*/
|
|
578
|
+
requiresResponsesApiForTools(modelId) {
|
|
579
|
+
if (!modelId) return false;
|
|
580
|
+
if (!/^gpt-5\.(?:[4-9]|\d{2,})/i.test(modelId)) return false;
|
|
581
|
+
const baseUrl = this.config.baseUrl;
|
|
582
|
+
if (!baseUrl) return true;
|
|
583
|
+
try {
|
|
584
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
585
|
+
} catch {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Convert one message's content to Responses content parts.
|
|
591
|
+
*
|
|
592
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
593
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
594
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
595
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
596
|
+
* array-shaped has to be translated rather than forwarded.
|
|
597
|
+
*/
|
|
598
|
+
toResponsesContent(content, role) {
|
|
599
|
+
const textType = role === "assistant" ? "output_text" : "input_text";
|
|
600
|
+
if (typeof content === "string") return content;
|
|
601
|
+
if (!Array.isArray(content)) {
|
|
602
|
+
return content == null ? "" : JSON.stringify(content);
|
|
603
|
+
}
|
|
604
|
+
return content.map((part) => {
|
|
605
|
+
const p = part;
|
|
606
|
+
if (p?.type === "text") return { type: textType, text: p.text ?? "" };
|
|
607
|
+
if (p?.type === "image_url") {
|
|
608
|
+
const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
|
|
609
|
+
return {
|
|
610
|
+
type: "input_image",
|
|
611
|
+
image_url: url,
|
|
612
|
+
...p.image_url?.detail ? { detail: p.image_url.detail } : {}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
return p;
|
|
616
|
+
});
|
|
558
617
|
}
|
|
559
618
|
buildResponsesInput(request) {
|
|
560
619
|
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
|
|
561
620
|
const input = [];
|
|
562
621
|
for (const message of sourceMessages) {
|
|
563
622
|
if (message.role === "system") {
|
|
623
|
+
if (!request.systemPrompt && message.content) {
|
|
624
|
+
input.push({
|
|
625
|
+
type: "message",
|
|
626
|
+
role: "developer",
|
|
627
|
+
content: this.toResponsesContent(message.content, "developer")
|
|
628
|
+
});
|
|
629
|
+
}
|
|
564
630
|
continue;
|
|
565
631
|
}
|
|
566
632
|
if (message.role === "assistant") {
|
|
567
|
-
const content =
|
|
568
|
-
if (content) {
|
|
633
|
+
const content = this.toResponsesContent(message.content, "assistant");
|
|
634
|
+
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
569
635
|
input.push({
|
|
570
636
|
type: "message",
|
|
571
637
|
role: "assistant",
|
|
@@ -591,15 +657,67 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
591
657
|
});
|
|
592
658
|
continue;
|
|
593
659
|
}
|
|
660
|
+
const role = message.role === "developer" ? "developer" : "user";
|
|
594
661
|
input.push({
|
|
595
662
|
type: "message",
|
|
596
|
-
role
|
|
597
|
-
content:
|
|
663
|
+
role,
|
|
664
|
+
content: this.toResponsesContent(message.content, role)
|
|
598
665
|
});
|
|
599
666
|
}
|
|
600
667
|
return input;
|
|
601
668
|
}
|
|
602
|
-
|
|
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) {
|
|
603
721
|
const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
|
|
604
722
|
type: "function",
|
|
605
723
|
name: tool.name,
|
|
@@ -614,10 +732,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
614
732
|
strict: true,
|
|
615
733
|
defer_loading: tool.deferLoading === true
|
|
616
734
|
}));
|
|
617
|
-
|
|
735
|
+
const builtins = [];
|
|
736
|
+
if (includeToolSearch) builtins.push({ type: "tool_search" });
|
|
737
|
+
if (webSearchTool) builtins.push(webSearchTool);
|
|
738
|
+
return [...builtins, ...nativeTools];
|
|
618
739
|
}
|
|
619
740
|
parseResponsesResult(response) {
|
|
620
|
-
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 : "");
|
|
621
747
|
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
|
|
622
748
|
id: item.call_id ?? item.id ?? generateToolCallId(),
|
|
623
749
|
name: item.name,
|
|
@@ -640,59 +766,139 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
640
766
|
rawResponse: response
|
|
641
767
|
};
|
|
642
768
|
}
|
|
643
|
-
|
|
644
|
-
const client = await this.getClient();
|
|
769
|
+
buildResponsesPayload(request, stream) {
|
|
645
770
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
646
771
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
647
772
|
request.config?.responseFormat
|
|
648
773
|
);
|
|
774
|
+
const modelIdForPayload = request.config?.model || this.model;
|
|
649
775
|
const payload = {
|
|
650
|
-
model:
|
|
776
|
+
model: modelIdForPayload,
|
|
651
777
|
instructions: request.systemPrompt,
|
|
652
778
|
input: this.buildResponsesInput(request),
|
|
653
|
-
tools: this.buildResponsesTools(
|
|
779
|
+
tools: this.buildResponsesTools(
|
|
780
|
+
request.toolDefinitions ?? [],
|
|
781
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
782
|
+
this.buildResponsesWebSearchTool(request)
|
|
783
|
+
),
|
|
654
784
|
tool_choice: openaiToolOptions?.toolChoice === "required" ? "required" : openaiToolOptions?.toolChoice === "auto" ? "auto" : void 0,
|
|
655
785
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
656
|
-
|
|
786
|
+
// Reasoning models reject `temperature` outright — sending it would swap one
|
|
787
|
+
// 400 for another. isOpenAIReasoningModel already encodes that family test.
|
|
788
|
+
...isOpenAIReasoningModel(modelIdForPayload) ? {} : {
|
|
789
|
+
temperature: request.config?.temperature ?? this.config.temperature
|
|
790
|
+
},
|
|
657
791
|
max_output_tokens: request.config?.maxTokens ?? this.config.maxTokens,
|
|
658
792
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
659
|
-
stream
|
|
793
|
+
stream
|
|
660
794
|
};
|
|
795
|
+
return payload;
|
|
796
|
+
}
|
|
797
|
+
async completeWithResponses(request) {
|
|
798
|
+
const client = await this.getClient();
|
|
799
|
+
const payload = this.buildResponsesPayload(request, false);
|
|
661
800
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
662
801
|
const response = await client.responses.create(payload);
|
|
663
802
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
664
803
|
return this.parseResponsesResult(response);
|
|
665
804
|
}
|
|
805
|
+
/**
|
|
806
|
+
* Stream from /v1/responses as real SSE.
|
|
807
|
+
*
|
|
808
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
809
|
+
* deltas, so the interesting ones are:
|
|
810
|
+
* response.output_text.delta — incremental assistant text
|
|
811
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
812
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
813
|
+
* response.completed — terminal, carries usage
|
|
814
|
+
*
|
|
815
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
816
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
817
|
+
*/
|
|
818
|
+
async *streamWithResponses(request) {
|
|
819
|
+
const client = await this.getClient();
|
|
820
|
+
const payload = this.buildResponsesPayload(request, true);
|
|
821
|
+
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
822
|
+
const stream = await client.responses.create(payload);
|
|
823
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
824
|
+
let usage;
|
|
825
|
+
const citations = [];
|
|
826
|
+
function* flushToolCalls() {
|
|
827
|
+
for (const call of toolCalls.values()) {
|
|
828
|
+
yield { type: "action:start", id: call.id, name: call.name };
|
|
829
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
830
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
831
|
+
}
|
|
832
|
+
toolCalls.clear();
|
|
833
|
+
}
|
|
834
|
+
for await (const event of stream) {
|
|
835
|
+
if (request.signal?.aborted) break;
|
|
836
|
+
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
837
|
+
const index = event?.output_index ?? 0;
|
|
838
|
+
switch (event?.type) {
|
|
839
|
+
case "response.output_text.delta":
|
|
840
|
+
if (event.delta) {
|
|
841
|
+
yield { type: "message:delta", content: event.delta };
|
|
842
|
+
}
|
|
843
|
+
break;
|
|
844
|
+
case "response.output_item.added":
|
|
845
|
+
if (event.item?.type === "function_call") {
|
|
846
|
+
toolCalls.set(index, {
|
|
847
|
+
id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
|
|
848
|
+
name: event.item.name ?? "",
|
|
849
|
+
arguments: event.item.arguments ?? ""
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
break;
|
|
853
|
+
case "response.function_call_arguments.delta": {
|
|
854
|
+
const existing = toolCalls.get(index);
|
|
855
|
+
if (existing && event.delta) existing.arguments += event.delta;
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
case "response.function_call_arguments.done": {
|
|
859
|
+
const existing = toolCalls.get(index);
|
|
860
|
+
if (existing && typeof event.arguments === "string") {
|
|
861
|
+
existing.arguments = event.arguments;
|
|
862
|
+
}
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
case "response.completed":
|
|
866
|
+
case "response.incomplete": {
|
|
867
|
+
const found = this.collectResponsesCitations(
|
|
868
|
+
event.response?.output,
|
|
869
|
+
citations.length
|
|
870
|
+
);
|
|
871
|
+
if (found.length > 0) citations.push(...found);
|
|
872
|
+
const u = event.response?.usage;
|
|
873
|
+
if (u) {
|
|
874
|
+
usage = {
|
|
875
|
+
prompt_tokens: u.input_tokens ?? 0,
|
|
876
|
+
completion_tokens: u.output_tokens ?? 0,
|
|
877
|
+
total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
case "error":
|
|
883
|
+
case "response.failed":
|
|
884
|
+
throw new Error(
|
|
885
|
+
event.message ?? event.response?.error?.message ?? "Responses stream failed"
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
yield* flushToolCalls();
|
|
890
|
+
if (citations.length > 0) {
|
|
891
|
+
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
892
|
+
}
|
|
893
|
+
yield { type: "message:end" };
|
|
894
|
+
yield { type: "done", usage };
|
|
895
|
+
}
|
|
666
896
|
async *stream(request) {
|
|
667
897
|
if (this.shouldUseResponsesApi(request)) {
|
|
668
898
|
const messageId2 = generateMessageId();
|
|
669
899
|
yield { type: "message:start", id: messageId2 };
|
|
670
900
|
try {
|
|
671
|
-
|
|
672
|
-
if (result.content) {
|
|
673
|
-
yield { type: "message:delta", content: result.content };
|
|
674
|
-
}
|
|
675
|
-
for (const toolCall of result.toolCalls) {
|
|
676
|
-
yield {
|
|
677
|
-
type: "action:start",
|
|
678
|
-
id: toolCall.id,
|
|
679
|
-
name: toolCall.name
|
|
680
|
-
};
|
|
681
|
-
yield {
|
|
682
|
-
type: "action:args",
|
|
683
|
-
id: toolCall.id,
|
|
684
|
-
args: JSON.stringify(toolCall.args)
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
yield { type: "message:end" };
|
|
688
|
-
yield {
|
|
689
|
-
type: "done",
|
|
690
|
-
usage: result.usage ? {
|
|
691
|
-
prompt_tokens: result.usage.promptTokens,
|
|
692
|
-
completion_tokens: result.usage.completionTokens,
|
|
693
|
-
total_tokens: result.usage.totalTokens
|
|
694
|
-
} : void 0
|
|
695
|
-
};
|
|
901
|
+
yield* this.streamWithResponses(request);
|
|
696
902
|
return;
|
|
697
903
|
} catch (error) {
|
|
698
904
|
yield {
|