@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.
@@ -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
- if (normalized.additionalProperties === void 0) {
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
- return request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false && Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
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 = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : message.content ? JSON.stringify(message.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: message.role === "developer" ? "developer" : "user",
548
- content: typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : JSON.stringify(message.content ?? "")
614
+ role,
615
+ content: this.toResponsesContent(message.content, role)
549
616
  });
550
617
  }
551
618
  return input;
552
619
  }
553
- buildResponsesTools(tools) {
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
- return [{ type: "tool_search" }, ...nativeTools];
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 content = typeof response?.output_text === "string" ? response.output_text : "";
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
- async completeWithResponses(request) {
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: request.config?.model || this.model,
727
+ model: modelIdForPayload,
602
728
  instructions: request.systemPrompt,
603
729
  input: this.buildResponsesInput(request),
604
- tools: this.buildResponsesTools(request.toolDefinitions ?? []),
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
- temperature: request.config?.temperature ?? this.config.temperature,
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: false
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
- const result = await this.completeWithResponses(request);
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
- toolDefinitions: nativeToolSearch ? effectiveSelectedTools : void 0,
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 = serverToolResults.map(
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
- toolDefinitions: nativeToolSearch ? effectiveSelectedTools : void 0,
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 = serverToolResults.map((tr) => {
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;