@ai-sdk/workflow 1.0.0-beta.2 → 1.0.0-beta.21

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.
@@ -21,6 +21,8 @@ import {
21
21
  type ToolChoice,
22
22
  type ToolSet,
23
23
  type UIMessage,
24
+ LanguageModel,
25
+ experimental_filterActiveTools as filterActiveTools,
24
26
  } from 'ai';
25
27
  import {
26
28
  convertToLanguageModelPrompt,
@@ -34,6 +36,15 @@ import type { CompatibleLanguageModel } from './types.js';
34
36
  // Re-export for consumers
35
37
  export type { CompatibleLanguageModel } from './types.js';
36
38
 
39
+ /**
40
+ * Callback function to be called after each step completes.
41
+ * Alias for the AI SDK's StreamTextOnStepFinishCallback, using
42
+ * WorkflowAgent-consistent naming.
43
+ */
44
+ export type WorkflowAgentOnStepFinishCallback<
45
+ TTools extends ToolSet = ToolSet,
46
+ > = StreamTextOnStepFinishCallback<TTools, any>;
47
+
37
48
  /**
38
49
  * Infer the type of the tools of a workflow agent.
39
50
  */
@@ -82,7 +93,7 @@ export type ProviderOptions = SharedV4ProviderOptions;
82
93
  /**
83
94
  * Telemetry settings for observability.
84
95
  */
85
- export interface TelemetrySettings {
96
+ export interface TelemetryOptions {
86
97
  /**
87
98
  * Enable or disable telemetry. Defaults to true.
88
99
  */
@@ -248,10 +259,7 @@ export interface PrepareStepInfo<TTools extends ToolSet = ToolSet> {
248
259
  * The current model configuration (string or function).
249
260
  * The function should return a LanguageModelV4 instance.
250
261
  */
251
- model:
252
- | string
253
- | CompatibleLanguageModel
254
- | (() => Promise<CompatibleLanguageModel>);
262
+ model: LanguageModel;
255
263
 
256
264
  /**
257
265
  * The current step number (0-indexed).
@@ -282,12 +290,8 @@ export interface PrepareStepInfo<TTools extends ToolSet = ToolSet> {
282
290
  export interface PrepareStepResult extends Partial<GenerationSettings> {
283
291
  /**
284
292
  * Override the model for this step.
285
- * The function should return a LanguageModelV4 instance.
286
293
  */
287
- model?:
288
- | string
289
- | CompatibleLanguageModel
290
- | (() => Promise<CompatibleLanguageModel>);
294
+ model?: LanguageModel;
291
295
 
292
296
  /**
293
297
  * Override the system message for this step.
@@ -332,14 +336,11 @@ export type PrepareStepCallback<TTools extends ToolSet = ToolSet> = (
332
336
  export interface PrepareCallOptions<
333
337
  TTools extends ToolSet = ToolSet,
334
338
  > extends Partial<GenerationSettings> {
335
- model:
336
- | string
337
- | CompatibleLanguageModel
338
- | (() => Promise<CompatibleLanguageModel>);
339
+ model: LanguageModel;
339
340
  tools: TTools;
340
341
  instructions?: string | SystemModelMessage | Array<SystemModelMessage>;
341
342
  toolChoice?: ToolChoice<TTools>;
342
- experimental_telemetry?: TelemetrySettings;
343
+ experimental_telemetry?: TelemetryOptions;
343
344
  experimental_context?: unknown;
344
345
  messages: ModelMessage[];
345
346
  }
@@ -367,16 +368,18 @@ export type PrepareCallCallback<TTools extends ToolSet = ToolSet> = (
367
368
  export interface WorkflowAgentOptions<
368
369
  TTools extends ToolSet = ToolSet,
369
370
  > extends GenerationSettings {
371
+ /**
372
+ * The id of the agent.
373
+ */
374
+ id?: string;
375
+
370
376
  /**
371
377
  * The model provider to use for the agent.
372
378
  *
373
379
  * This should be a string compatible with the Vercel AI Gateway (e.g., 'anthropic/claude-opus'),
374
- * or a step function that returns a LanguageModelV4 instance.
380
+ * or a LanguageModelV4 instance from a provider.
375
381
  */
376
- model:
377
- | string
378
- | CompatibleLanguageModel
379
- | (() => Promise<CompatibleLanguageModel>);
382
+ model: LanguageModel;
380
383
 
381
384
  /**
382
385
  * A set of tools available to the agent.
@@ -405,7 +408,7 @@ export interface WorkflowAgentOptions<
405
408
  /**
406
409
  * Optional telemetry configuration (experimental).
407
410
  */
408
- experimental_telemetry?: TelemetrySettings;
411
+ experimental_telemetry?: TelemetryOptions;
409
412
 
410
413
  /**
411
414
  * Default context that is passed into tool execution for every stream call on this agent.
@@ -416,6 +419,46 @@ export interface WorkflowAgentOptions<
416
419
  */
417
420
  experimental_context?: unknown;
418
421
 
422
+ /**
423
+ * Default stop condition for the agent loop. When the condition is an array,
424
+ * any of the conditions can be met to stop the generation.
425
+ *
426
+ * Per-stream `stopWhen` values passed to `stream()` override this default.
427
+ */
428
+ stopWhen?:
429
+ | StopCondition<NoInfer<ToolSet>, any>
430
+ | Array<StopCondition<NoInfer<ToolSet>, any>>;
431
+
432
+ /**
433
+ * Default set of active tools that limits which tools the model can call,
434
+ * without changing the tool call and result types in the result.
435
+ *
436
+ * Per-stream `activeTools` values passed to `stream()` override this default.
437
+ */
438
+ activeTools?: Array<keyof NoInfer<TTools>>;
439
+
440
+ /**
441
+ * Default output specification for structured outputs.
442
+ * Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
443
+ *
444
+ * Per-stream `output` values passed to `stream()` override this default.
445
+ */
446
+ output?: OutputSpecification<any, any>;
447
+
448
+ /**
449
+ * Default function that attempts to repair a tool call that failed to parse.
450
+ *
451
+ * Per-stream `experimental_repairToolCall` values passed to `stream()` override this default.
452
+ */
453
+ experimental_repairToolCall?: ToolCallRepairFunction<TTools>;
454
+
455
+ /**
456
+ * Default custom download function to use for URLs.
457
+ *
458
+ * Per-stream `experimental_download` values passed to `stream()` override this default.
459
+ */
460
+ experimental_download?: DownloadFunction;
461
+
419
462
  /**
420
463
  * Default callback function called before each step in the agent loop.
421
464
  * Use this to modify settings, manage context, or inject messages dynamically
@@ -428,12 +471,12 @@ export interface WorkflowAgentOptions<
428
471
  /**
429
472
  * Callback function to be called after each step completes.
430
473
  */
431
- onStepFinish?: StreamTextOnStepFinishCallback<ToolSet, any>;
474
+ onStepFinish?: WorkflowAgentOnStepFinishCallback<ToolSet>;
432
475
 
433
476
  /**
434
477
  * Callback that is called when the LLM response and all request tool executions are finished.
435
478
  */
436
- onFinish?: StreamTextOnFinishCallback<ToolSet>;
479
+ onFinish?: WorkflowAgentOnFinishCallback<ToolSet>;
437
480
 
438
481
  /**
439
482
  * Callback called when the agent starts streaming, before any LLM calls.
@@ -466,7 +509,7 @@ export interface WorkflowAgentOptions<
466
509
  /**
467
510
  * Callback that is called when the LLM response and all request tool executions are finished.
468
511
  */
469
- export type StreamTextOnFinishCallback<
512
+ export type WorkflowAgentOnFinishCallback<
470
513
  TTools extends ToolSet = ToolSet,
471
514
  OUTPUT = never,
472
515
  > = (event: {
@@ -510,14 +553,14 @@ export type StreamTextOnFinishCallback<
510
553
  /**
511
554
  * Callback that is invoked when an error occurs during streaming.
512
555
  */
513
- export type StreamTextOnErrorCallback = (event: {
556
+ export type WorkflowAgentOnErrorCallback = (event: {
514
557
  error: unknown;
515
558
  }) => PromiseLike<void> | void;
516
559
 
517
560
  /**
518
561
  * Callback that is set using the `onAbort` option.
519
562
  */
520
- export type StreamTextOnAbortCallback<TTools extends ToolSet = ToolSet> =
563
+ export type WorkflowAgentOnAbortCallback<TTools extends ToolSet = ToolSet> =
521
564
  (event: {
522
565
  /**
523
566
  * Details for all previously finished steps.
@@ -530,10 +573,7 @@ export type StreamTextOnAbortCallback<TTools extends ToolSet = ToolSet> =
530
573
  */
531
574
  export type WorkflowAgentOnStartCallback = (event: {
532
575
  /** The model being used */
533
- readonly model:
534
- | string
535
- | CompatibleLanguageModel
536
- | (() => Promise<CompatibleLanguageModel>);
576
+ readonly model: LanguageModel;
537
577
  /** The messages being sent */
538
578
  readonly messages: ModelMessage[];
539
579
  }) => PromiseLike<void> | void;
@@ -541,17 +581,17 @@ export type WorkflowAgentOnStartCallback = (event: {
541
581
  /**
542
582
  * Callback that is called before each step (LLM call) begins.
543
583
  */
544
- export type WorkflowAgentOnStepStartCallback = (event: {
545
- /** The current step number (0-based) */
546
- readonly stepNumber: number;
547
- /** The model being used for this step */
548
- readonly model:
549
- | string
550
- | CompatibleLanguageModel
551
- | (() => Promise<CompatibleLanguageModel>);
552
- /** The messages being sent for this step */
553
- readonly messages: ModelMessage[];
554
- }) => PromiseLike<void> | void;
584
+ export type WorkflowAgentOnStepStartCallback<TTools extends ToolSet = ToolSet> =
585
+ (event: {
586
+ /** The current step number (0-based) */
587
+ readonly stepNumber: number;
588
+ /** The model being used for this step */
589
+ readonly model: LanguageModel;
590
+ /** The messages being sent for this step */
591
+ readonly messages: ModelMessage[];
592
+ /** Results from all previously finished steps */
593
+ readonly steps: ReadonlyArray<StepResult<TTools, any>>;
594
+ }) => PromiseLike<void> | void;
555
595
 
556
596
  /**
557
597
  * Callback that is called before a tool's execute function runs.
@@ -559,235 +599,280 @@ export type WorkflowAgentOnStepStartCallback = (event: {
559
599
  export type WorkflowAgentOnToolCallStartCallback = (event: {
560
600
  /** The tool call being executed */
561
601
  readonly toolCall: ToolCall;
602
+ /** The current step number (0-based) */
603
+ readonly stepNumber: number;
562
604
  }) => PromiseLike<void> | void;
563
605
 
564
606
  /**
565
607
  * Callback that is called after a tool execution completes.
608
+ * Uses a discriminated union pattern: check `success` to determine
609
+ * whether `output` or `error` is available.
566
610
  */
567
- export type WorkflowAgentOnToolCallFinishCallback = (event: {
568
- /** The tool call that was executed */
569
- readonly toolCall: ToolCall;
570
- /** The tool result (undefined if execution failed) */
571
- readonly result?: unknown;
572
- /** The error if execution failed */
573
- readonly error?: unknown;
574
- }) => PromiseLike<void> | void;
611
+ export type WorkflowAgentOnToolCallFinishCallback = (
612
+ event:
613
+ | {
614
+ /** The tool call that was executed */
615
+ readonly toolCall: ToolCall;
616
+ /** The current step number (0-based) */
617
+ readonly stepNumber: number;
618
+ /** Execution time in milliseconds */
619
+ readonly durationMs: number;
620
+ /** Whether the tool call succeeded */
621
+ readonly success: true;
622
+ /** The tool result */
623
+ readonly output: unknown;
624
+ readonly error?: never;
625
+ }
626
+ | {
627
+ /** The tool call that was executed */
628
+ readonly toolCall: ToolCall;
629
+ /** The current step number (0-based) */
630
+ readonly stepNumber: number;
631
+ /** Execution time in milliseconds */
632
+ readonly durationMs: number;
633
+ /** Whether the tool call succeeded */
634
+ readonly success: false;
635
+ /** The error that occurred */
636
+ readonly error: unknown;
637
+ readonly output?: never;
638
+ },
639
+ ) => PromiseLike<void> | void;
575
640
 
576
641
  /**
577
642
  * Options for the {@link WorkflowAgent.stream} method.
578
643
  */
579
- export interface WorkflowAgentStreamOptions<
644
+ export type WorkflowAgentStreamOptions<
580
645
  TTools extends ToolSet = ToolSet,
581
646
  OUTPUT = never,
582
647
  PARTIAL_OUTPUT = never,
583
- > extends Partial<GenerationSettings> {
584
- /**
585
- * The conversation messages to process. Should follow the AI SDK's ModelMessage format.
586
- */
587
- messages: ModelMessage[];
588
-
589
- /**
590
- * Optional system prompt override. If provided, overrides the system prompt from the constructor.
591
- */
592
- system?: string;
593
-
594
- /**
595
- * A WritableStream that receives raw LanguageModelV4StreamPart chunks in real-time
596
- * as the model generates them. This enables streaming to the client without
597
- * coupling WorkflowAgent to UIMessageChunk format.
598
- *
599
- * Convert to UIMessageChunks at the response boundary using
600
- * `createUIMessageChunkTransform()` from `@ai-sdk/workflow`.
601
- *
602
- * @example
603
- * ```typescript
604
- * // In the workflow:
605
- * await agent.stream({
606
- * messages,
607
- * writable: getWritable<ModelCallStreamPart>(),
608
- * });
609
- *
610
- * // In the route handler:
611
- * return createUIMessageStreamResponse({
612
- * stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
613
- * });
614
- * ```
615
- */
616
- writable?: WritableStream<ModelCallStreamPart<ToolSet>>;
648
+ > = Partial<GenerationSettings> &
649
+ (
650
+ | {
651
+ /**
652
+ * A prompt. It can be either a text prompt or a list of messages.
653
+ *
654
+ * You can either use `prompt` or `messages` but not both.
655
+ */
656
+ prompt: string | Array<ModelMessage>;
657
+
658
+ /**
659
+ * A list of messages.
660
+ *
661
+ * You can either use `prompt` or `messages` but not both.
662
+ */
663
+ messages?: never;
664
+ }
665
+ | {
666
+ /**
667
+ * The conversation messages to process. Should follow the AI SDK's ModelMessage format.
668
+ *
669
+ * You can either use `prompt` or `messages` but not both.
670
+ */
671
+ messages: Array<ModelMessage>;
672
+
673
+ /**
674
+ * A prompt. It can be either a text prompt or a list of messages.
675
+ *
676
+ * You can either use `prompt` or `messages` but not both.
677
+ */
678
+ prompt?: never;
679
+ }
680
+ ) & {
681
+ /**
682
+ * Optional system prompt override. If provided, overrides the system prompt from the constructor.
683
+ */
684
+ system?: string;
617
685
 
618
- /**
619
- * Condition for stopping the generation when there are tool results in the last step.
620
- * When the condition is an array, any of the conditions can be met to stop the generation.
621
- */
622
- stopWhen?:
623
- | StopCondition<NoInfer<ToolSet>, any>
624
- | Array<StopCondition<NoInfer<ToolSet>, any>>;
686
+ /**
687
+ * A WritableStream that receives raw LanguageModelV4StreamPart chunks in real-time
688
+ * as the model generates them. This enables streaming to the client without
689
+ * coupling WorkflowAgent to UIMessageChunk format.
690
+ *
691
+ * Convert to UIMessageChunks at the response boundary using
692
+ * `createUIMessageChunkTransform()` from `@ai-sdk/workflow`.
693
+ *
694
+ * @example
695
+ * ```typescript
696
+ * // In the workflow:
697
+ * await agent.stream({
698
+ * messages,
699
+ * writable: getWritable<ModelCallStreamPart>(),
700
+ * });
701
+ *
702
+ * // In the route handler:
703
+ * return createUIMessageStreamResponse({
704
+ * stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
705
+ * });
706
+ * ```
707
+ */
708
+ writable?: WritableStream<ModelCallStreamPart<ToolSet>>;
625
709
 
626
- /**
627
- * Maximum number of sequential LLM calls (steps), e.g. when you use tool calls.
628
- * A maximum number can be set to prevent infinite loops in the case of misconfigured tools.
629
- * By default, it's unlimited (the agent loops until completion).
630
- */
631
- maxSteps?: number;
710
+ /**
711
+ * Condition for stopping the generation when there are tool results in the last step.
712
+ * When the condition is an array, any of the conditions can be met to stop the generation.
713
+ */
714
+ stopWhen?:
715
+ | StopCondition<NoInfer<ToolSet>, any>
716
+ | Array<StopCondition<NoInfer<ToolSet>, any>>;
632
717
 
633
- /**
634
- * The tool choice strategy. Default: 'auto'.
635
- * Overrides the toolChoice from the constructor if provided.
636
- */
637
- toolChoice?: ToolChoice<TTools>;
718
+ /**
719
+ * The tool choice strategy. Default: 'auto'.
720
+ * Overrides the toolChoice from the constructor if provided.
721
+ */
722
+ toolChoice?: ToolChoice<TTools>;
638
723
 
639
- /**
640
- * Limits the tools that are available for the model to call without
641
- * changing the tool call and result types in the result.
642
- */
643
- activeTools?: Array<keyof NoInfer<TTools>>;
724
+ /**
725
+ * Limits the tools that are available for the model to call without
726
+ * changing the tool call and result types in the result.
727
+ */
728
+ activeTools?: Array<keyof NoInfer<TTools>>;
644
729
 
645
- /**
646
- * Optional telemetry configuration (experimental).
647
- */
648
- experimental_telemetry?: TelemetrySettings;
730
+ /**
731
+ * Optional telemetry configuration (experimental).
732
+ */
733
+ experimental_telemetry?: TelemetryOptions;
649
734
 
650
- /**
651
- * Context that is passed into tool execution.
652
- * Experimental (can break in patch releases).
653
- * @default undefined
654
- */
655
- experimental_context?: unknown;
735
+ /**
736
+ * Context that is passed into tool execution.
737
+ * Experimental (can break in patch releases).
738
+ * @default undefined
739
+ */
740
+ experimental_context?: unknown;
656
741
 
657
- /**
658
- * Optional specification for parsing structured outputs from the LLM response.
659
- * Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
660
- *
661
- * @example
662
- * ```typescript
663
- * import { Output } from '@workflow/ai';
664
- * import { z } from 'zod';
665
- *
666
- * const result = await agent.stream({
667
- * messages: [...],
668
- * writable: getWritable(),
669
- * output: Output.object({
670
- * schema: z.object({
671
- * sentiment: z.enum(['positive', 'negative', 'neutral']),
672
- * confidence: z.number(),
673
- * }),
674
- * }),
675
- * });
676
- *
677
- * console.log(result.output); // { sentiment: 'positive', confidence: 0.95 }
678
- * ```
679
- */
680
- output?: OutputSpecification<OUTPUT, PARTIAL_OUTPUT>;
742
+ /**
743
+ * Optional specification for parsing structured outputs from the LLM response.
744
+ * Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
745
+ *
746
+ * @example
747
+ * ```typescript
748
+ * import { Output } from '@workflow/ai';
749
+ * import { z } from 'zod';
750
+ *
751
+ * const result = await agent.stream({
752
+ * messages: [...],
753
+ * writable: getWritable(),
754
+ * output: Output.object({
755
+ * schema: z.object({
756
+ * sentiment: z.enum(['positive', 'negative', 'neutral']),
757
+ * confidence: z.number(),
758
+ * }),
759
+ * }),
760
+ * });
761
+ *
762
+ * console.log(result.output); // { sentiment: 'positive', confidence: 0.95 }
763
+ * ```
764
+ */
765
+ output?: OutputSpecification<OUTPUT, PARTIAL_OUTPUT>;
681
766
 
682
- /**
683
- * Whether to include raw chunks from the provider in the stream.
684
- * When enabled, you will receive raw chunks with type 'raw' that contain the unprocessed data from the provider.
685
- * This allows access to cutting-edge provider features not yet wrapped by the AI SDK.
686
- * Defaults to false.
687
- */
688
- includeRawChunks?: boolean;
767
+ /**
768
+ * Whether to include raw chunks from the provider in the stream.
769
+ * When enabled, you will receive raw chunks with type 'raw' that contain the unprocessed data from the provider.
770
+ * This allows access to cutting-edge provider features not yet wrapped by the AI SDK.
771
+ * Defaults to false.
772
+ */
773
+ includeRawChunks?: boolean;
689
774
 
690
- /**
691
- * A function that attempts to repair a tool call that failed to parse.
692
- */
693
- experimental_repairToolCall?: ToolCallRepairFunction<TTools>;
775
+ /**
776
+ * A function that attempts to repair a tool call that failed to parse.
777
+ */
778
+ experimental_repairToolCall?: ToolCallRepairFunction<TTools>;
694
779
 
695
- /**
696
- * Optional stream transformations.
697
- * They are applied in the order they are provided.
698
- * The stream transformations must maintain the stream structure for streamText to work correctly.
699
- */
700
- experimental_transform?:
701
- | StreamTextTransform<TTools>
702
- | Array<StreamTextTransform<TTools>>;
780
+ /**
781
+ * Optional stream transformations.
782
+ * They are applied in the order they are provided.
783
+ * The stream transformations must maintain the stream structure for streamText to work correctly.
784
+ */
785
+ experimental_transform?:
786
+ | StreamTextTransform<TTools>
787
+ | Array<StreamTextTransform<TTools>>;
703
788
 
704
- /**
705
- * Custom download function to use for URLs.
706
- * By default, files are downloaded if the model does not support the URL for the given media type.
707
- */
708
- experimental_download?: DownloadFunction;
789
+ /**
790
+ * Custom download function to use for URLs.
791
+ * By default, files are downloaded if the model does not support the URL for the given media type.
792
+ */
793
+ experimental_download?: DownloadFunction;
709
794
 
710
- /**
711
- * Callback function to be called after each step completes.
712
- */
713
- onStepFinish?: StreamTextOnStepFinishCallback<TTools, any>;
795
+ /**
796
+ * Callback function to be called after each step completes.
797
+ */
798
+ onStepFinish?: WorkflowAgentOnStepFinishCallback<TTools>;
714
799
 
715
- /**
716
- * Callback that is invoked when an error occurs during streaming.
717
- * You can use it to log errors.
718
- */
719
- onError?: StreamTextOnErrorCallback;
800
+ /**
801
+ * Callback that is invoked when an error occurs during streaming.
802
+ * You can use it to log errors.
803
+ */
804
+ onError?: WorkflowAgentOnErrorCallback;
720
805
 
721
- /**
722
- * Callback that is called when the LLM response and all request tool executions
723
- * (for tools that have an `execute` function) are finished.
724
- */
725
- onFinish?: StreamTextOnFinishCallback<TTools, OUTPUT>;
806
+ /**
807
+ * Callback that is called when the LLM response and all request tool executions
808
+ * (for tools that have an `execute` function) are finished.
809
+ */
810
+ onFinish?: WorkflowAgentOnFinishCallback<TTools, OUTPUT>;
726
811
 
727
- /**
728
- * Callback that is called when the operation is aborted.
729
- */
730
- onAbort?: StreamTextOnAbortCallback<TTools>;
812
+ /**
813
+ * Callback that is called when the operation is aborted.
814
+ */
815
+ onAbort?: WorkflowAgentOnAbortCallback<TTools>;
731
816
 
732
- /**
733
- * Callback called when the agent starts streaming, before any LLM calls.
734
- */
735
- experimental_onStart?: WorkflowAgentOnStartCallback;
817
+ /**
818
+ * Callback called when the agent starts streaming, before any LLM calls.
819
+ */
820
+ experimental_onStart?: WorkflowAgentOnStartCallback;
736
821
 
737
- /**
738
- * Callback called before each step (LLM call) begins.
739
- */
740
- experimental_onStepStart?: WorkflowAgentOnStepStartCallback;
822
+ /**
823
+ * Callback called before each step (LLM call) begins.
824
+ */
825
+ experimental_onStepStart?: WorkflowAgentOnStepStartCallback;
741
826
 
742
- /**
743
- * Callback called before a tool's execute function runs.
744
- */
745
- experimental_onToolCallStart?: WorkflowAgentOnToolCallStartCallback;
827
+ /**
828
+ * Callback called before a tool's execute function runs.
829
+ */
830
+ experimental_onToolCallStart?: WorkflowAgentOnToolCallStartCallback;
746
831
 
747
- /**
748
- * Callback called after a tool execution completes.
749
- */
750
- experimental_onToolCallFinish?: WorkflowAgentOnToolCallFinishCallback;
832
+ /**
833
+ * Callback called after a tool execution completes.
834
+ */
835
+ experimental_onToolCallFinish?: WorkflowAgentOnToolCallFinishCallback;
751
836
 
752
- /**
753
- * Callback function called before each step in the agent loop.
754
- * Use this to modify settings, manage context, or inject messages dynamically.
755
- *
756
- * @example
757
- * ```typescript
758
- * prepareStep: async ({ messages, stepNumber }) => {
759
- * // Inject messages from a queue
760
- * const queuedMessages = await getQueuedMessages();
761
- * if (queuedMessages.length > 0) {
762
- * return {
763
- * messages: [...messages, ...queuedMessages],
764
- * };
765
- * }
766
- * return {};
767
- * }
768
- * ```
769
- */
770
- prepareStep?: PrepareStepCallback<TTools>;
837
+ /**
838
+ * Callback function called before each step in the agent loop.
839
+ * Use this to modify settings, manage context, or inject messages dynamically.
840
+ *
841
+ * @example
842
+ * ```typescript
843
+ * prepareStep: async ({ messages, stepNumber }) => {
844
+ * // Inject messages from a queue
845
+ * const queuedMessages = await getQueuedMessages();
846
+ * if (queuedMessages.length > 0) {
847
+ * return {
848
+ * messages: [...messages, ...queuedMessages],
849
+ * };
850
+ * }
851
+ * return {};
852
+ * }
853
+ * ```
854
+ */
855
+ prepareStep?: PrepareStepCallback<TTools>;
771
856
 
772
- /**
773
- * Timeout in milliseconds for the stream operation.
774
- * When specified, creates an AbortSignal that will abort the operation after the given time.
775
- * If both `timeout` and `abortSignal` are provided, whichever triggers first will abort.
776
- */
777
- timeout?: number;
857
+ /**
858
+ * Timeout in milliseconds for the stream operation.
859
+ * When specified, creates an AbortSignal that will abort the operation after the given time.
860
+ * If both `timeout` and `abortSignal` are provided, whichever triggers first will abort.
861
+ */
862
+ timeout?: number;
778
863
 
779
- /**
780
- * Whether to send a 'finish' chunk to the writable stream when streaming completes.
781
- * @default true
782
- */
783
- sendFinish?: boolean;
864
+ /**
865
+ * Whether to send a 'finish' chunk to the writable stream when streaming completes.
866
+ * @default true
867
+ */
868
+ sendFinish?: boolean;
784
869
 
785
- /**
786
- * Whether to prevent the writable stream from being closed after streaming completes.
787
- * @default false
788
- */
789
- preventClose?: boolean;
790
- }
870
+ /**
871
+ * Whether to prevent the writable stream from being closed after streaming completes.
872
+ * @default false
873
+ */
874
+ preventClose?: boolean;
875
+ };
791
876
 
792
877
  /**
793
878
  * A tool call made by the model. Matches the AI SDK's tool call shape.
@@ -898,10 +983,12 @@ export interface WorkflowAgentStreamResult<
898
983
  * ```
899
984
  */
900
985
  export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
901
- private model:
902
- | string
903
- | CompatibleLanguageModel
904
- | (() => Promise<CompatibleLanguageModel>);
986
+ /**
987
+ * The id of the agent.
988
+ */
989
+ public readonly id: string | undefined;
990
+
991
+ private model: LanguageModel;
905
992
  /**
906
993
  * The tool set configured for this agent.
907
994
  */
@@ -912,14 +999,18 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
912
999
  | Array<SystemModelMessage>;
913
1000
  private generationSettings: GenerationSettings;
914
1001
  private toolChoice?: ToolChoice<TBaseTools>;
915
- private telemetry?: TelemetrySettings;
1002
+ private telemetry?: TelemetryOptions;
916
1003
  private experimentalContext: unknown;
1004
+ private stopWhen?:
1005
+ | StopCondition<ToolSet, any>
1006
+ | Array<StopCondition<ToolSet, any>>;
1007
+ private activeTools?: Array<keyof TBaseTools>;
1008
+ private output?: OutputSpecification<any, any>;
1009
+ private experimentalRepairToolCall?: ToolCallRepairFunction<TBaseTools>;
1010
+ private experimentalDownload?: DownloadFunction;
917
1011
  private prepareStep?: PrepareStepCallback<TBaseTools>;
918
- private constructorOnStepFinish?: StreamTextOnStepFinishCallback<
919
- ToolSet,
920
- any
921
- >;
922
- private constructorOnFinish?: StreamTextOnFinishCallback<ToolSet>;
1012
+ private constructorOnStepFinish?: WorkflowAgentOnStepFinishCallback<ToolSet>;
1013
+ private constructorOnFinish?: WorkflowAgentOnFinishCallback<ToolSet>;
923
1014
  private constructorOnStart?: WorkflowAgentOnStartCallback;
924
1015
  private constructorOnStepStart?: WorkflowAgentOnStepStartCallback;
925
1016
  private constructorOnToolCallStart?: WorkflowAgentOnToolCallStartCallback;
@@ -927,6 +1018,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
927
1018
  private prepareCall?: PrepareCallCallback<TBaseTools>;
928
1019
 
929
1020
  constructor(options: WorkflowAgentOptions<TBaseTools>) {
1021
+ this.id = options.id;
930
1022
  this.model = options.model;
931
1023
  this.tools = (options.tools ?? {}) as TBaseTools;
932
1024
  // `instructions` takes precedence over deprecated `system`
@@ -934,6 +1026,11 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
934
1026
  this.toolChoice = options.toolChoice;
935
1027
  this.telemetry = options.experimental_telemetry;
936
1028
  this.experimentalContext = options.experimental_context;
1029
+ this.stopWhen = options.stopWhen;
1030
+ this.activeTools = options.activeTools;
1031
+ this.output = options.output;
1032
+ this.experimentalRepairToolCall = options.experimental_repairToolCall;
1033
+ this.experimentalDownload = options.experimental_download;
937
1034
  this.prepareStep = options.prepareStep;
938
1035
  this.constructorOnStepFinish = options.onStepFinish;
939
1036
  this.constructorOnFinish = options.onFinish;
@@ -972,12 +1069,11 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
972
1069
  options: WorkflowAgentStreamOptions<TTools, OUTPUT, PARTIAL_OUTPUT>,
973
1070
  ): Promise<WorkflowAgentStreamResult<TTools, OUTPUT>> {
974
1071
  // Call prepareCall to transform parameters before the agent loop
975
- let effectiveModel:
976
- | string
977
- | CompatibleLanguageModel
978
- | (() => Promise<CompatibleLanguageModel>) = this.model;
1072
+ let effectiveModel: LanguageModel = this.model;
979
1073
  let effectiveInstructions = options.system ?? this.instructions;
980
- let effectiveMessages = options.messages;
1074
+ let effectivePrompt: string | Array<ModelMessage> | undefined =
1075
+ options.prompt;
1076
+ let effectiveMessages: Array<ModelMessage> | undefined = options.messages;
981
1077
  let effectiveGenerationSettings = { ...this.generationSettings };
982
1078
  let effectiveExperimentalContext =
983
1079
  options.experimental_context ?? this.experimentalContext;
@@ -985,6 +1081,14 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
985
1081
  let effectiveTelemetryFromPrepare =
986
1082
  options.experimental_telemetry ?? this.telemetry;
987
1083
 
1084
+ // Resolve messages for prepareCall: use messages directly, or convert prompt
1085
+ const resolvedMessagesForPrepareCall: ModelMessage[] =
1086
+ effectiveMessages ??
1087
+ (typeof effectivePrompt === 'string'
1088
+ ? [{ role: 'user' as const, content: effectivePrompt }]
1089
+ : (effectivePrompt as ModelMessage[])) ??
1090
+ [];
1091
+
988
1092
  if (this.prepareCall) {
989
1093
  const prepared = await this.prepareCall({
990
1094
  model: effectiveModel,
@@ -993,16 +1097,17 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
993
1097
  toolChoice: effectiveToolChoiceFromPrepare as ToolChoice<TBaseTools>,
994
1098
  experimental_telemetry: effectiveTelemetryFromPrepare,
995
1099
  experimental_context: effectiveExperimentalContext,
996
- messages: effectiveMessages as ModelMessage[],
1100
+ messages: resolvedMessagesForPrepareCall,
997
1101
  ...effectiveGenerationSettings,
998
1102
  } as PrepareCallOptions<TBaseTools>);
999
1103
 
1000
1104
  if (prepared.model !== undefined) effectiveModel = prepared.model;
1001
1105
  if (prepared.instructions !== undefined)
1002
1106
  effectiveInstructions = prepared.instructions;
1003
- if (prepared.messages !== undefined)
1004
- effectiveMessages =
1005
- prepared.messages as WorkflowAgentStreamOptions<TTools>['messages'];
1107
+ if (prepared.messages !== undefined) {
1108
+ effectiveMessages = prepared.messages as Array<ModelMessage>;
1109
+ effectivePrompt = undefined; // messages from prepareCall take precedence
1110
+ }
1006
1111
  if (prepared.experimental_context !== undefined)
1007
1112
  effectiveExperimentalContext = prepared.experimental_context;
1008
1113
  if (prepared.toolChoice !== undefined)
@@ -1035,7 +1140,9 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1035
1140
 
1036
1141
  const prompt = await standardizePrompt({
1037
1142
  system: effectiveInstructions,
1038
- messages: effectiveMessages,
1143
+ ...(effectivePrompt != null
1144
+ ? { prompt: effectivePrompt }
1145
+ : { messages: effectiveMessages! }),
1039
1146
  });
1040
1147
 
1041
1148
  // Process tool approval responses before starting the agent loop.
@@ -1164,7 +1271,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1164
1271
  const modelPrompt = await convertToLanguageModelPrompt({
1165
1272
  prompt,
1166
1273
  supportedUrls: {},
1167
- download: options.experimental_download,
1274
+ download: options.experimental_download ?? this.experimentalDownload,
1168
1275
  });
1169
1276
 
1170
1277
  const effectiveAbortSignal = mergeAbortSignals(
@@ -1210,13 +1317,13 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1210
1317
  // Merge constructor + stream callbacks (constructor first, then stream)
1211
1318
  const mergedOnStepFinish = mergeCallbacks(
1212
1319
  this.constructorOnStepFinish as
1213
- | StreamTextOnStepFinishCallback<TTools, any>
1320
+ | WorkflowAgentOnStepFinishCallback<TTools>
1214
1321
  | undefined,
1215
1322
  options.onStepFinish,
1216
1323
  );
1217
1324
  const mergedOnFinish = mergeCallbacks(
1218
1325
  this.constructorOnFinish as
1219
- | StreamTextOnFinishCallback<TTools, OUTPUT>
1326
+ | WorkflowAgentOnFinishCallback<TTools, OUTPUT>
1220
1327
  | undefined,
1221
1328
  options.onFinish,
1222
1329
  );
@@ -1243,10 +1350,14 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1243
1350
  // Merge telemetry settings
1244
1351
  const effectiveTelemetry = effectiveTelemetryFromPrepare;
1245
1352
 
1246
- // Filter tools if activeTools is specified
1353
+ // Filter tools if activeTools is specified (stream-level overrides constructor default)
1354
+ const effectiveActiveTools = options.activeTools ?? this.activeTools;
1247
1355
  const effectiveTools =
1248
- options.activeTools && options.activeTools.length > 0
1249
- ? filterTools(this.tools, options.activeTools as string[])
1356
+ effectiveActiveTools && effectiveActiveTools.length > 0
1357
+ ? (filterActiveTools({
1358
+ tools: this.tools,
1359
+ activeTools: effectiveActiveTools as string[],
1360
+ }) ?? this.tools)
1250
1361
  : this.tools;
1251
1362
 
1252
1363
  // Initialize context
@@ -1262,7 +1373,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1262
1373
  if (mergedOnStart) {
1263
1374
  await mergedOnStart({
1264
1375
  model: effectiveModel,
1265
- messages: effectiveMessages as ModelMessage[],
1376
+ messages: prompt.messages,
1266
1377
  });
1267
1378
  }
1268
1379
 
@@ -1272,59 +1383,67 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1272
1383
  tools: ToolSet,
1273
1384
  messages: LanguageModelV4Prompt,
1274
1385
  context?: unknown,
1386
+ currentStepNumber: number = 0,
1275
1387
  ): Promise<LanguageModelV4ToolResultPart> => {
1388
+ const toolCallEvent: ToolCall = {
1389
+ type: 'tool-call',
1390
+ toolCallId: toolCall.toolCallId,
1391
+ toolName: toolCall.toolName,
1392
+ input: toolCall.input,
1393
+ };
1394
+
1276
1395
  if (mergedOnToolCallStart) {
1277
1396
  await mergedOnToolCallStart({
1278
- toolCall: {
1279
- type: 'tool-call',
1280
- toolCallId: toolCall.toolCallId,
1281
- toolName: toolCall.toolName,
1282
- input: toolCall.input,
1283
- },
1397
+ toolCall: toolCallEvent,
1398
+ stepNumber: currentStepNumber,
1284
1399
  });
1285
1400
  }
1401
+
1402
+ const startTime = Date.now();
1286
1403
  let result: LanguageModelV4ToolResultPart;
1287
1404
  try {
1288
1405
  result = await executeTool(toolCall, tools, messages, context);
1289
1406
  } catch (err) {
1407
+ const durationMs = Date.now() - startTime;
1290
1408
  if (mergedOnToolCallFinish) {
1291
1409
  await mergedOnToolCallFinish({
1292
- toolCall: {
1293
- type: 'tool-call',
1294
- toolCallId: toolCall.toolCallId,
1295
- toolName: toolCall.toolName,
1296
- input: toolCall.input,
1297
- },
1410
+ toolCall: toolCallEvent,
1411
+ stepNumber: currentStepNumber,
1412
+ durationMs,
1413
+ success: false,
1298
1414
  error: err,
1299
1415
  });
1300
1416
  }
1301
1417
  throw err;
1302
1418
  }
1419
+
1420
+ const durationMs = Date.now() - startTime;
1303
1421
  if (mergedOnToolCallFinish) {
1304
1422
  const isError =
1305
1423
  result.output &&
1306
1424
  'type' in result.output &&
1307
1425
  (result.output.type === 'error-text' ||
1308
1426
  result.output.type === 'error-json');
1309
- await mergedOnToolCallFinish({
1310
- toolCall: {
1311
- type: 'tool-call',
1312
- toolCallId: toolCall.toolCallId,
1313
- toolName: toolCall.toolName,
1314
- input: toolCall.input,
1315
- },
1316
- ...(isError
1317
- ? {
1318
- error:
1319
- 'value' in result.output ? result.output.value : undefined,
1320
- }
1321
- : {
1322
- result:
1323
- result.output && 'value' in result.output
1324
- ? result.output.value
1325
- : undefined,
1326
- }),
1327
- });
1427
+ if (isError) {
1428
+ await mergedOnToolCallFinish({
1429
+ toolCall: toolCallEvent,
1430
+ stepNumber: currentStepNumber,
1431
+ durationMs,
1432
+ success: false,
1433
+ error: 'value' in result.output ? result.output.value : undefined,
1434
+ });
1435
+ } else {
1436
+ await mergedOnToolCallFinish({
1437
+ toolCall: toolCallEvent,
1438
+ stepNumber: currentStepNumber,
1439
+ durationMs,
1440
+ success: true,
1441
+ output:
1442
+ result.output && 'value' in result.output
1443
+ ? result.output.value
1444
+ : undefined,
1445
+ });
1446
+ }
1328
1447
  }
1329
1448
  return result;
1330
1449
  };
@@ -1335,7 +1454,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1335
1454
  await options.onAbort({ steps });
1336
1455
  }
1337
1456
  return {
1338
- messages: options.messages as unknown as ModelMessage[],
1457
+ messages: prompt.messages,
1339
1458
  steps,
1340
1459
  toolCalls: [],
1341
1460
  toolResults: [],
@@ -1348,8 +1467,8 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1348
1467
  tools: effectiveTools as ToolSet,
1349
1468
  writable: options.writable,
1350
1469
  prompt: modelPrompt,
1351
- stopConditions: options.stopWhen,
1352
- maxSteps: options.maxSteps,
1470
+ stopConditions: options.stopWhen ?? this.stopWhen,
1471
+
1353
1472
  onStepFinish: mergedOnStepFinish,
1354
1473
  onStepStart: mergedOnStepStart,
1355
1474
  onError: options.onError,
@@ -1361,9 +1480,11 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1361
1480
  experimental_context: experimentalContext,
1362
1481
  experimental_telemetry: effectiveTelemetry,
1363
1482
  includeRawChunks: options.includeRawChunks ?? false,
1364
- repairToolCall:
1365
- options.experimental_repairToolCall as ToolCallRepairFunction<ToolSet>,
1366
- responseFormat: await options.output?.responseFormat,
1483
+ repairToolCall: (options.experimental_repairToolCall ??
1484
+ this.experimentalRepairToolCall) as
1485
+ | ToolCallRepairFunction<ToolSet>
1486
+ | undefined,
1487
+ responseFormat: await (options.output ?? this.output)?.responseFormat,
1367
1488
  });
1368
1489
 
1369
1490
  // Track the final conversation messages from the iterator
@@ -1390,6 +1511,8 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1390
1511
  context,
1391
1512
  providerExecutedToolResults,
1392
1513
  } = result.value;
1514
+ // Capture current step number before pushing (0-based)
1515
+ const currentStepNumber = steps.length;
1393
1516
  if (step) {
1394
1517
  steps.push(step as unknown as StepResult<TTools, any>);
1395
1518
  }
@@ -1453,6 +1576,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1453
1576
  effectiveTools as ToolSet,
1454
1577
  iterMessages,
1455
1578
  experimentalContext,
1579
+ currentStepNumber,
1456
1580
  ),
1457
1581
  ),
1458
1582
  );
@@ -1553,6 +1677,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1553
1677
  effectiveTools as ToolSet,
1554
1678
  iterMessages,
1555
1679
  experimentalContext,
1680
+ currentStepNumber,
1556
1681
  ),
1557
1682
  ),
1558
1683
  );
@@ -1641,18 +1766,19 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1641
1766
  // Don't throw yet - we want to call onFinish first
1642
1767
  }
1643
1768
 
1644
- // Use the final messages from the iterator, or fall back to original messages
1769
+ // Use the final messages from the iterator, or fall back to standardized messages
1645
1770
  const messages = (finalMessages ??
1646
- options.messages) as unknown as ModelMessage[];
1771
+ prompt.messages) as unknown as ModelMessage[];
1647
1772
 
1648
- // Parse structured output if output is specified
1773
+ // Parse structured output if output is specified (stream-level overrides constructor default)
1774
+ const effectiveOutput = options.output ?? this.output;
1649
1775
  let experimentalOutput: OUTPUT = undefined as OUTPUT;
1650
- if (options.output && steps.length > 0) {
1776
+ if (effectiveOutput && steps.length > 0) {
1651
1777
  const lastStep = steps[steps.length - 1];
1652
1778
  const text = lastStep.text;
1653
1779
  if (text) {
1654
1780
  try {
1655
- experimentalOutput = await options.output.parseCompleteOutput(
1781
+ experimentalOutput = await effectiveOutput.parseCompleteOutput(
1656
1782
  { text },
1657
1783
  {
1658
1784
  response: lastStep.response,
@@ -1850,19 +1976,6 @@ function aggregateUsage(steps: StepResult<any, any>[]): LanguageModelUsage {
1850
1976
  } as LanguageModelUsage;
1851
1977
  }
1852
1978
 
1853
- function filterTools<TTools extends ToolSet>(
1854
- tools: TTools,
1855
- activeTools: string[],
1856
- ): ToolSet {
1857
- const filtered: ToolSet = {};
1858
- for (const toolName of activeTools) {
1859
- if (toolName in tools) {
1860
- filtered[toolName] = tools[toolName];
1861
- }
1862
- }
1863
- return filtered;
1864
- }
1865
-
1866
1979
  // Matches AI SDK's getErrorMessage from @ai-sdk/provider-utils
1867
1980
  function getErrorMessage(error: unknown): string {
1868
1981
  if (error == null) {