@anuma/sdk 5.0.3 → 5.0.4

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.
@@ -1137,7 +1137,7 @@ type LlmapiResponseRequest = {
1137
1137
  */
1138
1138
  tools?: Array<LlmapiResponseTool>;
1139
1139
  };
1140
- type LlmapiResponseResponse = {
1140
+ type LlmapiResponseResponse$1 = {
1141
1141
  /**
1142
1142
  * ClientInjectedTools are tool names the client provided in the original request.
1143
1143
  */
@@ -8562,7 +8562,7 @@ type PostApiV1ResponsesResponses = {
8562
8562
  /**
8563
8563
  * OK
8564
8564
  */
8565
- 200: LlmapiResponseResponse | string;
8565
+ 200: LlmapiResponseResponse$1 | string;
8566
8566
  };
8567
8567
  type PostApiV1ResponsesResponse = PostApiV1ResponsesResponses[keyof PostApiV1ResponsesResponses];
8568
8568
  type GetApiV1SharesData = {
@@ -9563,7 +9563,7 @@ type PostApiV1UtilityResponsesResponses = {
9563
9563
  /**
9564
9564
  * OK
9565
9565
  */
9566
- 200: LlmapiResponseResponse | string;
9566
+ 200: LlmapiResponseResponse$1 | string;
9567
9567
  };
9568
9568
  type PostApiV1UtilityResponsesResponse = PostApiV1UtilityResponsesResponses[keyof PostApiV1UtilityResponsesResponses];
9569
9569
  type GetApiV1WalletsBindingData = {
@@ -11237,6 +11237,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
11237
11237
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
11238
11238
  * deprecation plan.
11239
11239
  */
11240
+ /**
11241
+ * Add the Responses API's terminal-state fields, which the portal's generated
11242
+ * schema does not declare.
11243
+ *
11244
+ * Without them the two transports are asymmetric: a completions consumer reads
11245
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
11246
+ * while a Responses consumer has no field to read at all — the truncation the
11247
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
11248
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
11249
+ * its own result, but a caller holding only a response object cannot (#805).
11250
+ *
11251
+ * These are standard OpenAI Responses fields, so this widens the type toward
11252
+ * the wire rather than inventing shape: they are optional because a
11253
+ * non-streaming direct call returns only what the portal serializes today.
11254
+ * Drop the override once the portal declares them upstream.
11255
+ */
11256
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
11257
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
11258
+ status?: string;
11259
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
11260
+ incomplete_details?: {
11261
+ reason?: string;
11262
+ };
11263
+ };
11240
11264
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
11241
11265
  usage?: LlmapiChatCompletionUsage;
11242
11266
  inference_id?: string;
@@ -1137,7 +1137,7 @@ type LlmapiResponseRequest = {
1137
1137
  */
1138
1138
  tools?: Array<LlmapiResponseTool>;
1139
1139
  };
1140
- type LlmapiResponseResponse = {
1140
+ type LlmapiResponseResponse$1 = {
1141
1141
  /**
1142
1142
  * ClientInjectedTools are tool names the client provided in the original request.
1143
1143
  */
@@ -8562,7 +8562,7 @@ type PostApiV1ResponsesResponses = {
8562
8562
  /**
8563
8563
  * OK
8564
8564
  */
8565
- 200: LlmapiResponseResponse | string;
8565
+ 200: LlmapiResponseResponse$1 | string;
8566
8566
  };
8567
8567
  type PostApiV1ResponsesResponse = PostApiV1ResponsesResponses[keyof PostApiV1ResponsesResponses];
8568
8568
  type GetApiV1SharesData = {
@@ -9563,7 +9563,7 @@ type PostApiV1UtilityResponsesResponses = {
9563
9563
  /**
9564
9564
  * OK
9565
9565
  */
9566
- 200: LlmapiResponseResponse | string;
9566
+ 200: LlmapiResponseResponse$1 | string;
9567
9567
  };
9568
9568
  type PostApiV1UtilityResponsesResponse = PostApiV1UtilityResponsesResponses[keyof PostApiV1UtilityResponsesResponses];
9569
9569
  type GetApiV1WalletsBindingData = {
@@ -11237,6 +11237,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
11237
11237
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
11238
11238
  * deprecation plan.
11239
11239
  */
11240
+ /**
11241
+ * Add the Responses API's terminal-state fields, which the portal's generated
11242
+ * schema does not declare.
11243
+ *
11244
+ * Without them the two transports are asymmetric: a completions consumer reads
11245
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
11246
+ * while a Responses consumer has no field to read at all — the truncation the
11247
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
11248
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
11249
+ * its own result, but a caller holding only a response object cannot (#805).
11250
+ *
11251
+ * These are standard OpenAI Responses fields, so this widens the type toward
11252
+ * the wire rather than inventing shape: they are optional because a
11253
+ * non-streaming direct call returns only what the portal serializes today.
11254
+ * Drop the override once the portal declares them upstream.
11255
+ */
11256
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
11257
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
11258
+ status?: string;
11259
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
11260
+ incomplete_details?: {
11261
+ reason?: string;
11262
+ };
11263
+ };
11240
11264
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
11241
11265
  usage?: LlmapiChatCompletionUsage;
11242
11266
  inference_id?: string;
@@ -10784,6 +10784,16 @@ var ResponsesStrategy = class {
10784
10784
  }));
10785
10785
  mergeToolCallEventsIntoAccumulator(accumulator, toolCallEvents);
10786
10786
  }
10787
+ const fbStatus = typeof resp.status === "string" ? resp.status : void 0;
10788
+ const fbIncompleteReason = resp.incomplete_details?.reason;
10789
+ const fbFinishReason = typeof resp.finish_reason === "string" ? resp.finish_reason : void 0;
10790
+ if (fbStatus !== void 0) accumulator.responseStatus = fbStatus;
10791
+ if (fbIncompleteReason !== void 0) accumulator.incompleteReason = fbIncompleteReason;
10792
+ if (fbFinishReason !== void 0) {
10793
+ accumulator.finishReason = fbFinishReason;
10794
+ } else if (fbStatus === "incomplete" && fbIncompleteReason === "max_output_tokens") {
10795
+ accumulator.finishReason = "length";
10796
+ }
10787
10797
  return result;
10788
10798
  }
10789
10799
  if (typedChunk.type === "response.created" && typedChunk.response) {
@@ -10819,6 +10829,8 @@ var ResponsesStrategy = class {
10819
10829
  if (looksIncomplete && resp?.incomplete_details?.reason === "max_output_tokens") {
10820
10830
  accumulator.finishReason = "length";
10821
10831
  }
10832
+ accumulator.responseStatus = looksIncomplete ? "incomplete" : resp?.status;
10833
+ accumulator.incompleteReason = resp?.incomplete_details?.reason;
10822
10834
  if (typedChunk.response?.tools_checksum && !accumulator.toolsChecksum) {
10823
10835
  accumulator.toolsChecksum = typedChunk.response.tools_checksum;
10824
10836
  }
@@ -11087,7 +11099,17 @@ var ResponsesStrategy = class {
11087
11099
  output,
11088
11100
  usage: Object.keys(accumulator.usage).length > 0 ? accumulator.usage : void 0,
11089
11101
  tools_checksum: accumulator.toolsChecksum,
11090
- tool_call_events: accumulator.toolCallEvents
11102
+ tool_call_events: accumulator.toolCallEvents,
11103
+ // Response-level terminal state (#805). Note the individual output items
11104
+ // above hardcode `status: "completed"` — that is the item's own status
11105
+ // and says nothing about the turn. Only these two fields can tell a
11106
+ // caller the turn was cut off, and until now neither survived the stream.
11107
+ // Omitted rather than emitted as `undefined` so a clean turn's response
11108
+ // shape is unchanged.
11109
+ ...accumulator.responseStatus !== void 0 && { status: accumulator.responseStatus },
11110
+ ...accumulator.incompleteReason !== void 0 && {
11111
+ incomplete_details: { reason: accumulator.incompleteReason }
11112
+ }
11091
11113
  };
11092
11114
  }
11093
11115
  };
@@ -12106,6 +12128,9 @@ Executing tool: ${toolInfo}
12106
12128
  usage: {
12107
12129
  inputTokens: currentAccumulator.usage.prompt_tokens,
12108
12130
  outputTokens: currentAccumulator.usage.completion_tokens
12131
+ },
12132
+ ...currentAccumulator.finishReason !== void 0 && {
12133
+ finishReason: currentAccumulator.finishReason
12109
12134
  }
12110
12135
  });
12111
12136
  }
@@ -407,7 +407,7 @@ type LlmapiResponseReasoning = {
407
407
  */
408
408
  summary?: string;
409
409
  };
410
- type LlmapiResponseResponse = {
410
+ type LlmapiResponseResponse$1 = {
411
411
  /**
412
412
  * ClientInjectedTools are tool names the client provided in the original request.
413
413
  */
@@ -1234,6 +1234,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
1234
1234
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
1235
1235
  * deprecation plan.
1236
1236
  */
1237
+ /**
1238
+ * Add the Responses API's terminal-state fields, which the portal's generated
1239
+ * schema does not declare.
1240
+ *
1241
+ * Without them the two transports are asymmetric: a completions consumer reads
1242
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
1243
+ * while a Responses consumer has no field to read at all — the truncation the
1244
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
1245
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
1246
+ * its own result, but a caller holding only a response object cannot (#805).
1247
+ *
1248
+ * These are standard OpenAI Responses fields, so this widens the type toward
1249
+ * the wire rather than inventing shape: they are optional because a
1250
+ * non-streaming direct call returns only what the portal serializes today.
1251
+ * Drop the override once the portal declares them upstream.
1252
+ */
1253
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
1254
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
1255
+ status?: string;
1256
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
1257
+ incomplete_details?: {
1258
+ reason?: string;
1259
+ };
1260
+ };
1237
1261
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
1238
1262
  usage?: LlmapiChatCompletionUsage;
1239
1263
  inference_id?: string;
@@ -2428,6 +2452,21 @@ type StepFinishEvent = {
2428
2452
  inputTokens?: number;
2429
2453
  outputTokens?: number;
2430
2454
  };
2455
+ /**
2456
+ * The round's own finish reason, as the provider sent it — `"length"` means
2457
+ * this round hit the output ceiling.
2458
+ *
2459
+ * Per-round, not per-turn: a round can truncate and the loop still recover on
2460
+ * the next one, which is why the loop does not treat it as an error on its
2461
+ * own (see the truncation guard below). Without it a consumer watching steps
2462
+ * cannot tell a round that said everything it meant to from one that was cut
2463
+ * off mid-argument — they differ only in this field (#805).
2464
+ *
2465
+ * Absent when the provider sent no finish reason. Note that no step event
2466
+ * fires for the round that *ends* a turn, so use {@link RunTerminalState} on
2467
+ * the result for the final round.
2468
+ */
2469
+ finishReason?: string;
2431
2470
  };
2432
2471
  /**
2433
2472
  * Terminal state of the LAST model response in the turn, normalized to the
@@ -2435,13 +2474,14 @@ type StepFinishEvent = {
2435
2474
  * specific values). `undefined` when the stream carried no finish reason at all.
2436
2475
  *
2437
2476
  * Exposed because neither response shape reliably carries it out to a caller.
2438
- * The Responses API shape has no field for it — `LlmapiResponseResponse` declares
2439
- * neither `status` nor `incomplete_details` — so `buildFinalResponse` cannot
2440
- * surface truncation there even though the loop detected it (`responses.ts`
2441
- * normalizes it onto the accumulator). Completions does expose
2442
- * `choices[0].finish_reason`, but omits `tool_calls` entirely when there are
2443
- * none, so a caller sniffing the response cannot tell "no tool calls" from
2444
- * "field absent".
2477
+ * The Responses shape now carries `status` / `incomplete_details` — added in
2478
+ * `clientCompat` and emitted by `buildFinalResponse` — but only in the
2479
+ * Responses vocabulary, so a caller reading it has to translate
2480
+ * `incomplete_details.reason === "max_output_tokens"` itself. Completions
2481
+ * exposes `choices[0].finish_reason`, but omits `tool_calls` entirely when
2482
+ * there are none, so a caller sniffing the response cannot tell "no tool calls"
2483
+ * from "field absent". This field answers both questions in one vocabulary,
2484
+ * for either transport.
2445
2485
  *
2446
2486
  * The loop already knows both, unambiguously and identically for either API.
2447
2487
  * Reporting them here means callers and test harnesses stop reverse-engineering
@@ -407,7 +407,7 @@ type LlmapiResponseReasoning = {
407
407
  */
408
408
  summary?: string;
409
409
  };
410
- type LlmapiResponseResponse = {
410
+ type LlmapiResponseResponse$1 = {
411
411
  /**
412
412
  * ClientInjectedTools are tool names the client provided in the original request.
413
413
  */
@@ -1234,6 +1234,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
1234
1234
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
1235
1235
  * deprecation plan.
1236
1236
  */
1237
+ /**
1238
+ * Add the Responses API's terminal-state fields, which the portal's generated
1239
+ * schema does not declare.
1240
+ *
1241
+ * Without them the two transports are asymmetric: a completions consumer reads
1242
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
1243
+ * while a Responses consumer has no field to read at all — the truncation the
1244
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
1245
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
1246
+ * its own result, but a caller holding only a response object cannot (#805).
1247
+ *
1248
+ * These are standard OpenAI Responses fields, so this widens the type toward
1249
+ * the wire rather than inventing shape: they are optional because a
1250
+ * non-streaming direct call returns only what the portal serializes today.
1251
+ * Drop the override once the portal declares them upstream.
1252
+ */
1253
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
1254
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
1255
+ status?: string;
1256
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
1257
+ incomplete_details?: {
1258
+ reason?: string;
1259
+ };
1260
+ };
1237
1261
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
1238
1262
  usage?: LlmapiChatCompletionUsage;
1239
1263
  inference_id?: string;
@@ -2428,6 +2452,21 @@ type StepFinishEvent = {
2428
2452
  inputTokens?: number;
2429
2453
  outputTokens?: number;
2430
2454
  };
2455
+ /**
2456
+ * The round's own finish reason, as the provider sent it — `"length"` means
2457
+ * this round hit the output ceiling.
2458
+ *
2459
+ * Per-round, not per-turn: a round can truncate and the loop still recover on
2460
+ * the next one, which is why the loop does not treat it as an error on its
2461
+ * own (see the truncation guard below). Without it a consumer watching steps
2462
+ * cannot tell a round that said everything it meant to from one that was cut
2463
+ * off mid-argument — they differ only in this field (#805).
2464
+ *
2465
+ * Absent when the provider sent no finish reason. Note that no step event
2466
+ * fires for the round that *ends* a turn, so use {@link RunTerminalState} on
2467
+ * the result for the final round.
2468
+ */
2469
+ finishReason?: string;
2431
2470
  };
2432
2471
  /**
2433
2472
  * Terminal state of the LAST model response in the turn, normalized to the
@@ -2435,13 +2474,14 @@ type StepFinishEvent = {
2435
2474
  * specific values). `undefined` when the stream carried no finish reason at all.
2436
2475
  *
2437
2476
  * Exposed because neither response shape reliably carries it out to a caller.
2438
- * The Responses API shape has no field for it — `LlmapiResponseResponse` declares
2439
- * neither `status` nor `incomplete_details` — so `buildFinalResponse` cannot
2440
- * surface truncation there even though the loop detected it (`responses.ts`
2441
- * normalizes it onto the accumulator). Completions does expose
2442
- * `choices[0].finish_reason`, but omits `tool_calls` entirely when there are
2443
- * none, so a caller sniffing the response cannot tell "no tool calls" from
2444
- * "field absent".
2477
+ * The Responses shape now carries `status` / `incomplete_details` — added in
2478
+ * `clientCompat` and emitted by `buildFinalResponse` — but only in the
2479
+ * Responses vocabulary, so a caller reading it has to translate
2480
+ * `incomplete_details.reason === "max_output_tokens"` itself. Completions
2481
+ * exposes `choices[0].finish_reason`, but omits `tool_calls` entirely when
2482
+ * there are none, so a caller sniffing the response cannot tell "no tool calls"
2483
+ * from "field absent". This field answers both questions in one vocabulary,
2484
+ * for either transport.
2445
2485
  *
2446
2486
  * The loop already knows both, unambiguously and identically for either API.
2447
2487
  * Reporting them here means callers and test harnesses stop reverse-engineering
@@ -1823,6 +1823,16 @@ var ResponsesStrategy = class {
1823
1823
  }));
1824
1824
  mergeToolCallEventsIntoAccumulator(accumulator, toolCallEvents);
1825
1825
  }
1826
+ const fbStatus = typeof resp.status === "string" ? resp.status : void 0;
1827
+ const fbIncompleteReason = resp.incomplete_details?.reason;
1828
+ const fbFinishReason = typeof resp.finish_reason === "string" ? resp.finish_reason : void 0;
1829
+ if (fbStatus !== void 0) accumulator.responseStatus = fbStatus;
1830
+ if (fbIncompleteReason !== void 0) accumulator.incompleteReason = fbIncompleteReason;
1831
+ if (fbFinishReason !== void 0) {
1832
+ accumulator.finishReason = fbFinishReason;
1833
+ } else if (fbStatus === "incomplete" && fbIncompleteReason === "max_output_tokens") {
1834
+ accumulator.finishReason = "length";
1835
+ }
1826
1836
  return result;
1827
1837
  }
1828
1838
  if (typedChunk.type === "response.created" && typedChunk.response) {
@@ -1858,6 +1868,8 @@ var ResponsesStrategy = class {
1858
1868
  if (looksIncomplete && resp?.incomplete_details?.reason === "max_output_tokens") {
1859
1869
  accumulator.finishReason = "length";
1860
1870
  }
1871
+ accumulator.responseStatus = looksIncomplete ? "incomplete" : resp?.status;
1872
+ accumulator.incompleteReason = resp?.incomplete_details?.reason;
1861
1873
  if (typedChunk.response?.tools_checksum && !accumulator.toolsChecksum) {
1862
1874
  accumulator.toolsChecksum = typedChunk.response.tools_checksum;
1863
1875
  }
@@ -2126,7 +2138,17 @@ var ResponsesStrategy = class {
2126
2138
  output,
2127
2139
  usage: Object.keys(accumulator.usage).length > 0 ? accumulator.usage : void 0,
2128
2140
  tools_checksum: accumulator.toolsChecksum,
2129
- tool_call_events: accumulator.toolCallEvents
2141
+ tool_call_events: accumulator.toolCallEvents,
2142
+ // Response-level terminal state (#805). Note the individual output items
2143
+ // above hardcode `status: "completed"` — that is the item's own status
2144
+ // and says nothing about the turn. Only these two fields can tell a
2145
+ // caller the turn was cut off, and until now neither survived the stream.
2146
+ // Omitted rather than emitted as `undefined` so a clean turn's response
2147
+ // shape is unchanged.
2148
+ ...accumulator.responseStatus !== void 0 && { status: accumulator.responseStatus },
2149
+ ...accumulator.incompleteReason !== void 0 && {
2150
+ incomplete_details: { reason: accumulator.incompleteReason }
2151
+ }
2130
2152
  };
2131
2153
  }
2132
2154
  };
@@ -3145,6 +3167,9 @@ Executing tool: ${toolInfo}
3145
3167
  usage: {
3146
3168
  inputTokens: currentAccumulator.usage.prompt_tokens,
3147
3169
  outputTokens: currentAccumulator.usage.completion_tokens
3170
+ },
3171
+ ...currentAccumulator.finishReason !== void 0 && {
3172
+ finishReason: currentAccumulator.finishReason
3148
3173
  }
3149
3174
  });
3150
3175
  }
package/dist/index.d.mts CHANGED
@@ -990,7 +990,7 @@ type LlmapiResponseRequest = {
990
990
  */
991
991
  tools?: Array<LlmapiResponseTool>;
992
992
  };
993
- type LlmapiResponseResponse = {
993
+ type LlmapiResponseResponse$1 = {
994
994
  /**
995
995
  * ClientInjectedTools are tool names the client provided in the original request.
996
996
  */
@@ -5840,7 +5840,7 @@ type PostApiV1ResponsesResponses = {
5840
5840
  /**
5841
5841
  * OK
5842
5842
  */
5843
- 200: LlmapiResponseResponse | string;
5843
+ 200: LlmapiResponseResponse$1 | string;
5844
5844
  };
5845
5845
  type PostApiV1ResponsesResponse = PostApiV1ResponsesResponses[keyof PostApiV1ResponsesResponses];
5846
5846
  type PostApiV1SubscriptionsCancelData = {
@@ -7166,6 +7166,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
7166
7166
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
7167
7167
  * deprecation plan.
7168
7168
  */
7169
+ /**
7170
+ * Add the Responses API's terminal-state fields, which the portal's generated
7171
+ * schema does not declare.
7172
+ *
7173
+ * Without them the two transports are asymmetric: a completions consumer reads
7174
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
7175
+ * while a Responses consumer has no field to read at all — the truncation the
7176
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
7177
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
7178
+ * its own result, but a caller holding only a response object cannot (#805).
7179
+ *
7180
+ * These are standard OpenAI Responses fields, so this widens the type toward
7181
+ * the wire rather than inventing shape: they are optional because a
7182
+ * non-streaming direct call returns only what the portal serializes today.
7183
+ * Drop the override once the portal declares them upstream.
7184
+ */
7185
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
7186
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
7187
+ status?: string;
7188
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
7189
+ incomplete_details?: {
7190
+ reason?: string;
7191
+ };
7192
+ };
7169
7193
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
7170
7194
  usage?: LlmapiChatCompletionUsage;
7171
7195
  inference_id?: string;
package/dist/index.d.ts CHANGED
@@ -990,7 +990,7 @@ type LlmapiResponseRequest = {
990
990
  */
991
991
  tools?: Array<LlmapiResponseTool>;
992
992
  };
993
- type LlmapiResponseResponse = {
993
+ type LlmapiResponseResponse$1 = {
994
994
  /**
995
995
  * ClientInjectedTools are tool names the client provided in the original request.
996
996
  */
@@ -5840,7 +5840,7 @@ type PostApiV1ResponsesResponses = {
5840
5840
  /**
5841
5841
  * OK
5842
5842
  */
5843
- 200: LlmapiResponseResponse | string;
5843
+ 200: LlmapiResponseResponse$1 | string;
5844
5844
  };
5845
5845
  type PostApiV1ResponsesResponse = PostApiV1ResponsesResponses[keyof PostApiV1ResponsesResponses];
5846
5846
  type PostApiV1SubscriptionsCancelData = {
@@ -7166,6 +7166,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
7166
7166
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
7167
7167
  * deprecation plan.
7168
7168
  */
7169
+ /**
7170
+ * Add the Responses API's terminal-state fields, which the portal's generated
7171
+ * schema does not declare.
7172
+ *
7173
+ * Without them the two transports are asymmetric: a completions consumer reads
7174
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
7175
+ * while a Responses consumer has no field to read at all — the truncation the
7176
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
7177
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
7178
+ * its own result, but a caller holding only a response object cannot (#805).
7179
+ *
7180
+ * These are standard OpenAI Responses fields, so this widens the type toward
7181
+ * the wire rather than inventing shape: they are optional because a
7182
+ * non-streaming direct call returns only what the portal serializes today.
7183
+ * Drop the override once the portal declares them upstream.
7184
+ */
7185
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
7186
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
7187
+ status?: string;
7188
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
7189
+ incomplete_details?: {
7190
+ reason?: string;
7191
+ };
7192
+ };
7169
7193
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
7170
7194
  usage?: LlmapiChatCompletionUsage;
7171
7195
  inference_id?: string;
@@ -11144,6 +11144,16 @@ var ResponsesStrategy = class {
11144
11144
  }));
11145
11145
  mergeToolCallEventsIntoAccumulator(accumulator, toolCallEvents);
11146
11146
  }
11147
+ const fbStatus = typeof resp.status === "string" ? resp.status : void 0;
11148
+ const fbIncompleteReason = resp.incomplete_details?.reason;
11149
+ const fbFinishReason = typeof resp.finish_reason === "string" ? resp.finish_reason : void 0;
11150
+ if (fbStatus !== void 0) accumulator.responseStatus = fbStatus;
11151
+ if (fbIncompleteReason !== void 0) accumulator.incompleteReason = fbIncompleteReason;
11152
+ if (fbFinishReason !== void 0) {
11153
+ accumulator.finishReason = fbFinishReason;
11154
+ } else if (fbStatus === "incomplete" && fbIncompleteReason === "max_output_tokens") {
11155
+ accumulator.finishReason = "length";
11156
+ }
11147
11157
  return result;
11148
11158
  }
11149
11159
  if (typedChunk.type === "response.created" && typedChunk.response) {
@@ -11179,6 +11189,8 @@ var ResponsesStrategy = class {
11179
11189
  if (looksIncomplete && resp?.incomplete_details?.reason === "max_output_tokens") {
11180
11190
  accumulator.finishReason = "length";
11181
11191
  }
11192
+ accumulator.responseStatus = looksIncomplete ? "incomplete" : resp?.status;
11193
+ accumulator.incompleteReason = resp?.incomplete_details?.reason;
11182
11194
  if (typedChunk.response?.tools_checksum && !accumulator.toolsChecksum) {
11183
11195
  accumulator.toolsChecksum = typedChunk.response.tools_checksum;
11184
11196
  }
@@ -11447,7 +11459,17 @@ var ResponsesStrategy = class {
11447
11459
  output,
11448
11460
  usage: Object.keys(accumulator.usage).length > 0 ? accumulator.usage : void 0,
11449
11461
  tools_checksum: accumulator.toolsChecksum,
11450
- tool_call_events: accumulator.toolCallEvents
11462
+ tool_call_events: accumulator.toolCallEvents,
11463
+ // Response-level terminal state (#805). Note the individual output items
11464
+ // above hardcode `status: "completed"` — that is the item's own status
11465
+ // and says nothing about the turn. Only these two fields can tell a
11466
+ // caller the turn was cut off, and until now neither survived the stream.
11467
+ // Omitted rather than emitted as `undefined` so a clean turn's response
11468
+ // shape is unchanged.
11469
+ ...accumulator.responseStatus !== void 0 && { status: accumulator.responseStatus },
11470
+ ...accumulator.incompleteReason !== void 0 && {
11471
+ incomplete_details: { reason: accumulator.incompleteReason }
11472
+ }
11451
11473
  };
11452
11474
  }
11453
11475
  };
@@ -12466,6 +12488,9 @@ Executing tool: ${toolInfo}
12466
12488
  usage: {
12467
12489
  inputTokens: currentAccumulator.usage.prompt_tokens,
12468
12490
  outputTokens: currentAccumulator.usage.completion_tokens
12491
+ },
12492
+ ...currentAccumulator.finishReason !== void 0 && {
12493
+ finishReason: currentAccumulator.finishReason
12469
12494
  }
12470
12495
  });
12471
12496
  }
@@ -703,7 +703,7 @@ type LlmapiResponseReasoning = {
703
703
  */
704
704
  summary?: string;
705
705
  };
706
- type LlmapiResponseResponse = {
706
+ type LlmapiResponseResponse$1 = {
707
707
  /**
708
708
  * ClientInjectedTools are tool names the client provided in the original request.
709
709
  */
@@ -1669,6 +1669,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
1669
1669
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
1670
1670
  * deprecation plan.
1671
1671
  */
1672
+ /**
1673
+ * Add the Responses API's terminal-state fields, which the portal's generated
1674
+ * schema does not declare.
1675
+ *
1676
+ * Without them the two transports are asymmetric: a completions consumer reads
1677
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
1678
+ * while a Responses consumer has no field to read at all — the truncation the
1679
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
1680
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
1681
+ * its own result, but a caller holding only a response object cannot (#805).
1682
+ *
1683
+ * These are standard OpenAI Responses fields, so this widens the type toward
1684
+ * the wire rather than inventing shape: they are optional because a
1685
+ * non-streaming direct call returns only what the portal serializes today.
1686
+ * Drop the override once the portal declares them upstream.
1687
+ */
1688
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
1689
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
1690
+ status?: string;
1691
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
1692
+ incomplete_details?: {
1693
+ reason?: string;
1694
+ };
1695
+ };
1672
1696
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
1673
1697
  usage?: LlmapiChatCompletionUsage;
1674
1698
  inference_id?: string;
@@ -2390,6 +2414,21 @@ type StepFinishEvent = {
2390
2414
  inputTokens?: number;
2391
2415
  outputTokens?: number;
2392
2416
  };
2417
+ /**
2418
+ * The round's own finish reason, as the provider sent it — `"length"` means
2419
+ * this round hit the output ceiling.
2420
+ *
2421
+ * Per-round, not per-turn: a round can truncate and the loop still recover on
2422
+ * the next one, which is why the loop does not treat it as an error on its
2423
+ * own (see the truncation guard below). Without it a consumer watching steps
2424
+ * cannot tell a round that said everything it meant to from one that was cut
2425
+ * off mid-argument — they differ only in this field (#805).
2426
+ *
2427
+ * Absent when the provider sent no finish reason. Note that no step event
2428
+ * fires for the round that *ends* a turn, so use {@link RunTerminalState} on
2429
+ * the result for the final round.
2430
+ */
2431
+ finishReason?: string;
2393
2432
  };
2394
2433
  /** Capability header: tells the portal this client can resume a detached stream. Pinned with zeta-chain/ai-portal#1139. */
2395
2434
  declare const STREAM_RESUMABLE_HEADER = "X-Stream-Resumable";
@@ -703,7 +703,7 @@ type LlmapiResponseReasoning = {
703
703
  */
704
704
  summary?: string;
705
705
  };
706
- type LlmapiResponseResponse = {
706
+ type LlmapiResponseResponse$1 = {
707
707
  /**
708
708
  * ClientInjectedTools are tool names the client provided in the original request.
709
709
  */
@@ -1669,6 +1669,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
1669
1669
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
1670
1670
  * deprecation plan.
1671
1671
  */
1672
+ /**
1673
+ * Add the Responses API's terminal-state fields, which the portal's generated
1674
+ * schema does not declare.
1675
+ *
1676
+ * Without them the two transports are asymmetric: a completions consumer reads
1677
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
1678
+ * while a Responses consumer has no field to read at all — the truncation the
1679
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
1680
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
1681
+ * its own result, but a caller holding only a response object cannot (#805).
1682
+ *
1683
+ * These are standard OpenAI Responses fields, so this widens the type toward
1684
+ * the wire rather than inventing shape: they are optional because a
1685
+ * non-streaming direct call returns only what the portal serializes today.
1686
+ * Drop the override once the portal declares them upstream.
1687
+ */
1688
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
1689
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
1690
+ status?: string;
1691
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
1692
+ incomplete_details?: {
1693
+ reason?: string;
1694
+ };
1695
+ };
1672
1696
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
1673
1697
  usage?: LlmapiChatCompletionUsage;
1674
1698
  inference_id?: string;
@@ -2390,6 +2414,21 @@ type StepFinishEvent = {
2390
2414
  inputTokens?: number;
2391
2415
  outputTokens?: number;
2392
2416
  };
2417
+ /**
2418
+ * The round's own finish reason, as the provider sent it — `"length"` means
2419
+ * this round hit the output ceiling.
2420
+ *
2421
+ * Per-round, not per-turn: a round can truncate and the loop still recover on
2422
+ * the next one, which is why the loop does not treat it as an error on its
2423
+ * own (see the truncation guard below). Without it a consumer watching steps
2424
+ * cannot tell a round that said everything it meant to from one that was cut
2425
+ * off mid-argument — they differ only in this field (#805).
2426
+ *
2427
+ * Absent when the provider sent no finish reason. Note that no step event
2428
+ * fires for the round that *ends* a turn, so use {@link RunTerminalState} on
2429
+ * the result for the final round.
2430
+ */
2431
+ finishReason?: string;
2393
2432
  };
2394
2433
  /** Capability header: tells the portal this client can resume a detached stream. Pinned with zeta-chain/ai-portal#1139. */
2395
2434
  declare const STREAM_RESUMABLE_HEADER = "X-Stream-Resumable";
@@ -1700,6 +1700,16 @@ var ResponsesStrategy = class {
1700
1700
  }));
1701
1701
  mergeToolCallEventsIntoAccumulator(accumulator, toolCallEvents);
1702
1702
  }
1703
+ const fbStatus = typeof resp.status === "string" ? resp.status : void 0;
1704
+ const fbIncompleteReason = resp.incomplete_details?.reason;
1705
+ const fbFinishReason = typeof resp.finish_reason === "string" ? resp.finish_reason : void 0;
1706
+ if (fbStatus !== void 0) accumulator.responseStatus = fbStatus;
1707
+ if (fbIncompleteReason !== void 0) accumulator.incompleteReason = fbIncompleteReason;
1708
+ if (fbFinishReason !== void 0) {
1709
+ accumulator.finishReason = fbFinishReason;
1710
+ } else if (fbStatus === "incomplete" && fbIncompleteReason === "max_output_tokens") {
1711
+ accumulator.finishReason = "length";
1712
+ }
1703
1713
  return result;
1704
1714
  }
1705
1715
  if (typedChunk.type === "response.created" && typedChunk.response) {
@@ -1735,6 +1745,8 @@ var ResponsesStrategy = class {
1735
1745
  if (looksIncomplete && resp?.incomplete_details?.reason === "max_output_tokens") {
1736
1746
  accumulator.finishReason = "length";
1737
1747
  }
1748
+ accumulator.responseStatus = looksIncomplete ? "incomplete" : resp?.status;
1749
+ accumulator.incompleteReason = resp?.incomplete_details?.reason;
1738
1750
  if (typedChunk.response?.tools_checksum && !accumulator.toolsChecksum) {
1739
1751
  accumulator.toolsChecksum = typedChunk.response.tools_checksum;
1740
1752
  }
@@ -2003,7 +2015,17 @@ var ResponsesStrategy = class {
2003
2015
  output,
2004
2016
  usage: Object.keys(accumulator.usage).length > 0 ? accumulator.usage : void 0,
2005
2017
  tools_checksum: accumulator.toolsChecksum,
2006
- tool_call_events: accumulator.toolCallEvents
2018
+ tool_call_events: accumulator.toolCallEvents,
2019
+ // Response-level terminal state (#805). Note the individual output items
2020
+ // above hardcode `status: "completed"` — that is the item's own status
2021
+ // and says nothing about the turn. Only these two fields can tell a
2022
+ // caller the turn was cut off, and until now neither survived the stream.
2023
+ // Omitted rather than emitted as `undefined` so a clean turn's response
2024
+ // shape is unchanged.
2025
+ ...accumulator.responseStatus !== void 0 && { status: accumulator.responseStatus },
2026
+ ...accumulator.incompleteReason !== void 0 && {
2027
+ incomplete_details: { reason: accumulator.incompleteReason }
2028
+ }
2007
2029
  };
2008
2030
  }
2009
2031
  };
@@ -3022,6 +3044,9 @@ Executing tool: ${toolInfo}
3022
3044
  usage: {
3023
3045
  inputTokens: currentAccumulator.usage.prompt_tokens,
3024
3046
  outputTokens: currentAccumulator.usage.completion_tokens
3047
+ },
3048
+ ...currentAccumulator.finishReason !== void 0 && {
3049
+ finishReason: currentAccumulator.finishReason
3025
3050
  }
3026
3051
  });
3027
3052
  }
@@ -34394,6 +34394,16 @@ var ResponsesStrategy = class {
34394
34394
  }));
34395
34395
  mergeToolCallEventsIntoAccumulator(accumulator, toolCallEvents);
34396
34396
  }
34397
+ const fbStatus = typeof resp.status === "string" ? resp.status : void 0;
34398
+ const fbIncompleteReason = resp.incomplete_details?.reason;
34399
+ const fbFinishReason = typeof resp.finish_reason === "string" ? resp.finish_reason : void 0;
34400
+ if (fbStatus !== void 0) accumulator.responseStatus = fbStatus;
34401
+ if (fbIncompleteReason !== void 0) accumulator.incompleteReason = fbIncompleteReason;
34402
+ if (fbFinishReason !== void 0) {
34403
+ accumulator.finishReason = fbFinishReason;
34404
+ } else if (fbStatus === "incomplete" && fbIncompleteReason === "max_output_tokens") {
34405
+ accumulator.finishReason = "length";
34406
+ }
34397
34407
  return result;
34398
34408
  }
34399
34409
  if (typedChunk.type === "response.created" && typedChunk.response) {
@@ -34429,6 +34439,8 @@ var ResponsesStrategy = class {
34429
34439
  if (looksIncomplete && resp?.incomplete_details?.reason === "max_output_tokens") {
34430
34440
  accumulator.finishReason = "length";
34431
34441
  }
34442
+ accumulator.responseStatus = looksIncomplete ? "incomplete" : resp?.status;
34443
+ accumulator.incompleteReason = resp?.incomplete_details?.reason;
34432
34444
  if (typedChunk.response?.tools_checksum && !accumulator.toolsChecksum) {
34433
34445
  accumulator.toolsChecksum = typedChunk.response.tools_checksum;
34434
34446
  }
@@ -34697,7 +34709,17 @@ var ResponsesStrategy = class {
34697
34709
  output,
34698
34710
  usage: Object.keys(accumulator.usage).length > 0 ? accumulator.usage : void 0,
34699
34711
  tools_checksum: accumulator.toolsChecksum,
34700
- tool_call_events: accumulator.toolCallEvents
34712
+ tool_call_events: accumulator.toolCallEvents,
34713
+ // Response-level terminal state (#805). Note the individual output items
34714
+ // above hardcode `status: "completed"` — that is the item's own status
34715
+ // and says nothing about the turn. Only these two fields can tell a
34716
+ // caller the turn was cut off, and until now neither survived the stream.
34717
+ // Omitted rather than emitted as `undefined` so a clean turn's response
34718
+ // shape is unchanged.
34719
+ ...accumulator.responseStatus !== void 0 && { status: accumulator.responseStatus },
34720
+ ...accumulator.incompleteReason !== void 0 && {
34721
+ incomplete_details: { reason: accumulator.incompleteReason }
34722
+ }
34701
34723
  };
34702
34724
  }
34703
34725
  };
@@ -35716,6 +35738,9 @@ Executing tool: ${toolInfo}
35716
35738
  usage: {
35717
35739
  inputTokens: currentAccumulator.usage.prompt_tokens,
35718
35740
  outputTokens: currentAccumulator.usage.completion_tokens
35741
+ },
35742
+ ...currentAccumulator.finishReason !== void 0 && {
35743
+ finishReason: currentAccumulator.finishReason
35719
35744
  }
35720
35745
  });
35721
35746
  }
@@ -830,7 +830,7 @@ type LlmapiResponseReasoning = {
830
830
  */
831
831
  summary?: string;
832
832
  };
833
- type LlmapiResponseResponse = {
833
+ type LlmapiResponseResponse$1 = {
834
834
  /**
835
835
  * ClientInjectedTools are tool names the client provided in the original request.
836
836
  */
@@ -1613,6 +1613,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
1613
1613
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
1614
1614
  * deprecation plan.
1615
1615
  */
1616
+ /**
1617
+ * Add the Responses API's terminal-state fields, which the portal's generated
1618
+ * schema does not declare.
1619
+ *
1620
+ * Without them the two transports are asymmetric: a completions consumer reads
1621
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
1622
+ * while a Responses consumer has no field to read at all — the truncation the
1623
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
1624
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
1625
+ * its own result, but a caller holding only a response object cannot (#805).
1626
+ *
1627
+ * These are standard OpenAI Responses fields, so this widens the type toward
1628
+ * the wire rather than inventing shape: they are optional because a
1629
+ * non-streaming direct call returns only what the portal serializes today.
1630
+ * Drop the override once the portal declares them upstream.
1631
+ */
1632
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
1633
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
1634
+ status?: string;
1635
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
1636
+ incomplete_details?: {
1637
+ reason?: string;
1638
+ };
1639
+ };
1616
1640
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
1617
1641
  usage?: LlmapiChatCompletionUsage;
1618
1642
  inference_id?: string;
@@ -2231,6 +2255,21 @@ type StepFinishEvent = {
2231
2255
  inputTokens?: number;
2232
2256
  outputTokens?: number;
2233
2257
  };
2258
+ /**
2259
+ * The round's own finish reason, as the provider sent it — `"length"` means
2260
+ * this round hit the output ceiling.
2261
+ *
2262
+ * Per-round, not per-turn: a round can truncate and the loop still recover on
2263
+ * the next one, which is why the loop does not treat it as an error on its
2264
+ * own (see the truncation guard below). Without it a consumer watching steps
2265
+ * cannot tell a round that said everything it meant to from one that was cut
2266
+ * off mid-argument — they differ only in this field (#805).
2267
+ *
2268
+ * Absent when the provider sent no finish reason. Note that no step event
2269
+ * fires for the round that *ends* a turn, so use {@link RunTerminalState} on
2270
+ * the result for the final round.
2271
+ */
2272
+ finishReason?: string;
2234
2273
  };
2235
2274
  /**
2236
2275
  * Options for `runToolLoop`.
@@ -2444,13 +2483,14 @@ type RunToolLoopOptions = {
2444
2483
  * specific values). `undefined` when the stream carried no finish reason at all.
2445
2484
  *
2446
2485
  * Exposed because neither response shape reliably carries it out to a caller.
2447
- * The Responses API shape has no field for it — `LlmapiResponseResponse` declares
2448
- * neither `status` nor `incomplete_details` — so `buildFinalResponse` cannot
2449
- * surface truncation there even though the loop detected it (`responses.ts`
2450
- * normalizes it onto the accumulator). Completions does expose
2451
- * `choices[0].finish_reason`, but omits `tool_calls` entirely when there are
2452
- * none, so a caller sniffing the response cannot tell "no tool calls" from
2453
- * "field absent".
2486
+ * The Responses shape now carries `status` / `incomplete_details` — added in
2487
+ * `clientCompat` and emitted by `buildFinalResponse` — but only in the
2488
+ * Responses vocabulary, so a caller reading it has to translate
2489
+ * `incomplete_details.reason === "max_output_tokens"` itself. Completions
2490
+ * exposes `choices[0].finish_reason`, but omits `tool_calls` entirely when
2491
+ * there are none, so a caller sniffing the response cannot tell "no tool calls"
2492
+ * from "field absent". This field answers both questions in one vocabulary,
2493
+ * for either transport.
2454
2494
  *
2455
2495
  * The loop already knows both, unambiguously and identically for either API.
2456
2496
  * Reporting them here means callers and test harnesses stop reverse-engineering
@@ -830,7 +830,7 @@ type LlmapiResponseReasoning = {
830
830
  */
831
831
  summary?: string;
832
832
  };
833
- type LlmapiResponseResponse = {
833
+ type LlmapiResponseResponse$1 = {
834
834
  /**
835
835
  * ClientInjectedTools are tool names the client provided in the original request.
836
836
  */
@@ -1613,6 +1613,30 @@ type LlmapiChatCompletionUsage = OpenaiCompletionUsage & {
1613
1613
  * strategies/completions.ts `buildFinalResponse`. See that TODO for the full
1614
1614
  * deprecation plan.
1615
1615
  */
1616
+ /**
1617
+ * Add the Responses API's terminal-state fields, which the portal's generated
1618
+ * schema does not declare.
1619
+ *
1620
+ * Without them the two transports are asymmetric: a completions consumer reads
1621
+ * `choices[0].finish_reason` and can see a turn cut off at the output ceiling,
1622
+ * while a Responses consumer has no field to read at all — the truncation the
1623
+ * SDK *did* detect (see the normalization in `strategies/responses.ts`) died at
1624
+ * the stream boundary. `runToolLoop` works around this with `terminalState` on
1625
+ * its own result, but a caller holding only a response object cannot (#805).
1626
+ *
1627
+ * These are standard OpenAI Responses fields, so this widens the type toward
1628
+ * the wire rather than inventing shape: they are optional because a
1629
+ * non-streaming direct call returns only what the portal serializes today.
1630
+ * Drop the override once the portal declares them upstream.
1631
+ */
1632
+ type LlmapiResponseResponse = LlmapiResponseResponse$1 & {
1633
+ /** `"completed"` | `"incomplete"` — the turn's own status, not an output item's. */
1634
+ status?: string;
1635
+ /** Present when `status` is `"incomplete"`; `"max_output_tokens"` is a truncation. */
1636
+ incomplete_details?: {
1637
+ reason?: string;
1638
+ };
1639
+ };
1616
1640
  type LlmapiChatCompletionResponse = Omit<LlmapiChatCompletionResponse$1, "usage"> & {
1617
1641
  usage?: LlmapiChatCompletionUsage;
1618
1642
  inference_id?: string;
@@ -2231,6 +2255,21 @@ type StepFinishEvent = {
2231
2255
  inputTokens?: number;
2232
2256
  outputTokens?: number;
2233
2257
  };
2258
+ /**
2259
+ * The round's own finish reason, as the provider sent it — `"length"` means
2260
+ * this round hit the output ceiling.
2261
+ *
2262
+ * Per-round, not per-turn: a round can truncate and the loop still recover on
2263
+ * the next one, which is why the loop does not treat it as an error on its
2264
+ * own (see the truncation guard below). Without it a consumer watching steps
2265
+ * cannot tell a round that said everything it meant to from one that was cut
2266
+ * off mid-argument — they differ only in this field (#805).
2267
+ *
2268
+ * Absent when the provider sent no finish reason. Note that no step event
2269
+ * fires for the round that *ends* a turn, so use {@link RunTerminalState} on
2270
+ * the result for the final round.
2271
+ */
2272
+ finishReason?: string;
2234
2273
  };
2235
2274
  /**
2236
2275
  * Options for `runToolLoop`.
@@ -2444,13 +2483,14 @@ type RunToolLoopOptions = {
2444
2483
  * specific values). `undefined` when the stream carried no finish reason at all.
2445
2484
  *
2446
2485
  * Exposed because neither response shape reliably carries it out to a caller.
2447
- * The Responses API shape has no field for it — `LlmapiResponseResponse` declares
2448
- * neither `status` nor `incomplete_details` — so `buildFinalResponse` cannot
2449
- * surface truncation there even though the loop detected it (`responses.ts`
2450
- * normalizes it onto the accumulator). Completions does expose
2451
- * `choices[0].finish_reason`, but omits `tool_calls` entirely when there are
2452
- * none, so a caller sniffing the response cannot tell "no tool calls" from
2453
- * "field absent".
2486
+ * The Responses shape now carries `status` / `incomplete_details` — added in
2487
+ * `clientCompat` and emitted by `buildFinalResponse` — but only in the
2488
+ * Responses vocabulary, so a caller reading it has to translate
2489
+ * `incomplete_details.reason === "max_output_tokens"` itself. Completions
2490
+ * exposes `choices[0].finish_reason`, but omits `tool_calls` entirely when
2491
+ * there are none, so a caller sniffing the response cannot tell "no tool calls"
2492
+ * from "field absent". This field answers both questions in one vocabulary,
2493
+ * for either transport.
2454
2494
  *
2455
2495
  * The loop already knows both, unambiguously and identically for either API.
2456
2496
  * Reporting them here means callers and test harnesses stop reverse-engineering
@@ -11065,6 +11065,16 @@ var ResponsesStrategy = class {
11065
11065
  }));
11066
11066
  mergeToolCallEventsIntoAccumulator(accumulator, toolCallEvents);
11067
11067
  }
11068
+ const fbStatus = typeof resp.status === "string" ? resp.status : void 0;
11069
+ const fbIncompleteReason = resp.incomplete_details?.reason;
11070
+ const fbFinishReason = typeof resp.finish_reason === "string" ? resp.finish_reason : void 0;
11071
+ if (fbStatus !== void 0) accumulator.responseStatus = fbStatus;
11072
+ if (fbIncompleteReason !== void 0) accumulator.incompleteReason = fbIncompleteReason;
11073
+ if (fbFinishReason !== void 0) {
11074
+ accumulator.finishReason = fbFinishReason;
11075
+ } else if (fbStatus === "incomplete" && fbIncompleteReason === "max_output_tokens") {
11076
+ accumulator.finishReason = "length";
11077
+ }
11068
11078
  return result;
11069
11079
  }
11070
11080
  if (typedChunk.type === "response.created" && typedChunk.response) {
@@ -11100,6 +11110,8 @@ var ResponsesStrategy = class {
11100
11110
  if (looksIncomplete && resp?.incomplete_details?.reason === "max_output_tokens") {
11101
11111
  accumulator.finishReason = "length";
11102
11112
  }
11113
+ accumulator.responseStatus = looksIncomplete ? "incomplete" : resp?.status;
11114
+ accumulator.incompleteReason = resp?.incomplete_details?.reason;
11103
11115
  if (typedChunk.response?.tools_checksum && !accumulator.toolsChecksum) {
11104
11116
  accumulator.toolsChecksum = typedChunk.response.tools_checksum;
11105
11117
  }
@@ -11368,7 +11380,17 @@ var ResponsesStrategy = class {
11368
11380
  output,
11369
11381
  usage: Object.keys(accumulator.usage).length > 0 ? accumulator.usage : void 0,
11370
11382
  tools_checksum: accumulator.toolsChecksum,
11371
- tool_call_events: accumulator.toolCallEvents
11383
+ tool_call_events: accumulator.toolCallEvents,
11384
+ // Response-level terminal state (#805). Note the individual output items
11385
+ // above hardcode `status: "completed"` — that is the item's own status
11386
+ // and says nothing about the turn. Only these two fields can tell a
11387
+ // caller the turn was cut off, and until now neither survived the stream.
11388
+ // Omitted rather than emitted as `undefined` so a clean turn's response
11389
+ // shape is unchanged.
11390
+ ...accumulator.responseStatus !== void 0 && { status: accumulator.responseStatus },
11391
+ ...accumulator.incompleteReason !== void 0 && {
11392
+ incomplete_details: { reason: accumulator.incompleteReason }
11393
+ }
11372
11394
  };
11373
11395
  }
11374
11396
  };
@@ -12387,6 +12409,9 @@ Executing tool: ${toolInfo}
12387
12409
  usage: {
12388
12410
  inputTokens: currentAccumulator.usage.prompt_tokens,
12389
12411
  outputTokens: currentAccumulator.usage.completion_tokens
12412
+ },
12413
+ ...currentAccumulator.finishReason !== void 0 && {
12414
+ finishReason: currentAccumulator.finishReason
12390
12415
  }
12391
12416
  });
12392
12417
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "5.0.3",
3
+ "version": "5.0.4",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",