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