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