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