ai 7.0.131 → 7.0.133

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/index.d.ts +122 -101
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +60 -11
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +21 -14
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +1 -1
  9. package/docs/03-ai-sdk-core/17-mcp-events.mdx +169 -0
  10. package/docs/03-ai-sdk-core/32-decisions.mdx +54 -6
  11. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +13 -0
  12. package/docs/07-reference/01-ai-sdk-core/14-decide.mdx +4 -2
  13. package/docs/07-reference/01-ai-sdk-core/17-create-agent-ui-stream.mdx +41 -2
  14. package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +10 -1
  15. package/docs/07-reference/01-ai-sdk-core/18-pipe-agent-ui-stream-to-response.mdx +10 -1
  16. package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +46 -1
  17. package/docs/07-reference/01-ai-sdk-core/24-mcp-events.mdx +797 -0
  18. package/docs/07-reference/01-ai-sdk-core/index.mdx +6 -0
  19. package/docs/07-reference/02-ai-sdk-ui/50-direct-chat-transport.mdx +13 -0
  20. package/package.json +12 -12
  21. package/src/agent/create-agent-ui-stream-response.ts +6 -10
  22. package/src/agent/create-agent-ui-stream.ts +22 -4
  23. package/src/agent/pipe-agent-ui-stream-to-response.ts +6 -10
  24. package/src/decide/decide-events.ts +5 -3
  25. package/src/decide/decide.ts +7 -9
  26. package/src/decide/decision-result.ts +1 -2
  27. package/src/decide/decision-state.ts +61 -0
  28. package/src/decide/index.ts +8 -2
  29. package/src/decide/validate-decision.ts +25 -5
  30. package/src/generate-text/stream-text-result.ts +11 -0
  31. package/src/generate-text/stream-text.ts +9 -0
  32. package/src/ui-message-stream/to-ui-message-stream.ts +10 -2
@@ -147,6 +147,12 @@ It also contains the following helper functions:
147
147
  description: 'Creates a client for connecting to MCP servers.',
148
148
  href: '/docs/reference/ai-sdk-core/create-mcp-client',
149
149
  },
150
+ {
151
+ title: 'MCP Events',
152
+ description:
153
+ 'Experimental event methods, webhook delivery, and subscription types.',
154
+ href: '/docs/reference/ai-sdk-core/mcp-events',
155
+ },
150
156
  {
151
157
  title: 'MCP Apps',
152
158
  description:
@@ -77,6 +77,19 @@ export default function Chat() {
77
77
  description:
78
78
  'Extracts message metadata that will be sent to the client. Called on `start` and `finish` events.',
79
79
  },
80
+ {
81
+ name: 'onStepEnd',
82
+ type: 'UIMessageStreamOnStepEndCallback',
83
+ isOptional: true,
84
+ description:
85
+ 'Called after each step is converted to UI message parts. Receives messages, isContinuation, and the accumulated responseMessage, including step metadata, for inspection or persistence.',
86
+ },
87
+ {
88
+ name: 'onStepFinish',
89
+ type: 'UIMessageStreamOnStepFinishCallback',
90
+ isOptional: true,
91
+ description: 'Deprecated alias for onStepEnd.',
92
+ },
80
93
  {
81
94
  name: 'sendReasoning',
82
95
  type: 'boolean',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.131",
3
+ "version": "7.0.133",
4
4
  "type": "module",
5
5
  "description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
6
6
  "license": "Apache-2.0",
@@ -42,20 +42,20 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.107",
46
- "@ai-sdk/provider": "4.0.24",
47
- "@ai-sdk/provider-utils": "5.0.56"
45
+ "@ai-sdk/gateway": "4.0.108",
46
+ "@ai-sdk/provider": "4.0.25",
47
+ "@ai-sdk/provider-utils": "5.0.57"
48
48
  },
49
49
  "devDependencies": {
50
- "@ai-sdk/amazon-bedrock": "5.0.109",
51
- "@ai-sdk/deepseek": "3.0.61",
52
- "@ai-sdk/google": "4.0.90",
53
- "@ai-sdk/groq": "4.0.57",
54
- "@ai-sdk/huggingface": "2.0.65",
55
- "@ai-sdk/moonshotai": "3.0.65",
56
- "@ai-sdk/openai": "4.0.87",
50
+ "@ai-sdk/amazon-bedrock": "5.0.111",
51
+ "@ai-sdk/deepseek": "3.0.62",
52
+ "@ai-sdk/google": "4.0.92",
53
+ "@ai-sdk/groq": "4.0.58",
54
+ "@ai-sdk/huggingface": "2.0.66",
55
+ "@ai-sdk/moonshotai": "3.0.66",
56
+ "@ai-sdk/openai": "4.0.89",
57
57
  "@ai-sdk/test-server": "2.0.4",
58
- "@ai-sdk/xai": "5.0.18",
58
+ "@ai-sdk/xai": "5.0.19",
59
59
  "@edge-runtime/vm": "^5.0.0",
60
60
  "@smithy/eventstream-codec": "^4.3.3",
61
61
  "@smithy/util-utf8": "^4.3.3",
@@ -4,20 +4,18 @@ import type {
4
4
  Experimental_SandboxSession as SandboxSession,
5
5
  ToolSet,
6
6
  } from '@ai-sdk/provider-utils';
7
- import type {
8
- GenerateTextOnStepEndCallback,
9
- GenerateTextOnStepFinishCallback,
10
- } from '../generate-text/generate-text-events';
11
7
  import type { Output } from '../generate-text/output';
12
8
  import type { StreamTextTransform } from '../generate-text/stream-text';
13
- import type { UIMessageStreamOptions } from '../generate-text/stream-text-result';
14
9
  import type { TimeoutConfiguration } from '../prompt/request-options';
15
10
  import { createUIMessageStreamResponse } from '../ui-message-stream';
16
11
  import type { UIMessageStreamResponseInit } from '../ui-message-stream/ui-message-stream-response-init';
17
12
  import type { InferUITools, UIMessage, UIDataTypes } from '../ui/ui-messages';
18
13
  import type { Agent } from './agent';
19
14
  import type { convertToModelMessages } from '../ui/convert-to-model-messages';
20
- import { createAgentUIStream } from './create-agent-ui-stream';
15
+ import {
16
+ createAgentUIStream,
17
+ type AgentUIMessageStreamOptions,
18
+ } from './create-agent-ui-stream';
21
19
 
22
20
  /**
23
21
  * Runs the agent and returns a response object with a UI message stream.
@@ -32,6 +30,7 @@ import { createAgentUIStream } from './create-agent-ui-stream';
32
30
  * @param experimental_transform - Stream transformations. Optional.
33
31
  * @param onStepEnd - Callback that is called when each step ends. Optional.
34
32
  * @param onStepFinish - Deprecated alias for `onStepEnd`. Optional.
33
+ * @param onUIMessageStepEnd - Callback with the accumulated UI message after each step. Optional.
35
34
  * @param headers - Additional headers for the response. Optional.
36
35
  * @param status - The status code for the response. Optional.
37
36
  * @param statusText - The status text for the response. Optional.
@@ -73,11 +72,8 @@ export async function createAgentUIStreamResponse<
73
72
  experimental_sandbox?: SandboxSession;
74
73
  options?: CALL_OPTIONS;
75
74
  experimental_transform?: Arrayable<StreamTextTransform<TOOLS>>;
76
- onStepEnd?: GenerateTextOnStepEndCallback<TOOLS>;
77
- /** @deprecated Use `onStepEnd` instead. */
78
- onStepFinish?: GenerateTextOnStepFinishCallback<TOOLS>;
79
75
  } & UIMessageStreamResponseInit &
80
- UIMessageStreamOptions<UI_MESSAGE>): Promise<Response> {
76
+ AgentUIMessageStreamOptions<TOOLS, UI_MESSAGE>): Promise<Response> {
81
77
  return createUIMessageStreamResponse({
82
78
  headers,
83
79
  status,
@@ -14,6 +14,7 @@ import type { StreamTextTransform } from '../generate-text/stream-text';
14
14
  import type { UIMessageStreamOptions } from '../generate-text/stream-text-result';
15
15
  import type { TimeoutConfiguration } from '../prompt/request-options';
16
16
  import type { InferUIMessageChunk } from '../ui-message-stream';
17
+ import type { UIMessageStreamOnStepEndCallback } from '../ui-message-stream/ui-message-stream-on-step-end-callback';
17
18
  import { toUIMessageStream } from '../ui-message-stream/to-ui-message-stream';
18
19
  import { convertToModelMessages } from '../ui/convert-to-model-messages';
19
20
  import type {
@@ -29,6 +30,23 @@ import {
29
30
  } from '../util/async-iterable-stream';
30
31
  import type { Agent } from './agent';
31
32
 
33
+ /**
34
+ * UI stream options with separate generation and UI message step callbacks.
35
+ */
36
+ export type AgentUIMessageStreamOptions<
37
+ TOOLS extends ToolSet,
38
+ UI_MESSAGE extends UIMessage,
39
+ > = Omit<UIMessageStreamOptions<UI_MESSAGE>, 'onStepEnd' | 'onStepFinish'> & {
40
+ onStepEnd?: GenerateTextOnStepEndCallback<TOOLS>;
41
+ /** @deprecated Use `onStepEnd` instead. */
42
+ onStepFinish?: GenerateTextOnStepFinishCallback<TOOLS>;
43
+ /**
44
+ * Called after each step is converted to UI message parts.
45
+ * Receives the accumulated UI message for inspection or persistence.
46
+ */
47
+ onUIMessageStepEnd?: UIMessageStreamOnStepEndCallback<UI_MESSAGE>;
48
+ };
49
+
32
50
  /**
33
51
  * Runs the agent and stream the output as a UI message stream.
34
52
  *
@@ -42,6 +60,7 @@ import type { Agent } from './agent';
42
60
  * @param experimental_transform - The stream transformations. Optional.
43
61
  * @param onStepEnd - Callback that is called when each step ends. Optional.
44
62
  * @param onStepFinish - Deprecated alias for `onStepEnd`. Optional.
63
+ * @param onUIMessageStepEnd - Callback with the accumulated UI message after each step. Optional.
45
64
  *
46
65
  * @returns The UI message stream.
47
66
  */
@@ -67,6 +86,7 @@ export async function createAgentUIStream<
67
86
  experimental_transform,
68
87
  onStepEnd,
69
88
  onStepFinish,
89
+ onUIMessageStepEnd,
70
90
  ...uiMessageStreamOptions
71
91
  }: {
72
92
  agent: Agent<CALL_OPTIONS, TOOLS, RUNTIME_CONTEXT, OUTPUT>;
@@ -83,11 +103,8 @@ export async function createAgentUIStream<
83
103
  experimental_sandbox?: SandboxSession;
84
104
  options?: CALL_OPTIONS;
85
105
  experimental_transform?: Arrayable<StreamTextTransform<TOOLS>>;
86
- onStepEnd?: GenerateTextOnStepEndCallback<TOOLS>;
87
- /** @deprecated Use `onStepEnd` instead. */
88
- onStepFinish?: GenerateTextOnStepFinishCallback<TOOLS>;
89
106
  // TODO `originalMessages` is part of this for bc, omit in v7
90
- } & UIMessageStreamOptions<UI_MESSAGE>): Promise<
107
+ } & AgentUIMessageStreamOptions<TOOLS, UI_MESSAGE>): Promise<
91
108
  AsyncIterableStream<InferUIMessageChunk<UI_MESSAGE>>
92
109
  > {
93
110
  const validatedMessages = await validateUIMessagesForAgent<UI_MESSAGE>({
@@ -125,6 +142,7 @@ export async function createAgentUIStream<
125
142
  toUIMessageStream({
126
143
  ...uiMessageStreamOptions,
127
144
  originalMessages,
145
+ onStepEnd: onUIMessageStepEnd,
128
146
  stream: result.stream,
129
147
  tools: agent.tools,
130
148
  }),
@@ -5,20 +5,18 @@ import type {
5
5
  ToolSet,
6
6
  } from '@ai-sdk/provider-utils';
7
7
  import type { ServerResponse } from 'node:http';
8
- import type {
9
- GenerateTextOnStepEndCallback,
10
- GenerateTextOnStepFinishCallback,
11
- } from '../generate-text/generate-text-events';
12
8
  import type { Output } from '../generate-text/output';
13
9
  import type { StreamTextTransform } from '../generate-text/stream-text';
14
- import type { UIMessageStreamOptions } from '../generate-text/stream-text-result';
15
10
  import type { TimeoutConfiguration } from '../prompt/request-options';
16
11
  import { pipeUIMessageStreamToResponse } from '../ui-message-stream';
17
12
  import type { UIMessageStreamResponseInit } from '../ui-message-stream/ui-message-stream-response-init';
18
13
  import type { InferUITools, UIMessage, UIDataTypes } from '../ui/ui-messages';
19
14
  import type { Agent } from './agent';
20
15
  import type { convertToModelMessages } from '../ui/convert-to-model-messages';
21
- import { createAgentUIStream } from './create-agent-ui-stream';
16
+ import {
17
+ createAgentUIStream,
18
+ type AgentUIMessageStreamOptions,
19
+ } from './create-agent-ui-stream';
22
20
 
23
21
  /**
24
22
  * Pipes the agent UI message stream to a Node.js ServerResponse object.
@@ -34,6 +32,7 @@ import { createAgentUIStream } from './create-agent-ui-stream';
34
32
  * @param experimental_transform - Stream transformations. Optional.
35
33
  * @param onStepEnd - Callback that is called when each step ends. Optional.
36
34
  * @param onStepFinish - Deprecated alias for `onStepEnd`. Optional.
35
+ * @param onUIMessageStepEnd - Callback with the accumulated UI message after each step. Optional.
37
36
  * @param headers - Additional headers for the response. Optional.
38
37
  * @param status - The status code for the response. Optional.
39
38
  * @param statusText - The status text for the response. Optional.
@@ -75,11 +74,8 @@ export async function pipeAgentUIStreamToResponse<
75
74
  experimental_sandbox?: SandboxSession;
76
75
  options?: CALL_OPTIONS;
77
76
  experimental_transform?: Arrayable<StreamTextTransform<TOOLS>>;
78
- onStepEnd?: GenerateTextOnStepEndCallback<TOOLS>;
79
- /** @deprecated Use `onStepEnd` instead. */
80
- onStepFinish?: GenerateTextOnStepFinishCallback<TOOLS>;
81
77
  } & UIMessageStreamResponseInit &
82
- UIMessageStreamOptions<UI_MESSAGE>): Promise<void> {
78
+ AgentUIMessageStreamOptions<TOOLS, UI_MESSAGE>): Promise<void> {
83
79
  return pipeUIMessageStreamToResponse({
84
80
  response,
85
81
  headers,
@@ -1,9 +1,11 @@
1
1
  import type {
2
2
  Experimental_DecisionModelV4CallOptions as DecisionModelV4CallOptions,
3
3
  Experimental_DecisionModelV4Result as DecisionModelV4Result,
4
+ Experimental_DecisionModelV4Question as DecisionQuestion,
4
5
  } from '@ai-sdk/provider';
5
6
  import type { Context, ProviderOptions } from '@ai-sdk/provider-utils';
6
- import type { DecisionQuestion, DecisionResult } from './decision-result';
7
+ import type { DecisionState } from './decision-state';
8
+ import type { DecisionResult } from './decision-result';
7
9
 
8
10
  /**
9
11
  * Event passed to the `onStart` callback for decision operations.
@@ -27,7 +29,7 @@ export type DecideStartEvent<RUNTIME_CONTEXT extends Context = Context> = {
27
29
  readonly modelId: string;
28
30
 
29
31
  /** The shared state used to make decisions. */
30
- readonly state: DecisionModelV4CallOptions['state'];
32
+ readonly state: DecisionState;
31
33
 
32
34
  /** The questions to decide against the shared state. */
33
35
  readonly questions: Readonly<Record<string, DecisionQuestion>>;
@@ -100,7 +102,7 @@ export type DecisionModelCallStartEvent = {
100
102
  readonly state: DecisionModelV4CallOptions['state'];
101
103
 
102
104
  /** The questions to decide against the shared state. */
103
- readonly questions: Readonly<Record<string, DecisionQuestion>>;
105
+ readonly questions: DecisionModelV4CallOptions['questions'];
104
106
  };
105
107
 
106
108
  /**
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Experimental_DecisionRefusalError as DecisionRefusalError,
3
3
  Experimental_DecisionUnsupportedQuestionTypeError as DecisionUnsupportedQuestionTypeError,
4
- type Experimental_DecisionModelV4CallOptions as DecisionModelV4CallOptions,
4
+ type Experimental_DecisionModelV4Question as DecisionQuestion,
5
5
  } from '@ai-sdk/provider';
6
6
  import {
7
7
  createIdGenerator,
@@ -9,6 +9,7 @@ import {
9
9
  type Context,
10
10
  type ProviderOptions,
11
11
  } from '@ai-sdk/provider-utils';
12
+ import { prepareDecisionState, type DecisionState } from './decision-state';
12
13
  import { resolveDecisionModel } from '../model/resolve-model';
13
14
  import { logWarnings } from '../logger/log-warnings';
14
15
  import type { TelemetryOptions } from '../telemetry/telemetry-options';
@@ -17,11 +18,7 @@ import { notify } from '../util/notify';
17
18
  import { prepareRetries } from '../util/prepare-retries';
18
19
  import { VERSION } from '../version';
19
20
  import type { DecideEndEvent, DecideStartEvent } from './decide-events';
20
- import type {
21
- DecisionModel,
22
- DecisionQuestion,
23
- DecisionResult,
24
- } from './decision-result';
21
+ import type { DecisionModel, DecisionResult } from './decision-result';
25
22
  import { createRestrictedTelemetryDispatcher } from './restricted-telemetry-dispatcher';
26
23
  import {
27
24
  validateDecisionInput,
@@ -53,7 +50,7 @@ export async function decide<
53
50
  }: {
54
51
  /** A decision model instance or an ID resolved by the configured default provider. */
55
52
  model: DecisionModel;
56
- state: DecisionModelV4CallOptions['state'];
53
+ state: DecisionState;
57
54
  questions: QUESTIONS;
58
55
  /** Maximum retries for transient provider failures. Defaults to 2. */
59
56
  maxRetries?: number;
@@ -122,12 +119,13 @@ export async function decide<
122
119
  });
123
120
 
124
121
  try {
122
+ const preparedState = await prepareDecisionState(state, abortSignal);
125
123
  const modelCallEvent = {
126
124
  callId,
127
125
  operationId: 'ai.decide.doDecide' as const,
128
126
  provider: model.provider,
129
127
  modelId: model.modelId,
130
- state,
128
+ state: preparedState,
131
129
  questions,
132
130
  };
133
131
  await notify({
@@ -139,7 +137,7 @@ export async function decide<
139
137
  const result = await retry(async () => {
140
138
  abortSignal?.throwIfAborted();
141
139
  return await model.doDecide({
142
- state,
140
+ state: preparedState,
143
141
  questions,
144
142
  abortSignal,
145
143
  headers: withUserAgentSuffix(headers ?? {}, `ai/${VERSION}`),
@@ -1,12 +1,11 @@
1
1
  import type {
2
2
  Experimental_DecisionModelV4 as DecisionModelV4,
3
3
  Experimental_EvaluationModelV4 as EvaluationModelV4,
4
- Experimental_DecisionModelV4Question as DecisionModelV4Question,
5
4
  Experimental_DecisionModelV4Result as DecisionModelV4Result,
5
+ Experimental_DecisionModelV4Question as DecisionQuestion,
6
6
  } from '@ai-sdk/provider';
7
7
 
8
8
  export type DecisionModel = string | DecisionModelV4 | EvaluationModelV4;
9
- export type DecisionQuestion = DecisionModelV4Question;
10
9
 
11
10
  export type DecisionAnswer<QUESTION extends DecisionQuestion> =
12
11
  QUESTION extends { type: 'choice'; criteria: infer CRITERIA }
@@ -0,0 +1,61 @@
1
+ import type {
2
+ Experimental_DecisionModelV4State as DecisionModelV4State,
3
+ JSONObject,
4
+ JSONValue,
5
+ } from '@ai-sdk/provider';
6
+ import type { FilePart, TextPart } from '@ai-sdk/provider-utils';
7
+ import { convertToLanguageModelPrompt } from '../prompt/convert-to-language-model-prompt';
8
+
9
+ /** Shared text, JSON data, or ordered multimodal parts for a decision. */
10
+ export type DecisionState =
11
+ | string
12
+ | Readonly<JSONObject>
13
+ | readonly DecisionStatePart[];
14
+
15
+ export type DecisionStatePart =
16
+ | TextPart
17
+ | FilePart
18
+ | { type: 'json'; value: JSONValue };
19
+
20
+ function isDecisionStateParts(
21
+ state: DecisionState,
22
+ ): state is readonly DecisionStatePart[] {
23
+ return Array.isArray(state);
24
+ }
25
+
26
+ export async function prepareDecisionState(
27
+ state: DecisionState,
28
+ abortSignal?: AbortSignal,
29
+ ): Promise<DecisionModelV4State> {
30
+ if (typeof state === 'string') {
31
+ return [{ type: 'text', text: state }];
32
+ }
33
+ if (!isDecisionStateParts(state)) {
34
+ return [{ type: 'json', value: state }];
35
+ }
36
+ if (
37
+ state.every(
38
+ (part): part is Exclude<DecisionStatePart, FilePart> =>
39
+ part.type !== 'file',
40
+ )
41
+ ) {
42
+ return state;
43
+ }
44
+ const files = state.filter((part): part is FilePart => part.type === 'file');
45
+
46
+ const prompt = await convertToLanguageModelPrompt({
47
+ prompt: {
48
+ instructions: undefined,
49
+ messages: [{ role: 'user', content: files }],
50
+ },
51
+ supportedUrls: {},
52
+ download: undefined,
53
+ abortSignal,
54
+ });
55
+ const message = prompt[0];
56
+ if (message.role !== 'user') throw new Error('Expected user message.');
57
+ let fileIndex = 0;
58
+ return state.map(part =>
59
+ part.type === 'file' ? message.content[fileIndex++] : part,
60
+ );
61
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Context } from '@ai-sdk/provider-utils';
2
2
  import { decide } from './decide';
3
+ import type { Experimental_DecisionModelV4Question as DecisionQuestion } from '@ai-sdk/provider';
3
4
  import type {
4
5
  DecideStartEvent,
5
6
  DecideEndEvent,
@@ -8,7 +9,6 @@ import type {
8
9
  } from './decide-events';
9
10
  import type {
10
11
  DecisionModel,
11
- DecisionQuestion,
12
12
  DecisionAnswer,
13
13
  DecisionResult,
14
14
  } from './decision-result';
@@ -22,7 +22,6 @@ export type {
22
22
  } from './decide-events';
23
23
  export type {
24
24
  DecisionModel as Experimental_DecisionModel,
25
- DecisionQuestion as Experimental_DecisionQuestion,
26
25
  DecisionAnswer as Experimental_DecisionAnswer,
27
26
  DecisionResult as Experimental_DecisionResult,
28
27
  } from './decision-result';
@@ -62,3 +61,10 @@ export type Experimental_EvaluationAnswer<QUESTION extends DecisionQuestion> =
62
61
  export type Experimental_EvaluationResult<
63
62
  QUESTIONS extends Record<string, DecisionQuestion>,
64
63
  > = DecisionResult<QUESTIONS>;
64
+
65
+ export type {
66
+ DecisionState as Experimental_DecisionState,
67
+ DecisionStatePart as Experimental_DecisionStatePart,
68
+ } from './decision-state';
69
+
70
+ export type { Experimental_DecisionModelV4Question as Experimental_DecisionQuestion } from '@ai-sdk/provider';
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  InvalidResponseDataError,
3
- type Experimental_DecisionModelV4CallOptions as DecisionModelV4CallOptions,
4
3
  type Experimental_DecisionModelV4Result as DecisionModelV4Result,
4
+ type Experimental_DecisionModelV4Question as DecisionQuestion,
5
5
  } from '@ai-sdk/provider';
6
+ import { filePartSchema, textPartSchema } from '../prompt/content-part';
7
+ import type { DecisionState } from './decision-state';
6
8
  import { InvalidArgumentError } from '../error/invalid-argument-error';
7
9
 
8
10
  // Absolute tolerance for sums and means. Never renormalize provider output.
@@ -50,12 +52,30 @@ function invalidInput(
50
52
  export function validateDecisionInput({
51
53
  state,
52
54
  questions,
53
- }: DecisionModelV4CallOptions) {
54
- if (!isInput(state)) {
55
+ }: {
56
+ state: DecisionState;
57
+ questions: Readonly<Record<string, DecisionQuestion>>;
58
+ }) {
59
+ const validState = Array.isArray(state)
60
+ ? Array.from(state).every(part => {
61
+ if (!isRecord(part)) return false;
62
+ switch (part.type) {
63
+ case 'text':
64
+ return textPartSchema.safeParse(part).success;
65
+ case 'file':
66
+ return filePartSchema.safeParse(part).success;
67
+ case 'json':
68
+ return isJSON(part.value);
69
+ default:
70
+ return false;
71
+ }
72
+ })
73
+ : (typeof state === 'string' || isRecord(state)) && isJSON(state);
74
+ if (!validState) {
55
75
  invalidInput(
56
76
  'state',
57
77
  state,
58
- 'must be a JSON-compatible string, object, or array',
78
+ 'must be a string, JSON object, or array of text, file, or json parts',
59
79
  );
60
80
  }
61
81
  if (!isRecord(questions) || Object.keys(questions).length === 0) {
@@ -178,7 +198,7 @@ export function validateDecisionAnswers({
178
198
  answers,
179
199
  rounding,
180
200
  }: {
181
- questions: DecisionModelV4CallOptions['questions'];
201
+ questions: Readonly<Record<string, DecisionQuestion>>;
182
202
  answers: unknown;
183
203
  rounding?: DecisionModelV4Result['rounding'];
184
204
  }) {
@@ -12,6 +12,8 @@ import type { LanguageModelResponseMetadata } from '../types/language-model-resp
12
12
  import type { LanguageModelUsage } from '../types/usage';
13
13
  import type { InferUIMessageChunk } from '../ui-message-stream/ui-message-chunks';
14
14
  import type { UIMessageStreamOnEndCallback } from '../ui-message-stream/ui-message-stream-on-end-callback';
15
+ import type { UIMessageStreamOnStepEndCallback } from '../ui-message-stream/ui-message-stream-on-step-end-callback';
16
+ import type { UIMessageStreamOnStepFinishCallback } from '../ui-message-stream/ui-message-stream-on-step-finish-callback';
15
17
  import type { UIMessageStreamResponseInit } from '../ui-message-stream/ui-message-stream-response-init';
16
18
  import type { InferUIMessageMetadata, UIMessage } from '../ui/ui-messages';
17
19
  import type { AsyncIterableStream } from '../util/async-iterable-stream';
@@ -57,6 +59,15 @@ export type UIMessageStreamOptions<UI_MESSAGE extends UIMessage> = {
57
59
  */
58
60
  generateMessageId?: IdGenerator;
59
61
 
62
+ /**
63
+ * Called after each step is converted to UI message parts.
64
+ * Receives the accumulated UI message for inspection or persistence.
65
+ */
66
+ onStepEnd?: UIMessageStreamOnStepEndCallback<UI_MESSAGE>;
67
+
68
+ /** @deprecated Use `onStepEnd` instead. */
69
+ onStepFinish?: UIMessageStreamOnStepFinishCallback<UI_MESSAGE>;
70
+
60
71
  onEnd?: UIMessageStreamOnEndCallback<UI_MESSAGE>;
61
72
 
62
73
  /**
@@ -3458,6 +3458,8 @@ class DefaultStreamTextResult<
3458
3458
  toUIMessageStream<UI_MESSAGE extends UIMessage>({
3459
3459
  originalMessages,
3460
3460
  generateMessageId,
3461
+ onStepEnd,
3462
+ onStepFinish,
3461
3463
  onEnd,
3462
3464
  onFinish,
3463
3465
  messageMetadata,
@@ -3475,6 +3477,7 @@ class DefaultStreamTextResult<
3475
3477
  tools: this.tools,
3476
3478
  originalMessages,
3477
3479
  generateMessageId,
3480
+ onStepEnd: onStepEnd ?? onStepFinish,
3478
3481
  onEnd: onEnd ?? onFinish,
3479
3482
  messageMetadata,
3480
3483
  sendReasoning,
@@ -3491,6 +3494,8 @@ class DefaultStreamTextResult<
3491
3494
  {
3492
3495
  originalMessages,
3493
3496
  generateMessageId,
3497
+ onStepEnd,
3498
+ onStepFinish,
3494
3499
  onEnd,
3495
3500
  onFinish,
3496
3501
  messageMetadata,
@@ -3507,6 +3512,7 @@ class DefaultStreamTextResult<
3507
3512
  stream: this.toUIMessageStream({
3508
3513
  originalMessages,
3509
3514
  generateMessageId,
3515
+ onStepEnd: onStepEnd ?? onStepFinish,
3510
3516
  onEnd: onEnd ?? onFinish,
3511
3517
  messageMetadata,
3512
3518
  sendReasoning,
@@ -3530,6 +3536,8 @@ class DefaultStreamTextResult<
3530
3536
  toUIMessageStreamResponse<UI_MESSAGE extends UIMessage>({
3531
3537
  originalMessages,
3532
3538
  generateMessageId,
3539
+ onStepEnd,
3540
+ onStepFinish,
3533
3541
  onEnd,
3534
3542
  onFinish,
3535
3543
  messageMetadata,
@@ -3545,6 +3553,7 @@ class DefaultStreamTextResult<
3545
3553
  stream: this.toUIMessageStream({
3546
3554
  originalMessages,
3547
3555
  generateMessageId,
3556
+ onStepEnd: onStepEnd ?? onStepFinish,
3548
3557
  onEnd: onEnd ?? onFinish,
3549
3558
  messageMetadata,
3550
3559
  sendReasoning,
@@ -14,7 +14,7 @@ import { toUIMessageChunk } from './to-ui-message-chunk';
14
14
  * Converts a stream of `TextStreamPart<TOOLS>` chunks (as emitted by
15
15
  * `streamText`'s `stream`) into a stream of `UIMessageChunk`s suitable for
16
16
  * UI message streaming, including response message ID injection and
17
- * `onEnd` handling.
17
+ * step and stream end callbacks.
18
18
  */
19
19
  export function toUIMessageStream<
20
20
  TOOLS extends ToolSet = ToolSet,
@@ -30,6 +30,8 @@ export function toUIMessageStream<
30
30
  messageMetadata,
31
31
  originalMessages,
32
32
  generateMessageId,
33
+ onStepEnd,
34
+ onStepFinish,
33
35
  onEnd,
34
36
  onFinish,
35
37
  }: {
@@ -130,7 +132,7 @@ export function toUIMessageStream<
130
132
  responseMessageId,
131
133
  });
132
134
 
133
- if (uiMessageChunk != null) {
135
+ if (uiMessageChunk != null && part.type !== 'finish-step') {
134
136
  controller.enqueue(uiMessageChunk);
135
137
  }
136
138
 
@@ -147,6 +149,11 @@ export function toUIMessageStream<
147
149
  });
148
150
  }
149
151
 
152
+ // Apply step metadata before the boundary triggers a UI snapshot.
153
+ if (uiMessageChunk != null && part.type === 'finish-step') {
154
+ controller.enqueue(uiMessageChunk);
155
+ }
156
+
150
157
  if (part.type === 'finish') {
151
158
  setSourceOutcome({ status: 'completed' });
152
159
  } else if (part.type === 'abort') {
@@ -166,6 +173,7 @@ export function toUIMessageStream<
166
173
  stream: uiMessageChunkStream,
167
174
  messageId: responseMessageId ?? generateMessageId?.(),
168
175
  originalMessages,
176
+ onStepEnd: onStepEnd ?? onStepFinish,
169
177
  onEnd: onEnd ?? onFinish,
170
178
  onError,
171
179
  getOutcome: () => outcome,