@ai-sdk/workflow 1.0.16 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @ai-sdk/workflow
2
2
 
3
+ ## 1.0.17
4
+
5
+ ### Patch Changes
6
+
7
+ - 148babc: `WorkflowChatTransport` now drops orphan UI chunks (deltas/ends and tool output/approval chunks whose part was started before the resumed window) when reconnecting with a negative `initialStartIndex`, instead of crashing the AI SDK client. Self-contained `tool-input-available`/`tool-input-error` chunks establish the tool part and are never dropped. A one-time warning links to docs on rewinding to a step boundary server-side.
8
+ - e660e45: Reduce the `doStreamStep` step-boundary payload by returning minimal raw aggregates and reconstructing the `StepResult` outside the step, instead of serializing the full `StepResult` plus the per-chunk array into the durable event log.
9
+ - cc773bc: Expose `totalUsage` and `finishReason` on the `WorkflowAgent.stream()` result, mirroring `GenerateTextResult`/`StreamTextResult` and the existing `onEnd` event payload.
10
+ - ai@7.0.17
11
+
3
12
  ## 1.0.16
4
13
 
5
14
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -876,6 +876,14 @@ interface WorkflowAgentStreamResult<TTools extends ToolSet = ToolSet, OUTPUT = n
876
876
  * This matches the AI SDK's `GenerateTextResult.toolResults` behavior.
877
877
  */
878
878
  toolResults: ToolResult[];
879
+ /**
880
+ * The finish reason from the last step.
881
+ */
882
+ finishReason: FinishReason;
883
+ /**
884
+ * The total token usage across all steps.
885
+ */
886
+ totalUsage: LanguageModelUsage;
879
887
  /**
880
888
  * The generated structured output. It uses the `output` specification.
881
889
  * Only available when `output` is specified.
package/dist/index.mjs CHANGED
@@ -144,7 +144,6 @@ function resolveSerializableTools(tools) {
144
144
  // src/do-stream-step.ts
145
145
  async function doStreamStep(conversationPrompt, modelInit, writable, serializedTools, options) {
146
146
  "use step";
147
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
148
147
  const model = typeof modelInit === "string" ? gateway.languageModel(modelInit) : modelInit;
149
148
  const tools = serializedTools ? resolveSerializableTools(serializedTools) : void 0;
150
149
  const { stream: modelStream } = await streamModelCall({
@@ -176,15 +175,11 @@ async function doStreamStep(conversationPrompt, modelInit, writable, serializedT
176
175
  let finish;
177
176
  let text = "";
178
177
  const reasoningParts = [];
179
- const chunks = [];
180
178
  let responseMetadata;
181
179
  let warnings;
182
180
  const writer = writable == null ? void 0 : writable.getWriter();
183
181
  try {
184
182
  for await (const part of modelStream) {
185
- if (part.type !== "model-call-start" && part.type !== "model-call-end" && part.type !== "model-call-response-metadata") {
186
- chunks.push(part);
187
- }
188
183
  switch (part.type) {
189
184
  case "text-delta":
190
185
  text += part.text;
@@ -251,99 +246,15 @@ async function doStreamStep(conversationPrompt, modelInit, writable, serializedT
251
246
  } finally {
252
247
  writer == null ? void 0 : writer.releaseLock();
253
248
  }
254
- const reasoningText = reasoningParts.map((r) => r.text).join("") || void 0;
255
- const step = {
256
- callId: "workflow-agent",
257
- stepNumber: (_a = options == null ? void 0 : options.stepNumber) != null ? _a : 0,
258
- model: {
259
- provider: (_c = (_b = responseMetadata == null ? void 0 : responseMetadata.modelId) == null ? void 0 : _b.split(":")[0]) != null ? _c : "unknown",
260
- modelId: (_d = responseMetadata == null ? void 0 : responseMetadata.modelId) != null ? _d : "unknown"
261
- },
262
- functionId: void 0,
263
- metadata: void 0,
264
- runtimeContext: (_e = options == null ? void 0 : options.runtimeContext) != null ? _e : {},
265
- toolsContext: (_f = options == null ? void 0 : options.toolsContext) != null ? _f : {},
266
- content: [
267
- ...text ? [{ type: "text", text }] : [],
268
- ...toolCalls.filter((tc) => !tc.invalid).map((tc) => ({
269
- type: "tool-call",
270
- toolCallId: tc.toolCallId,
271
- toolName: tc.toolName,
272
- input: tc.input,
273
- ...tc.dynamic ? { dynamic: true } : {}
274
- }))
275
- ],
276
- text,
277
- reasoning: reasoningParts.map((r) => ({
278
- type: "reasoning",
279
- text: r.text
280
- })),
281
- reasoningText,
282
- files: [],
283
- sources: [],
284
- toolCalls: toolCalls.filter((tc) => !tc.invalid).map((tc) => ({
285
- type: "tool-call",
286
- toolCallId: tc.toolCallId,
287
- toolName: tc.toolName,
288
- input: tc.input,
289
- ...tc.dynamic ? { dynamic: true } : {}
290
- })),
291
- staticToolCalls: [],
292
- dynamicToolCalls: toolCalls.filter((tc) => !tc.invalid && tc.dynamic).map((tc) => ({
293
- type: "tool-call",
294
- toolCallId: tc.toolCallId,
295
- toolName: tc.toolName,
296
- input: tc.input,
297
- dynamic: true
298
- })),
299
- toolResults: [],
300
- staticToolResults: [],
301
- dynamicToolResults: [],
302
- finishReason: (_g = finish == null ? void 0 : finish.finishReason) != null ? _g : "other",
303
- rawFinishReason: finish == null ? void 0 : finish.rawFinishReason,
304
- usage: (_h = finish == null ? void 0 : finish.usage) != null ? _h : {
305
- inputTokens: 0,
306
- inputTokenDetails: {
307
- noCacheTokens: void 0,
308
- cacheReadTokens: void 0,
309
- cacheWriteTokens: void 0
310
- },
311
- outputTokens: 0,
312
- outputTokenDetails: {
313
- textTokens: void 0,
314
- reasoningTokens: void 0
315
- },
316
- totalTokens: 0
317
- },
318
- performance: {
319
- effectiveOutputTokensPerSecond: 0,
320
- outputTokensPerSecond: void 0,
321
- inputTokensPerSecond: void 0,
322
- effectiveTotalTokensPerSecond: 0,
323
- stepTimeMs: 0,
324
- responseTimeMs: 0,
325
- toolExecutionMs: {},
326
- timeToFirstOutputMs: void 0
327
- },
328
- warnings,
329
- request: {
330
- body: "",
331
- messages: []
332
- // TODO implement step request messages
333
- },
334
- response: {
335
- id: (_i = responseMetadata == null ? void 0 : responseMetadata.id) != null ? _i : "unknown",
336
- timestamp: (_j = responseMetadata == null ? void 0 : responseMetadata.timestamp) != null ? _j : /* @__PURE__ */ new Date(),
337
- modelId: (_k = responseMetadata == null ? void 0 : responseMetadata.modelId) != null ? _k : "unknown",
338
- messages: []
339
- },
340
- providerMetadata: (_l = finish == null ? void 0 : finish.providerMetadata) != null ? _l : {}
341
- };
342
249
  return {
343
250
  toolCalls,
344
251
  finish,
345
- step,
346
- chunks,
252
+ raw: {
253
+ text,
254
+ reasoning: reasoningParts,
255
+ responseMetadata,
256
+ warnings
257
+ },
347
258
  providerExecutedToolResults
348
259
  };
349
260
  }
@@ -562,7 +473,7 @@ async function* streamTextIterator({
562
473
  providerOptions: currentGenerationSettings.providerOptions,
563
474
  headers: currentGenerationSettings.headers
564
475
  }));
565
- const { toolCalls, finish, step, providerExecutedToolResults } = await doStreamStep(
476
+ const { toolCalls, finish, raw, providerExecutedToolResults } = await doStreamStep(
566
477
  conversationPrompt,
567
478
  currentModel,
568
479
  writable,
@@ -572,12 +483,14 @@ async function* streamTextIterator({
572
483
  toolChoice: currentToolChoice,
573
484
  includeRawChunks,
574
485
  repairToolCall,
575
- responseFormat,
576
- runtimeContext: currentRuntimeContext,
577
- toolsContext: currentToolsContext,
578
- stepNumber
486
+ responseFormat
579
487
  }
580
488
  );
489
+ const step = buildStepResult(raw, toolCalls, finish, {
490
+ stepNumber,
491
+ runtimeContext: currentRuntimeContext,
492
+ toolsContext: currentToolsContext
493
+ });
581
494
  await ((_j = telemetryDispatcher.onLanguageModelCallEnd) == null ? void 0 : _j.call(telemetryDispatcher, {
582
495
  callId: step.callId,
583
496
  provider: (_g = (_f = step.model) == null ? void 0 : _f.provider) != null ? _g : "unknown",
@@ -692,6 +605,87 @@ function normalizeStepForTelemetry(step) {
692
605
  model: (_a = step.model) != null ? _a : { provider: "unknown", modelId: "unknown" }
693
606
  };
694
607
  }
608
+ function buildStepResult(raw, toolCalls, finish, opts) {
609
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
610
+ const { text, reasoning: reasoningParts, responseMetadata, warnings } = raw;
611
+ const reasoningText = reasoningParts.map((r) => r.text).join("") || void 0;
612
+ const validToolCalls = toolCalls.filter((tc) => !tc.invalid).map((tc) => ({
613
+ type: "tool-call",
614
+ toolCallId: tc.toolCallId,
615
+ toolName: tc.toolName,
616
+ input: tc.input,
617
+ ...tc.dynamic ? { dynamic: true } : {}
618
+ }));
619
+ return {
620
+ callId: "workflow-agent",
621
+ stepNumber: opts.stepNumber,
622
+ model: {
623
+ provider: (_b = (_a = responseMetadata == null ? void 0 : responseMetadata.modelId) == null ? void 0 : _a.split(":")[0]) != null ? _b : "unknown",
624
+ modelId: (_c = responseMetadata == null ? void 0 : responseMetadata.modelId) != null ? _c : "unknown"
625
+ },
626
+ functionId: void 0,
627
+ metadata: void 0,
628
+ runtimeContext: (_d = opts.runtimeContext) != null ? _d : {},
629
+ toolsContext: (_e = opts.toolsContext) != null ? _e : {},
630
+ content: [
631
+ ...text ? [{ type: "text", text }] : [],
632
+ ...validToolCalls
633
+ ],
634
+ text,
635
+ reasoning: reasoningParts.map((r) => ({
636
+ type: "reasoning",
637
+ text: r.text
638
+ })),
639
+ reasoningText,
640
+ files: [],
641
+ sources: [],
642
+ toolCalls: validToolCalls,
643
+ staticToolCalls: [],
644
+ dynamicToolCalls: validToolCalls.filter((tc) => tc.dynamic),
645
+ toolResults: [],
646
+ staticToolResults: [],
647
+ dynamicToolResults: [],
648
+ finishReason: (_f = finish == null ? void 0 : finish.finishReason) != null ? _f : "other",
649
+ rawFinishReason: finish == null ? void 0 : finish.rawFinishReason,
650
+ usage: (_g = finish == null ? void 0 : finish.usage) != null ? _g : {
651
+ inputTokens: 0,
652
+ inputTokenDetails: {
653
+ noCacheTokens: void 0,
654
+ cacheReadTokens: void 0,
655
+ cacheWriteTokens: void 0
656
+ },
657
+ outputTokens: 0,
658
+ outputTokenDetails: {
659
+ textTokens: void 0,
660
+ reasoningTokens: void 0
661
+ },
662
+ totalTokens: 0
663
+ },
664
+ performance: {
665
+ effectiveOutputTokensPerSecond: 0,
666
+ outputTokensPerSecond: void 0,
667
+ inputTokensPerSecond: void 0,
668
+ effectiveTotalTokensPerSecond: 0,
669
+ stepTimeMs: 0,
670
+ responseTimeMs: 0,
671
+ toolExecutionMs: {},
672
+ timeToFirstOutputMs: void 0
673
+ },
674
+ warnings,
675
+ request: {
676
+ body: "",
677
+ messages: []
678
+ // TODO implement step request messages
679
+ },
680
+ response: {
681
+ id: (_h = responseMetadata == null ? void 0 : responseMetadata.id) != null ? _h : "unknown",
682
+ timestamp: (_i = responseMetadata == null ? void 0 : responseMetadata.timestamp) != null ? _i : /* @__PURE__ */ new Date(),
683
+ modelId: (_j = responseMetadata == null ? void 0 : responseMetadata.modelId) != null ? _j : "unknown",
684
+ messages: []
685
+ },
686
+ providerMetadata: (_k = finish == null ? void 0 : finish.providerMetadata) != null ? _k : {}
687
+ };
688
+ }
695
689
  function sanitizeProviderMetadataForToolCall(metadata) {
696
690
  if (metadata == null) return void 0;
697
691
  const meta = metadata;
@@ -1356,6 +1350,8 @@ var WorkflowAgent = class {
1356
1350
  steps,
1357
1351
  toolCalls: [],
1358
1352
  toolResults: [],
1353
+ finishReason: "other",
1354
+ totalUsage: aggregateUsage(steps),
1359
1355
  output: void 0
1360
1356
  };
1361
1357
  }
@@ -1515,16 +1511,17 @@ var WorkflowAgent = class {
1515
1511
  });
1516
1512
  }
1517
1513
  const messages2 = iterMessages;
1514
+ const lastStep2 = steps[steps.length - 1];
1515
+ const totalUsage2 = aggregateUsage(steps);
1516
+ const finishReason2 = (_E = lastStep2 == null ? void 0 : lastStep2.finishReason) != null ? _E : "other";
1518
1517
  if (mergedOnEnd && !wasAborted) {
1519
- const lastStep = steps[steps.length - 1];
1520
- const totalUsage = aggregateUsage(steps);
1521
1518
  await mergedOnEnd({
1522
1519
  steps,
1523
1520
  messages: messages2,
1524
- text: (_E = lastStep == null ? void 0 : lastStep.text) != null ? _E : "",
1525
- finishReason: (_F = lastStep == null ? void 0 : lastStep.finishReason) != null ? _F : "other",
1526
- usage: totalUsage,
1527
- totalUsage,
1521
+ text: (_F = lastStep2 == null ? void 0 : lastStep2.text) != null ? _F : "",
1522
+ finishReason: finishReason2,
1523
+ usage: totalUsage2,
1524
+ totalUsage: totalUsage2,
1528
1525
  runtimeContext,
1529
1526
  toolsContext,
1530
1527
  output: void 0
@@ -1532,13 +1529,12 @@ var WorkflowAgent = class {
1532
1529
  }
1533
1530
  if (!wasAborted && steps.length > 0) {
1534
1531
  const telemetrySteps = steps.map(normalizeStepForTelemetry2);
1535
- const lastStep = telemetrySteps[telemetrySteps.length - 1];
1536
- const totalUsage = aggregateUsage(steps);
1532
+ const lastTelemetryStep = telemetrySteps[telemetrySteps.length - 1];
1537
1533
  await ((_G = telemetryDispatcher.onEnd) == null ? void 0 : _G.call(telemetryDispatcher, {
1538
- ...lastStep,
1534
+ ...lastTelemetryStep,
1539
1535
  steps: telemetrySteps,
1540
- usage: totalUsage,
1541
- totalUsage
1536
+ usage: totalUsage2,
1537
+ totalUsage: totalUsage2
1542
1538
  }));
1543
1539
  }
1544
1540
  if (options.writable) {
@@ -1570,6 +1566,8 @@ var WorkflowAgent = class {
1570
1566
  steps,
1571
1567
  toolCalls: allToolCalls,
1572
1568
  toolResults: allToolResults,
1569
+ finishReason: finishReason2,
1570
+ totalUsage: totalUsage2,
1573
1571
  output: void 0
1574
1572
  };
1575
1573
  }
@@ -1690,16 +1688,16 @@ var WorkflowAgent = class {
1690
1688
  const effectiveOutput = (_K = options.output) != null ? _K : this.output;
1691
1689
  let experimentalOutput = void 0;
1692
1690
  if (effectiveOutput && steps.length > 0) {
1693
- const lastStep = steps[steps.length - 1];
1694
- const text = lastStep.text;
1691
+ const lastStep2 = steps[steps.length - 1];
1692
+ const text = lastStep2.text;
1695
1693
  if (text) {
1696
1694
  try {
1697
1695
  experimentalOutput = await effectiveOutput.parseCompleteOutput(
1698
1696
  { text },
1699
1697
  {
1700
- response: lastStep.response,
1701
- usage: lastStep.usage,
1702
- finishReason: lastStep.finishReason
1698
+ response: lastStep2.response,
1699
+ usage: lastStep2.usage,
1700
+ finishReason: lastStep2.finishReason
1703
1701
  }
1704
1702
  );
1705
1703
  } catch (parseError) {
@@ -1709,14 +1707,15 @@ var WorkflowAgent = class {
1709
1707
  }
1710
1708
  }
1711
1709
  }
1710
+ const lastStep = steps[steps.length - 1];
1711
+ const totalUsage = aggregateUsage(steps);
1712
+ const finishReason = (_L = lastStep == null ? void 0 : lastStep.finishReason) != null ? _L : "other";
1712
1713
  if (mergedOnEnd && !wasAborted) {
1713
- const lastStep = steps[steps.length - 1];
1714
- const totalUsage = aggregateUsage(steps);
1715
1714
  await mergedOnEnd({
1716
1715
  steps,
1717
1716
  messages,
1718
- text: (_L = lastStep == null ? void 0 : lastStep.text) != null ? _L : "",
1719
- finishReason: (_M = lastStep == null ? void 0 : lastStep.finishReason) != null ? _M : "other",
1717
+ text: (_M = lastStep == null ? void 0 : lastStep.text) != null ? _M : "",
1718
+ finishReason,
1720
1719
  usage: totalUsage,
1721
1720
  totalUsage,
1722
1721
  runtimeContext,
@@ -1726,10 +1725,9 @@ var WorkflowAgent = class {
1726
1725
  }
1727
1726
  if (!wasAborted && steps.length > 0) {
1728
1727
  const telemetrySteps = steps.map(normalizeStepForTelemetry2);
1729
- const lastStep = telemetrySteps[telemetrySteps.length - 1];
1730
- const totalUsage = aggregateUsage(steps);
1728
+ const lastTelemetryStep = telemetrySteps[telemetrySteps.length - 1];
1731
1729
  await ((_N = telemetryDispatcher.onEnd) == null ? void 0 : _N.call(telemetryDispatcher, {
1732
- ...lastStep,
1730
+ ...lastTelemetryStep,
1733
1731
  steps: telemetrySteps,
1734
1732
  usage: totalUsage,
1735
1733
  totalUsage
@@ -1757,6 +1755,8 @@ var WorkflowAgent = class {
1757
1755
  steps,
1758
1756
  toolCalls: lastStepToolCalls,
1759
1757
  toolResults: lastStepToolResults,
1758
+ finishReason,
1759
+ totalUsage,
1760
1760
  output: experimentalOutput
1761
1761
  };
1762
1762
  }
@@ -2249,6 +2249,54 @@ async function* normalizeUIMessageStreamParts(source) {
2249
2249
  }
2250
2250
 
2251
2251
  // src/workflow-chat-transport.ts
2252
+ function createOrphanFilter() {
2253
+ const seenStartedIds = /* @__PURE__ */ new Set();
2254
+ const seenStartedToolCallIds = /* @__PURE__ */ new Set();
2255
+ let warnedOnce = false;
2256
+ function warnOnce(orphanKind, orphanRef) {
2257
+ if (warnedOnce) return;
2258
+ warnedOnce = true;
2259
+ console.warn(
2260
+ `[WorkflowChatTransport] Dropping orphan UI chunk (${orphanKind} for id "${orphanRef}") on resume \u2014 the resume position landed mid-part. The dropped chunk(s) reference a part whose start chunk wasn't in the resumed window. To preserve the full message, configure your stream endpoint to rewind to a step boundary before returning the readable. See: https://workflow.dev/docs/ai/resumable-streams#mid-part-resumes`
2261
+ );
2262
+ }
2263
+ function shouldDrop(chunk) {
2264
+ switch (chunk.type) {
2265
+ case "text-start":
2266
+ case "reasoning-start":
2267
+ seenStartedIds.add(chunk.id);
2268
+ return false;
2269
+ case "tool-input-start":
2270
+ // `tool-input-available` / `tool-input-error` are self-contained: the
2271
+ // AI SDK creates the tool part from them directly (non-streamed tool
2272
+ // calls are emitted as a bare `tool-input-available`), so they must
2273
+ // never be dropped. They also carry the full input, so they recover a
2274
+ // tool call whose `tool-input-start` fell outside the resumed window.
2275
+ case "tool-input-available":
2276
+ case "tool-input-error":
2277
+ seenStartedToolCallIds.add(chunk.toolCallId);
2278
+ return false;
2279
+ case "text-delta":
2280
+ case "text-end":
2281
+ case "reasoning-delta":
2282
+ case "reasoning-end":
2283
+ if (seenStartedIds.has(chunk.id)) return false;
2284
+ warnOnce(chunk.type, chunk.id);
2285
+ return true;
2286
+ case "tool-input-delta":
2287
+ case "tool-approval-request":
2288
+ case "tool-output-available":
2289
+ case "tool-output-error":
2290
+ case "tool-output-denied":
2291
+ if (seenStartedToolCallIds.has(chunk.toolCallId)) return false;
2292
+ warnOnce(chunk.type, chunk.toolCallId);
2293
+ return true;
2294
+ default:
2295
+ return false;
2296
+ }
2297
+ }
2298
+ return { shouldDrop };
2299
+ }
2252
2300
  var WorkflowChatTransport = class {
2253
2301
  /**
2254
2302
  * Creates a new WorkflowChatTransport instance.
@@ -2394,6 +2442,7 @@ var WorkflowChatTransport = class {
2394
2442
  let gotFinish = false;
2395
2443
  let consecutiveErrors = 0;
2396
2444
  let replayFromStart = false;
2445
+ const orphanFilter = useExplicitStartIndex && explicitStartIndex < 0 ? createOrphanFilter() : null;
2397
2446
  while (!gotFinish) {
2398
2447
  const startIndex = useExplicitStartIndex ? explicitStartIndex : replayFromStart ? 0 : chunkIndex;
2399
2448
  const url = `${baseUrl}?startIndex=${startIndex}`;
@@ -2434,6 +2483,7 @@ var WorkflowChatTransport = class {
2434
2483
  throw chunk.error;
2435
2484
  }
2436
2485
  chunkIndex++;
2486
+ if (orphanFilter == null ? void 0 : orphanFilter.shouldDrop(chunk.value)) continue;
2437
2487
  yield chunk.value;
2438
2488
  if (chunk.value.type === "finish") {
2439
2489
  gotFinish = true;