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