@cydm/pie 2.7.0 → 2.7.2

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.
@@ -475,6 +475,7 @@ function calculateCost(model, usage) {
475
475
  const cacheRead = model.cost.cacheRead / 1e6 * usage.cacheRead;
476
476
  const cacheWrite = model.cost.cacheWrite / 1e6 * usage.cacheWrite;
477
477
  const cacheWriteLongTokens = usage.cacheWriteLong ?? 0;
478
+ const hasExplicitLongRate = model.cost.cacheWriteLong !== void 0;
478
479
  const cacheWriteLongRate = model.cost.cacheWriteLong ?? model.cost.input * 2;
479
480
  const cacheWriteLong = cacheWriteLongTokens > 0 ? cacheWriteLongRate / 1e6 * cacheWriteLongTokens : 0;
480
481
  return {
@@ -482,7 +483,10 @@ function calculateCost(model, usage) {
482
483
  output,
483
484
  cacheRead,
484
485
  cacheWrite,
485
- ...cacheWriteLongTokens > 0 ? { cacheWriteLong } : {},
486
+ ...cacheWriteLongTokens > 0 ? {
487
+ cacheWriteLong,
488
+ ...hasExplicitLongRate ? {} : { cacheWriteLongEstimated: true }
489
+ } : {},
486
490
  total: input + output + cacheRead + cacheWrite + cacheWriteLong
487
491
  };
488
492
  }
@@ -1872,6 +1876,8 @@ function getHttpAgent() {
1872
1876
  return globalHttpAgent;
1873
1877
  }
1874
1878
  var NodeHttpClient = class {
1879
+ /** The Node client never re-sends a request: one `request()` call is one submission. */
1880
+ transportCapabilities = { singleAttempt: true };
1875
1881
  async request(url, options = {}) {
1876
1882
  const requestStartTime = Date.now();
1877
1883
  const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
@@ -1884,6 +1890,10 @@ var NodeHttpClient = class {
1884
1890
  let tieredBodyByteSeen = false;
1885
1891
  let tierMarkBodyByte = null;
1886
1892
  let requestTimeoutError;
1893
+ const setTimeoutKind = (error, kind) => {
1894
+ if (!error) return;
1895
+ error.timeoutKind = kind;
1896
+ };
1887
1897
  const clearTieredFirstByteTimer = () => {
1888
1898
  if (tieredFirstByteTimer !== void 0) {
1889
1899
  clearTimeout(tieredFirstByteTimer);
@@ -1927,6 +1937,7 @@ var NodeHttpClient = class {
1927
1937
  res.on("timeout", () => {
1928
1938
  if (res.readableEnded || res.complete) return;
1929
1939
  requestTimeoutError = new Error(`Request timeout (stream idle) after ${tieredStreamIdleTimeoutMs}ms`);
1940
+ setTimeoutKind(requestTimeoutError, "stream_idle");
1930
1941
  logHttp("WARN", `Request [${reqTag}] stream idle timeout`, { idleTimeoutMs: tieredStreamIdleTimeoutMs });
1931
1942
  req.destroy(requestTimeoutError);
1932
1943
  });
@@ -2172,6 +2183,7 @@ var NodeHttpClient = class {
2172
2183
  if (!tieredStreamTimeout) {
2173
2184
  req.on("timeout", () => {
2174
2185
  requestTimeoutError = new Error("Request timeout");
2186
+ setTimeoutKind(requestTimeoutError, "total");
2175
2187
  logHttp("ERROR", `Request [${reqTag}] timeout`);
2176
2188
  req.destroy(requestTimeoutError);
2177
2189
  });
@@ -2180,6 +2192,7 @@ var NodeHttpClient = class {
2180
2192
  tieredFirstByteTimer = setTimeout(() => {
2181
2193
  tieredFirstByteTimer = void 0;
2182
2194
  requestTimeoutError = new Error(`Request timeout (first byte) after ${tieredFirstByteTimeoutMs}ms`);
2195
+ setTimeoutKind(requestTimeoutError, "first_byte");
2183
2196
  logHttp("WARN", `Request [${reqTag}] first byte timeout`, { firstByteTimeoutMs: tieredFirstByteTimeoutMs });
2184
2197
  req.destroy(requestTimeoutError);
2185
2198
  }, tieredFirstByteTimeoutMs);
@@ -2194,6 +2207,8 @@ var NodeHttpClient = class {
2194
2207
  }
2195
2208
  };
2196
2209
  var PuerTSHttpClient = class {
2210
+ /** No internal re-send: each call starts exactly one bridge request. */
2211
+ transportCapabilities = { singleAttempt: true };
2197
2212
  async request(url, options = {}) {
2198
2213
  logHttp("DEBUG", `PuerTS Request started`, {
2199
2214
  url: url.slice(0, 100),
@@ -3316,6 +3331,9 @@ function getHttpClient() {
3316
3331
  }
3317
3332
 
3318
3333
  // ../../packages/ai/src/utils/http.ts
3334
+ function getHttpTransport() {
3335
+ return getHttpClient();
3336
+ }
3319
3337
  function httpRequest(url, options = {}) {
3320
3338
  const client = getHttpClient();
3321
3339
  return client.request(url, options);
@@ -3357,8 +3375,13 @@ function buildBaseOptions(model, options, apiKey) {
3357
3375
  sessionId: options?.sessionId,
3358
3376
  headers: options?.headers,
3359
3377
  timeoutMs: options?.timeoutMs,
3378
+ firstResponseTimeoutMs: options?.firstResponseTimeoutMs,
3379
+ streamIdleTimeoutMs: options?.streamIdleTimeoutMs,
3380
+ requestTimeoutMs: options?.requestTimeoutMs,
3381
+ connectionIdleTimeoutMs: options?.connectionIdleTimeoutMs,
3360
3382
  onPayload: options?.onPayload,
3361
3383
  onTransportDiagnostic: options?.onTransportDiagnostic,
3384
+ onTransportLifecycle: options?.onTransportLifecycle,
3362
3385
  maxRetryDelayMs: options?.maxRetryDelayMs,
3363
3386
  metadata: options?.metadata
3364
3387
  };
@@ -4411,6 +4434,35 @@ import Anthropic from "@anthropic-ai/sdk";
4411
4434
  function sanitizeSurrogates(text) {
4412
4435
  return text.replace(/[\uD800-\uDFFF]/g, "");
4413
4436
  }
4437
+ function applyAnthropicUsageUpdate(model, usage, payload) {
4438
+ if (payload.input_tokens != null) usage.input = Math.max(0, payload.input_tokens);
4439
+ if (payload.output_tokens != null) usage.output = Math.max(0, payload.output_tokens);
4440
+ if (payload.cache_read_input_tokens != null) usage.cacheRead = Math.max(0, payload.cache_read_input_tokens);
4441
+ const reportedWriteTotal = payload.cache_creation_input_tokens;
4442
+ const breakdown = payload.cache_creation ?? void 0;
4443
+ const breakdownTotal = (breakdown?.ephemeral_5m_input_tokens ?? 0) + (breakdown?.ephemeral_1h_input_tokens ?? 0);
4444
+ if (reportedWriteTotal == null && breakdown !== void 0 && breakdownTotal === 0) {
4445
+ usage.cacheWrite = Math.max(0, usage.cacheWrite);
4446
+ } else if (reportedWriteTotal != null || breakdown !== void 0) {
4447
+ const previousLong = usage.cacheWriteLong ?? 0;
4448
+ const previousTotal = usage.cacheWrite + previousLong;
4449
+ const writeTotal = Math.max(
4450
+ 0,
4451
+ reportedWriteTotal != null ? reportedWriteTotal : breakdownTotal > 0 ? breakdownTotal : previousTotal
4452
+ );
4453
+ const longTokens = breakdown?.ephemeral_1h_input_tokens ?? 0;
4454
+ if (longTokens > 0) {
4455
+ const long = Math.min(longTokens, writeTotal);
4456
+ usage.cacheWriteLong = long;
4457
+ usage.cacheWrite = writeTotal - long;
4458
+ } else {
4459
+ delete usage.cacheWriteLong;
4460
+ usage.cacheWrite = writeTotal;
4461
+ }
4462
+ }
4463
+ usage.totalTokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite + (usage.cacheWriteLong ?? 0);
4464
+ usage.cost = calculateCost(model, usage);
4465
+ }
4414
4466
  var ANTHROPIC_PROVIDER_HEADERS = {
4415
4467
  accept: "application/json",
4416
4468
  "anthropic-dangerous-direct-browser-access": "true",
@@ -4696,17 +4748,7 @@ var streamAnthropic = (model, context, options) => {
4696
4748
  const blocks = output.content;
4697
4749
  for await (const event of anthropicStream) {
4698
4750
  if (event.type === "message_start") {
4699
- output.usage.input = event.message.usage.input_tokens || 0;
4700
- output.usage.output = event.message.usage.output_tokens || 0;
4701
- output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;
4702
- output.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;
4703
- const creation = event.message.usage.cache_creation;
4704
- if (creation?.ephemeral_1h_input_tokens) {
4705
- output.usage.cacheWriteLong = creation.ephemeral_1h_input_tokens;
4706
- output.usage.cacheWrite = Math.max(0, output.usage.cacheWrite - creation.ephemeral_1h_input_tokens);
4707
- }
4708
- output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite + (output.usage.cacheWriteLong ?? 0);
4709
- calculateCost(model, output.usage);
4751
+ applyAnthropicUsageUpdate(model, output.usage, event.message.usage);
4710
4752
  } else if (event.type === "content_block_start") {
4711
4753
  if (event.content_block.type === "text") {
4712
4754
  const block = { type: "text", text: "", index: event.index };
@@ -4810,19 +4852,7 @@ var streamAnthropic = (model, context, options) => {
4810
4852
  if (event.delta.stop_reason) {
4811
4853
  output.stopReason = mapStopReason(event.delta.stop_reason);
4812
4854
  }
4813
- if (event.usage.input_tokens != null) output.usage.input = event.usage.input_tokens;
4814
- if (event.usage.output_tokens != null) output.usage.output = event.usage.output_tokens;
4815
- if (event.usage.cache_read_input_tokens != null) output.usage.cacheRead = event.usage.cache_read_input_tokens;
4816
- if (event.usage.cache_creation_input_tokens != null) {
4817
- output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
4818
- const creationDelta = event.usage.cache_creation;
4819
- if (creationDelta?.ephemeral_1h_input_tokens) {
4820
- output.usage.cacheWriteLong = creationDelta.ephemeral_1h_input_tokens;
4821
- output.usage.cacheWrite = Math.max(0, output.usage.cacheWrite - creationDelta.ephemeral_1h_input_tokens);
4822
- }
4823
- }
4824
- output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite + (output.usage.cacheWriteLong ?? 0);
4825
- calculateCost(model, output.usage);
4855
+ applyAnthropicUsageUpdate(model, output.usage, event.usage);
4826
4856
  }
4827
4857
  }
4828
4858
  if (options?.signal?.aborted) {
@@ -4868,7 +4898,10 @@ function getResponsesCompat(model) {
4868
4898
  supportsStore: compat.supportsStore ?? true,
4869
4899
  supportsReasoningEffort: compat.supportsReasoningEffort ?? true,
4870
4900
  supportsEncryptedReasoning: compat.supportsEncryptedReasoning ?? true,
4871
- responsesContinuation: compat.responsesContinuation ?? false
4901
+ responsesContinuation: compat.responsesContinuation ?? false,
4902
+ supportsReasoningSummary: compat.supportsReasoningSummary ?? false,
4903
+ reasoningSummary: compat.reasoningSummary ?? "auto",
4904
+ timeoutReplay: compat.timeoutReplay ?? false
4872
4905
  };
4873
4906
  }
4874
4907
 
@@ -5055,6 +5088,10 @@ function buildResponsesRequest(model, context, options, compat = getResponsesCom
5055
5088
  if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
5056
5089
  params.reasoning = { effort: options.reasoningEffort };
5057
5090
  }
5091
+ if (model.reasoning && compat.supportsReasoningSummary && compat.reasoningSummary !== "none") {
5092
+ params.reasoning = params.reasoning ?? {};
5093
+ params.reasoning.summary = compat.reasoningSummary;
5094
+ }
5058
5095
  if (compat.supportsEncryptedReasoning && model.reasoning) {
5059
5096
  params.include = ["reasoning.encrypted_content"];
5060
5097
  }
@@ -5567,10 +5604,28 @@ function reportSubmission(options, body, details) {
5567
5604
  } catch {
5568
5605
  }
5569
5606
  }
5607
+ function reportLifecycle(options, event) {
5608
+ if (!options?.onTransportLifecycle) return;
5609
+ try {
5610
+ options.onTransportLifecycle({ ...event });
5611
+ } catch {
5612
+ }
5613
+ }
5614
+ var localRequestCounter = 0;
5615
+ function nextLocalRequestId(prefix = "req") {
5616
+ localRequestCounter += 1;
5617
+ return `${prefix}-${localRequestCounter}-${Math.random().toString(36).slice(2, 8)}`;
5618
+ }
5619
+ function monotonicNow() {
5620
+ const perf = globalThis.performance;
5621
+ return typeof perf?.now === "function" ? perf.now() : Date.now();
5622
+ }
5570
5623
 
5571
5624
  // ../../packages/ai/src/providers/responses/responses-sse.ts
5572
5625
  var DEFAULT_RESPONSES_STREAM_IDLE_TIMEOUT_MS = 2 * 60 * 1e3;
5573
5626
  var DEFAULT_RESPONSES_FIRST_BYTE_TIMEOUT_MS = 3 * 60 * 1e3;
5627
+ var REASONING_STREAM_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
5628
+ var REASONING_FIRST_BYTE_TIMEOUT_MS = 5 * 60 * 1e3;
5574
5629
  function parseSSEDataLine2(line) {
5575
5630
  const trimmed = line.trim();
5576
5631
  if (!trimmed || trimmed.startsWith(":")) return null;
@@ -5578,15 +5633,137 @@ function parseSSEDataLine2(line) {
5578
5633
  const data = trimmed.slice(5);
5579
5634
  return data.startsWith(" ") ? data.slice(1) : data;
5580
5635
  }
5636
+ function singleAttemptSupport() {
5637
+ let capabilities;
5638
+ try {
5639
+ capabilities = getHttpTransport().transportCapabilities;
5640
+ } catch {
5641
+ capabilities = void 0;
5642
+ }
5643
+ return capabilities?.singleAttempt === true ? "verified" : "unverified";
5644
+ }
5645
+ function assertSingleAttempt() {
5646
+ if (singleAttemptSupport() === "verified") return;
5647
+ throw new ResponsesProtocolError(
5648
+ "Responses SSE requires the platform HTTP adapter to declare transportCapabilities.singleAttempt; refusing to submit because a submitted generation must not be re-sent by the transport."
5649
+ );
5650
+ }
5651
+ function explicitBudget(value, field) {
5652
+ if (value === void 0) return void 0;
5653
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
5654
+ throw new ResponsesProtocolError(`Invalid ${field}: expected a finite positive number of milliseconds.`);
5655
+ }
5656
+ return value;
5657
+ }
5658
+ function resolveSSEBudgets(options, model) {
5659
+ const firstResponse = explicitBudget(options?.firstResponseTimeoutMs, "firstResponseTimeoutMs");
5660
+ const streamIdle = explicitBudget(options?.streamIdleTimeoutMs, "streamIdleTimeoutMs");
5661
+ const requestTimeoutMs = explicitBudget(options?.requestTimeoutMs, "requestTimeoutMs");
5662
+ explicitBudget(options?.connectionIdleTimeoutMs, "connectionIdleTimeoutMs");
5663
+ const reasoning = model.reasoning === true;
5664
+ const defaultFirstByte = reasoning ? REASONING_FIRST_BYTE_TIMEOUT_MS : DEFAULT_RESPONSES_FIRST_BYTE_TIMEOUT_MS;
5665
+ const defaultIdle = reasoning ? REASONING_STREAM_IDLE_TIMEOUT_MS : DEFAULT_RESPONSES_STREAM_IDLE_TIMEOUT_MS;
5666
+ if (firstResponse !== void 0 || streamIdle !== void 0) {
5667
+ return {
5668
+ http: {
5669
+ firstByteTimeoutMs: firstResponse ?? defaultFirstByte,
5670
+ streamIdleTimeoutMs: streamIdle ?? defaultIdle
5671
+ },
5672
+ requestTimeoutMs
5673
+ };
5674
+ }
5675
+ if (options?.timeoutMs !== void 0) return { http: { timeoutMs: options.timeoutMs }, requestTimeoutMs };
5676
+ return {
5677
+ http: {
5678
+ firstByteTimeoutMs: defaultFirstByte,
5679
+ streamIdleTimeoutMs: defaultIdle
5680
+ },
5681
+ requestTimeoutMs
5682
+ };
5683
+ }
5581
5684
  function streamResponsesSSE(model, context, options) {
5582
5685
  const stream = new AssistantMessageEventStream();
5583
5686
  void (async () => {
5584
5687
  const reducer = new ResponsesEventReducer(model);
5688
+ const localRequestId = nextLocalRequestId("sse");
5689
+ const startedAt = monotonicNow();
5690
+ let lastReceiveAt = startedAt;
5691
+ let receivedMessageCount = 0;
5692
+ let sawModelContent = false;
5693
+ let submitted = false;
5694
+ let terminalAccepted = false;
5695
+ let phase = "request_start";
5696
+ let budgets = {};
5697
+ let totalTimer;
5698
+ let internalTimeout;
5699
+ let localFailure = false;
5700
+ const requestAbort = new AbortController();
5701
+ const onUserAbort = () => requestAbort.abort();
5702
+ const clearTotalTimer = () => {
5703
+ if (totalTimer !== void 0) {
5704
+ clearTimeout(totalTimer);
5705
+ totalTimer = void 0;
5706
+ }
5707
+ };
5708
+ const lifecycle = (extra = {}) => reportLifecycle(options, {
5709
+ ...extra,
5710
+ transport: "sse",
5711
+ localRequestId,
5712
+ requestGeneration: 1,
5713
+ reusedConnection: false,
5714
+ phase,
5715
+ requestAgeMs: Math.round(monotonicNow() - startedAt),
5716
+ lastReceiveAgeMs: Math.round(monotonicNow() - lastReceiveAt),
5717
+ budgets,
5718
+ receivedMessageCount,
5719
+ sawModelContent,
5720
+ submissionAttempted: submitted,
5721
+ terminalAccepted,
5722
+ singleAttempt: singleAttemptSupport()
5723
+ });
5585
5724
  try {
5725
+ let resolved;
5726
+ try {
5727
+ resolved = resolveSSEBudgets(options, model);
5728
+ } catch (error) {
5729
+ localFailure = true;
5730
+ throw error;
5731
+ }
5732
+ budgets = {
5733
+ firstResponseTimeoutMs: resolved.http.firstByteTimeoutMs ?? resolved.http.timeoutMs,
5734
+ streamIdleTimeoutMs: resolved.http.streamIdleTimeoutMs ?? resolved.http.timeoutMs,
5735
+ requestTimeoutMs: resolved.requestTimeoutMs
5736
+ };
5737
+ options?.signal?.addEventListener("abort", onUserAbort, { once: true });
5738
+ if (options?.signal?.aborted) requestAbort.abort();
5739
+ if (resolved.requestTimeoutMs !== void 0) {
5740
+ totalTimer = setTimeout(() => {
5741
+ totalTimer = void 0;
5742
+ if (terminalAccepted || requestAbort.signal.aborted) return;
5743
+ internalTimeout = "request_total";
5744
+ phase = "timeout";
5745
+ lifecycle({
5746
+ timeoutKind: "request_total",
5747
+ terminalAccepted: false,
5748
+ cancelSource: "internal_timeout",
5749
+ retryAllowed: !submitted,
5750
+ retryDecision: submitted ? "not_retryable_after_submission" : "not_submitted"
5751
+ });
5752
+ requestAbort.abort();
5753
+ }, resolved.requestTimeoutMs);
5754
+ totalTimer.unref?.();
5755
+ }
5756
+ lifecycle();
5586
5757
  const apiKey = options?.apiKey || getEnvApiKey(model.provider, model.apiKeyEnv) || "";
5587
5758
  if (!apiKey) {
5588
5759
  throw new Error(`No API key for provider: ${model.provider}`);
5589
5760
  }
5761
+ try {
5762
+ assertSingleAttempt();
5763
+ } catch (error) {
5764
+ localFailure = true;
5765
+ throw error;
5766
+ }
5590
5767
  const payload = buildResponsesSSEPayload(model, context, options);
5591
5768
  options?.onPayload?.(payload);
5592
5769
  const headers = {
@@ -5598,15 +5775,17 @@ function streamResponsesSSE(model, context, options) {
5598
5775
  };
5599
5776
  const body = JSON.stringify(payload);
5600
5777
  reportSubmission(options, body, { transport: "sse", reusedConnection: false, continuation: false, attempt: 1 });
5778
+ submitted = true;
5779
+ phase = "submission_attempted";
5780
+ lifecycle();
5601
5781
  const response = await httpRequest(`${model.baseUrl}/responses`, {
5602
5782
  method: "POST",
5603
5783
  headers,
5604
5784
  body,
5605
- signal: options?.signal,
5606
- ...options?.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {
5607
- firstByteTimeoutMs: DEFAULT_RESPONSES_FIRST_BYTE_TIMEOUT_MS,
5608
- streamIdleTimeoutMs: DEFAULT_RESPONSES_STREAM_IDLE_TIMEOUT_MS
5609
- },
5785
+ signal: requestAbort.signal,
5786
+ // The transport must not re-send a submitted generation.
5787
+ retryPolicy: "never",
5788
+ ...resolved.http,
5610
5789
  label: `${model.provider}/${model.id}`
5611
5790
  });
5612
5791
  if (!response.ok) {
@@ -5616,9 +5795,15 @@ function streamResponsesSSE(model, context, options) {
5616
5795
  if (!response.readSSELines) {
5617
5796
  throw new Error("SSE streaming not supported on this platform");
5618
5797
  }
5619
- for await (const line of response.readSSELines(options?.signal)) {
5798
+ for await (const line of response.readSSELines(requestAbort.signal)) {
5799
+ receivedMessageCount += 1;
5800
+ lastReceiveAt = monotonicNow();
5620
5801
  const data = parseSSEDataLine2(line);
5621
5802
  if (data === null) continue;
5803
+ if (phase === "submission_attempted") {
5804
+ phase = "first_application_message";
5805
+ lifecycle();
5806
+ }
5622
5807
  if (data === "[DONE]") {
5623
5808
  continue;
5624
5809
  }
@@ -5632,35 +5817,59 @@ function streamResponsesSSE(model, context, options) {
5632
5817
  if (!isResponsesEventLike(event)) {
5633
5818
  throw new ResponsesProtocolError("Responses SSE payload is not an event object with a string type.");
5634
5819
  }
5820
+ if (typeof event.type === "string" && (event.type.startsWith("response.output") || event.type.startsWith("response.content") || event.type.startsWith("response.reasoning"))) {
5821
+ sawModelContent = true;
5822
+ }
5635
5823
  for (const assistantEvent of reducer.feed(event)) {
5636
5824
  stream.push(assistantEvent);
5637
5825
  }
5638
- if (reducer.isTerminal) break;
5826
+ if (reducer.isTerminal) {
5827
+ terminalAccepted = true;
5828
+ phase = "terminal_accepted";
5829
+ clearTotalTimer();
5830
+ lifecycle({ retryAllowed: reducer.message.retryable !== false, retryDecision: "terminal" });
5831
+ break;
5832
+ }
5639
5833
  }
5640
5834
  if (reducer.isTerminal) {
5641
5835
  stream.end();
5642
5836
  return;
5643
5837
  }
5644
- if (options?.signal?.aborted) {
5645
- throw new Error("Request was aborted");
5646
- }
5647
- if (!reducer.isTerminal) {
5648
- throw reducer.finishWithoutTerminal();
5838
+ if (requestAbort.signal.aborted) {
5839
+ throw new Error(internalTimeout ? `Responses request timeout (${internalTimeout})` : "Request was aborted");
5649
5840
  }
5650
- if (reducer.message.stopReason === "error" || reducer.message.stopReason === "aborted") {
5651
- throw new Error(reducer.message.errorMessage ?? "An unknown error occurred");
5652
- }
5653
- stream.end();
5841
+ throw reducer.finishWithoutTerminal();
5654
5842
  } catch (error) {
5655
5843
  if (reducer.isTerminal) {
5656
5844
  stream.end();
5657
5845
  return;
5658
5846
  }
5659
5847
  const message = reducer.message;
5660
- message.stopReason = options?.signal?.aborted ? "aborted" : "error";
5661
- message.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
5848
+ const userAborted = options?.signal?.aborted === true;
5849
+ message.stopReason = userAborted ? "aborted" : "error";
5850
+ const adapterTimeoutKind = error instanceof Error ? error.timeoutKind : void 0;
5851
+ if (!internalTimeout && (adapterTimeoutKind === "first_byte" || adapterTimeoutKind === "stream_idle")) {
5852
+ internalTimeout = adapterTimeoutKind === "first_byte" ? "first_response" : "stream_idle";
5853
+ }
5854
+ message.errorMessage = internalTimeout ? `Responses request timeout (${internalTimeout}) after ${Math.round(monotonicNow() - startedAt)}ms` : error instanceof Error ? error.message : JSON.stringify(error);
5855
+ if (internalTimeout && getResponsesCompat(model).timeoutReplay) {
5856
+ message.replayEligible = true;
5857
+ message.replayClass = internalTimeout;
5858
+ }
5859
+ if (submitted || localFailure) message.retryable = false;
5860
+ phase = "cleanup";
5861
+ lifecycle({
5862
+ timeoutKind: internalTimeout,
5863
+ retryAllowed: !submitted && !userAborted && !localFailure,
5864
+ retryDecision: submitted ? "not_retryable_after_submission" : localFailure ? "local_configuration_error" : "not_submitted",
5865
+ cancelSource: userAborted ? "user" : internalTimeout ? "internal_timeout" : "none",
5866
+ cleanupOutcome: "request_abandoned"
5867
+ });
5662
5868
  stream.push({ type: "error", reason: message.stopReason, error: message });
5663
5869
  stream.end();
5870
+ } finally {
5871
+ clearTotalTimer();
5872
+ options?.signal?.removeEventListener("abort", onUserAbort);
5664
5873
  }
5665
5874
  })();
5666
5875
  return stream;
@@ -5669,26 +5878,80 @@ function streamResponsesSSE(model, context, options) {
5669
5878
  // ../../packages/ai/src/providers/responses/responses-ws.ts
5670
5879
  var owners = /* @__PURE__ */ new WeakMap();
5671
5880
  var MAX_ANCHOR_BYTES = 1024 * 1024;
5881
+ var DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
5882
+ var DEFAULT_FIRST_RESPONSE_TIMEOUT_MS = 2 * 60 * 1e3;
5883
+ var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 2 * 60 * 1e3;
5884
+ var DEFAULT_CONNECTION_IDLE_TIMEOUT_MS = 2 * 60 * 1e3;
5885
+ var REASONING_FIRST_RESPONSE_TIMEOUT_MS = 5 * 60 * 1e3;
5886
+ var REASONING_STREAM_IDLE_TIMEOUT_MS2 = 5 * 60 * 1e3;
5887
+ var ITERATOR_CLEANUP_TIMEOUT_MS = 1e3;
5888
+ function explicitBudget2(value, field) {
5889
+ if (value === void 0) return void 0;
5890
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
5891
+ throw new ResponsesProtocolError(`Invalid ${field}: expected a finite positive number of milliseconds.`);
5892
+ }
5893
+ return value;
5894
+ }
5895
+ function resolveBudgets(options, model) {
5896
+ const firstResponse = explicitBudget2(options?.firstResponseTimeoutMs, "firstResponseTimeoutMs");
5897
+ const streamIdle = explicitBudget2(options?.streamIdleTimeoutMs, "streamIdleTimeoutMs");
5898
+ const requestTimeout = explicitBudget2(options?.requestTimeoutMs, "requestTimeoutMs");
5899
+ const connectionIdle = explicitBudget2(options?.connectionIdleTimeoutMs, "connectionIdleTimeoutMs");
5900
+ const legacy = options?.timeoutMs !== void 0 && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : void 0;
5901
+ const reasoning = model.reasoning === true;
5902
+ return {
5903
+ connectTimeoutMs: legacy ?? DEFAULT_CONNECT_TIMEOUT_MS,
5904
+ firstResponseTimeoutMs: firstResponse ?? legacy ?? (reasoning ? REASONING_FIRST_RESPONSE_TIMEOUT_MS : DEFAULT_FIRST_RESPONSE_TIMEOUT_MS),
5905
+ streamIdleTimeoutMs: streamIdle ?? legacy ?? (reasoning ? REASONING_STREAM_IDLE_TIMEOUT_MS2 : DEFAULT_STREAM_IDLE_TIMEOUT_MS),
5906
+ requestTimeoutMs: requestTimeout,
5907
+ connectionIdleTimeoutMs: connectionIdle ?? DEFAULT_CONNECTION_IDLE_TIMEOUT_MS
5908
+ };
5909
+ }
5672
5910
  function disposeModelTransports(owner) {
5673
5911
  const state = owners.get(owner);
5674
5912
  if (!state) return;
5675
5913
  disposeState(owner, state);
5676
5914
  }
5915
+ function clearConnectionIdleTimer(state) {
5916
+ if (state.idleTimer !== void 0) {
5917
+ clearTimeout(state.idleTimer);
5918
+ state.idleTimer = void 0;
5919
+ }
5920
+ }
5677
5921
  function disposeState(owner, state) {
5678
5922
  if (owners.get(owner) === state) owners.delete(owner);
5923
+ clearConnectionIdleTimer(state);
5679
5924
  state.anchor = void 0;
5680
5925
  state.abort.abort();
5681
- void state.connection?.close().catch(() => {
5926
+ const connection = state.connection;
5927
+ state.connection = void 0;
5928
+ void connection?.close().catch(() => {
5682
5929
  });
5683
5930
  }
5684
5931
  function stateFor(owner) {
5685
5932
  let state = owners.get(owner);
5686
5933
  if (!state) {
5687
- state = { busy: false, abort: new AbortController() };
5934
+ state = { busy: false, abort: new AbortController(), requestGeneration: 0, connectionGeneration: 0 };
5688
5935
  owners.set(owner, state);
5689
5936
  }
5690
5937
  return state;
5691
5938
  }
5939
+ function armConnectionIdleRecycle(state, connection, generation, timeoutMs) {
5940
+ clearConnectionIdleTimer(state);
5941
+ if (!(timeoutMs > 0)) return;
5942
+ const timer = setTimeout(() => {
5943
+ if (state.idleTimer !== timer) return;
5944
+ state.idleTimer = void 0;
5945
+ if (state.busy || state.connectionGeneration !== generation || state.connection !== connection) return;
5946
+ state.connection = void 0;
5947
+ state.anchor = void 0;
5948
+ state.identity = void 0;
5949
+ void connection.close().catch(() => {
5950
+ });
5951
+ }, timeoutMs);
5952
+ timer.unref?.();
5953
+ state.idleTimer = timer;
5954
+ }
5692
5955
  function websocketUrl(base) {
5693
5956
  const url = new URL(`${base.replace(/\/$/, "")}/responses`);
5694
5957
  if (url.protocol !== "https:" && url.protocol !== "http:") throw new ResponsesProtocolError("Responses requires an HTTP(S) API root.");
@@ -5706,19 +5969,193 @@ function streamResponsesWS(model, context, options) {
5706
5969
  stream.end();
5707
5970
  return stream;
5708
5971
  }
5972
+ let budgets;
5973
+ try {
5974
+ budgets = resolveBudgets(options, model);
5975
+ } catch (error) {
5976
+ reducer.message.stopReason = "error";
5977
+ reducer.message.errorMessage = error instanceof Error ? error.message : String(error);
5978
+ reducer.message.retryable = false;
5979
+ stream.push({ type: "error", reason: "error", error: reducer.message });
5980
+ stream.end();
5981
+ return stream;
5982
+ }
5709
5983
  state.busy = true;
5984
+ const localRequestId = nextLocalRequestId("ws");
5985
+ const requestGeneration = ++state.requestGeneration;
5986
+ const startedAt = monotonicNow();
5987
+ let lastReceiveAt = startedAt;
5988
+ let receivedMessageCount = 0;
5989
+ let sawResponseCreated = false;
5990
+ let sawModelContent = false;
5991
+ let reusedConnection = false;
5992
+ let connectionGeneration = state.connectionGeneration;
5710
5993
  void (async () => {
5711
5994
  let sent = false;
5712
5995
  let finished = false;
5713
5996
  let terminalEvents = [];
5714
5997
  let fallback = false;
5998
+ let timedOut;
5999
+ let retryDecision = "none";
6000
+ let requestTimer;
6001
+ let totalTimer;
6002
+ let receiveCleanupFailed = false;
6003
+ const compat = getResponsesCompat(model);
6004
+ let cleanupCancelSource;
6005
+ const requestAbort = new AbortController();
5715
6006
  const publish = (event) => {
5716
6007
  if (event.type === "done" || event.type === "error") terminalEvents.push(event);
5717
6008
  else stream.push(event);
5718
6009
  };
6010
+ const ageMs = () => Math.round(monotonicNow() - startedAt);
6011
+ const lastReceiveAgeMs = () => Math.round(monotonicNow() - lastReceiveAt);
6012
+ const lifecycle = (phase, extra = {}) => reportLifecycle(options, {
6013
+ ...extra,
6014
+ transport: "websocket",
6015
+ localRequestId,
6016
+ requestGeneration,
6017
+ connectionGeneration,
6018
+ reusedConnection,
6019
+ phase,
6020
+ requestAgeMs: ageMs(),
6021
+ lastReceiveAgeMs: lastReceiveAgeMs(),
6022
+ budgets,
6023
+ receivedMessageCount,
6024
+ sawResponseCreated,
6025
+ sawModelContent,
6026
+ // The WS transport never re-sends a submitted frame (no hidden retry).
6027
+ singleAttempt: "verified"
6028
+ });
6029
+ const clearRequestTimers = () => {
6030
+ if (requestTimer !== void 0) {
6031
+ clearTimeout(requestTimer);
6032
+ requestTimer = void 0;
6033
+ }
6034
+ if (totalTimer !== void 0) {
6035
+ clearTimeout(totalTimer);
6036
+ totalTimer = void 0;
6037
+ }
6038
+ };
6039
+ const armTotalTimer = () => {
6040
+ if (budgets.requestTimeoutMs === void 0 || totalTimer !== void 0) return;
6041
+ const generation = requestGeneration;
6042
+ totalTimer = setTimeout(() => {
6043
+ totalTimer = void 0;
6044
+ if (generation !== requestGeneration || finished || requestAbort.signal.aborted) return;
6045
+ timedOut = "request_total";
6046
+ retryDecision = sent ? "not_retryable_after_submission" : "not_submitted";
6047
+ lifecycle("timeout", {
6048
+ timeoutKind: "request_total",
6049
+ terminalAccepted: false,
6050
+ cancelSource: "internal_timeout",
6051
+ retryAllowed: !sent,
6052
+ retryDecision,
6053
+ submissionAttempted: sent
6054
+ });
6055
+ requestAbort.abort();
6056
+ }, budgets.requestTimeoutMs);
6057
+ totalTimer.unref?.();
6058
+ };
6059
+ const remainingRequestBudget = () => {
6060
+ if (budgets.requestTimeoutMs === void 0) return void 0;
6061
+ return Math.max(0, Math.floor(budgets.requestTimeoutMs - ageMs()));
6062
+ };
6063
+ let fallbackAbort;
6064
+ let detachFallbackAbort;
6065
+ const fallbackSignal = () => {
6066
+ if (!fallbackAbort) {
6067
+ const controller = new AbortController();
6068
+ const onAbort = () => controller.abort();
6069
+ options?.signal?.addEventListener("abort", onAbort, { once: true });
6070
+ state.abort.signal.addEventListener("abort", onAbort, { once: true });
6071
+ if (options?.signal?.aborted || state.abort.signal.aborted) controller.abort();
6072
+ fallbackAbort = controller;
6073
+ detachFallbackAbort = () => {
6074
+ options?.signal?.removeEventListener("abort", onAbort);
6075
+ state.abort.signal.removeEventListener("abort", onAbort);
6076
+ };
6077
+ }
6078
+ return fallbackAbort.signal;
6079
+ };
6080
+ const closeReceiveIterator = async (iterator) => {
6081
+ const returnFn = iterator.return;
6082
+ if (typeof returnFn !== "function") return;
6083
+ let timer;
6084
+ try {
6085
+ await Promise.race([
6086
+ Promise.resolve().then(() => returnFn.call(iterator)),
6087
+ new Promise((_, reject) => {
6088
+ timer = setTimeout(() => reject(new Error("bounded iterator cleanup")), ITERATOR_CLEANUP_TIMEOUT_MS);
6089
+ timer.unref?.();
6090
+ })
6091
+ ]);
6092
+ } catch {
6093
+ receiveCleanupFailed = true;
6094
+ state.connection = void 0;
6095
+ state.anchor = void 0;
6096
+ requestAbort.abort();
6097
+ } finally {
6098
+ if (timer !== void 0) clearTimeout(timer);
6099
+ }
6100
+ };
6101
+ const armPhaseTimer = (kind, timeoutMs) => {
6102
+ if (requestTimer !== void 0) {
6103
+ clearTimeout(requestTimer);
6104
+ requestTimer = void 0;
6105
+ }
6106
+ if (timeoutMs === void 0 || timeoutMs <= 0 || finished) return;
6107
+ const generation = requestGeneration;
6108
+ requestTimer = setTimeout(() => {
6109
+ requestTimer = void 0;
6110
+ if (generation !== requestGeneration || finished || requestAbort.signal.aborted) return;
6111
+ timedOut = kind;
6112
+ retryDecision = sent ? "not_retryable_after_submission" : "not_submitted";
6113
+ lifecycle("timeout", {
6114
+ timeoutKind: kind,
6115
+ terminalAccepted: false,
6116
+ cancelSource: "internal_timeout",
6117
+ retryAllowed: !sent,
6118
+ retryDecision,
6119
+ submissionAttempted: sent,
6120
+ // L2 diagnostic surface: this timeout qualifies for bounded replay.
6121
+ ...compat.timeoutReplay && sent ? { replayExempt: true } : {}
6122
+ });
6123
+ requestAbort.abort();
6124
+ }, timeoutMs);
6125
+ requestTimer.unref?.();
6126
+ };
6127
+ const onOwnerAbort = () => requestAbort.abort();
6128
+ if (state.abort.signal.aborted) requestAbort.abort();
6129
+ else state.abort.signal.addEventListener("abort", onOwnerAbort, { once: true });
5719
6130
  const abort = () => disposeState(owner, state);
5720
6131
  options?.signal?.addEventListener("abort", abort, { once: true });
6132
+ const runSSEFallback = async () => {
6133
+ const remaining = remainingRequestBudget();
6134
+ if (remaining !== void 0 && remaining <= 0) {
6135
+ timedOut = "request_total";
6136
+ retryDecision = sent ? "not_retryable_after_submission" : "not_submitted";
6137
+ lifecycle("timeout", {
6138
+ timeoutKind: "request_total",
6139
+ terminalAccepted: false,
6140
+ cancelSource: "internal_timeout",
6141
+ retryAllowed: !sent,
6142
+ retryDecision,
6143
+ submissionAttempted: sent
6144
+ });
6145
+ throw new Error("Responses request timeout (request_total)");
6146
+ }
6147
+ clearRequestTimers();
6148
+ fallback = true;
6149
+ const fallbackOptions = {
6150
+ ...options,
6151
+ signal: fallbackSignal(),
6152
+ ...remaining !== void 0 ? { requestTimeoutMs: remaining } : {}
6153
+ };
6154
+ for await (const event of streamResponsesSSE(model, context, fallbackOptions)) publish(event);
6155
+ finished = true;
6156
+ };
5721
6157
  try {
6158
+ armTotalTimer();
5722
6159
  if (options?.signal?.aborted) {
5723
6160
  abort();
5724
6161
  throw new Error("Request aborted");
@@ -5729,42 +6166,52 @@ function streamResponsesWS(model, context, options) {
5729
6166
  const headers = { Authorization: `Bearer ${key}`, ...model.headers, ...options?.headers };
5730
6167
  const { input, ...settings } = payload;
5731
6168
  const identity = JSON.stringify([model.provider, model.api, model.id, model.baseUrl, headers, settings]);
6169
+ clearConnectionIdleTimer(state);
5732
6170
  if (state.identity !== identity || state.connection?.state() !== "open") {
5733
- await state.connection?.close();
6171
+ const stale = state.connection;
5734
6172
  state.connection = void 0;
5735
6173
  state.anchor = void 0;
5736
6174
  state.identity = identity;
6175
+ await stale?.close().catch(() => {
6176
+ });
5737
6177
  }
6178
+ reusedConnection = !!state.connection;
5738
6179
  if (state.abort.signal.aborted) throw new Error("Request aborted");
6180
+ lifecycle("request_start");
5739
6181
  const client = getWebSocketClient();
5740
6182
  if (!client) {
5741
6183
  if (options?.transport === "auto") {
5742
- fallback = true;
5743
- for await (const event of streamResponsesSSE(model, context, { ...options, signal: state.abort.signal })) publish(event);
5744
- finished = true;
6184
+ await runSSEFallback();
5745
6185
  return;
5746
6186
  }
5747
6187
  throw new Error("WebSocket transport is not available on this host.");
5748
6188
  }
5749
- const reusedConnection = !!state.connection;
5750
- if (!state.connection) {
6189
+ let connection = state.connection;
6190
+ if (!connection) {
5751
6191
  try {
5752
- state.connection = await client.connect(websocketUrl(model.baseUrl), {
6192
+ connection = await client.connect(websocketUrl(model.baseUrl), {
5753
6193
  headers,
5754
- signal: state.abort.signal,
5755
- connectTimeoutMs: options?.timeoutMs ?? 3e4,
5756
- idleTimeoutMs: options?.timeoutMs ?? 12e4
6194
+ signal: requestAbort.signal,
6195
+ connectTimeoutMs: budgets.connectTimeoutMs
5757
6196
  });
5758
6197
  } catch (error) {
6198
+ if (error instanceof WebSocketTransportError && error.kind === "timeout") timedOut = "connect";
5759
6199
  if (options?.transport === "auto" && error instanceof WebSocketTransportError && error.kind === "handshake" && [404, 405, 426, 501].includes(error.status ?? 0) && !options.signal?.aborted && !state.abort.signal.aborted) {
5760
- fallback = true;
5761
- for await (const event of streamResponsesSSE(model, context, { ...options, signal: state.abort.signal })) publish(event);
5762
- finished = true;
6200
+ await runSSEFallback();
5763
6201
  return;
5764
6202
  }
5765
6203
  throw error;
5766
6204
  }
6205
+ state.connection = connection;
6206
+ connectionGeneration = ++state.connectionGeneration;
6207
+ if (state.abort.signal.aborted || requestAbort.signal.aborted) {
6208
+ state.connection = void 0;
6209
+ void connection.close().catch(() => {
6210
+ });
6211
+ throw new Error("Request aborted");
6212
+ }
5767
6213
  }
6214
+ const activeConnection = connection;
5768
6215
  const fullInput = input;
5769
6216
  const anchor = state.anchor;
5770
6217
  if (!options?.onPayload && model.compat?.responsesContinuation && anchor && fullInput.length > anchor.count && JSON.stringify(fullInput.slice(0, anchor.count)) === anchor.prefix) {
@@ -5775,65 +6222,127 @@ function streamResponsesWS(model, context, options) {
5775
6222
  for (let attempt = 0; attempt < 2; attempt++) {
5776
6223
  let rebuild = false;
5777
6224
  options?.onPayload?.(payload);
5778
- if (state.abort.signal.aborted) throw new Error("Request aborted");
6225
+ if (state.abort.signal.aborted || requestAbort.signal.aborted) throw new Error("Request aborted");
5779
6226
  sent = true;
5780
6227
  const body = JSON.stringify(payload);
5781
6228
  reportSubmission(options, body, { transport: "websocket", reusedConnection: reusedConnection || attempt > 0, continuation: !!payload.previous_response_id, attempt: attempt + 1 });
5782
- await state.connection.send(body, { signal: state.abort.signal });
6229
+ lifecycle("submission_attempted", { submissionAttempted: true });
6230
+ armPhaseTimer("first_response", budgets.firstResponseTimeoutMs);
6231
+ await activeConnection.send(body, { signal: requestAbort.signal });
5783
6232
  let started = false;
5784
6233
  let responseId;
5785
- for await (const message of state.connection.receive(state.abort.signal)) {
5786
- if (state.abort.signal.aborted) throw new Error("Request aborted");
5787
- if (message.isBinary) throw new ResponsesProtocolError("Responses requires JSON text messages.");
5788
- const event = JSON.parse(message.data);
5789
- if (!isRecord2(event)) throw new ResponsesProtocolError("Invalid Responses WebSocket event.");
5790
- if (!started && attempt === 0 && payload.previous_response_id && event.type === "error" && event.status === 400 && isRecord2(event.error) && event.error.code === "previous_response_not_found" && event.error.param === "previous_response_id") {
5791
- delete payload.previous_response_id;
5792
- payload.input = fullInput;
5793
- rebuild = true;
5794
- break;
5795
- }
5796
- if (!started && typeof event.type === "string" && event.type !== "error" && !event.type.startsWith("response.")) continue;
5797
- if (!started && event.type !== "error") {
5798
- if (event.type !== "response.created" || !isRecord2(event.response) || typeof event.response.id !== "string" || event.response.id === state.lastResponseId) {
5799
- throw new ResponsesProtocolError("Expected a new response.created before output.");
6234
+ const receiveIterator = activeConnection.receive(requestAbort.signal)[Symbol.asyncIterator]();
6235
+ let receiveDone = false;
6236
+ try {
6237
+ while (true) {
6238
+ const step = await receiveIterator.next();
6239
+ if (step.done) {
6240
+ receiveDone = true;
6241
+ break;
5800
6242
  }
5801
- started = true;
5802
- responseId = event.response.id;
5803
- }
5804
- if (responseId && isRecord2(event.response) && event.response.id !== responseId) throw new ResponsesProtocolError("Response identity changed within a request.");
5805
- const events = reducer.feed(event);
5806
- if (reducer.isTerminal) {
5807
- if (reducer.message.stopReason === "error") reducer.message.retryable = false;
5808
- const response = reducer.terminalResponse;
5809
- state.lastResponseId = response?.id;
5810
- if (!options?.onPayload && response?.status === "completed" && reducer.message.stopReason !== "error") {
5811
- const prefixItems = [...fullInput, ...buildResponsesOutput(reducer.message)];
5812
- const prefix = JSON.stringify(prefixItems);
5813
- if (prefix.length * 2 <= MAX_ANCHOR_BYTES) state.anchor = { id: response.id, prefix, count: prefixItems.length };
6243
+ const message = step.value;
6244
+ receivedMessageCount += 1;
6245
+ lastReceiveAt = monotonicNow();
6246
+ armPhaseTimer("stream_idle", budgets.streamIdleTimeoutMs);
6247
+ if (receivedMessageCount === 1) lifecycle("first_application_message");
6248
+ if (state.abort.signal.aborted || requestAbort.signal.aborted) {
6249
+ throw new Error(timedOut ? `Responses request timeout (${timedOut})` : "Request aborted");
6250
+ }
6251
+ if (message.isBinary) throw new ResponsesProtocolError("Responses requires JSON text messages.");
6252
+ const event = JSON.parse(message.data);
6253
+ if (!isRecord2(event)) throw new ResponsesProtocolError("Invalid Responses WebSocket event.");
6254
+ if (typeof event.type === "string") {
6255
+ if (event.type === "response.created") sawResponseCreated = true;
6256
+ else if (event.type.startsWith("response.output") || event.type.startsWith("response.content") || event.type.startsWith("response.reasoning")) sawModelContent = true;
6257
+ }
6258
+ if (!started && attempt === 0 && payload.previous_response_id && event.type === "error" && event.status === 400 && isRecord2(event.error) && event.error.code === "previous_response_not_found" && event.error.param === "previous_response_id") {
6259
+ delete payload.previous_response_id;
6260
+ payload.input = fullInput;
6261
+ rebuild = true;
6262
+ break;
6263
+ }
6264
+ if (!started && typeof event.type === "string" && event.type !== "error" && !event.type.startsWith("response.")) continue;
6265
+ if (!started && event.type !== "error") {
6266
+ if (event.type !== "response.created" || !isRecord2(event.response) || typeof event.response.id !== "string" || event.response.id === state.lastResponseId) {
6267
+ throw new ResponsesProtocolError("Expected a new response.created before output.");
6268
+ }
6269
+ started = true;
6270
+ responseId = event.response.id;
6271
+ }
6272
+ if (responseId && isRecord2(event.response) && event.response.id !== responseId) throw new ResponsesProtocolError("Response identity changed within a request.");
6273
+ const events = reducer.feed(event);
6274
+ if (reducer.isTerminal) {
6275
+ if (reducer.message.stopReason === "error") reducer.message.retryable = false;
6276
+ const response = reducer.terminalResponse;
6277
+ state.lastResponseId = response?.id;
6278
+ if (!options?.onPayload && response?.status === "completed" && reducer.message.stopReason !== "error") {
6279
+ const prefixItems = [...fullInput, ...buildResponsesOutput(reducer.message)];
6280
+ const prefix = JSON.stringify(prefixItems);
6281
+ if (prefix.length * 2 <= MAX_ANCHOR_BYTES) state.anchor = { id: response.id, prefix, count: prefixItems.length };
6282
+ }
6283
+ finished = true;
6284
+ clearRequestTimers();
6285
+ lifecycle("terminal_accepted", {
6286
+ terminalAccepted: true,
6287
+ retryAllowed: reducer.message.retryable !== false,
6288
+ retryDecision: "terminal"
6289
+ });
6290
+ for (const e of events) publish(e);
6291
+ break;
5814
6292
  }
5815
- finished = true;
5816
6293
  for (const e of events) publish(e);
5817
- break;
5818
6294
  }
5819
- for (const e of events) publish(e);
6295
+ } finally {
6296
+ if (!receiveDone) await closeReceiveIterator(receiveIterator);
6297
+ }
6298
+ if (rebuild) {
6299
+ retryDecision = "rebuild_previous_response";
6300
+ continue;
5820
6301
  }
5821
- if (rebuild) continue;
5822
6302
  if (!reducer.isTerminal) throw reducer.finishWithoutTerminal();
5823
6303
  break;
5824
6304
  }
5825
6305
  } catch (error) {
5826
6306
  if (!finished) {
5827
6307
  const message = reducer.message;
5828
- message.stopReason = options?.signal?.aborted || state.abort.signal.aborted ? "aborted" : "error";
5829
- message.errorMessage = error instanceof Error ? error.message : String(error);
5830
- if (sent) message.retryable = false;
6308
+ const userAborted = options?.signal?.aborted === true;
6309
+ const ownerDisposed = !userAborted && !timedOut && state.abort.signal.aborted;
6310
+ message.stopReason = userAborted || ownerDisposed ? "aborted" : "error";
6311
+ const cause = timedOut ? `Responses request timeout (${timedOut}) after ${ageMs()}ms` : error instanceof Error ? error.message : String(error);
6312
+ message.errorMessage = cause;
6313
+ if (timedOut && compat.timeoutReplay) {
6314
+ message.replayEligible = true;
6315
+ message.replayClass = timedOut;
6316
+ }
6317
+ if (sent) {
6318
+ message.retryable = false;
6319
+ retryDecision = "not_retryable_after_submission";
6320
+ }
6321
+ cleanupCancelSource = userAborted ? "user" : timedOut ? "internal_timeout" : ownerDisposed ? "owner_disposal" : "none";
5831
6322
  stream.push({ type: "error", reason: message.stopReason, error: message });
5832
6323
  }
5833
6324
  } finally {
6325
+ clearRequestTimers();
6326
+ detachFallbackAbort?.();
6327
+ state.abort.signal.removeEventListener("abort", onOwnerAbort);
5834
6328
  options?.signal?.removeEventListener("abort", abort);
5835
6329
  state.busy = false;
5836
- if (fallback || !finished || !options?.transportOwner || reducer.message.stopReason === "error") disposeState(owner, state);
6330
+ const cancelSource = cleanupCancelSource ?? (options?.signal?.aborted ? "user" : timedOut ? "internal_timeout" : state.abort.signal.aborted ? "owner_disposal" : "none");
6331
+ const retain = finished && !fallback && !receiveCleanupFailed && !!options?.transportOwner && reducer.message.stopReason !== "error" && !timedOut && state.connection !== void 0 && !state.abort.signal.aborted;
6332
+ if (!retain) disposeState(owner, state);
6333
+ else if (state.connection) {
6334
+ armConnectionIdleRecycle(state, state.connection, connectionGeneration, budgets.connectionIdleTimeoutMs);
6335
+ }
6336
+ const cleanupOutcome = receiveCleanupFailed ? "cleanup_failed" : retain ? "connection_reused" : finished ? "connection_closed" : "request_abandoned";
6337
+ lifecycle("cleanup", {
6338
+ terminalAccepted: finished,
6339
+ timeoutKind: timedOut,
6340
+ submissionAttempted: sent,
6341
+ retryAllowed: !finished && !sent && reducer.message.stopReason !== "aborted" && reducer.message.retryable !== false,
6342
+ cancelSource,
6343
+ retryDecision,
6344
+ cleanupOutcome
6345
+ });
5837
6346
  for (const event of terminalEvents) stream.push(event);
5838
6347
  stream.end();
5839
6348
  }