@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
package/dist/adapters/index.mjs
CHANGED
|
@@ -102,14 +102,19 @@ function normalizeObjectJsonSchema(schema) {
|
|
|
102
102
|
(value) => typeof value === "string"
|
|
103
103
|
) : [];
|
|
104
104
|
normalized.required = Array.from(/* @__PURE__ */ new Set([...required, ...propertyKeys]));
|
|
105
|
-
|
|
106
|
-
normalized.additionalProperties = false;
|
|
107
|
-
}
|
|
105
|
+
normalized.additionalProperties = false;
|
|
108
106
|
} else if (type === "array" && normalized.items && typeof normalized.items === "object") {
|
|
109
107
|
normalized.items = normalizeObjectJsonSchema(
|
|
110
108
|
normalized.items
|
|
111
109
|
);
|
|
112
110
|
}
|
|
111
|
+
for (const keyword of ["anyOf", "oneOf", "allOf"]) {
|
|
112
|
+
const branches = normalized[keyword];
|
|
113
|
+
if (!Array.isArray(branches)) continue;
|
|
114
|
+
normalized[keyword] = branches.map(
|
|
115
|
+
(branch) => branch && typeof branch === "object" ? normalizeObjectJsonSchema(branch) : branch
|
|
116
|
+
);
|
|
117
|
+
}
|
|
113
118
|
return normalized;
|
|
114
119
|
}
|
|
115
120
|
function isOpenAIReasoningModel(modelId) {
|
|
@@ -505,18 +510,79 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
505
510
|
return this.client;
|
|
506
511
|
}
|
|
507
512
|
shouldUseResponsesApi(request) {
|
|
508
|
-
|
|
513
|
+
const hasTools = Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
|
|
514
|
+
if (!hasTools) return false;
|
|
515
|
+
if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false) {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
return this.requiresResponsesApiForTools(
|
|
519
|
+
request.config?.model || this.model
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Whether this model rejects function tools on /v1/chat/completions.
|
|
524
|
+
*
|
|
525
|
+
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
526
|
+
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
527
|
+
* restriction and may not implement /v1/responses at all.
|
|
528
|
+
*/
|
|
529
|
+
requiresResponsesApiForTools(modelId) {
|
|
530
|
+
if (!modelId) return false;
|
|
531
|
+
if (!/^gpt-5\.(?:[4-9]|\d{2,})/i.test(modelId)) return false;
|
|
532
|
+
const baseUrl = this.config.baseUrl;
|
|
533
|
+
if (!baseUrl) return true;
|
|
534
|
+
try {
|
|
535
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
536
|
+
} catch {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Convert one message's content to Responses content parts.
|
|
542
|
+
*
|
|
543
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
544
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
545
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
546
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
547
|
+
* array-shaped has to be translated rather than forwarded.
|
|
548
|
+
*/
|
|
549
|
+
toResponsesContent(content, role) {
|
|
550
|
+
const textType = role === "assistant" ? "output_text" : "input_text";
|
|
551
|
+
if (typeof content === "string") return content;
|
|
552
|
+
if (!Array.isArray(content)) {
|
|
553
|
+
return content == null ? "" : JSON.stringify(content);
|
|
554
|
+
}
|
|
555
|
+
return content.map((part) => {
|
|
556
|
+
const p = part;
|
|
557
|
+
if (p?.type === "text") return { type: textType, text: p.text ?? "" };
|
|
558
|
+
if (p?.type === "image_url") {
|
|
559
|
+
const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
|
|
560
|
+
return {
|
|
561
|
+
type: "input_image",
|
|
562
|
+
image_url: url,
|
|
563
|
+
...p.image_url?.detail ? { detail: p.image_url.detail } : {}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return p;
|
|
567
|
+
});
|
|
509
568
|
}
|
|
510
569
|
buildResponsesInput(request) {
|
|
511
570
|
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
|
|
512
571
|
const input = [];
|
|
513
572
|
for (const message of sourceMessages) {
|
|
514
573
|
if (message.role === "system") {
|
|
574
|
+
if (!request.systemPrompt && message.content) {
|
|
575
|
+
input.push({
|
|
576
|
+
type: "message",
|
|
577
|
+
role: "developer",
|
|
578
|
+
content: this.toResponsesContent(message.content, "developer")
|
|
579
|
+
});
|
|
580
|
+
}
|
|
515
581
|
continue;
|
|
516
582
|
}
|
|
517
583
|
if (message.role === "assistant") {
|
|
518
|
-
const content =
|
|
519
|
-
if (content) {
|
|
584
|
+
const content = this.toResponsesContent(message.content, "assistant");
|
|
585
|
+
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
520
586
|
input.push({
|
|
521
587
|
type: "message",
|
|
522
588
|
role: "assistant",
|
|
@@ -542,15 +608,67 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
542
608
|
});
|
|
543
609
|
continue;
|
|
544
610
|
}
|
|
611
|
+
const role = message.role === "developer" ? "developer" : "user";
|
|
545
612
|
input.push({
|
|
546
613
|
type: "message",
|
|
547
|
-
role
|
|
548
|
-
content:
|
|
614
|
+
role,
|
|
615
|
+
content: this.toResponsesContent(message.content, role)
|
|
549
616
|
});
|
|
550
617
|
}
|
|
551
618
|
return input;
|
|
552
619
|
}
|
|
553
|
-
|
|
620
|
+
/**
|
|
621
|
+
* Build the web_search tool for the Responses API, or undefined when web
|
|
622
|
+
* search is off.
|
|
623
|
+
*
|
|
624
|
+
* The Chat Completions path uses `web_search_preview`; the Responses API
|
|
625
|
+
* names the same built-in `web_search`. Without this, enabling web search
|
|
626
|
+
* alongside a gpt-5.4+ function tool silently did nothing, because routing
|
|
627
|
+
* to /v1/responses skipped the Chat Completions tool assembly entirely.
|
|
628
|
+
*/
|
|
629
|
+
buildResponsesWebSearchTool(request) {
|
|
630
|
+
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
631
|
+
if (!webSearchConfig) return void 0;
|
|
632
|
+
const tool = { type: "web_search" };
|
|
633
|
+
const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
|
|
634
|
+
if (wsConfig.userLocation) {
|
|
635
|
+
tool.search_context_size = "medium";
|
|
636
|
+
}
|
|
637
|
+
return tool;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Extract url_citation annotations from Responses output items.
|
|
641
|
+
*
|
|
642
|
+
* Annotations hang off the output_text content parts rather than arriving as
|
|
643
|
+
* their own stream events, so both the streaming and non-streaming paths read
|
|
644
|
+
* them from the same place.
|
|
645
|
+
*/
|
|
646
|
+
collectResponsesCitations(output, startIndex = 0) {
|
|
647
|
+
if (!Array.isArray(output)) return [];
|
|
648
|
+
const citations = [];
|
|
649
|
+
let index = startIndex;
|
|
650
|
+
for (const item of output) {
|
|
651
|
+
if (item?.type !== "message" || !Array.isArray(item.content)) continue;
|
|
652
|
+
for (const part of item.content) {
|
|
653
|
+
if (!Array.isArray(part?.annotations)) continue;
|
|
654
|
+
for (const annotation of part.annotations) {
|
|
655
|
+
const url = annotation?.url;
|
|
656
|
+
if (annotation?.type !== "url_citation" || !url) continue;
|
|
657
|
+
index++;
|
|
658
|
+
const domain = extractDomain(url);
|
|
659
|
+
citations.push({
|
|
660
|
+
index,
|
|
661
|
+
url,
|
|
662
|
+
title: annotation.title || domain,
|
|
663
|
+
domain,
|
|
664
|
+
favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return citations;
|
|
670
|
+
}
|
|
671
|
+
buildResponsesTools(tools, includeToolSearch, webSearchTool) {
|
|
554
672
|
const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
|
|
555
673
|
type: "function",
|
|
556
674
|
name: tool.name,
|
|
@@ -565,10 +683,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
565
683
|
strict: true,
|
|
566
684
|
defer_loading: tool.deferLoading === true
|
|
567
685
|
}));
|
|
568
|
-
|
|
686
|
+
const builtins = [];
|
|
687
|
+
if (includeToolSearch) builtins.push({ type: "tool_search" });
|
|
688
|
+
if (webSearchTool) builtins.push(webSearchTool);
|
|
689
|
+
return [...builtins, ...nativeTools];
|
|
569
690
|
}
|
|
570
691
|
parseResponsesResult(response) {
|
|
571
|
-
const
|
|
692
|
+
const textFromOutput = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "message").flatMap(
|
|
693
|
+
(item) => Array.isArray(item.content) ? item.content.filter(
|
|
694
|
+
(part) => part?.type === "output_text" && typeof part.text === "string"
|
|
695
|
+
).map((part) => part.text) : []
|
|
696
|
+
).join("") : "";
|
|
697
|
+
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
572
698
|
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
|
|
573
699
|
id: item.call_id ?? item.id ?? generateToolCallId(),
|
|
574
700
|
name: item.name,
|
|
@@ -591,59 +717,139 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
591
717
|
rawResponse: response
|
|
592
718
|
};
|
|
593
719
|
}
|
|
594
|
-
|
|
595
|
-
const client = await this.getClient();
|
|
720
|
+
buildResponsesPayload(request, stream) {
|
|
596
721
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
597
722
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
598
723
|
request.config?.responseFormat
|
|
599
724
|
);
|
|
725
|
+
const modelIdForPayload = request.config?.model || this.model;
|
|
600
726
|
const payload = {
|
|
601
|
-
model:
|
|
727
|
+
model: modelIdForPayload,
|
|
602
728
|
instructions: request.systemPrompt,
|
|
603
729
|
input: this.buildResponsesInput(request),
|
|
604
|
-
tools: this.buildResponsesTools(
|
|
730
|
+
tools: this.buildResponsesTools(
|
|
731
|
+
request.toolDefinitions ?? [],
|
|
732
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
733
|
+
this.buildResponsesWebSearchTool(request)
|
|
734
|
+
),
|
|
605
735
|
tool_choice: openaiToolOptions?.toolChoice === "required" ? "required" : openaiToolOptions?.toolChoice === "auto" ? "auto" : void 0,
|
|
606
736
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
607
|
-
|
|
737
|
+
// Reasoning models reject `temperature` outright — sending it would swap one
|
|
738
|
+
// 400 for another. isOpenAIReasoningModel already encodes that family test.
|
|
739
|
+
...isOpenAIReasoningModel(modelIdForPayload) ? {} : {
|
|
740
|
+
temperature: request.config?.temperature ?? this.config.temperature
|
|
741
|
+
},
|
|
608
742
|
max_output_tokens: request.config?.maxTokens ?? this.config.maxTokens,
|
|
609
743
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
610
|
-
stream
|
|
744
|
+
stream
|
|
611
745
|
};
|
|
746
|
+
return payload;
|
|
747
|
+
}
|
|
748
|
+
async completeWithResponses(request) {
|
|
749
|
+
const client = await this.getClient();
|
|
750
|
+
const payload = this.buildResponsesPayload(request, false);
|
|
612
751
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
613
752
|
const response = await client.responses.create(payload);
|
|
614
753
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
615
754
|
return this.parseResponsesResult(response);
|
|
616
755
|
}
|
|
756
|
+
/**
|
|
757
|
+
* Stream from /v1/responses as real SSE.
|
|
758
|
+
*
|
|
759
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
760
|
+
* deltas, so the interesting ones are:
|
|
761
|
+
* response.output_text.delta — incremental assistant text
|
|
762
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
763
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
764
|
+
* response.completed — terminal, carries usage
|
|
765
|
+
*
|
|
766
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
767
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
768
|
+
*/
|
|
769
|
+
async *streamWithResponses(request) {
|
|
770
|
+
const client = await this.getClient();
|
|
771
|
+
const payload = this.buildResponsesPayload(request, true);
|
|
772
|
+
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
773
|
+
const stream = await client.responses.create(payload);
|
|
774
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
775
|
+
let usage;
|
|
776
|
+
const citations = [];
|
|
777
|
+
function* flushToolCalls() {
|
|
778
|
+
for (const call of toolCalls.values()) {
|
|
779
|
+
yield { type: "action:start", id: call.id, name: call.name };
|
|
780
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
781
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
782
|
+
}
|
|
783
|
+
toolCalls.clear();
|
|
784
|
+
}
|
|
785
|
+
for await (const event of stream) {
|
|
786
|
+
if (request.signal?.aborted) break;
|
|
787
|
+
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
788
|
+
const index = event?.output_index ?? 0;
|
|
789
|
+
switch (event?.type) {
|
|
790
|
+
case "response.output_text.delta":
|
|
791
|
+
if (event.delta) {
|
|
792
|
+
yield { type: "message:delta", content: event.delta };
|
|
793
|
+
}
|
|
794
|
+
break;
|
|
795
|
+
case "response.output_item.added":
|
|
796
|
+
if (event.item?.type === "function_call") {
|
|
797
|
+
toolCalls.set(index, {
|
|
798
|
+
id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
|
|
799
|
+
name: event.item.name ?? "",
|
|
800
|
+
arguments: event.item.arguments ?? ""
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
case "response.function_call_arguments.delta": {
|
|
805
|
+
const existing = toolCalls.get(index);
|
|
806
|
+
if (existing && event.delta) existing.arguments += event.delta;
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
case "response.function_call_arguments.done": {
|
|
810
|
+
const existing = toolCalls.get(index);
|
|
811
|
+
if (existing && typeof event.arguments === "string") {
|
|
812
|
+
existing.arguments = event.arguments;
|
|
813
|
+
}
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
case "response.completed":
|
|
817
|
+
case "response.incomplete": {
|
|
818
|
+
const found = this.collectResponsesCitations(
|
|
819
|
+
event.response?.output,
|
|
820
|
+
citations.length
|
|
821
|
+
);
|
|
822
|
+
if (found.length > 0) citations.push(...found);
|
|
823
|
+
const u = event.response?.usage;
|
|
824
|
+
if (u) {
|
|
825
|
+
usage = {
|
|
826
|
+
prompt_tokens: u.input_tokens ?? 0,
|
|
827
|
+
completion_tokens: u.output_tokens ?? 0,
|
|
828
|
+
total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
case "error":
|
|
834
|
+
case "response.failed":
|
|
835
|
+
throw new Error(
|
|
836
|
+
event.message ?? event.response?.error?.message ?? "Responses stream failed"
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
yield* flushToolCalls();
|
|
841
|
+
if (citations.length > 0) {
|
|
842
|
+
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
843
|
+
}
|
|
844
|
+
yield { type: "message:end" };
|
|
845
|
+
yield { type: "done", usage };
|
|
846
|
+
}
|
|
617
847
|
async *stream(request) {
|
|
618
848
|
if (this.shouldUseResponsesApi(request)) {
|
|
619
849
|
const messageId2 = generateMessageId();
|
|
620
850
|
yield { type: "message:start", id: messageId2 };
|
|
621
851
|
try {
|
|
622
|
-
|
|
623
|
-
if (result.content) {
|
|
624
|
-
yield { type: "message:delta", content: result.content };
|
|
625
|
-
}
|
|
626
|
-
for (const toolCall of result.toolCalls) {
|
|
627
|
-
yield {
|
|
628
|
-
type: "action:start",
|
|
629
|
-
id: toolCall.id,
|
|
630
|
-
name: toolCall.name
|
|
631
|
-
};
|
|
632
|
-
yield {
|
|
633
|
-
type: "action:args",
|
|
634
|
-
id: toolCall.id,
|
|
635
|
-
args: JSON.stringify(toolCall.args)
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
yield { type: "message:end" };
|
|
639
|
-
yield {
|
|
640
|
-
type: "done",
|
|
641
|
-
usage: result.usage ? {
|
|
642
|
-
prompt_tokens: result.usage.promptTokens,
|
|
643
|
-
completion_tokens: result.usage.completionTokens,
|
|
644
|
-
total_tokens: result.usage.totalTokens
|
|
645
|
-
} : void 0
|
|
646
|
-
};
|
|
852
|
+
yield* this.streamWithResponses(request);
|
|
647
853
|
return;
|
|
648
854
|
} catch (error) {
|
|
649
855
|
yield {
|
package/dist/index.js
CHANGED
|
@@ -1052,6 +1052,14 @@ var StreamResult = class {
|
|
|
1052
1052
|
}
|
|
1053
1053
|
if (event.requiresAction) {
|
|
1054
1054
|
collected.requiresAction = true;
|
|
1055
|
+
const resolvedToolIds = new Set(
|
|
1056
|
+
(event.messages ?? []).filter((m) => m.role === "tool" && m.tool_call_id).map((m) => m.tool_call_id)
|
|
1057
|
+
);
|
|
1058
|
+
if (resolvedToolIds.size > 0) {
|
|
1059
|
+
collected.toolCalls = collected.toolCalls.filter(
|
|
1060
|
+
(tc) => !resolvedToolIds.has(tc.id)
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1055
1063
|
}
|
|
1056
1064
|
if (event.usage) {
|
|
1057
1065
|
this.capturedUsage = event.usage;
|
|
@@ -2203,7 +2211,13 @@ var Runtime = class {
|
|
|
2203
2211
|
// Not used when rawMessages is provided
|
|
2204
2212
|
rawMessages: request.messages,
|
|
2205
2213
|
actions: nativeToolSearch ? void 0 : this.convertToolsToActions(effectiveSelectedTools),
|
|
2206
|
-
|
|
2214
|
+
// Always supplied. `actions` drives the Chat Completions path; adapters
|
|
2215
|
+
// that must use /v1/responses (native tool search, or gpt-5.4+ where
|
|
2216
|
+
// Chat Completions rejects function tools alongside reasoning) need the
|
|
2217
|
+
// richer ToolDefinition shape. Gating this on nativeToolSearch meant the
|
|
2218
|
+
// reasoning-model route could never see any tools, so it silently never
|
|
2219
|
+
// engaged and those requests still 400'd on Chat Completions.
|
|
2220
|
+
toolDefinitions: effectiveSelectedTools,
|
|
2207
2221
|
systemPrompt,
|
|
2208
2222
|
config: request.config,
|
|
2209
2223
|
signal,
|
|
@@ -2353,6 +2367,49 @@ var Runtime = class {
|
|
|
2353
2367
|
const clientToolCalls = toolCalls.filter(
|
|
2354
2368
|
(tc) => !serverToolIds.has(tc.id)
|
|
2355
2369
|
);
|
|
2370
|
+
const serverToolResultMessages = serverToolResults.map((tr) => {
|
|
2371
|
+
const aiContent = buildToolResultForAI(tr.tool, tr.result, tr.args);
|
|
2372
|
+
const content = typeof aiContent === "string" ? aiContent : JSON.stringify(serializeToolResultContent(aiContent));
|
|
2373
|
+
return {
|
|
2374
|
+
role: "tool",
|
|
2375
|
+
content,
|
|
2376
|
+
tool_call_id: tr.id
|
|
2377
|
+
};
|
|
2378
|
+
});
|
|
2379
|
+
if (serverToolResults.length > 0 && clientToolCalls.length > 0) {
|
|
2380
|
+
if (debug) {
|
|
2381
|
+
console.log(
|
|
2382
|
+
`[Copilot SDK] Mixed turn: ${serverToolResults.length} server + ${clientToolCalls.length} client tool(s). Suspending for client tools.`
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
const assistantMessage = {
|
|
2386
|
+
role: "assistant",
|
|
2387
|
+
content: accumulatedText || null,
|
|
2388
|
+
tool_calls: toolCalls.map((tc) => ({
|
|
2389
|
+
id: tc.id,
|
|
2390
|
+
type: "function",
|
|
2391
|
+
function: {
|
|
2392
|
+
name: tc.name,
|
|
2393
|
+
arguments: JSON.stringify(tc.args)
|
|
2394
|
+
},
|
|
2395
|
+
...tc.extra_content ? { extra_content: tc.extra_content } : {}
|
|
2396
|
+
}))
|
|
2397
|
+
};
|
|
2398
|
+
newMessages.push(assistantMessage);
|
|
2399
|
+
newMessages.push(...serverToolResultMessages);
|
|
2400
|
+
yield {
|
|
2401
|
+
type: "tool_calls",
|
|
2402
|
+
toolCalls: clientToolCalls,
|
|
2403
|
+
assistantMessage
|
|
2404
|
+
};
|
|
2405
|
+
yield {
|
|
2406
|
+
type: "done",
|
|
2407
|
+
requiresAction: true,
|
|
2408
|
+
messages: newMessages,
|
|
2409
|
+
usage: adapterUsage
|
|
2410
|
+
};
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2356
2413
|
if (serverToolResults.length > 0) {
|
|
2357
2414
|
if (debug) {
|
|
2358
2415
|
console.log(
|
|
@@ -2375,17 +2432,7 @@ var Runtime = class {
|
|
|
2375
2432
|
};
|
|
2376
2433
|
})
|
|
2377
2434
|
};
|
|
2378
|
-
const toolResultMessages =
|
|
2379
|
-
(tr) => {
|
|
2380
|
-
const aiContent = buildToolResultForAI(tr.tool, tr.result, tr.args);
|
|
2381
|
-
const content = typeof aiContent === "string" ? aiContent : JSON.stringify(serializeToolResultContent(aiContent));
|
|
2382
|
-
return {
|
|
2383
|
-
role: "tool",
|
|
2384
|
-
content,
|
|
2385
|
-
tool_call_id: tr.id
|
|
2386
|
-
};
|
|
2387
|
-
}
|
|
2388
|
-
);
|
|
2435
|
+
const toolResultMessages = serverToolResultMessages;
|
|
2389
2436
|
newMessages.push(assistantWithToolCalls);
|
|
2390
2437
|
newMessages.push(...toolResultMessages);
|
|
2391
2438
|
const messagesWithResults = [
|
|
@@ -2532,7 +2579,13 @@ var Runtime = class {
|
|
|
2532
2579
|
messages: [],
|
|
2533
2580
|
rawMessages: conversationMessages,
|
|
2534
2581
|
actions: nativeToolSearch ? void 0 : this.convertToolsToActions(effectiveSelectedTools),
|
|
2535
|
-
|
|
2582
|
+
// Always supplied. `actions` drives the Chat Completions path; adapters
|
|
2583
|
+
// that must use /v1/responses (native tool search, or gpt-5.4+ where
|
|
2584
|
+
// Chat Completions rejects function tools alongside reasoning) need the
|
|
2585
|
+
// richer ToolDefinition shape. Gating this on nativeToolSearch meant the
|
|
2586
|
+
// reasoning-model route could never see any tools, so it silently never
|
|
2587
|
+
// engaged and those requests still 400'd on Chat Completions.
|
|
2588
|
+
toolDefinitions: effectiveSelectedTools,
|
|
2536
2589
|
systemPrompt,
|
|
2537
2590
|
config: request.config,
|
|
2538
2591
|
signal,
|
|
@@ -2640,6 +2693,48 @@ var Runtime = class {
|
|
|
2640
2693
|
}
|
|
2641
2694
|
}
|
|
2642
2695
|
}
|
|
2696
|
+
const serverToolResultMessages = serverToolResults.map((tr) => {
|
|
2697
|
+
const aiContent = buildToolResultForAI(
|
|
2698
|
+
tr.tool,
|
|
2699
|
+
tr.result,
|
|
2700
|
+
tr.args
|
|
2701
|
+
);
|
|
2702
|
+
const content = typeof aiContent === "string" ? aiContent : JSON.stringify(serializeToolResultContent(aiContent));
|
|
2703
|
+
return {
|
|
2704
|
+
role: "tool",
|
|
2705
|
+
content,
|
|
2706
|
+
tool_call_id: tr.id
|
|
2707
|
+
};
|
|
2708
|
+
});
|
|
2709
|
+
if (serverToolResults.length > 0 && clientToolCalls.length > 0) {
|
|
2710
|
+
const assistantMessage = {
|
|
2711
|
+
role: "assistant",
|
|
2712
|
+
content: result.content || null,
|
|
2713
|
+
tool_calls: result.toolCalls.map((tc) => ({
|
|
2714
|
+
id: tc.id,
|
|
2715
|
+
type: "function",
|
|
2716
|
+
function: {
|
|
2717
|
+
name: tc.name,
|
|
2718
|
+
arguments: JSON.stringify(tc.args)
|
|
2719
|
+
},
|
|
2720
|
+
...tc.extra_content ? { extra_content: tc.extra_content } : {}
|
|
2721
|
+
}))
|
|
2722
|
+
};
|
|
2723
|
+
newMessages.push(assistantMessage);
|
|
2724
|
+
newMessages.push(...serverToolResultMessages);
|
|
2725
|
+
yield {
|
|
2726
|
+
type: "tool_calls",
|
|
2727
|
+
toolCalls: clientToolCalls,
|
|
2728
|
+
assistantMessage
|
|
2729
|
+
};
|
|
2730
|
+
yield {
|
|
2731
|
+
type: "done",
|
|
2732
|
+
requiresAction: true,
|
|
2733
|
+
messages: newMessages,
|
|
2734
|
+
usage: accumulatedUsage.total_tokens > 0 ? accumulatedUsage : void 0
|
|
2735
|
+
};
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2643
2738
|
if (serverToolResults.length > 0) {
|
|
2644
2739
|
const assistantWithToolCalls = {
|
|
2645
2740
|
role: "assistant",
|
|
@@ -2654,19 +2749,7 @@ var Runtime = class {
|
|
|
2654
2749
|
...tc.extra_content ? { extra_content: tc.extra_content } : {}
|
|
2655
2750
|
}))
|
|
2656
2751
|
};
|
|
2657
|
-
const toolResultMessages =
|
|
2658
|
-
const aiContent = buildToolResultForAI(
|
|
2659
|
-
tr.tool,
|
|
2660
|
-
tr.result,
|
|
2661
|
-
tr.args
|
|
2662
|
-
);
|
|
2663
|
-
const content = typeof aiContent === "string" ? aiContent : JSON.stringify(serializeToolResultContent(aiContent));
|
|
2664
|
-
return {
|
|
2665
|
-
role: "tool",
|
|
2666
|
-
content,
|
|
2667
|
-
tool_call_id: tr.id
|
|
2668
|
-
};
|
|
2669
|
-
});
|
|
2752
|
+
const toolResultMessages = serverToolResultMessages;
|
|
2670
2753
|
newMessages.push(assistantWithToolCalls);
|
|
2671
2754
|
newMessages.push(...toolResultMessages);
|
|
2672
2755
|
conversationMessages = [
|
|
@@ -3005,10 +3088,23 @@ var Runtime = class {
|
|
|
3005
3088
|
result: event.result || event.error
|
|
3006
3089
|
});
|
|
3007
3090
|
break;
|
|
3008
|
-
case "done":
|
|
3091
|
+
case "done": {
|
|
3009
3092
|
messages = event.messages || [];
|
|
3010
3093
|
requiresAction = event.requiresAction || false;
|
|
3094
|
+
if (requiresAction) {
|
|
3095
|
+
const resolvedToolIds = new Set(
|
|
3096
|
+
messages.filter((m) => m.role === "tool" && m.tool_call_id).map((m) => m.tool_call_id)
|
|
3097
|
+
);
|
|
3098
|
+
if (resolvedToolIds.size > 0) {
|
|
3099
|
+
for (let i = toolCalls.length - 1; i >= 0; i--) {
|
|
3100
|
+
if (resolvedToolIds.has(toolCalls[i].id)) {
|
|
3101
|
+
toolCalls.splice(i, 1);
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3011
3106
|
break;
|
|
3107
|
+
}
|
|
3012
3108
|
case "error":
|
|
3013
3109
|
error = { message: event.message, code: event.code };
|
|
3014
3110
|
break;
|