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