@ai-sdk/workflow 1.0.0-beta.3 → 1.0.0-beta.30

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.
@@ -7,21 +7,24 @@ import type {
7
7
  SharedV4ProviderOptions,
8
8
  } from '@ai-sdk/provider';
9
9
  import {
10
- type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
10
+ Output,
11
+ experimental_filterActiveTools as filterActiveTools,
11
12
  type FinishReason,
12
13
  type LanguageModelResponseMetadata,
13
14
  type LanguageModelUsage,
15
+ type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
14
16
  type ModelMessage,
15
- Output,
16
17
  type StepResult,
17
18
  type StopCondition,
18
- type StreamTextOnStepFinishCallback,
19
+ type GenerateTextOnStepFinishCallback,
19
20
  type SystemModelMessage,
21
+ type ActiveTools,
20
22
  type ToolCallRepairFunction,
21
23
  type ToolChoice,
22
24
  type ToolSet,
23
25
  type UIMessage,
24
- LanguageModel,
26
+ type LanguageModel,
27
+ type Prompt,
25
28
  } from 'ai';
26
29
  import {
27
30
  convertToLanguageModelPrompt,
@@ -30,19 +33,18 @@ import {
30
33
  standardizePrompt,
31
34
  } from 'ai/internal';
32
35
  import { streamTextIterator } from './stream-text-iterator.js';
33
- import type { CompatibleLanguageModel } from './types.js';
34
36
 
35
37
  // Re-export for consumers
36
38
  export type { CompatibleLanguageModel } from './types.js';
37
39
 
38
40
  /**
39
41
  * Callback function to be called after each step completes.
40
- * Alias for the AI SDK's StreamTextOnStepFinishCallback, using
42
+ * Alias for the AI SDK's GenerateTextOnStepFinishCallback, using
41
43
  * WorkflowAgent-consistent naming.
42
44
  */
43
45
  export type WorkflowAgentOnStepFinishCallback<
44
46
  TTools extends ToolSet = ToolSet,
45
- > = StreamTextOnStepFinishCallback<TTools, any>;
47
+ > = GenerateTextOnStepFinishCallback<TTools, any>;
46
48
 
47
49
  /**
48
50
  * Infer the type of the tools of a workflow agent.
@@ -92,7 +94,7 @@ export type ProviderOptions = SharedV4ProviderOptions;
92
94
  /**
93
95
  * Telemetry settings for observability.
94
96
  */
95
- export interface TelemetrySettings {
97
+ export interface TelemetryOptions {
96
98
  /**
97
99
  * Enable or disable telemetry. Defaults to true.
98
100
  */
@@ -339,7 +341,11 @@ export interface PrepareCallOptions<
339
341
  tools: TTools;
340
342
  instructions?: string | SystemModelMessage | Array<SystemModelMessage>;
341
343
  toolChoice?: ToolChoice<TTools>;
342
- experimental_telemetry?: TelemetrySettings;
344
+ telemetry?: TelemetryOptions;
345
+ /**
346
+ * @deprecated Use `telemetry` instead. This alias will be removed in a future major release.
347
+ */
348
+ experimental_telemetry?: TelemetryOptions;
343
349
  experimental_context?: unknown;
344
350
  messages: ModelMessage[];
345
351
  }
@@ -367,6 +373,11 @@ export type PrepareCallCallback<TTools extends ToolSet = ToolSet> = (
367
373
  export interface WorkflowAgentOptions<
368
374
  TTools extends ToolSet = ToolSet,
369
375
  > extends GenerationSettings {
376
+ /**
377
+ * The id of the agent.
378
+ */
379
+ id?: string;
380
+
370
381
  /**
371
382
  * The model provider to use for the agent.
372
383
  *
@@ -400,9 +411,16 @@ export interface WorkflowAgentOptions<
400
411
  toolChoice?: ToolChoice<TTools>;
401
412
 
402
413
  /**
403
- * Optional telemetry configuration (experimental).
414
+ * Optional telemetry configuration.
404
415
  */
405
- experimental_telemetry?: TelemetrySettings;
416
+ telemetry?: TelemetryOptions;
417
+
418
+ /**
419
+ * Optional telemetry configuration.
420
+ *
421
+ * @deprecated Use `telemetry` instead. This alias will be removed in a future major release.
422
+ */
423
+ experimental_telemetry?: TelemetryOptions;
406
424
 
407
425
  /**
408
426
  * Default context that is passed into tool execution for every stream call on this agent.
@@ -413,6 +431,46 @@ export interface WorkflowAgentOptions<
413
431
  */
414
432
  experimental_context?: unknown;
415
433
 
434
+ /**
435
+ * Default stop condition for the agent loop. When the condition is an array,
436
+ * any of the conditions can be met to stop the generation.
437
+ *
438
+ * Per-stream `stopWhen` values passed to `stream()` override this default.
439
+ */
440
+ stopWhen?:
441
+ | StopCondition<NoInfer<ToolSet>, any>
442
+ | Array<StopCondition<NoInfer<ToolSet>, any>>;
443
+
444
+ /**
445
+ * Default set of active tools that limits which tools the model can call,
446
+ * without changing the tool call and result types in the result.
447
+ *
448
+ * Per-stream `activeTools` values passed to `stream()` override this default.
449
+ */
450
+ activeTools?: ActiveTools<NoInfer<TTools>>;
451
+
452
+ /**
453
+ * Default output specification for structured outputs.
454
+ * Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
455
+ *
456
+ * Per-stream `output` values passed to `stream()` override this default.
457
+ */
458
+ output?: OutputSpecification<any, any>;
459
+
460
+ /**
461
+ * Default function that attempts to repair a tool call that failed to parse.
462
+ *
463
+ * Per-stream `experimental_repairToolCall` values passed to `stream()` override this default.
464
+ */
465
+ experimental_repairToolCall?: ToolCallRepairFunction<TTools>;
466
+
467
+ /**
468
+ * Default custom download function to use for URLs.
469
+ *
470
+ * Per-stream `experimental_download` values passed to `stream()` override this default.
471
+ */
472
+ experimental_download?: DownloadFunction;
473
+
416
474
  /**
417
475
  * Default callback function called before each step in the agent loop.
418
476
  * Use this to modify settings, manage context, or inject messages dynamically
@@ -445,12 +503,12 @@ export interface WorkflowAgentOptions<
445
503
  /**
446
504
  * Callback called before a tool's execute function runs.
447
505
  */
448
- experimental_onToolCallStart?: WorkflowAgentOnToolCallStartCallback;
506
+ experimental_onToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback;
449
507
 
450
508
  /**
451
509
  * Callback called after a tool execution completes.
452
510
  */
453
- experimental_onToolCallFinish?: WorkflowAgentOnToolCallFinishCallback;
511
+ experimental_onToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback;
454
512
 
455
513
  /**
456
514
  * Prepare the parameters for the stream call.
@@ -535,250 +593,305 @@ export type WorkflowAgentOnStartCallback = (event: {
535
593
  /**
536
594
  * Callback that is called before each step (LLM call) begins.
537
595
  */
538
- export type WorkflowAgentOnStepStartCallback = (event: {
539
- /** The current step number (0-based) */
540
- readonly stepNumber: number;
541
- /** The model being used for this step */
542
- readonly model: LanguageModel;
543
- /** The messages being sent for this step */
544
- readonly messages: ModelMessage[];
545
- }) => PromiseLike<void> | void;
596
+ export type WorkflowAgentOnStepStartCallback<TTools extends ToolSet = ToolSet> =
597
+ (event: {
598
+ /** The current step number (0-based) */
599
+ readonly stepNumber: number;
600
+ /** The model being used for this step */
601
+ readonly model: LanguageModel;
602
+ /** The messages being sent for this step */
603
+ readonly messages: ModelMessage[];
604
+ /** Results from all previously finished steps */
605
+ readonly steps: ReadonlyArray<StepResult<TTools, any>>;
606
+ }) => PromiseLike<void> | void;
546
607
 
547
608
  /**
548
609
  * Callback that is called before a tool's execute function runs.
549
610
  */
550
- export type WorkflowAgentOnToolCallStartCallback = (event: {
611
+ export type WorkflowAgentOnToolExecutionStartCallback = (event: {
551
612
  /** The tool call being executed */
552
613
  readonly toolCall: ToolCall;
614
+ /** The current step number (0-based) */
615
+ readonly stepNumber: number;
553
616
  }) => PromiseLike<void> | void;
554
617
 
555
618
  /**
556
619
  * Callback that is called after a tool execution completes.
620
+ * Uses a discriminated union pattern: check `success` to determine
621
+ * whether `output` or `error` is available.
557
622
  */
558
- export type WorkflowAgentOnToolCallFinishCallback = (event: {
559
- /** The tool call that was executed */
560
- readonly toolCall: ToolCall;
561
- /** The tool result (undefined if execution failed) */
562
- readonly result?: unknown;
563
- /** The error if execution failed */
564
- readonly error?: unknown;
565
- }) => PromiseLike<void> | void;
623
+ export type WorkflowAgentOnToolExecutionEndCallback = (
624
+ event:
625
+ | {
626
+ /** The tool call that was executed */
627
+ readonly toolCall: ToolCall;
628
+ /** The current step number (0-based) */
629
+ readonly stepNumber: number;
630
+ /** Execution time in milliseconds */
631
+ readonly durationMs: number;
632
+ /** Whether the tool call succeeded */
633
+ readonly success: true;
634
+ /** The tool result */
635
+ readonly output: unknown;
636
+ readonly error?: never;
637
+ }
638
+ | {
639
+ /** The tool call that was executed */
640
+ readonly toolCall: ToolCall;
641
+ /** The current step number (0-based) */
642
+ readonly stepNumber: number;
643
+ /** Execution time in milliseconds */
644
+ readonly durationMs: number;
645
+ /** Whether the tool call succeeded */
646
+ readonly success: false;
647
+ /** The error that occurred */
648
+ readonly error: unknown;
649
+ readonly output?: never;
650
+ },
651
+ ) => PromiseLike<void> | void;
566
652
 
567
653
  /**
568
654
  * Options for the {@link WorkflowAgent.stream} method.
569
655
  */
570
- export interface WorkflowAgentStreamOptions<
656
+ export type WorkflowAgentStreamOptions<
571
657
  TTools extends ToolSet = ToolSet,
572
658
  OUTPUT = never,
573
659
  PARTIAL_OUTPUT = never,
574
- > extends Partial<GenerationSettings> {
575
- /**
576
- * The conversation messages to process. Should follow the AI SDK's ModelMessage format.
577
- */
578
- messages: ModelMessage[];
579
-
580
- /**
581
- * Optional system prompt override. If provided, overrides the system prompt from the constructor.
582
- */
583
- system?: string;
660
+ > = Partial<GenerationSettings> &
661
+ (
662
+ | {
663
+ /**
664
+ * A prompt. It can be either a text prompt or a list of messages.
665
+ *
666
+ * You can either use `prompt` or `messages` but not both.
667
+ */
668
+ prompt: string | Array<ModelMessage>;
669
+
670
+ /**
671
+ * A list of messages.
672
+ *
673
+ * You can either use `prompt` or `messages` but not both.
674
+ */
675
+ messages?: never;
676
+ }
677
+ | {
678
+ /**
679
+ * The conversation messages to process. Should follow the AI SDK's ModelMessage format.
680
+ *
681
+ * You can either use `prompt` or `messages` but not both.
682
+ */
683
+ messages: Array<ModelMessage>;
684
+
685
+ /**
686
+ * A prompt. It can be either a text prompt or a list of messages.
687
+ *
688
+ * You can either use `prompt` or `messages` but not both.
689
+ */
690
+ prompt?: never;
691
+ }
692
+ ) & {
693
+ /**
694
+ * Optional system prompt override. If provided, overrides the system prompt from the constructor.
695
+ */
696
+ system?: string;
584
697
 
585
- /**
586
- * A WritableStream that receives raw LanguageModelV4StreamPart chunks in real-time
587
- * as the model generates them. This enables streaming to the client without
588
- * coupling WorkflowAgent to UIMessageChunk format.
589
- *
590
- * Convert to UIMessageChunks at the response boundary using
591
- * `createUIMessageChunkTransform()` from `@ai-sdk/workflow`.
592
- *
593
- * @example
594
- * ```typescript
595
- * // In the workflow:
596
- * await agent.stream({
597
- * messages,
598
- * writable: getWritable<ModelCallStreamPart>(),
599
- * });
600
- *
601
- * // In the route handler:
602
- * return createUIMessageStreamResponse({
603
- * stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
604
- * });
605
- * ```
606
- */
607
- writable?: WritableStream<ModelCallStreamPart<ToolSet>>;
698
+ /**
699
+ * A WritableStream that receives raw LanguageModelV4StreamPart chunks in real-time
700
+ * as the model generates them. This enables streaming to the client without
701
+ * coupling WorkflowAgent to UIMessageChunk format.
702
+ *
703
+ * Convert to UIMessageChunks at the response boundary using
704
+ * `createUIMessageChunkTransform()` from `@ai-sdk/workflow`.
705
+ *
706
+ * @example
707
+ * ```typescript
708
+ * // In the workflow:
709
+ * await agent.stream({
710
+ * messages,
711
+ * writable: getWritable<ModelCallStreamPart>(),
712
+ * });
713
+ *
714
+ * // In the route handler:
715
+ * return createUIMessageStreamResponse({
716
+ * stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
717
+ * });
718
+ * ```
719
+ */
720
+ writable?: WritableStream<ModelCallStreamPart<ToolSet>>;
608
721
 
609
- /**
610
- * Condition for stopping the generation when there are tool results in the last step.
611
- * When the condition is an array, any of the conditions can be met to stop the generation.
612
- */
613
- stopWhen?:
614
- | StopCondition<NoInfer<ToolSet>, any>
615
- | Array<StopCondition<NoInfer<ToolSet>, any>>;
722
+ /**
723
+ * Condition for stopping the generation when there are tool results in the last step.
724
+ * When the condition is an array, any of the conditions can be met to stop the generation.
725
+ */
726
+ stopWhen?:
727
+ | StopCondition<NoInfer<ToolSet>, any>
728
+ | Array<StopCondition<NoInfer<ToolSet>, any>>;
616
729
 
617
- /**
618
- * Maximum number of sequential LLM calls (steps), e.g. when you use tool calls.
619
- * A maximum number can be set to prevent infinite loops in the case of misconfigured tools.
620
- * By default, it's unlimited (the agent loops until completion).
621
- */
622
- maxSteps?: number;
730
+ /**
731
+ * The tool choice strategy. Default: 'auto'.
732
+ * Overrides the toolChoice from the constructor if provided.
733
+ */
734
+ toolChoice?: ToolChoice<TTools>;
623
735
 
624
- /**
625
- * The tool choice strategy. Default: 'auto'.
626
- * Overrides the toolChoice from the constructor if provided.
627
- */
628
- toolChoice?: ToolChoice<TTools>;
736
+ /**
737
+ * Limits the tools that are available for the model to call without
738
+ * changing the tool call and result types in the result.
739
+ */
740
+ activeTools?: ActiveTools<NoInfer<TTools>>;
629
741
 
630
- /**
631
- * Limits the tools that are available for the model to call without
632
- * changing the tool call and result types in the result.
633
- */
634
- activeTools?: Array<keyof NoInfer<TTools>>;
742
+ /**
743
+ * Optional telemetry configuration.
744
+ */
745
+ telemetry?: TelemetryOptions;
635
746
 
636
- /**
637
- * Optional telemetry configuration (experimental).
638
- */
639
- experimental_telemetry?: TelemetrySettings;
747
+ /**
748
+ * Optional telemetry configuration.
749
+ *
750
+ * @deprecated Use `telemetry` instead. This alias will be removed in a future major release.
751
+ */
752
+ experimental_telemetry?: TelemetryOptions;
640
753
 
641
- /**
642
- * Context that is passed into tool execution.
643
- * Experimental (can break in patch releases).
644
- * @default undefined
645
- */
646
- experimental_context?: unknown;
754
+ /**
755
+ * Context that is passed into tool execution.
756
+ * Experimental (can break in patch releases).
757
+ * @default undefined
758
+ */
759
+ experimental_context?: unknown;
647
760
 
648
- /**
649
- * Optional specification for parsing structured outputs from the LLM response.
650
- * Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
651
- *
652
- * @example
653
- * ```typescript
654
- * import { Output } from '@workflow/ai';
655
- * import { z } from 'zod';
656
- *
657
- * const result = await agent.stream({
658
- * messages: [...],
659
- * writable: getWritable(),
660
- * output: Output.object({
661
- * schema: z.object({
662
- * sentiment: z.enum(['positive', 'negative', 'neutral']),
663
- * confidence: z.number(),
664
- * }),
665
- * }),
666
- * });
667
- *
668
- * console.log(result.output); // { sentiment: 'positive', confidence: 0.95 }
669
- * ```
670
- */
671
- output?: OutputSpecification<OUTPUT, PARTIAL_OUTPUT>;
761
+ /**
762
+ * Optional specification for parsing structured outputs from the LLM response.
763
+ * Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
764
+ *
765
+ * @example
766
+ * ```typescript
767
+ * import { Output } from '@workflow/ai';
768
+ * import { z } from 'zod';
769
+ *
770
+ * const result = await agent.stream({
771
+ * messages: [...],
772
+ * writable: getWritable(),
773
+ * output: Output.object({
774
+ * schema: z.object({
775
+ * sentiment: z.enum(['positive', 'negative', 'neutral']),
776
+ * confidence: z.number(),
777
+ * }),
778
+ * }),
779
+ * });
780
+ *
781
+ * console.log(result.output); // { sentiment: 'positive', confidence: 0.95 }
782
+ * ```
783
+ */
784
+ output?: OutputSpecification<OUTPUT, PARTIAL_OUTPUT>;
672
785
 
673
- /**
674
- * Whether to include raw chunks from the provider in the stream.
675
- * When enabled, you will receive raw chunks with type 'raw' that contain the unprocessed data from the provider.
676
- * This allows access to cutting-edge provider features not yet wrapped by the AI SDK.
677
- * Defaults to false.
678
- */
679
- includeRawChunks?: boolean;
786
+ /**
787
+ * Whether to include raw chunks from the provider in the stream.
788
+ * When enabled, you will receive raw chunks with type 'raw' that contain the unprocessed data from the provider.
789
+ * This allows access to cutting-edge provider features not yet wrapped by the AI SDK.
790
+ * Defaults to false.
791
+ */
792
+ includeRawChunks?: boolean;
680
793
 
681
- /**
682
- * A function that attempts to repair a tool call that failed to parse.
683
- */
684
- experimental_repairToolCall?: ToolCallRepairFunction<TTools>;
794
+ /**
795
+ * A function that attempts to repair a tool call that failed to parse.
796
+ */
797
+ experimental_repairToolCall?: ToolCallRepairFunction<TTools>;
685
798
 
686
- /**
687
- * Optional stream transformations.
688
- * They are applied in the order they are provided.
689
- * The stream transformations must maintain the stream structure for streamText to work correctly.
690
- */
691
- experimental_transform?:
692
- | StreamTextTransform<TTools>
693
- | Array<StreamTextTransform<TTools>>;
799
+ /**
800
+ * Optional stream transformations.
801
+ * They are applied in the order they are provided.
802
+ * The stream transformations must maintain the stream structure for streamText to work correctly.
803
+ */
804
+ experimental_transform?:
805
+ | StreamTextTransform<TTools>
806
+ | Array<StreamTextTransform<TTools>>;
694
807
 
695
- /**
696
- * Custom download function to use for URLs.
697
- * By default, files are downloaded if the model does not support the URL for the given media type.
698
- */
699
- experimental_download?: DownloadFunction;
808
+ /**
809
+ * Custom download function to use for URLs.
810
+ * By default, files are downloaded if the model does not support the URL for the given media type.
811
+ */
812
+ experimental_download?: DownloadFunction;
700
813
 
701
- /**
702
- * Callback function to be called after each step completes.
703
- */
704
- onStepFinish?: WorkflowAgentOnStepFinishCallback<TTools>;
814
+ /**
815
+ * Callback function to be called after each step completes.
816
+ */
817
+ onStepFinish?: WorkflowAgentOnStepFinishCallback<TTools>;
705
818
 
706
- /**
707
- * Callback that is invoked when an error occurs during streaming.
708
- * You can use it to log errors.
709
- */
710
- onError?: WorkflowAgentOnErrorCallback;
819
+ /**
820
+ * Callback that is invoked when an error occurs during streaming.
821
+ * You can use it to log errors.
822
+ */
823
+ onError?: WorkflowAgentOnErrorCallback;
711
824
 
712
- /**
713
- * Callback that is called when the LLM response and all request tool executions
714
- * (for tools that have an `execute` function) are finished.
715
- */
716
- onFinish?: WorkflowAgentOnFinishCallback<TTools, OUTPUT>;
825
+ /**
826
+ * Callback that is called when the LLM response and all request tool executions
827
+ * (for tools that have an `execute` function) are finished.
828
+ */
829
+ onFinish?: WorkflowAgentOnFinishCallback<TTools, OUTPUT>;
717
830
 
718
- /**
719
- * Callback that is called when the operation is aborted.
720
- */
721
- onAbort?: WorkflowAgentOnAbortCallback<TTools>;
831
+ /**
832
+ * Callback that is called when the operation is aborted.
833
+ */
834
+ onAbort?: WorkflowAgentOnAbortCallback<TTools>;
722
835
 
723
- /**
724
- * Callback called when the agent starts streaming, before any LLM calls.
725
- */
726
- experimental_onStart?: WorkflowAgentOnStartCallback;
836
+ /**
837
+ * Callback called when the agent starts streaming, before any LLM calls.
838
+ */
839
+ experimental_onStart?: WorkflowAgentOnStartCallback;
727
840
 
728
- /**
729
- * Callback called before each step (LLM call) begins.
730
- */
731
- experimental_onStepStart?: WorkflowAgentOnStepStartCallback;
841
+ /**
842
+ * Callback called before each step (LLM call) begins.
843
+ */
844
+ experimental_onStepStart?: WorkflowAgentOnStepStartCallback;
732
845
 
733
- /**
734
- * Callback called before a tool's execute function runs.
735
- */
736
- experimental_onToolCallStart?: WorkflowAgentOnToolCallStartCallback;
846
+ /**
847
+ * Callback called before a tool's execute function runs.
848
+ */
849
+ experimental_onToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback;
737
850
 
738
- /**
739
- * Callback called after a tool execution completes.
740
- */
741
- experimental_onToolCallFinish?: WorkflowAgentOnToolCallFinishCallback;
851
+ /**
852
+ * Callback called after a tool execution completes.
853
+ */
854
+ experimental_onToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback;
742
855
 
743
- /**
744
- * Callback function called before each step in the agent loop.
745
- * Use this to modify settings, manage context, or inject messages dynamically.
746
- *
747
- * @example
748
- * ```typescript
749
- * prepareStep: async ({ messages, stepNumber }) => {
750
- * // Inject messages from a queue
751
- * const queuedMessages = await getQueuedMessages();
752
- * if (queuedMessages.length > 0) {
753
- * return {
754
- * messages: [...messages, ...queuedMessages],
755
- * };
756
- * }
757
- * return {};
758
- * }
759
- * ```
760
- */
761
- prepareStep?: PrepareStepCallback<TTools>;
856
+ /**
857
+ * Callback function called before each step in the agent loop.
858
+ * Use this to modify settings, manage context, or inject messages dynamically.
859
+ *
860
+ * @example
861
+ * ```typescript
862
+ * prepareStep: async ({ messages, stepNumber }) => {
863
+ * // Inject messages from a queue
864
+ * const queuedMessages = await getQueuedMessages();
865
+ * if (queuedMessages.length > 0) {
866
+ * return {
867
+ * messages: [...messages, ...queuedMessages],
868
+ * };
869
+ * }
870
+ * return {};
871
+ * }
872
+ * ```
873
+ */
874
+ prepareStep?: PrepareStepCallback<TTools>;
762
875
 
763
- /**
764
- * Timeout in milliseconds for the stream operation.
765
- * When specified, creates an AbortSignal that will abort the operation after the given time.
766
- * If both `timeout` and `abortSignal` are provided, whichever triggers first will abort.
767
- */
768
- timeout?: number;
876
+ /**
877
+ * Timeout in milliseconds for the stream operation.
878
+ * When specified, creates an AbortSignal that will abort the operation after the given time.
879
+ * If both `timeout` and `abortSignal` are provided, whichever triggers first will abort.
880
+ */
881
+ timeout?: number;
769
882
 
770
- /**
771
- * Whether to send a 'finish' chunk to the writable stream when streaming completes.
772
- * @default true
773
- */
774
- sendFinish?: boolean;
883
+ /**
884
+ * Whether to send a 'finish' chunk to the writable stream when streaming completes.
885
+ * @default true
886
+ */
887
+ sendFinish?: boolean;
775
888
 
776
- /**
777
- * Whether to prevent the writable stream from being closed after streaming completes.
778
- * @default false
779
- */
780
- preventClose?: boolean;
781
- }
889
+ /**
890
+ * Whether to prevent the writable stream from being closed after streaming completes.
891
+ * @default false
892
+ */
893
+ preventClose?: boolean;
894
+ };
782
895
 
783
896
  /**
784
897
  * A tool call made by the model. Matches the AI SDK's tool call shape.
@@ -889,6 +1002,11 @@ export interface WorkflowAgentStreamResult<
889
1002
  * ```
890
1003
  */
891
1004
  export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1005
+ /**
1006
+ * The id of the agent.
1007
+ */
1008
+ public readonly id: string | undefined;
1009
+
892
1010
  private model: LanguageModel;
893
1011
  /**
894
1012
  * The tool set configured for this agent.
@@ -900,32 +1018,47 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
900
1018
  | Array<SystemModelMessage>;
901
1019
  private generationSettings: GenerationSettings;
902
1020
  private toolChoice?: ToolChoice<TBaseTools>;
903
- private telemetry?: TelemetrySettings;
1021
+ private telemetry?: TelemetryOptions;
904
1022
  private experimentalContext: unknown;
1023
+ private stopWhen?:
1024
+ | StopCondition<ToolSet, any>
1025
+ | Array<StopCondition<ToolSet, any>>;
1026
+ private activeTools?: ActiveTools<TBaseTools>;
1027
+ private output?: OutputSpecification<any, any>;
1028
+ private experimentalRepairToolCall?: ToolCallRepairFunction<TBaseTools>;
1029
+ private experimentalDownload?: DownloadFunction;
905
1030
  private prepareStep?: PrepareStepCallback<TBaseTools>;
906
1031
  private constructorOnStepFinish?: WorkflowAgentOnStepFinishCallback<ToolSet>;
907
1032
  private constructorOnFinish?: WorkflowAgentOnFinishCallback<ToolSet>;
908
1033
  private constructorOnStart?: WorkflowAgentOnStartCallback;
909
1034
  private constructorOnStepStart?: WorkflowAgentOnStepStartCallback;
910
- private constructorOnToolCallStart?: WorkflowAgentOnToolCallStartCallback;
911
- private constructorOnToolCallFinish?: WorkflowAgentOnToolCallFinishCallback;
1035
+ private constructorOnToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback;
1036
+ private constructorOnToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback;
912
1037
  private prepareCall?: PrepareCallCallback<TBaseTools>;
913
1038
 
914
1039
  constructor(options: WorkflowAgentOptions<TBaseTools>) {
1040
+ this.id = options.id;
915
1041
  this.model = options.model;
916
1042
  this.tools = (options.tools ?? {}) as TBaseTools;
917
1043
  // `instructions` takes precedence over deprecated `system`
918
1044
  this.instructions = options.instructions ?? options.system;
919
1045
  this.toolChoice = options.toolChoice;
920
- this.telemetry = options.experimental_telemetry;
1046
+ this.telemetry = options.telemetry ?? options.experimental_telemetry;
921
1047
  this.experimentalContext = options.experimental_context;
1048
+ this.stopWhen = options.stopWhen;
1049
+ this.activeTools = options.activeTools;
1050
+ this.output = options.output;
1051
+ this.experimentalRepairToolCall = options.experimental_repairToolCall;
1052
+ this.experimentalDownload = options.experimental_download;
922
1053
  this.prepareStep = options.prepareStep;
923
1054
  this.constructorOnStepFinish = options.onStepFinish;
924
1055
  this.constructorOnFinish = options.onFinish;
925
1056
  this.constructorOnStart = options.experimental_onStart;
926
1057
  this.constructorOnStepStart = options.experimental_onStepStart;
927
- this.constructorOnToolCallStart = options.experimental_onToolCallStart;
928
- this.constructorOnToolCallFinish = options.experimental_onToolCallFinish;
1058
+ this.constructorOnToolExecutionStart =
1059
+ options.experimental_onToolExecutionStart;
1060
+ this.constructorOnToolExecutionEnd =
1061
+ options.experimental_onToolExecutionEnd;
929
1062
  this.prepareCall = options.prepareCall;
930
1063
 
931
1064
  // Extract generation settings
@@ -959,13 +1092,23 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
959
1092
  // Call prepareCall to transform parameters before the agent loop
960
1093
  let effectiveModel: LanguageModel = this.model;
961
1094
  let effectiveInstructions = options.system ?? this.instructions;
962
- let effectiveMessages = options.messages;
1095
+ let effectivePrompt: string | Array<ModelMessage> | undefined =
1096
+ options.prompt;
1097
+ let effectiveMessages: Array<ModelMessage> | undefined = options.messages;
963
1098
  let effectiveGenerationSettings = { ...this.generationSettings };
964
1099
  let effectiveExperimentalContext =
965
1100
  options.experimental_context ?? this.experimentalContext;
966
1101
  let effectiveToolChoiceFromPrepare = options.toolChoice ?? this.toolChoice;
967
1102
  let effectiveTelemetryFromPrepare =
968
- options.experimental_telemetry ?? this.telemetry;
1103
+ options.telemetry ?? options.experimental_telemetry ?? this.telemetry;
1104
+
1105
+ // Resolve messages for prepareCall: use messages directly, or convert prompt
1106
+ const resolvedMessagesForPrepareCall: ModelMessage[] =
1107
+ effectiveMessages ??
1108
+ (typeof effectivePrompt === 'string'
1109
+ ? [{ role: 'user' as const, content: effectivePrompt }]
1110
+ : (effectivePrompt as ModelMessage[])) ??
1111
+ [];
969
1112
 
970
1113
  if (this.prepareCall) {
971
1114
  const prepared = await this.prepareCall({
@@ -973,24 +1116,28 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
973
1116
  tools: this.tools,
974
1117
  instructions: effectiveInstructions,
975
1118
  toolChoice: effectiveToolChoiceFromPrepare as ToolChoice<TBaseTools>,
1119
+ telemetry: effectiveTelemetryFromPrepare,
976
1120
  experimental_telemetry: effectiveTelemetryFromPrepare,
977
1121
  experimental_context: effectiveExperimentalContext,
978
- messages: effectiveMessages as ModelMessage[],
1122
+ messages: resolvedMessagesForPrepareCall,
979
1123
  ...effectiveGenerationSettings,
980
1124
  } as PrepareCallOptions<TBaseTools>);
981
1125
 
982
1126
  if (prepared.model !== undefined) effectiveModel = prepared.model;
983
1127
  if (prepared.instructions !== undefined)
984
1128
  effectiveInstructions = prepared.instructions;
985
- if (prepared.messages !== undefined)
986
- effectiveMessages =
987
- prepared.messages as WorkflowAgentStreamOptions<TTools>['messages'];
1129
+ if (prepared.messages !== undefined) {
1130
+ effectiveMessages = prepared.messages as Array<ModelMessage>;
1131
+ effectivePrompt = undefined; // messages from prepareCall take precedence
1132
+ }
988
1133
  if (prepared.experimental_context !== undefined)
989
1134
  effectiveExperimentalContext = prepared.experimental_context;
990
1135
  if (prepared.toolChoice !== undefined)
991
1136
  effectiveToolChoiceFromPrepare =
992
1137
  prepared.toolChoice as ToolChoice<TBaseTools>;
993
- if (prepared.experimental_telemetry !== undefined)
1138
+ if (prepared.telemetry !== undefined)
1139
+ effectiveTelemetryFromPrepare = prepared.telemetry;
1140
+ else if (prepared.experimental_telemetry !== undefined)
994
1141
  effectiveTelemetryFromPrepare = prepared.experimental_telemetry;
995
1142
  if (prepared.maxOutputTokens !== undefined)
996
1143
  effectiveGenerationSettings.maxOutputTokens = prepared.maxOutputTokens;
@@ -1017,8 +1164,11 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1017
1164
 
1018
1165
  const prompt = await standardizePrompt({
1019
1166
  system: effectiveInstructions,
1020
- messages: effectiveMessages,
1021
- });
1167
+ allowSystemInMessages: true, // TODO: consider exposing this as a parameter
1168
+ ...(effectivePrompt != null
1169
+ ? { prompt: effectivePrompt }
1170
+ : { messages: effectiveMessages! }),
1171
+ } as Prompt);
1022
1172
 
1023
1173
  // Process tool approval responses before starting the agent loop.
1024
1174
  // This mirrors how stream-text.ts handles tool-approval-response parts:
@@ -1146,14 +1296,12 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1146
1296
  const modelPrompt = await convertToLanguageModelPrompt({
1147
1297
  prompt,
1148
1298
  supportedUrls: {},
1149
- download: options.experimental_download,
1299
+ download: options.experimental_download ?? this.experimentalDownload,
1150
1300
  });
1151
1301
 
1152
1302
  const effectiveAbortSignal = mergeAbortSignals(
1153
1303
  options.abortSignal ?? effectiveGenerationSettings.abortSignal,
1154
- options.timeout != null
1155
- ? AbortSignal.timeout(options.timeout)
1156
- : undefined,
1304
+ options.timeout,
1157
1305
  );
1158
1306
 
1159
1307
  // Merge generation settings: constructor defaults < prepareCall < stream options
@@ -1210,13 +1358,13 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1210
1358
  this.constructorOnStepStart,
1211
1359
  options.experimental_onStepStart,
1212
1360
  );
1213
- const mergedOnToolCallStart = mergeCallbacks(
1214
- this.constructorOnToolCallStart,
1215
- options.experimental_onToolCallStart,
1361
+ const mergedOnToolExecutionStart = mergeCallbacks(
1362
+ this.constructorOnToolExecutionStart,
1363
+ options.experimental_onToolExecutionStart,
1216
1364
  );
1217
- const mergedOnToolCallFinish = mergeCallbacks(
1218
- this.constructorOnToolCallFinish,
1219
- options.experimental_onToolCallFinish,
1365
+ const mergedOnToolExecutionEnd = mergeCallbacks(
1366
+ this.constructorOnToolExecutionEnd,
1367
+ options.experimental_onToolExecutionEnd,
1220
1368
  );
1221
1369
 
1222
1370
  // Determine effective tool choice
@@ -1225,10 +1373,14 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1225
1373
  // Merge telemetry settings
1226
1374
  const effectiveTelemetry = effectiveTelemetryFromPrepare;
1227
1375
 
1228
- // Filter tools if activeTools is specified
1376
+ // Filter tools if activeTools is specified (stream-level overrides constructor default)
1377
+ const effectiveActiveTools = options.activeTools ?? this.activeTools;
1229
1378
  const effectiveTools =
1230
- options.activeTools && options.activeTools.length > 0
1231
- ? filterTools(this.tools, options.activeTools as string[])
1379
+ effectiveActiveTools && effectiveActiveTools.length > 0
1380
+ ? (filterActiveTools({
1381
+ tools: this.tools,
1382
+ activeTools: effectiveActiveTools,
1383
+ }) ?? this.tools)
1232
1384
  : this.tools;
1233
1385
 
1234
1386
  // Initialize context
@@ -1244,69 +1396,77 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1244
1396
  if (mergedOnStart) {
1245
1397
  await mergedOnStart({
1246
1398
  model: effectiveModel,
1247
- messages: effectiveMessages as ModelMessage[],
1399
+ messages: prompt.messages,
1248
1400
  });
1249
1401
  }
1250
1402
 
1251
- // Helper to wrap executeTool with onToolCallStart/onToolCallFinish callbacks
1403
+ // Helper to wrap executeTool with onToolExecutionStart/onToolExecutionEnd callbacks
1252
1404
  const executeToolWithCallbacks = async (
1253
1405
  toolCall: { toolCallId: string; toolName: string; input: unknown },
1254
1406
  tools: ToolSet,
1255
1407
  messages: LanguageModelV4Prompt,
1256
1408
  context?: unknown,
1409
+ currentStepNumber: number = 0,
1257
1410
  ): Promise<LanguageModelV4ToolResultPart> => {
1258
- if (mergedOnToolCallStart) {
1259
- await mergedOnToolCallStart({
1260
- toolCall: {
1261
- type: 'tool-call',
1262
- toolCallId: toolCall.toolCallId,
1263
- toolName: toolCall.toolName,
1264
- input: toolCall.input,
1265
- },
1411
+ const toolCallEvent: ToolCall = {
1412
+ type: 'tool-call',
1413
+ toolCallId: toolCall.toolCallId,
1414
+ toolName: toolCall.toolName,
1415
+ input: toolCall.input,
1416
+ };
1417
+
1418
+ if (mergedOnToolExecutionStart) {
1419
+ await mergedOnToolExecutionStart({
1420
+ toolCall: toolCallEvent,
1421
+ stepNumber: currentStepNumber,
1266
1422
  });
1267
1423
  }
1424
+
1425
+ const startTime = Date.now();
1268
1426
  let result: LanguageModelV4ToolResultPart;
1269
1427
  try {
1270
1428
  result = await executeTool(toolCall, tools, messages, context);
1271
1429
  } catch (err) {
1272
- if (mergedOnToolCallFinish) {
1273
- await mergedOnToolCallFinish({
1274
- toolCall: {
1275
- type: 'tool-call',
1276
- toolCallId: toolCall.toolCallId,
1277
- toolName: toolCall.toolName,
1278
- input: toolCall.input,
1279
- },
1430
+ const durationMs = Date.now() - startTime;
1431
+ if (mergedOnToolExecutionEnd) {
1432
+ await mergedOnToolExecutionEnd({
1433
+ toolCall: toolCallEvent,
1434
+ stepNumber: currentStepNumber,
1435
+ durationMs,
1436
+ success: false,
1280
1437
  error: err,
1281
1438
  });
1282
1439
  }
1283
1440
  throw err;
1284
1441
  }
1285
- if (mergedOnToolCallFinish) {
1442
+
1443
+ const durationMs = Date.now() - startTime;
1444
+ if (mergedOnToolExecutionEnd) {
1286
1445
  const isError =
1287
1446
  result.output &&
1288
1447
  'type' in result.output &&
1289
1448
  (result.output.type === 'error-text' ||
1290
1449
  result.output.type === 'error-json');
1291
- await mergedOnToolCallFinish({
1292
- toolCall: {
1293
- type: 'tool-call',
1294
- toolCallId: toolCall.toolCallId,
1295
- toolName: toolCall.toolName,
1296
- input: toolCall.input,
1297
- },
1298
- ...(isError
1299
- ? {
1300
- error:
1301
- 'value' in result.output ? result.output.value : undefined,
1302
- }
1303
- : {
1304
- result:
1305
- result.output && 'value' in result.output
1306
- ? result.output.value
1307
- : undefined,
1308
- }),
1309
- });
1450
+ if (isError) {
1451
+ await mergedOnToolExecutionEnd({
1452
+ toolCall: toolCallEvent,
1453
+ stepNumber: currentStepNumber,
1454
+ durationMs,
1455
+ success: false,
1456
+ error: 'value' in result.output ? result.output.value : undefined,
1457
+ });
1458
+ } else {
1459
+ await mergedOnToolExecutionEnd({
1460
+ toolCall: toolCallEvent,
1461
+ stepNumber: currentStepNumber,
1462
+ durationMs,
1463
+ success: true,
1464
+ output:
1465
+ result.output && 'value' in result.output
1466
+ ? result.output.value
1467
+ : undefined,
1468
+ });
1469
+ }
1310
1470
  }
1311
1471
  return result;
1312
1472
  };
@@ -1317,7 +1477,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1317
1477
  await options.onAbort({ steps });
1318
1478
  }
1319
1479
  return {
1320
- messages: options.messages as unknown as ModelMessage[],
1480
+ messages: prompt.messages,
1321
1481
  steps,
1322
1482
  toolCalls: [],
1323
1483
  toolResults: [],
@@ -1330,8 +1490,8 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1330
1490
  tools: effectiveTools as ToolSet,
1331
1491
  writable: options.writable,
1332
1492
  prompt: modelPrompt,
1333
- stopConditions: options.stopWhen,
1334
- maxSteps: options.maxSteps,
1493
+ stopConditions: options.stopWhen ?? this.stopWhen,
1494
+
1335
1495
  onStepFinish: mergedOnStepFinish,
1336
1496
  onStepStart: mergedOnStepStart,
1337
1497
  onError: options.onError,
@@ -1341,11 +1501,13 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1341
1501
  generationSettings: mergedGenerationSettings,
1342
1502
  toolChoice: effectiveToolChoice as ToolChoice<ToolSet>,
1343
1503
  experimental_context: experimentalContext,
1344
- experimental_telemetry: effectiveTelemetry,
1504
+ telemetry: effectiveTelemetry,
1345
1505
  includeRawChunks: options.includeRawChunks ?? false,
1346
- repairToolCall:
1347
- options.experimental_repairToolCall as ToolCallRepairFunction<ToolSet>,
1348
- responseFormat: await options.output?.responseFormat,
1506
+ repairToolCall: (options.experimental_repairToolCall ??
1507
+ this.experimentalRepairToolCall) as
1508
+ | ToolCallRepairFunction<ToolSet>
1509
+ | undefined,
1510
+ responseFormat: await (options.output ?? this.output)?.responseFormat,
1349
1511
  });
1350
1512
 
1351
1513
  // Track the final conversation messages from the iterator
@@ -1372,6 +1534,8 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1372
1534
  context,
1373
1535
  providerExecutedToolResults,
1374
1536
  } = result.value;
1537
+ // Capture current step number before pushing (0-based)
1538
+ const currentStepNumber = steps.length;
1375
1539
  if (step) {
1376
1540
  steps.push(step as unknown as StepResult<TTools, any>);
1377
1541
  }
@@ -1381,11 +1545,16 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1381
1545
 
1382
1546
  // Only execute tools if there are tool calls
1383
1547
  if (toolCalls.length > 0) {
1548
+ const invalidToolCalls = toolCalls.filter(tc => tc.invalid === true);
1549
+ const validToolCalls = toolCalls.filter(tc => tc.invalid !== true);
1550
+
1384
1551
  // Separate provider-executed tool calls from client-executed ones
1385
- const nonProviderToolCalls = toolCalls.filter(
1552
+ const nonProviderToolCalls = validToolCalls.filter(
1386
1553
  tc => !tc.providerExecuted,
1387
1554
  );
1388
- const providerToolCalls = toolCalls.filter(tc => tc.providerExecuted);
1555
+ const providerToolCalls = validToolCalls.filter(
1556
+ tc => tc.providerExecuted,
1557
+ );
1389
1558
 
1390
1559
  // Check which tools need approval (can be async)
1391
1560
  const approvalNeeded = await Promise.all(
@@ -1435,6 +1604,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1435
1604
  effectiveTools as ToolSet,
1436
1605
  iterMessages,
1437
1606
  experimentalContext,
1607
+ currentStepNumber,
1438
1608
  ),
1439
1609
  ),
1440
1610
  );
@@ -1448,7 +1618,15 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1448
1618
  ),
1449
1619
  );
1450
1620
 
1451
- const resolvedResults = [...executableResults, ...providerResults];
1621
+ const continuationInvalidResults = invalidToolCalls.map(
1622
+ createInvalidToolResult,
1623
+ );
1624
+ const resolvedResults = [
1625
+ ...executableResults,
1626
+ ...providerResults,
1627
+ ...continuationInvalidResults,
1628
+ ];
1629
+ const executedResults = [...executableResults, ...providerResults];
1452
1630
 
1453
1631
  const allToolCalls: ToolCall[] = toolCalls.map(tc => ({
1454
1632
  type: 'tool-call' as const,
@@ -1457,7 +1635,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1457
1635
  input: tc.input,
1458
1636
  }));
1459
1637
 
1460
- const allToolResults: ToolResult[] = resolvedResults.map(r => ({
1638
+ const allToolResults: ToolResult[] = executedResults.map(r => ({
1461
1639
  type: 'tool-result' as const,
1462
1640
  toolCallId: r.toolCallId,
1463
1641
  toolName: r.toolName,
@@ -1535,6 +1713,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1535
1713
  effectiveTools as ToolSet,
1536
1714
  iterMessages,
1537
1715
  experimentalContext,
1716
+ currentStepNumber,
1538
1717
  ),
1539
1718
  ),
1540
1719
  );
@@ -1544,25 +1723,32 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1544
1723
  providerToolCalls.map(toolCall =>
1545
1724
  resolveProviderToolResult(toolCall, providerExecutedToolResults),
1546
1725
  );
1726
+ const continuationInvalidToolResults = invalidToolCalls.map(
1727
+ createInvalidToolResult,
1728
+ );
1547
1729
 
1548
- // Combine results in the original order
1549
- const toolResults = toolCalls.map(tc => {
1730
+ // Combine executable/provider results in the original order,
1731
+ // while preserving invalid tool calls as error results for the
1732
+ // next model step without emitting them as synthetic UI success.
1733
+ const continuationToolResults = toolCalls.flatMap(tc => {
1734
+ const invalidResult = continuationInvalidToolResults.find(
1735
+ r => r.toolCallId === tc.toolCallId,
1736
+ );
1737
+ if (invalidResult) return [invalidResult];
1550
1738
  const clientResult = clientToolResults.find(
1551
1739
  r => r.toolCallId === tc.toolCallId,
1552
1740
  );
1553
- if (clientResult) return clientResult;
1741
+ if (clientResult) return [clientResult];
1554
1742
  const providerResult = providerToolResults.find(
1555
1743
  r => r.toolCallId === tc.toolCallId,
1556
1744
  );
1557
- if (providerResult) return providerResult;
1558
- // This should never happen, but return empty result as fallback
1559
- return {
1560
- type: 'tool-result' as const,
1561
- toolCallId: tc.toolCallId,
1562
- toolName: tc.toolName,
1563
- output: { type: 'text' as const, value: '' },
1564
- };
1745
+ if (providerResult) return [providerResult];
1746
+ return [];
1565
1747
  });
1748
+ const executedToolResults = continuationToolResults.filter(
1749
+ result =>
1750
+ !invalidToolCalls.some(tc => tc.toolCallId === result.toolCallId),
1751
+ );
1566
1752
 
1567
1753
  // Write tool results and step boundaries to the stream so the
1568
1754
  // UI can transition tool parts to output-available state and
@@ -1570,7 +1756,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1570
1756
  if (options.writable) {
1571
1757
  await writeToolResultsWithStepBoundary(
1572
1758
  options.writable,
1573
- toolResults.map(r => ({
1759
+ executedToolResults.map(r => ({
1574
1760
  toolCallId: r.toolCallId,
1575
1761
  toolName: r.toolName,
1576
1762
  input: toolCalls.find(tc => tc.toolCallId === r.toolCallId)
@@ -1587,7 +1773,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1587
1773
  toolName: tc.toolName,
1588
1774
  input: tc.input,
1589
1775
  }));
1590
- lastStepToolResults = toolResults.map(r => ({
1776
+ lastStepToolResults = executedToolResults.map(r => ({
1591
1777
  type: 'tool-result' as const,
1592
1778
  toolCallId: r.toolCallId,
1593
1779
  toolName: r.toolName,
@@ -1595,7 +1781,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1595
1781
  output: 'value' in r.output ? r.output.value : undefined,
1596
1782
  }));
1597
1783
 
1598
- result = await iterator.next(toolResults);
1784
+ result = await iterator.next(continuationToolResults);
1599
1785
  } else {
1600
1786
  // Final step with no tool calls - reset tracking
1601
1787
  lastStepToolCalls = [];
@@ -1623,18 +1809,19 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1623
1809
  // Don't throw yet - we want to call onFinish first
1624
1810
  }
1625
1811
 
1626
- // Use the final messages from the iterator, or fall back to original messages
1812
+ // Use the final messages from the iterator, or fall back to standardized messages
1627
1813
  const messages = (finalMessages ??
1628
- options.messages) as unknown as ModelMessage[];
1814
+ prompt.messages) as unknown as ModelMessage[];
1629
1815
 
1630
- // Parse structured output if output is specified
1816
+ // Parse structured output if output is specified (stream-level overrides constructor default)
1817
+ const effectiveOutput = options.output ?? this.output;
1631
1818
  let experimentalOutput: OUTPUT = undefined as OUTPUT;
1632
- if (options.output && steps.length > 0) {
1819
+ if (effectiveOutput && steps.length > 0) {
1633
1820
  const lastStep = steps[steps.length - 1];
1634
1821
  const text = lastStep.text;
1635
1822
  if (text) {
1636
1823
  try {
1637
- experimentalOutput = await options.output.parseCompleteOutput(
1824
+ experimentalOutput = await effectiveOutput.parseCompleteOutput(
1638
1825
  { text },
1639
1826
  {
1640
1827
  response: lastStep.response,
@@ -1832,19 +2019,6 @@ function aggregateUsage(steps: StepResult<any, any>[]): LanguageModelUsage {
1832
2019
  } as LanguageModelUsage;
1833
2020
  }
1834
2021
 
1835
- function filterTools<TTools extends ToolSet>(
1836
- tools: TTools,
1837
- activeTools: string[],
1838
- ): ToolSet {
1839
- const filtered: ToolSet = {};
1840
- for (const toolName of activeTools) {
1841
- if (toolName in tools) {
1842
- filtered[toolName] = tools[toolName];
1843
- }
1844
- }
1845
- return filtered;
1846
- }
1847
-
1848
2022
  // Matches AI SDK's getErrorMessage from @ai-sdk/provider-utils
1849
2023
  function getErrorMessage(error: unknown): string {
1850
2024
  if (error == null) {
@@ -1909,6 +2083,22 @@ function resolveProviderToolResult(
1909
2083
  };
1910
2084
  }
1911
2085
 
2086
+ function createInvalidToolResult(toolCall: {
2087
+ toolCallId: string;
2088
+ toolName: string;
2089
+ error?: unknown;
2090
+ }): LanguageModelV4ToolResultPart {
2091
+ return {
2092
+ type: 'tool-result' as const,
2093
+ toolCallId: toolCall.toolCallId,
2094
+ toolName: toolCall.toolName,
2095
+ output: {
2096
+ type: 'error-text' as const,
2097
+ value: getErrorMessage(toolCall.error),
2098
+ },
2099
+ };
2100
+ }
2101
+
1912
2102
  async function executeTool(
1913
2103
  toolCall: { toolCallId: string; toolName: string; input: unknown },
1914
2104
  tools: ToolSet,