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