From 1febce0e487abec5da64d67b0d57ebeff7f970a5 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 11:34:38 +0100 Subject: [PATCH 01/41] 1 --- .../ai/src/generate-text/generate-text.ts | 22 +++++++++---------- packages/ai/src/generate-text/prepare-step.ts | 12 +++++++--- packages/provider-utils/src/types/context.ts | 5 +++++ packages/provider-utils/src/types/index.ts | 3 ++- .../provider-utils/src/types/tool.test-d.ts | 12 +++++----- packages/provider-utils/src/types/tool.ts | 22 ++++++++++++++----- 6 files changed, 49 insertions(+), 27 deletions(-) create mode 100644 packages/provider-utils/src/types/context.ts diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 355310668877..72612e04f2cf 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -2,20 +2,20 @@ import { LanguageModelV4, LanguageModelV4Content, LanguageModelV4ToolCall, - LanguageModelV4ToolChoice, } from '@ai-sdk/provider'; import { + Context, + ContextRegistry, createIdGenerator, getErrorMessage, IdGenerator, ProviderOptions, - SystemModelMessage, ToolApprovalResponse, withUserAgentSuffix, } from '@ai-sdk/provider-utils'; import { Tracer } from '@opentelemetry/api'; import { NoOutputGeneratedError } from '../error'; -import { notify } from '../util/notify'; +import { ToolCallNotFoundForApprovalError } from '../error/tool-call-not-found-for-approval-error'; import { logWarnings } from '../logger/log-warnings'; import { resolveLanguageModel } from '../model/resolve-model'; import { ModelMessage } from '../prompt'; @@ -23,7 +23,6 @@ import { CallSettings, getStepTimeoutMs, getTotalTimeoutMs, - TimeoutConfiguration, } from '../prompt/call-settings'; import { convertToLanguageModelPrompt } from '../prompt/convert-to-language-model-prompt'; import { createToolModelOutput } from '../prompt/create-tool-model-output'; @@ -32,14 +31,13 @@ import { prepareToolsAndToolChoice } from '../prompt/prepare-tools-and-tool-choi import { Prompt } from '../prompt/prompt'; import { standardizePrompt } from '../prompt/standardize-prompt'; import { wrapGatewayError } from '../prompt/wrap-gateway-error'; -import { ToolCallNotFoundForApprovalError } from '../error/tool-call-not-found-for-approval-error'; import { assembleOperationName } from '../telemetry/assemble-operation-name'; import { getBaseTelemetryAttributes } from '../telemetry/get-base-telemetry-attributes'; +import { getGlobalTelemetryIntegration } from '../telemetry/get-global-telemetry-integration'; import { getTracer } from '../telemetry/get-tracer'; import { recordSpan } from '../telemetry/record-span'; import { selectTelemetryAttributes } from '../telemetry/select-telemetry-attributes'; import { stringifyForTelemetry } from '../telemetry/stringify-for-telemetry'; -import { getGlobalTelemetryIntegration } from '../telemetry/get-global-telemetry-integration'; import { TelemetrySettings } from '../telemetry/telemetry-settings'; import { LanguageModel, @@ -53,7 +51,9 @@ import { } from '../types/usage'; import { asArray } from '../util/as-array'; import { DownloadFunction } from '../util/download/download-function'; +import { mergeAbortSignals } from '../util/merge-abort-signals'; import { mergeObjects } from '../util/merge-objects'; +import { notify } from '../util/notify'; import { prepareRetries } from '../util/prepare-retries'; import { VERSION } from '../version'; import type { @@ -91,7 +91,6 @@ import { TypedToolError } from './tool-error'; import { ToolOutput } from './tool-output'; import { TypedToolResult } from './tool-result'; import { ToolSet } from './tool-set'; -import { mergeAbortSignals } from '../util/merge-abort-signals'; const originalGenerateId = createIdGenerator({ prefix: 'aitxt', @@ -247,6 +246,7 @@ export type GenerateTextOnFinishCallback = ( */ export async function generateText< TOOLS extends ToolSet, + CONTEXT extends Partial = ContextRegistry, OUTPUT extends Output = Output, >({ model: modelArg, @@ -270,7 +270,7 @@ export async function generateText< prepareStep = experimental_prepareStep, experimental_repairToolCall: repairToolCall, experimental_download: download, - experimental_context, + experimental_context = {} as Context, experimental_include: include, _internal: { generateId = originalGenerateId } = {}, experimental_onStart: onStart, @@ -352,12 +352,12 @@ export async function generateText< /** * @deprecated Use `prepareStep` instead. */ - experimental_prepareStep?: PrepareStepFunction>; + experimental_prepareStep?: PrepareStepFunction, CONTEXT>; /** * Optional function that you can use to provide different settings for a step. */ - prepareStep?: PrepareStepFunction>; + prepareStep?: PrepareStepFunction, CONTEXT>; /** * A function that attempts to repair a tool call that failed to parse. @@ -410,7 +410,7 @@ export async function generateText< * * @default undefined */ - experimental_context?: unknown; + experimental_context?: Context; /** * Settings for controlling what data is included in step results. diff --git a/packages/ai/src/generate-text/prepare-step.ts b/packages/ai/src/generate-text/prepare-step.ts index f3f367094aee..334cdfaf4246 100644 --- a/packages/ai/src/generate-text/prepare-step.ts +++ b/packages/ai/src/generate-text/prepare-step.ts @@ -1,4 +1,6 @@ import { + Context, + ContextRegistry, ModelMessage, ProviderOptions, SystemModelMessage, @@ -22,6 +24,7 @@ import { StepResult } from './step-result'; */ export type PrepareStepFunction< TOOLS extends Record = Record, + CONTEXT extends Partial = ContextRegistry, > = (options: { /** * The steps that have been executed so far. @@ -46,8 +49,10 @@ export type PrepareStepFunction< /** * The context passed via the experimental_context setting (experimental). */ - experimental_context: unknown; -}) => PromiseLike> | PrepareStepResult; + experimental_context: Context; +}) => + | PromiseLike> + | PrepareStepResult; /** * The result type returned by a {@link PrepareStepFunction}, @@ -55,6 +60,7 @@ export type PrepareStepFunction< */ export type PrepareStepResult< TOOLS extends Record = Record, + CONTEXT extends Partial = ContextRegistry, > = | { /** @@ -90,7 +96,7 @@ export type PrepareStepResult< * Changing the context will affect the context in this step * and all subsequent steps. */ - experimental_context?: unknown; + experimental_context?: Context; /** * Additional provider-specific options for this step. diff --git a/packages/provider-utils/src/types/context.ts b/packages/provider-utils/src/types/context.ts new file mode 100644 index 000000000000..92927870b874 --- /dev/null +++ b/packages/provider-utils/src/types/context.ts @@ -0,0 +1,5 @@ +export interface ContextRegistry {} // open via declaration merging + +export type Context = ContextRegistry> = { + [K in keyof T]: T[K]; +}; diff --git a/packages/provider-utils/src/types/index.ts b/packages/provider-utils/src/types/index.ts index 7cb7fb0e3459..eb3ead960a9d 100644 --- a/packages/provider-utils/src/types/index.ts +++ b/packages/provider-utils/src/types/index.ts @@ -11,6 +11,7 @@ export type { ToolResultOutput, ToolResultPart, } from './content-part'; +export type { Context, ContextRegistry } from './context'; export type { DataContent } from './data-content'; export { executeTool } from './execute-tool'; export type { ModelMessage } from './model-message'; @@ -22,8 +23,8 @@ export { type InferToolInput, type InferToolOutput, type Tool, - type ToolExecutionOptions, type ToolExecuteFunction, + type ToolExecutionOptions, type ToolNeedsApprovalFunction, } from './tool'; export type { ToolApprovalRequest } from './tool-approval-request'; diff --git a/packages/provider-utils/src/types/tool.test-d.ts b/packages/provider-utils/src/types/tool.test-d.ts index 5b6a6904d96f..b09178639534 100644 --- a/packages/provider-utils/src/types/tool.test-d.ts +++ b/packages/provider-utils/src/types/tool.test-d.ts @@ -1,10 +1,10 @@ -import { LanguageModelV3ToolResultPart } from '@ai-sdk/provider'; import { describe, expectTypeOf, it } from 'vitest'; import { z } from 'zod/v4'; import { FlexibleSchema } from '../schema'; +import { ToolResultOutput } from './content-part'; +import { Context, ContextRegistry } from './context'; import { ModelMessage } from './model-message'; import { Tool, tool, ToolExecuteFunction } from './tool'; -import { ToolResultOutput } from './content-part'; describe('tool type', () => { describe('input type', () => { @@ -176,7 +176,7 @@ describe('tool type', () => { expectTypeOf(options).toEqualTypeOf<{ toolCallId: string; messages: ModelMessage[]; - experimental_context?: unknown; + experimental_context?: Context | undefined; }>(); return true; }, @@ -189,7 +189,7 @@ describe('tool type', () => { options: { toolCallId: string; messages: ModelMessage[]; - experimental_context: unknown; + experimental_context?: Context | undefined; }, ) => boolean | PromiseLike) | undefined @@ -205,7 +205,7 @@ describe('tool type', () => { expectTypeOf(options).toEqualTypeOf<{ toolCallId: string; messages: ModelMessage[]; - experimental_context?: unknown; + experimental_context?: Context | undefined; }>(); return true; }, @@ -218,7 +218,7 @@ describe('tool type', () => { options: { toolCallId: string; messages: ModelMessage[]; - experimental_context: unknown; + experimental_context?: Context | undefined; }, ) => boolean | PromiseLike) | undefined diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index b07089fe24ba..68c97f2e73a0 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -3,11 +3,14 @@ import { FlexibleSchema } from '../schema'; import { ToolResultOutput } from './content-part'; import { ModelMessage } from './model-message'; import { ProviderOptions } from './provider-options'; +import { Context, ContextRegistry } from './context'; /** * Additional options that are sent into each tool call. */ -export interface ToolExecutionOptions { +export interface ToolExecutionOptions< + CONTEXT extends Partial = ContextRegistry, +> { /** * The ID of the tool call. You can use it e.g. when sending tool-call related information with stream data. */ @@ -36,13 +39,16 @@ export interface ToolExecutionOptions { * * Experimental (can break in patch releases). */ - experimental_context?: unknown; + experimental_context?: Context; } /** * Function that is called to determine if the tool needs approval before it can be executed. */ -export type ToolNeedsApprovalFunction = ( +export type ToolNeedsApprovalFunction< + INPUT, + CONTEXT extends Partial = ContextRegistry, +> = ( input: INPUT, options: { /** @@ -61,13 +67,17 @@ export type ToolNeedsApprovalFunction = ( * * Experimental (can break in patch releases). */ - experimental_context?: unknown; + experimental_context?: Context; }, ) => boolean | PromiseLike; -export type ToolExecuteFunction = ( +export type ToolExecuteFunction< + INPUT, + OUTPUT, + CONTEXT extends Partial = ContextRegistry, +> = ( input: INPUT, - options: ToolExecutionOptions, + options: ToolExecutionOptions, ) => AsyncIterable | PromiseLike | OUTPUT; // 0 extends 1 & N checks for any From 672f5edf4837027b2579ee86782898d8eba8c268 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 12:08:21 +0100 Subject: [PATCH 02/41] 2 --- .../openai/tool-call-with-context.ts | 29 ++++++- .../ai/src/generate-text/callback-events.ts | 63 +++++++++------- packages/ai/src/generate-text/content-part.ts | 14 ++-- .../ai/src/generate-text/generate-text.ts | 27 ++++--- packages/ai/src/generate-text/prepare-step.ts | 10 +-- packages/ai/src/generate-text/step-result.ts | 13 ++-- .../ai/src/generate-text/stop-condition.ts | 8 +- .../tool-approval-request-output.ts | 8 +- packages/ai/src/generate-text/tool-call.ts | 18 +++-- packages/ai/src/generate-text/tool-error.ts | 16 ++-- packages/ai/src/generate-text/tool-result.ts | 22 ++++-- packages/ai/src/generate-text/tool-set.ts | 15 +++- .../src/provider-tool-factory.ts | 75 +++++++++++-------- .../provider-utils/src/types/execute-tool.ts | 11 ++- .../provider-utils/src/types/tool.test-d.ts | 20 +++-- packages/provider-utils/src/types/tool.ts | 51 ++++++++----- 16 files changed, 257 insertions(+), 143 deletions(-) diff --git a/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts b/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts index 743ca0c8b363..cae53ba9f0e1 100644 --- a/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts +++ b/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts @@ -12,10 +12,13 @@ run(async () => { inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), + contextSchema: z.object({ + weatherApiKey: z.string().describe('The API key for the weather API'), + }), execute: async ({ location }, { experimental_context: context }) => { - const typedContext = context as { weatherApiKey: string }; // or use type validation library + console.log(context); - console.log(typedContext); + context satisfies { weatherApiKey: string }; return { location, @@ -23,8 +26,28 @@ run(async () => { }; }, }), + calculator: tool({ + description: 'Calculate mathematical expressions', + inputSchema: z.object({ + expression: z + .string() + .describe('The mathematical expression to calculate'), + }), + contextSchema: z.object({ + calculatorApiKey: z + .string() + .describe('The API key for the calculator API'), + }), + execute: async ({ expression }, { experimental_context: context }) => { + console.log(context); + return { + expression, + result: eval(expression), + }; + }, + }), }, - experimental_context: { weatherApiKey: '123' }, + experimental_context: { weatherApiKey: '123', calculatorApiKey: '456' }, prompt: 'What is the weather in San Francisco?', }); diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index 454be6f96a9a..afb35b9282fb 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -1,5 +1,6 @@ import type { LanguageModelV4ToolChoice } from '@ai-sdk/provider'; import type { + ContextRegistry, ModelMessage, ProviderOptions, SystemModelMessage, @@ -129,7 +130,8 @@ export interface OnStartEvent< * Each step represents a single LLM invocation. */ export interface OnStepStartEvent< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -184,8 +186,8 @@ export interface OnStepStartEvent< * When the condition is an array, any of the conditions can be met to stop. */ readonly stopWhen: - | StopCondition - | Array> + | StopCondition + | Array> | undefined; /** The output specification for structured outputs, if configured. */ @@ -216,7 +218,10 @@ export interface OnStepStartEvent< * * Called when a tool execution begins, before the tool's `execute` function is invoked. */ -export interface OnToolCallStartEvent { +export interface OnToolCallStartEvent< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> { /** Zero-based index of the current step where this tool call occurs. */ readonly stepNumber: number | undefined; @@ -224,7 +229,7 @@ export interface OnToolCallStartEvent { readonly model: CallbackModelInfo | undefined; /** The full tool call object. */ - readonly toolCall: TypedToolCall; + readonly toolCall: TypedToolCall; /** The conversation messages available at tool execution time. */ readonly messages: Array; @@ -307,26 +312,28 @@ export type OnStepFinishEvent = * Called when the entire generation completes (all steps finished). * Includes the final step's result along with aggregated data from all steps. */ -export type OnFinishEvent = - StepResult & { - /** Array containing results from all steps in the generation. */ - readonly steps: StepResult[]; - - /** Aggregated token usage across all steps. */ - readonly totalUsage: LanguageModelUsage; - - /** - * The final state of the user-defined context object. - * - * Experimental (can break in patch releases). - * - * @default undefined - */ - experimental_context: unknown; - - /** Identifier from telemetry settings for grouping related operations. */ - readonly functionId: string | undefined; - - /** Additional metadata from telemetry settings. */ - readonly metadata: Record | undefined; - }; +export type OnFinishEvent< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = StepResult & { + /** Array containing results from all steps in the generation. */ + readonly steps: StepResult[]; + + /** Aggregated token usage across all steps. */ + readonly totalUsage: LanguageModelUsage; + + /** + * The final state of the user-defined context object. + * + * Experimental (can break in patch releases). + * + * @default undefined + */ + experimental_context: unknown; + + /** Identifier from telemetry settings for grouping related operations. */ + readonly functionId: string | undefined; + + /** Additional metadata from telemetry settings. */ + readonly metadata: Record | undefined; +}; diff --git a/packages/ai/src/generate-text/content-part.ts b/packages/ai/src/generate-text/content-part.ts index 925d1a3663e8..df1e9b1eb90a 100644 --- a/packages/ai/src/generate-text/content-part.ts +++ b/packages/ai/src/generate-text/content-part.ts @@ -1,3 +1,4 @@ +import { ContextRegistry } from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { Source } from '../types/language-model'; import { GeneratedFile } from './generated-file'; @@ -8,18 +9,21 @@ import { TypedToolError } from './tool-error'; import { TypedToolResult } from './tool-result'; import { ToolSet } from './tool-set'; -export type ContentPart = +export type ContentPart< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = | { type: 'text'; text: string; providerMetadata?: ProviderMetadata } | ReasoningOutput | ({ type: 'source' } & Source) | { type: 'file'; file: GeneratedFile; providerMetadata?: ProviderMetadata } // different because of GeneratedFile object - | ({ type: 'tool-call' } & TypedToolCall & { + | ({ type: 'tool-call' } & TypedToolCall & { providerMetadata?: ProviderMetadata; }) - | ({ type: 'tool-result' } & TypedToolResult & { + | ({ type: 'tool-result' } & TypedToolResult & { providerMetadata?: ProviderMetadata; }) - | ({ type: 'tool-error' } & TypedToolError & { + | ({ type: 'tool-error' } & TypedToolError & { providerMetadata?: ProviderMetadata; }) - | ToolApprovalRequestOutput; + | ToolApprovalRequestOutput; diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 72612e04f2cf..e15f8e2113c3 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -131,10 +131,11 @@ export type GenerateTextOnStartCallback< * @param event - The event object containing step configuration. */ export type GenerateTextOnStepStartCallback< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, > = ( - event: OnStepStartEvent, + event: OnStepStartEvent, ) => PromiseLike | void; /** @@ -146,8 +147,9 @@ export type GenerateTextOnStepStartCallback< * @param event - The event object containing tool call information. */ export type GenerateTextOnToolCallStartCallback< - TOOLS extends ToolSet = ToolSet, -> = (event: OnToolCallStartEvent) => PromiseLike | void; + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = (event: OnToolCallStartEvent) => PromiseLike | void; /** * Callback that is set using the `experimental_onToolCallFinish` option. @@ -186,9 +188,10 @@ export type GenerateTextOnStepFinishCallback = ( * * @param event - The final result along with aggregated step data. */ -export type GenerateTextOnFinishCallback = ( - event: OnFinishEvent, -) => PromiseLike | void; +export type GenerateTextOnFinishCallback< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = (event: OnFinishEvent) => PromiseLike | void; /** * Generate a text and call tools for a given prompt using a language model. @@ -245,8 +248,8 @@ export type GenerateTextOnFinishCallback = ( * A result object that contains the generated text, the results of the tool calls, and additional information. */ export async function generateText< - TOOLS extends ToolSet, - CONTEXT extends Partial = ContextRegistry, + CONTEXT extends Partial, + TOOLS extends ToolSet, OUTPUT extends Output = Output, >({ model: modelArg, @@ -352,12 +355,12 @@ export async function generateText< /** * @deprecated Use `prepareStep` instead. */ - experimental_prepareStep?: PrepareStepFunction, CONTEXT>; + experimental_prepareStep?: PrepareStepFunction>; /** * Optional function that you can use to provide different settings for a step. */ - prepareStep?: PrepareStepFunction, CONTEXT>; + prepareStep?: PrepareStepFunction>; /** * A function that attempts to repair a tool call that failed to parse. @@ -1191,7 +1194,7 @@ export async function generateText< onFinish, globalTelemetry.onFinish as | undefined - | GenerateTextOnFinishCallback, + | GenerateTextOnFinishCallback, ], }); diff --git a/packages/ai/src/generate-text/prepare-step.ts b/packages/ai/src/generate-text/prepare-step.ts index 334cdfaf4246..8ba9901b631d 100644 --- a/packages/ai/src/generate-text/prepare-step.ts +++ b/packages/ai/src/generate-text/prepare-step.ts @@ -4,10 +4,10 @@ import { ModelMessage, ProviderOptions, SystemModelMessage, - Tool, } from '@ai-sdk/provider-utils'; import { LanguageModel, ToolChoice } from '../types/language-model'; import { StepResult } from './step-result'; +import { ToolSet } from './tool-set'; /** * Function that you can use to provide different settings for a step. @@ -23,8 +23,8 @@ import { StepResult } from './step-result'; * If you return undefined (or for undefined settings), the settings from the outer level will be used. */ export type PrepareStepFunction< - TOOLS extends Record = Record, CONTEXT extends Partial = ContextRegistry, + TOOLS extends ToolSet = ToolSet, > = (options: { /** * The steps that have been executed so far. @@ -51,16 +51,16 @@ export type PrepareStepFunction< */ experimental_context: Context; }) => - | PromiseLike> - | PrepareStepResult; + | PromiseLike> + | PrepareStepResult; /** * The result type returned by a {@link PrepareStepFunction}, * allowing per-step overrides of model, tools, or messages. */ export type PrepareStepResult< - TOOLS extends Record = Record, CONTEXT extends Partial = ContextRegistry, + TOOLS extends ToolSet = ToolSet, > = | { /** diff --git a/packages/ai/src/generate-text/step-result.ts b/packages/ai/src/generate-text/step-result.ts index 73b53f084c67..94d7a9e92fd2 100644 --- a/packages/ai/src/generate-text/step-result.ts +++ b/packages/ai/src/generate-text/step-result.ts @@ -1,4 +1,4 @@ -import { ReasoningPart } from '@ai-sdk/provider-utils'; +import { ContextRegistry, ReasoningPart } from '@ai-sdk/provider-utils'; import { CallWarning, FinishReason, @@ -22,7 +22,10 @@ import { ToolSet } from './tool-set'; /** * The result of a single step in the generation process. */ -export type StepResult = { +export type StepResult< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = { /** * Zero-based index of this step. */ @@ -58,7 +61,7 @@ export type StepResult = { /** * The content that was generated in the last step. */ - readonly content: Array>; + readonly content: Array>; /** * The generated text. @@ -88,7 +91,7 @@ export type StepResult = { /** * The tool calls that were made during the generation. */ - readonly toolCalls: Array>; + readonly toolCalls: Array>; /** * The static tool calls that were made in the last step. @@ -103,7 +106,7 @@ export type StepResult = { /** * The results of the tool calls. */ - readonly toolResults: Array>; + readonly toolResults: Array>; /** * The static tool results that were made in the last step. diff --git a/packages/ai/src/generate-text/stop-condition.ts b/packages/ai/src/generate-text/stop-condition.ts index 4a2bd316ee49..37ce347927f1 100644 --- a/packages/ai/src/generate-text/stop-condition.ts +++ b/packages/ai/src/generate-text/stop-condition.ts @@ -1,8 +1,12 @@ +import { ContextRegistry } from '@ai-sdk/provider-utils'; import { StepResult } from './step-result'; import { ToolSet } from './tool-set'; -export type StopCondition = (options: { - steps: Array>; +export type StopCondition< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = (options: { + steps: Array>; }) => PromiseLike | boolean; export function stepCountIs(stepCount: number): StopCondition { diff --git a/packages/ai/src/generate-text/tool-approval-request-output.ts b/packages/ai/src/generate-text/tool-approval-request-output.ts index 2880b67e0a78..b43ac9f11bc2 100644 --- a/packages/ai/src/generate-text/tool-approval-request-output.ts +++ b/packages/ai/src/generate-text/tool-approval-request-output.ts @@ -1,3 +1,4 @@ +import { ContextRegistry } from '@ai-sdk/provider-utils'; import { TypedToolCall } from './tool-call'; import { ToolSet } from './tool-set'; @@ -6,7 +7,10 @@ import { ToolSet } from './tool-set'; * * The tool approval request can be approved or denied in the next tool message. */ -export type ToolApprovalRequestOutput = { +export type ToolApprovalRequestOutput< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = { type: 'tool-approval-request'; /** @@ -17,5 +21,5 @@ export type ToolApprovalRequestOutput = { /** * Tool call that the approval request is for. */ - toolCall: TypedToolCall; + toolCall: TypedToolCall; }; diff --git a/packages/ai/src/generate-text/tool-call.ts b/packages/ai/src/generate-text/tool-call.ts index b6f087a675f9..b214f62b0405 100644 --- a/packages/ai/src/generate-text/tool-call.ts +++ b/packages/ai/src/generate-text/tool-call.ts @@ -1,4 +1,4 @@ -import { Tool } from '@ai-sdk/provider-utils'; +import { ContextRegistry, Tool } from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { ValueOf } from '../util/value-of'; import { ToolSet } from './tool-set'; @@ -10,10 +10,15 @@ type BaseToolCall = { providerMetadata?: ProviderMetadata; }; -export type StaticToolCall = ValueOf<{ +export type StaticToolCall< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = ValueOf<{ [NAME in keyof TOOLS]: BaseToolCall & { toolName: NAME & string; - input: TOOLS[NAME] extends Tool ? PARAMETERS : never; + input: TOOLS[NAME] extends Tool + ? PARAMETERS + : never; dynamic?: false | undefined; invalid?: false | undefined; error?: never; @@ -42,6 +47,7 @@ export type DynamicToolCall = BaseToolCall & { error?: unknown; }; -export type TypedToolCall = - | StaticToolCall - | DynamicToolCall; +export type TypedToolCall< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = StaticToolCall | DynamicToolCall; diff --git a/packages/ai/src/generate-text/tool-error.ts b/packages/ai/src/generate-text/tool-error.ts index 12d0540ef8f7..da2bca8d2d00 100644 --- a/packages/ai/src/generate-text/tool-error.ts +++ b/packages/ai/src/generate-text/tool-error.ts @@ -1,14 +1,17 @@ -import { InferToolInput } from '@ai-sdk/provider-utils'; +import { ContextRegistry, InferToolInput } from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { ValueOf } from '../util/value-of'; import { ToolSet } from './tool-set'; -export type StaticToolError = ValueOf<{ +export type StaticToolError< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = ValueOf<{ [NAME in keyof TOOLS]: { type: 'tool-error'; toolCallId: string; toolName: NAME & string; - input: InferToolInput; + input: InferToolInput; error: unknown; providerExecuted?: boolean; providerMetadata?: ProviderMetadata; @@ -29,6 +32,7 @@ export type DynamicToolError = { title?: string; }; -export type TypedToolError = - | StaticToolError - | DynamicToolError; +export type TypedToolError< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = StaticToolError | DynamicToolError; diff --git a/packages/ai/src/generate-text/tool-result.ts b/packages/ai/src/generate-text/tool-result.ts index 406deef0231e..1f2943f644d0 100644 --- a/packages/ai/src/generate-text/tool-result.ts +++ b/packages/ai/src/generate-text/tool-result.ts @@ -1,15 +1,22 @@ -import { InferToolInput, InferToolOutput } from '@ai-sdk/provider-utils'; +import { + ContextRegistry, + InferToolInput, + InferToolOutput, +} from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { ValueOf } from '../../src/util/value-of'; import { ToolSet } from './tool-set'; -export type StaticToolResult = ValueOf<{ +export type StaticToolResult< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = ValueOf<{ [NAME in keyof TOOLS]: { type: 'tool-result'; toolCallId: string; toolName: NAME & string; - input: InferToolInput; - output: InferToolOutput; + input: InferToolInput; + output: InferToolOutput; providerExecuted?: boolean; providerMetadata?: ProviderMetadata; dynamic?: false | undefined; @@ -31,6 +38,7 @@ export type DynamicToolResult = { title?: string; }; -export type TypedToolResult = - | StaticToolResult - | DynamicToolResult; +export type TypedToolResult< + CONTEXT extends Partial, + TOOLS extends ToolSet = ToolSet, +> = StaticToolResult | DynamicToolResult; diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index 09e64774d48e..68d308fb5a25 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -1,10 +1,17 @@ -import { Tool } from '@ai-sdk/provider-utils'; +import { ContextRegistry, Tool } from '@ai-sdk/provider-utils'; -export type ToolSet = Record< +export type ToolSet< + CONTEXT extends Partial = ContextRegistry, +> = Record< string, - (Tool | Tool | Tool | Tool) & + ( + | Tool + | Tool + | Tool + | Tool + ) & Pick< - Tool, + Tool, | 'execute' | 'onInputAvailable' | 'onInputStart' diff --git a/packages/provider-utils/src/provider-tool-factory.ts b/packages/provider-utils/src/provider-tool-factory.ts index 2c29130e0a51..7595d518c8d9 100644 --- a/packages/provider-utils/src/provider-tool-factory.ts +++ b/packages/provider-utils/src/provider-tool-factory.ts @@ -1,24 +1,33 @@ import { tool, Tool, ToolExecuteFunction } from './types/tool'; import { FlexibleSchema } from './schema'; +import { ContextRegistry } from './types/context'; -export type ProviderToolFactory = ( +export type ProviderToolFactory< + CONTEXT extends Partial, + INPUT, + ARGS extends object, +> = ( options: ARGS & { - execute?: ToolExecuteFunction; - needsApproval?: Tool['needsApproval']; - toModelOutput?: Tool['toModelOutput']; - onInputStart?: Tool['onInputStart']; - onInputDelta?: Tool['onInputDelta']; - onInputAvailable?: Tool['onInputAvailable']; + execute?: ToolExecuteFunction; + needsApproval?: Tool['needsApproval']; + toModelOutput?: Tool['toModelOutput']; + onInputStart?: Tool['onInputStart']; + onInputDelta?: Tool['onInputDelta']; + onInputAvailable?: Tool['onInputAvailable']; }, -) => Tool; +) => Tool; -export function createProviderToolFactory({ +export function createProviderToolFactory< + CONTEXT extends Partial, + INPUT, + ARGS extends object, +>({ id, inputSchema, }: { id: `${string}.${string}`; inputSchema: FlexibleSchema; -}): ProviderToolFactory { +}): ProviderToolFactory { return ({ execute, outputSchema, @@ -29,14 +38,14 @@ export function createProviderToolFactory({ onInputAvailable, ...args }: ARGS & { - execute?: ToolExecuteFunction; + execute?: ToolExecuteFunction; outputSchema?: FlexibleSchema; - needsApproval?: Tool['needsApproval']; - toModelOutput?: Tool['toModelOutput']; - onInputStart?: Tool['onInputStart']; - onInputDelta?: Tool['onInputDelta']; - onInputAvailable?: Tool['onInputAvailable']; - }): Tool => + needsApproval?: Tool['needsApproval']; + toModelOutput?: Tool['toModelOutput']; + onInputStart?: Tool['onInputStart']; + onInputDelta?: Tool['onInputDelta']; + onInputAvailable?: Tool['onInputAvailable']; + }): Tool => tool({ type: 'provider', id, @@ -53,21 +62,23 @@ export function createProviderToolFactory({ } export type ProviderToolFactoryWithOutputSchema< + CONTEXT extends Partial, INPUT, OUTPUT, ARGS extends object, > = ( options: ARGS & { - execute?: ToolExecuteFunction; - needsApproval?: Tool['needsApproval']; - toModelOutput?: Tool['toModelOutput']; - onInputStart?: Tool['onInputStart']; - onInputDelta?: Tool['onInputDelta']; - onInputAvailable?: Tool['onInputAvailable']; + execute?: ToolExecuteFunction; + needsApproval?: Tool['needsApproval']; + toModelOutput?: Tool['toModelOutput']; + onInputStart?: Tool['onInputStart']; + onInputDelta?: Tool['onInputDelta']; + onInputAvailable?: Tool['onInputAvailable']; }, -) => Tool; +) => Tool; export function createProviderToolFactoryWithOutputSchema< + CONTEXT extends Partial, INPUT, OUTPUT, ARGS extends object, @@ -91,7 +102,7 @@ export function createProviderToolFactoryWithOutputSchema< * @default false */ supportsDeferredResults?: boolean; -}): ProviderToolFactoryWithOutputSchema { +}): ProviderToolFactoryWithOutputSchema { return ({ execute, needsApproval, @@ -101,13 +112,13 @@ export function createProviderToolFactoryWithOutputSchema< onInputAvailable, ...args }: ARGS & { - execute?: ToolExecuteFunction; - needsApproval?: Tool['needsApproval']; - toModelOutput?: Tool['toModelOutput']; - onInputStart?: Tool['onInputStart']; - onInputDelta?: Tool['onInputDelta']; - onInputAvailable?: Tool['onInputAvailable']; - }): Tool => + execute?: ToolExecuteFunction; + needsApproval?: Tool['needsApproval']; + toModelOutput?: Tool['toModelOutput']; + onInputStart?: Tool['onInputStart']; + onInputDelta?: Tool['onInputDelta']; + onInputAvailable?: Tool['onInputAvailable']; + }): Tool => tool({ type: 'provider', id, diff --git a/packages/provider-utils/src/types/execute-tool.ts b/packages/provider-utils/src/types/execute-tool.ts index 7780d582f4ce..4220dbd43744 100644 --- a/packages/provider-utils/src/types/execute-tool.ts +++ b/packages/provider-utils/src/types/execute-tool.ts @@ -1,14 +1,19 @@ import { isAsyncIterable } from '../is-async-iterable'; +import { ContextRegistry } from './context'; import { ToolExecutionOptions, ToolExecuteFunction } from './tool'; -export async function* executeTool({ +export async function* executeTool< + CONTEXT extends Partial, + INPUT, + OUTPUT, +>({ execute, input, options, }: { - execute: ToolExecuteFunction; + execute: ToolExecuteFunction; input: INPUT; - options: ToolExecutionOptions; + options: ToolExecutionOptions; }): AsyncGenerator< { type: 'preliminary'; output: OUTPUT } | { type: 'final'; output: OUTPUT } > { diff --git a/packages/provider-utils/src/types/tool.test-d.ts b/packages/provider-utils/src/types/tool.test-d.ts index b09178639534..76318d7d804d 100644 --- a/packages/provider-utils/src/types/tool.test-d.ts +++ b/packages/provider-utils/src/types/tool.test-d.ts @@ -13,7 +13,9 @@ describe('tool type', () => { inputSchema: z.object({ number: z.number() }), }); - expectTypeOf(aTool).toEqualTypeOf>(); + expectTypeOf(aTool).toEqualTypeOf< + Tool + >(); expectTypeOf(aTool.execute).toEqualTypeOf(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); expectTypeOf(aTool.inputSchema).toEqualTypeOf< @@ -26,7 +28,7 @@ describe('tool type', () => { inputSchema: null as unknown as FlexibleSchema, }); - expectTypeOf(aTool).toEqualTypeOf>(); + expectTypeOf(aTool).toEqualTypeOf>(); expectTypeOf(aTool.execute).toEqualTypeOf(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); expectTypeOf(aTool.inputSchema).toEqualTypeOf>(); @@ -78,9 +80,12 @@ describe('tool type', () => { }, }); - expectTypeOf(aTool).toEqualTypeOf>(); + expectTypeOf(aTool).toEqualTypeOf< + Tool + >(); expectTypeOf(aTool.execute).toMatchTypeOf< - ToolExecuteFunction<{ number: number }, 'test'> | undefined + | ToolExecuteFunction + | undefined >(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); expectTypeOf(aTool.inputSchema).toEqualTypeOf< @@ -96,9 +101,12 @@ describe('tool type', () => { }, }); - expectTypeOf(aTool).toEqualTypeOf>(); + expectTypeOf(aTool).toEqualTypeOf< + Tool + >(); expectTypeOf(aTool.execute).toEqualTypeOf< - ToolExecuteFunction<{ number: number }, 'test'> | undefined + | ToolExecuteFunction + | undefined >(); expectTypeOf(aTool.inputSchema).toEqualTypeOf< FlexibleSchema<{ number: number }> diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index 68c97f2e73a0..625b0df8af9f 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -39,7 +39,7 @@ export interface ToolExecutionOptions< * * Experimental (can break in patch releases). */ - experimental_context?: Context; + experimental_context: Context; } /** @@ -72,9 +72,9 @@ export type ToolNeedsApprovalFunction< ) => boolean | PromiseLike; export type ToolExecuteFunction< + CONTEXT extends Partial, INPUT, OUTPUT, - CONTEXT extends Partial = ContextRegistry, > = ( input: INPUT, options: ToolExecutionOptions, @@ -88,7 +88,11 @@ type NeverOptional = 0 extends 1 & N ? Partial> : T; -type ToolOutputProperties = NeverOptional< +type ToolOutputProperties< + CONTEXT extends Partial, + INPUT, + OUTPUT, +> = NeverOptional< OUTPUT, | { /** @@ -98,7 +102,7 @@ type ToolOutputProperties = NeverOptional< * @args is the input of the tool call. * @options.abortSignal is a signal that can be used to abort the tool call. */ - execute: ToolExecuteFunction; + execute: ToolExecuteFunction; outputSchema?: FlexibleSchema; } @@ -116,6 +120,7 @@ type ToolOutputProperties = NeverOptional< * The tool can also contain an optional execute function for the actual execution function of the tool. */ export type Tool< + CONTEXT extends Partial = ContextRegistry, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any, > = { @@ -153,6 +158,8 @@ export type Tool< */ inputExamples?: Array<{ input: NoInfer }>; + contextSchema?: FlexibleSchema; + /** * Whether the tool needs approval before it can be executed. */ @@ -192,7 +199,7 @@ export type Tool< input: [INPUT] extends [never] ? unknown : INPUT; } & ToolExecutionOptions, ) => void | PromiseLike; -} & ToolOutputProperties & { +} & ToolOutputProperties & { /** * Optional conversion function that maps the tool result to an output that can be used by the language model. * @@ -268,25 +275,35 @@ export type Tool< /** * Infer the input type of a tool. */ -export type InferToolInput = - TOOL extends Tool ? INPUT : never; +export type InferToolInput< + CONTEXT extends Partial, + TOOL extends Tool, +> = TOOL extends Tool ? INPUT : never; /** * Infer the output type of a tool. */ -export type InferToolOutput = - TOOL extends Tool ? OUTPUT : never; +export type InferToolOutput< + CONTEXT extends Partial, + TOOL extends Tool, +> = TOOL extends Tool ? OUTPUT : never; /** * Helper function for inferring the execute args of a tool. */ // Note: overload order is important for auto-completion -export function tool( - tool: Tool, -): Tool; -export function tool(tool: Tool): Tool; -export function tool(tool: Tool): Tool; -export function tool(tool: Tool): Tool; +export function tool, INPUT, OUTPUT>( + tool: Tool, +): Tool; +export function tool, INPUT>( + tool: Tool, +): Tool; +export function tool, OUTPUT>( + tool: Tool, +): Tool; +export function tool>( + tool: Tool, +): Tool; export function tool(tool: any): any { return tool; } @@ -299,7 +316,7 @@ export function dynamicTool(tool: { title?: string; providerOptions?: ProviderOptions; inputSchema: FlexibleSchema; - execute: ToolExecuteFunction; + execute: ToolExecuteFunction; /** * Optional conversion function that maps the tool result to an output that can be used by the language model. @@ -327,7 +344,7 @@ export function dynamicTool(tool: { * Whether the tool needs approval before it can be executed. */ needsApproval?: boolean | ToolNeedsApprovalFunction; -}): Tool & { +}): Tool & { type: 'dynamic'; } { return { ...tool, type: 'dynamic' }; From eaf33e252b2c143592e2dc1f2204dec13052213f Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 15:40:10 +0100 Subject: [PATCH 03/41] proof of concept --- .../ai-functions/src/test/typed-context.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/ai-functions/src/test/typed-context.ts diff --git a/examples/ai-functions/src/test/typed-context.ts b/examples/ai-functions/src/test/typed-context.ts new file mode 100644 index 000000000000..dd53f7cd0bf2 --- /dev/null +++ b/examples/ai-functions/src/test/typed-context.ts @@ -0,0 +1,42 @@ +import { run } from '../lib/run'; + +interface Tool { + execute: (context: CONTEXT) => Promise; +} + +export type ToolSet = Record>; + +function executeTool({ + tools, + toolName, + context, +}: { + tools: ToolSet; + toolName: keyof ToolSet; + context: CONTEXT; +}) { + return tools[toolName].execute(context); +} + +run(async () => { + const tool1: Tool<{ name: string }> = { + execute: async context => { + console.log(context); + }, + }; + + const tool2: Tool<{ age: number }> = { + execute: async context => { + console.log(context); + }, + }; + + executeTool({ + tools: { + tool1, + tool2, + }, + toolName: 'tool1', + context: { name: 'John', age: 30 }, + }); +}); From d67d969125424768ba882ec7dc164753486bf1de Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 15:45:36 +0100 Subject: [PATCH 04/41] s --- .../src/provider-tool-factory.ts | 10 ++--- packages/provider-utils/src/types/context.ts | 6 +-- .../provider-utils/src/types/execute-tool.ts | 10 ++--- packages/provider-utils/src/types/index.ts | 2 +- .../provider-utils/src/types/tool.test-d.ts | 24 ++++++------ packages/provider-utils/src/types/tool.ts | 38 ++++++++----------- 6 files changed, 37 insertions(+), 53 deletions(-) diff --git a/packages/provider-utils/src/provider-tool-factory.ts b/packages/provider-utils/src/provider-tool-factory.ts index 7595d518c8d9..73590059e9e9 100644 --- a/packages/provider-utils/src/provider-tool-factory.ts +++ b/packages/provider-utils/src/provider-tool-factory.ts @@ -1,9 +1,9 @@ import { tool, Tool, ToolExecuteFunction } from './types/tool'; import { FlexibleSchema } from './schema'; -import { ContextRegistry } from './types/context'; +import { Context } from './types/context'; export type ProviderToolFactory< - CONTEXT extends Partial, + CONTEXT extends Context, INPUT, ARGS extends object, > = ( @@ -18,7 +18,7 @@ export type ProviderToolFactory< ) => Tool; export function createProviderToolFactory< - CONTEXT extends Partial, + CONTEXT extends Context, INPUT, ARGS extends object, >({ @@ -62,7 +62,7 @@ export function createProviderToolFactory< } export type ProviderToolFactoryWithOutputSchema< - CONTEXT extends Partial, + CONTEXT extends Context, INPUT, OUTPUT, ARGS extends object, @@ -78,7 +78,7 @@ export type ProviderToolFactoryWithOutputSchema< ) => Tool; export function createProviderToolFactoryWithOutputSchema< - CONTEXT extends Partial, + CONTEXT extends Context, INPUT, OUTPUT, ARGS extends object, diff --git a/packages/provider-utils/src/types/context.ts b/packages/provider-utils/src/types/context.ts index 92927870b874..9a1d29a311fd 100644 --- a/packages/provider-utils/src/types/context.ts +++ b/packages/provider-utils/src/types/context.ts @@ -1,5 +1 @@ -export interface ContextRegistry {} // open via declaration merging - -export type Context = ContextRegistry> = { - [K in keyof T]: T[K]; -}; +export type Context = Record; diff --git a/packages/provider-utils/src/types/execute-tool.ts b/packages/provider-utils/src/types/execute-tool.ts index 4220dbd43744..68961ccb9c34 100644 --- a/packages/provider-utils/src/types/execute-tool.ts +++ b/packages/provider-utils/src/types/execute-tool.ts @@ -1,12 +1,8 @@ import { isAsyncIterable } from '../is-async-iterable'; -import { ContextRegistry } from './context'; -import { ToolExecutionOptions, ToolExecuteFunction } from './tool'; +import { Context } from './context'; +import { ToolExecuteFunction, ToolExecutionOptions } from './tool'; -export async function* executeTool< - CONTEXT extends Partial, - INPUT, - OUTPUT, ->({ +export async function* executeTool({ execute, input, options, diff --git a/packages/provider-utils/src/types/index.ts b/packages/provider-utils/src/types/index.ts index eb3ead960a9d..b1fca4b85201 100644 --- a/packages/provider-utils/src/types/index.ts +++ b/packages/provider-utils/src/types/index.ts @@ -11,7 +11,7 @@ export type { ToolResultOutput, ToolResultPart, } from './content-part'; -export type { Context, ContextRegistry } from './context'; +export type { Context } from './context'; export type { DataContent } from './data-content'; export { executeTool } from './execute-tool'; export type { ModelMessage } from './model-message'; diff --git a/packages/provider-utils/src/types/tool.test-d.ts b/packages/provider-utils/src/types/tool.test-d.ts index 76318d7d804d..8a3e9d1fba96 100644 --- a/packages/provider-utils/src/types/tool.test-d.ts +++ b/packages/provider-utils/src/types/tool.test-d.ts @@ -2,7 +2,7 @@ import { describe, expectTypeOf, it } from 'vitest'; import { z } from 'zod/v4'; import { FlexibleSchema } from '../schema'; import { ToolResultOutput } from './content-part'; -import { Context, ContextRegistry } from './context'; +import { Context } from './context'; import { ModelMessage } from './model-message'; import { Tool, tool, ToolExecuteFunction } from './tool'; @@ -14,7 +14,7 @@ describe('tool type', () => { }); expectTypeOf(aTool).toEqualTypeOf< - Tool + Tool >(); expectTypeOf(aTool.execute).toEqualTypeOf(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); @@ -28,7 +28,7 @@ describe('tool type', () => { inputSchema: null as unknown as FlexibleSchema, }); - expectTypeOf(aTool).toEqualTypeOf>(); + expectTypeOf(aTool).toEqualTypeOf>(); expectTypeOf(aTool.execute).toEqualTypeOf(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); expectTypeOf(aTool.inputSchema).toEqualTypeOf>(); @@ -81,11 +81,10 @@ describe('tool type', () => { }); expectTypeOf(aTool).toEqualTypeOf< - Tool + Tool >(); expectTypeOf(aTool.execute).toMatchTypeOf< - | ToolExecuteFunction - | undefined + ToolExecuteFunction | undefined >(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); expectTypeOf(aTool.inputSchema).toEqualTypeOf< @@ -102,11 +101,10 @@ describe('tool type', () => { }); expectTypeOf(aTool).toEqualTypeOf< - Tool + Tool >(); expectTypeOf(aTool.execute).toEqualTypeOf< - | ToolExecuteFunction - | undefined + ToolExecuteFunction | undefined >(); expectTypeOf(aTool.inputSchema).toEqualTypeOf< FlexibleSchema<{ number: number }> @@ -184,7 +182,7 @@ describe('tool type', () => { expectTypeOf(options).toEqualTypeOf<{ toolCallId: string; messages: ModelMessage[]; - experimental_context?: Context | undefined; + experimental_context?: Context | undefined; }>(); return true; }, @@ -197,7 +195,7 @@ describe('tool type', () => { options: { toolCallId: string; messages: ModelMessage[]; - experimental_context?: Context | undefined; + experimental_context?: Context | undefined; }, ) => boolean | PromiseLike) | undefined @@ -213,7 +211,7 @@ describe('tool type', () => { expectTypeOf(options).toEqualTypeOf<{ toolCallId: string; messages: ModelMessage[]; - experimental_context?: Context | undefined; + experimental_context?: Context | undefined; }>(); return true; }, @@ -226,7 +224,7 @@ describe('tool type', () => { options: { toolCallId: string; messages: ModelMessage[]; - experimental_context?: Context | undefined; + experimental_context?: Context | undefined; }, ) => boolean | PromiseLike) | undefined diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index 625b0df8af9f..5785670b5332 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -3,14 +3,12 @@ import { FlexibleSchema } from '../schema'; import { ToolResultOutput } from './content-part'; import { ModelMessage } from './model-message'; import { ProviderOptions } from './provider-options'; -import { Context, ContextRegistry } from './context'; +import { Context } from './context'; /** * Additional options that are sent into each tool call. */ -export interface ToolExecutionOptions< - CONTEXT extends Partial = ContextRegistry, -> { +export interface ToolExecutionOptions { /** * The ID of the tool call. You can use it e.g. when sending tool-call related information with stream data. */ @@ -39,7 +37,7 @@ export interface ToolExecutionOptions< * * Experimental (can break in patch releases). */ - experimental_context: Context; + experimental_context: CONTEXT; } /** @@ -47,7 +45,7 @@ export interface ToolExecutionOptions< */ export type ToolNeedsApprovalFunction< INPUT, - CONTEXT extends Partial = ContextRegistry, + CONTEXT extends Context = Context, > = ( input: INPUT, options: { @@ -67,15 +65,11 @@ export type ToolNeedsApprovalFunction< * * Experimental (can break in patch releases). */ - experimental_context?: Context; + experimental_context?: CONTEXT; }, ) => boolean | PromiseLike; -export type ToolExecuteFunction< - CONTEXT extends Partial, - INPUT, - OUTPUT, -> = ( +export type ToolExecuteFunction = ( input: INPUT, options: ToolExecutionOptions, ) => AsyncIterable | PromiseLike | OUTPUT; @@ -89,7 +83,7 @@ type NeverOptional = 0 extends 1 & N : T; type ToolOutputProperties< - CONTEXT extends Partial, + CONTEXT extends Context, INPUT, OUTPUT, > = NeverOptional< @@ -120,7 +114,7 @@ type ToolOutputProperties< * The tool can also contain an optional execute function for the actual execution function of the tool. */ export type Tool< - CONTEXT extends Partial = ContextRegistry, + CONTEXT extends Context, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any, > = { @@ -276,7 +270,7 @@ export type Tool< * Infer the input type of a tool. */ export type InferToolInput< - CONTEXT extends Partial, + CONTEXT extends Context, TOOL extends Tool, > = TOOL extends Tool ? INPUT : never; @@ -284,7 +278,7 @@ export type InferToolInput< * Infer the output type of a tool. */ export type InferToolOutput< - CONTEXT extends Partial, + CONTEXT extends Context, TOOL extends Tool, > = TOOL extends Tool ? OUTPUT : never; @@ -292,16 +286,16 @@ export type InferToolOutput< * Helper function for inferring the execute args of a tool. */ // Note: overload order is important for auto-completion -export function tool, INPUT, OUTPUT>( +export function tool( tool: Tool, ): Tool; -export function tool, INPUT>( +export function tool( tool: Tool, ): Tool; -export function tool, OUTPUT>( +export function tool( tool: Tool, ): Tool; -export function tool>( +export function tool( tool: Tool, ): Tool; export function tool(tool: any): any { @@ -316,7 +310,7 @@ export function dynamicTool(tool: { title?: string; providerOptions?: ProviderOptions; inputSchema: FlexibleSchema; - execute: ToolExecuteFunction; + execute: ToolExecuteFunction; /** * Optional conversion function that maps the tool result to an output that can be used by the language model. @@ -344,7 +338,7 @@ export function dynamicTool(tool: { * Whether the tool needs approval before it can be executed. */ needsApproval?: boolean | ToolNeedsApprovalFunction; -}): Tool & { +}): Tool & { type: 'dynamic'; } { return { ...tool, type: 'dynamic' }; From 9f48c008d8251bf34f0d129c534f58e4b9acf0ba Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 16:00:54 +0100 Subject: [PATCH 05/41] x --- .../ai/src/generate-text/callback-events.ts | 63 +++++++++---------- .../ai/src/generate-text/execute-tool-call.ts | 9 ++- .../ai/src/generate-text/generate-text.ts | 32 +++++----- .../ai/src/generate-text/stop-condition.ts | 8 +-- packages/ai/src/generate-text/tool-call.ts | 18 ++---- packages/ai/src/generate-text/tool-result.ts | 22 +++---- packages/ai/src/generate-text/tool-set.ts | 6 +- packages/provider-utils/src/types/index.ts | 5 +- packages/provider-utils/src/types/tool.ts | 36 +++++------ 9 files changed, 86 insertions(+), 113 deletions(-) diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index afb35b9282fb..454be6f96a9a 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -1,6 +1,5 @@ import type { LanguageModelV4ToolChoice } from '@ai-sdk/provider'; import type { - ContextRegistry, ModelMessage, ProviderOptions, SystemModelMessage, @@ -130,8 +129,7 @@ export interface OnStartEvent< * Each step represents a single LLM invocation. */ export interface OnStepStartEvent< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -186,8 +184,8 @@ export interface OnStepStartEvent< * When the condition is an array, any of the conditions can be met to stop. */ readonly stopWhen: - | StopCondition - | Array> + | StopCondition + | Array> | undefined; /** The output specification for structured outputs, if configured. */ @@ -218,10 +216,7 @@ export interface OnStepStartEvent< * * Called when a tool execution begins, before the tool's `execute` function is invoked. */ -export interface OnToolCallStartEvent< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> { +export interface OnToolCallStartEvent { /** Zero-based index of the current step where this tool call occurs. */ readonly stepNumber: number | undefined; @@ -229,7 +224,7 @@ export interface OnToolCallStartEvent< readonly model: CallbackModelInfo | undefined; /** The full tool call object. */ - readonly toolCall: TypedToolCall; + readonly toolCall: TypedToolCall; /** The conversation messages available at tool execution time. */ readonly messages: Array; @@ -312,28 +307,26 @@ export type OnStepFinishEvent = * Called when the entire generation completes (all steps finished). * Includes the final step's result along with aggregated data from all steps. */ -export type OnFinishEvent< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = StepResult & { - /** Array containing results from all steps in the generation. */ - readonly steps: StepResult[]; - - /** Aggregated token usage across all steps. */ - readonly totalUsage: LanguageModelUsage; - - /** - * The final state of the user-defined context object. - * - * Experimental (can break in patch releases). - * - * @default undefined - */ - experimental_context: unknown; - - /** Identifier from telemetry settings for grouping related operations. */ - readonly functionId: string | undefined; - - /** Additional metadata from telemetry settings. */ - readonly metadata: Record | undefined; -}; +export type OnFinishEvent = + StepResult & { + /** Array containing results from all steps in the generation. */ + readonly steps: StepResult[]; + + /** Aggregated token usage across all steps. */ + readonly totalUsage: LanguageModelUsage; + + /** + * The final state of the user-defined context object. + * + * Experimental (can break in patch releases). + * + * @default undefined + */ + experimental_context: unknown; + + /** Identifier from telemetry settings for grouping related operations. */ + readonly functionId: string | undefined; + + /** Additional metadata from telemetry settings. */ + readonly metadata: Record | undefined; + }; diff --git a/packages/ai/src/generate-text/execute-tool-call.ts b/packages/ai/src/generate-text/execute-tool-call.ts index 55644265c277..80a311f0c90d 100644 --- a/packages/ai/src/generate-text/execute-tool-call.ts +++ b/packages/ai/src/generate-text/execute-tool-call.ts @@ -1,4 +1,4 @@ -import { executeTool, ModelMessage } from '@ai-sdk/provider-utils'; +import { Context, executeTool, ModelMessage } from '@ai-sdk/provider-utils'; import { Tracer } from '@opentelemetry/api'; import { notify } from '../util/notify'; import { assembleOperationName } from '../telemetry/assemble-operation-name'; @@ -27,7 +27,10 @@ import { TypedToolError } from './tool-error'; * * @returns The tool output (result or error), or undefined if the tool has no execute function. */ -export async function executeToolCall({ +export async function executeToolCall< + CONTEXT extends Context, + TOOLS extends ToolSet, +>({ toolCall, tools, tracer, @@ -47,7 +50,7 @@ export async function executeToolCall({ telemetry: TelemetrySettings | undefined; messages: ModelMessage[]; abortSignal: AbortSignal | undefined; - experimental_context: unknown; + experimental_context: CONTEXT; stepNumber?: number; model?: { provider: string; modelId: string }; onPreliminaryToolResult?: (result: TypedToolResult) => void; diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index e15f8e2113c3..8d9f82ddc4fd 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -5,7 +5,6 @@ import { } from '@ai-sdk/provider'; import { Context, - ContextRegistry, createIdGenerator, getErrorMessage, IdGenerator, @@ -131,11 +130,10 @@ export type GenerateTextOnStartCallback< * @param event - The event object containing step configuration. */ export type GenerateTextOnStepStartCallback< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, > = ( - event: OnStepStartEvent, + event: OnStepStartEvent, ) => PromiseLike | void; /** @@ -147,9 +145,8 @@ export type GenerateTextOnStepStartCallback< * @param event - The event object containing tool call information. */ export type GenerateTextOnToolCallStartCallback< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = (event: OnToolCallStartEvent) => PromiseLike | void; + TOOLS extends ToolSet = ToolSet, +> = (event: OnToolCallStartEvent) => PromiseLike | void; /** * Callback that is set using the `experimental_onToolCallFinish` option. @@ -188,10 +185,9 @@ export type GenerateTextOnStepFinishCallback = ( * * @param event - The final result along with aggregated step data. */ -export type GenerateTextOnFinishCallback< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = (event: OnFinishEvent) => PromiseLike | void; +export type GenerateTextOnFinishCallback = ( + event: OnFinishEvent, +) => PromiseLike | void; /** * Generate a text and call tools for a given prompt using a language model. @@ -248,8 +244,8 @@ export type GenerateTextOnFinishCallback< * A result object that contains the generated text, the results of the tool calls, and additional information. */ export async function generateText< - CONTEXT extends Partial, - TOOLS extends ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, >({ model: modelArg, @@ -273,7 +269,7 @@ export async function generateText< prepareStep = experimental_prepareStep, experimental_repairToolCall: repairToolCall, experimental_download: download, - experimental_context = {} as Context, + experimental_context = {} as CONTEXT, experimental_include: include, _internal: { generateId = originalGenerateId } = {}, experimental_onStart: onStart, @@ -413,7 +409,7 @@ export async function generateText< * * @default undefined */ - experimental_context?: Context; + experimental_context?: CONTEXT; /** * Settings for controlling what data is included in step results. @@ -1194,7 +1190,7 @@ export async function generateText< onFinish, globalTelemetry.onFinish as | undefined - | GenerateTextOnFinishCallback, + | GenerateTextOnFinishCallback, ], }); @@ -1224,7 +1220,7 @@ export async function generateText< } } -async function executeTools({ +async function executeTools({ toolCalls, tools, tracer, @@ -1243,7 +1239,7 @@ async function executeTools({ telemetry: TelemetrySettings | undefined; messages: ModelMessage[]; abortSignal: AbortSignal | undefined; - experimental_context: unknown; + experimental_context: CONTEXT; stepNumber: number; model: { provider: string; modelId: string }; onToolCallStart: diff --git a/packages/ai/src/generate-text/stop-condition.ts b/packages/ai/src/generate-text/stop-condition.ts index 37ce347927f1..a9438b923fc1 100644 --- a/packages/ai/src/generate-text/stop-condition.ts +++ b/packages/ai/src/generate-text/stop-condition.ts @@ -1,12 +1,8 @@ -import { ContextRegistry } from '@ai-sdk/provider-utils'; import { StepResult } from './step-result'; import { ToolSet } from './tool-set'; -export type StopCondition< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = (options: { - steps: Array>; +export type StopCondition = (options: { + steps: Array>; }) => PromiseLike | boolean; export function stepCountIs(stepCount: number): StopCondition { diff --git a/packages/ai/src/generate-text/tool-call.ts b/packages/ai/src/generate-text/tool-call.ts index b214f62b0405..59698ce0b550 100644 --- a/packages/ai/src/generate-text/tool-call.ts +++ b/packages/ai/src/generate-text/tool-call.ts @@ -1,4 +1,4 @@ -import { ContextRegistry, Tool } from '@ai-sdk/provider-utils'; +import { Tool } from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { ValueOf } from '../util/value-of'; import { ToolSet } from './tool-set'; @@ -10,15 +10,10 @@ type BaseToolCall = { providerMetadata?: ProviderMetadata; }; -export type StaticToolCall< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = ValueOf<{ +export type StaticToolCall = ValueOf<{ [NAME in keyof TOOLS]: BaseToolCall & { toolName: NAME & string; - input: TOOLS[NAME] extends Tool - ? PARAMETERS - : never; + input: TOOLS[NAME] extends Tool ? PARAMETERS : never; dynamic?: false | undefined; invalid?: false | undefined; error?: never; @@ -47,7 +42,6 @@ export type DynamicToolCall = BaseToolCall & { error?: unknown; }; -export type TypedToolCall< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = StaticToolCall | DynamicToolCall; +export type TypedToolCall = + | StaticToolCall + | DynamicToolCall; diff --git a/packages/ai/src/generate-text/tool-result.ts b/packages/ai/src/generate-text/tool-result.ts index 1f2943f644d0..422f3dafb283 100644 --- a/packages/ai/src/generate-text/tool-result.ts +++ b/packages/ai/src/generate-text/tool-result.ts @@ -1,22 +1,15 @@ -import { - ContextRegistry, - InferToolInput, - InferToolOutput, -} from '@ai-sdk/provider-utils'; +import { InferToolInput, InferToolOutput } from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { ValueOf } from '../../src/util/value-of'; import { ToolSet } from './tool-set'; -export type StaticToolResult< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = ValueOf<{ +export type StaticToolResult = ValueOf<{ [NAME in keyof TOOLS]: { type: 'tool-result'; toolCallId: string; toolName: NAME & string; - input: InferToolInput; - output: InferToolOutput; + input: InferToolInput; + output: InferToolOutput; providerExecuted?: boolean; providerMetadata?: ProviderMetadata; dynamic?: false | undefined; @@ -38,7 +31,6 @@ export type DynamicToolResult = { title?: string; }; -export type TypedToolResult< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = StaticToolResult | DynamicToolResult; +export type TypedToolResult = + | StaticToolResult + | DynamicToolResult; diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index 68d308fb5a25..061410448768 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -1,8 +1,6 @@ -import { ContextRegistry, Tool } from '@ai-sdk/provider-utils'; +import { Context, Tool } from '@ai-sdk/provider-utils'; -export type ToolSet< - CONTEXT extends Partial = ContextRegistry, -> = Record< +export type ToolSet = Record< string, ( | Tool diff --git a/packages/provider-utils/src/types/index.ts b/packages/provider-utils/src/types/index.ts index b1fca4b85201..c70878137ece 100644 --- a/packages/provider-utils/src/types/index.ts +++ b/packages/provider-utils/src/types/index.ts @@ -33,9 +33,12 @@ export type { ToolCall } from './tool-call'; export type { ToolContent, ToolModelMessage } from './tool-model-message'; export type { ToolResult } from './tool-result'; export type { UserContent, UserModelMessage } from './user-model-message'; + import type { ToolExecutionOptions } from './tool'; +import type { Context } from './context'; /** * @deprecated Use ToolExecutionOptions instead. */ -export type ToolCallOptions = ToolExecutionOptions; +export type ToolCallOptions = + ToolExecutionOptions; diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index 5785670b5332..d7745e029767 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -8,7 +8,7 @@ import { Context } from './context'; /** * Additional options that are sent into each tool call. */ -export interface ToolExecutionOptions { +export interface ToolExecutionOptions { /** * The ID of the tool call. You can use it e.g. when sending tool-call related information with stream data. */ @@ -43,10 +43,7 @@ export interface ToolExecutionOptions { /** * Function that is called to determine if the tool needs approval before it can be executed. */ -export type ToolNeedsApprovalFunction< - INPUT, - CONTEXT extends Context = Context, -> = ( +export type ToolNeedsApprovalFunction = ( input: INPUT, options: { /** @@ -114,7 +111,7 @@ type ToolOutputProperties< * The tool can also contain an optional execute function for the actual execution function of the tool. */ export type Tool< - CONTEXT extends Context, + CONTEXT extends Context = any, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any, > = { @@ -159,7 +156,10 @@ export type Tool< */ needsApproval?: | boolean - | ToolNeedsApprovalFunction<[INPUT] extends [never] ? unknown : INPUT>; + | ToolNeedsApprovalFunction< + [INPUT] extends [never] ? unknown : INPUT, + CONTEXT + >; /** * Strict mode setting for the tool. @@ -174,14 +174,16 @@ export type Tool< * Optional function that is called when the argument streaming starts. * Only called when the tool is used in a streaming context. */ - onInputStart?: (options: ToolExecutionOptions) => void | PromiseLike; + onInputStart?: ( + options: ToolExecutionOptions, + ) => void | PromiseLike; /** * Optional function that is called when an argument streaming delta is available. * Only called when the tool is used in a streaming context. */ onInputDelta?: ( - options: { inputTextDelta: string } & ToolExecutionOptions, + options: { inputTextDelta: string } & ToolExecutionOptions, ) => void | PromiseLike; /** @@ -191,7 +193,7 @@ export type Tool< onInputAvailable?: ( options: { input: [INPUT] extends [never] ? unknown : INPUT; - } & ToolExecutionOptions, + } & ToolExecutionOptions, ) => void | PromiseLike; } & ToolOutputProperties & { /** @@ -269,18 +271,14 @@ export type Tool< /** * Infer the input type of a tool. */ -export type InferToolInput< - CONTEXT extends Context, - TOOL extends Tool, -> = TOOL extends Tool ? INPUT : never; +export type InferToolInput> = + TOOL extends Tool ? INPUT : never; /** * Infer the output type of a tool. */ -export type InferToolOutput< - CONTEXT extends Context, - TOOL extends Tool, -> = TOOL extends Tool ? OUTPUT : never; +export type InferToolOutput> = + TOOL extends Tool ? OUTPUT : never; /** * Helper function for inferring the execute args of a tool. @@ -337,7 +335,7 @@ export function dynamicTool(tool: { /** * Whether the tool needs approval before it can be executed. */ - needsApproval?: boolean | ToolNeedsApprovalFunction; + needsApproval?: boolean | ToolNeedsApprovalFunction; }): Tool & { type: 'dynamic'; } { From 37d2b0a2883f90f0ddc12a4bb59e6f3b89610b88 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 16:12:13 +0100 Subject: [PATCH 06/41] xx --- .../ai/src/generate-text/callback-events.ts | 71 +++++++++++-------- .../src/generate-text/generate-text-result.ts | 4 +- .../ai/src/generate-text/generate-text.ts | 65 ++++++++++------- packages/ai/src/generate-text/prepare-step.ts | 9 ++- packages/ai/src/generate-text/step-result.ts | 17 +++-- .../ai/src/generate-text/stop-condition.ts | 13 +++- .../tool-call-repair-function.ts | 6 +- packages/ai/src/generate-text/tool-call.ts | 25 ++++--- 8 files changed, 127 insertions(+), 83 deletions(-) diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index 454be6f96a9a..3df6de677145 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -1,5 +1,6 @@ import type { LanguageModelV4ToolChoice } from '@ai-sdk/provider'; import type { + Context, ModelMessage, ProviderOptions, SystemModelMessage, @@ -29,7 +30,8 @@ export interface CallbackModelInfo { * Called when the generation operation begins, before any LLM calls. */ export interface OnStartEvent< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -129,7 +131,8 @@ export interface OnStartEvent< * Each step represents a single LLM invocation. */ export interface OnStepStartEvent< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -216,7 +219,10 @@ export interface OnStepStartEvent< * * Called when a tool execution begins, before the tool's `execute` function is invoked. */ -export interface OnToolCallStartEvent { +export interface OnToolCallStartEvent< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +> { /** Zero-based index of the current step where this tool call occurs. */ readonly stepNumber: number | undefined; @@ -248,7 +254,10 @@ export interface OnToolCallStartEvent { * Called when a tool execution completes, either successfully or with an error. * Uses a discriminated union on the `success` field. */ -export type OnToolCallFinishEvent = { +export type OnToolCallFinishEvent< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +> = { /** Zero-based index of the current step where this tool call occurred. */ readonly stepNumber: number | undefined; @@ -298,8 +307,10 @@ export type OnToolCallFinishEvent = { * Called when a step (LLM call) completes. * This is simply the StepResult for that step. */ -export type OnStepFinishEvent = - StepResult; +export type OnStepFinishEvent< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +> = StepResult; /** * Event passed to the `onFinish` callback. @@ -307,26 +318,28 @@ export type OnStepFinishEvent = * Called when the entire generation completes (all steps finished). * Includes the final step's result along with aggregated data from all steps. */ -export type OnFinishEvent = - StepResult & { - /** Array containing results from all steps in the generation. */ - readonly steps: StepResult[]; - - /** Aggregated token usage across all steps. */ - readonly totalUsage: LanguageModelUsage; - - /** - * The final state of the user-defined context object. - * - * Experimental (can break in patch releases). - * - * @default undefined - */ - experimental_context: unknown; - - /** Identifier from telemetry settings for grouping related operations. */ - readonly functionId: string | undefined; - - /** Additional metadata from telemetry settings. */ - readonly metadata: Record | undefined; - }; +export type OnFinishEvent< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +> = StepResult & { + /** Array containing results from all steps in the generation. */ + readonly steps: StepResult[]; + + /** Aggregated token usage across all steps. */ + readonly totalUsage: LanguageModelUsage; + + /** + * The final state of the user-defined context object. + * + * Experimental (can break in patch releases). + * + * @default undefined + */ + experimental_context: unknown; + + /** Identifier from telemetry settings for grouping related operations. */ + readonly functionId: string | undefined; + + /** Additional metadata from telemetry settings. */ + readonly metadata: Record | undefined; +}; diff --git a/packages/ai/src/generate-text/generate-text-result.ts b/packages/ai/src/generate-text/generate-text-result.ts index 6f37f8d69eaf..f2311ba68ab9 100644 --- a/packages/ai/src/generate-text/generate-text-result.ts +++ b/packages/ai/src/generate-text/generate-text-result.ts @@ -1,3 +1,4 @@ +import { Context } from '@ai-sdk/provider-utils'; import { CallWarning, FinishReason, ProviderMetadata } from '../types'; import { Source } from '../types/language-model'; import { LanguageModelRequestMetadata } from '../types/language-model-request-metadata'; @@ -23,7 +24,8 @@ import { ToolSet } from './tool-set'; * It contains the generated text, the tool calls that were made during the generation, and the results of the tool calls. */ export interface GenerateTextResult< - TOOLS extends ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet, OUTPUT extends Output, > { /** diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 8d9f82ddc4fd..494ef59459bf 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -114,10 +114,11 @@ type GenerateTextIncludeSettings = { * @param event - The event object containing generation configuration. */ export type GenerateTextOnStartCallback< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, > = ( - event: OnStartEvent, + event: OnStartEvent, ) => PromiseLike | void; /** @@ -130,10 +131,11 @@ export type GenerateTextOnStartCallback< * @param event - The event object containing step configuration. */ export type GenerateTextOnStepStartCallback< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, > = ( - event: OnStepStartEvent, + event: OnStepStartEvent, ) => PromiseLike | void; /** @@ -145,7 +147,8 @@ export type GenerateTextOnStepStartCallback< * @param event - The event object containing tool call information. */ export type GenerateTextOnToolCallStartCallback< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, > = (event: OnToolCallStartEvent) => PromiseLike | void; /** @@ -161,7 +164,8 @@ export type GenerateTextOnToolCallStartCallback< * @param event - The event object containing tool call result information. */ export type GenerateTextOnToolCallFinishCallback< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, > = (event: OnToolCallFinishEvent) => PromiseLike | void; /** @@ -172,9 +176,10 @@ export type GenerateTextOnToolCallFinishCallback< * * @param stepResult - The result of the step. */ -export type GenerateTextOnStepFinishCallback = ( - event: OnStepFinishEvent, -) => Promise | void; +export type GenerateTextOnStepFinishCallback< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +> = (event: OnStepFinishEvent) => Promise | void; /** * Callback that is set using the `onFinish` option. @@ -185,9 +190,10 @@ export type GenerateTextOnStepFinishCallback = ( * * @param event - The final result along with aggregated step data. */ -export type GenerateTextOnFinishCallback = ( - event: OnFinishEvent, -) => PromiseLike | void; +export type GenerateTextOnFinishCallback< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +> = (event: OnFinishEvent) => PromiseLike | void; /** * Generate a text and call tools for a given prompt using a language model. @@ -245,7 +251,7 @@ export type GenerateTextOnFinishCallback = ( */ export async function generateText< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, >({ model: modelArg, @@ -367,13 +373,18 @@ export async function generateText< * Callback that is called when the generateText operation begins, * before any LLM calls are made. */ - experimental_onStart?: GenerateTextOnStartCallback, OUTPUT>; + experimental_onStart?: GenerateTextOnStartCallback< + CONTEXT, + NoInfer, + OUTPUT + >; /** * Callback that is called when a step (LLM call) begins, * before the provider is called. */ experimental_onStepStart?: GenerateTextOnStepStartCallback< + CONTEXT, NoInfer, OUTPUT >; @@ -439,7 +450,7 @@ export async function generateText< _internal?: { generateId?: IdGenerator; }; - }): Promise> { + }): Promise> { const model = resolveLanguageModel(modelArg); const createGlobalTelemetry = getGlobalTelemetryIntegration(); const stopConditions = asArray(stopWhen); @@ -516,7 +527,7 @@ export async function generateText< onStart, globalTelemetry.onStart as | undefined - | GenerateTextOnStartCallback, + | GenerateTextOnStartCallback, ], }); @@ -661,7 +672,7 @@ export async function generateText< > & { response: { id: string; timestamp: Date; modelId: string } }; let clientToolCalls: Array> = []; let clientToolOutputs: Array> = []; - const steps: GenerateTextResult['steps'] = []; + const steps: StepResult[] = []; // Track provider-executed tool calls that support deferred results // (e.g., code_execution in programmatic tool calling scenarios). @@ -757,7 +768,7 @@ export async function generateText< onStepStart, globalTelemetry.onStepStart as | undefined - | GenerateTextOnStepStartCallback, + | GenerateTextOnStepStartCallback, ], }); @@ -1220,7 +1231,10 @@ export async function generateText< } } -async function executeTools({ +async function executeTools< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +>({ toolCalls, tools, tracer, @@ -1274,15 +1288,18 @@ async function executeTools({ ); } -class DefaultGenerateTextResult - implements GenerateTextResult +class DefaultGenerateTextResult< + CONTEXT extends Context, + TOOLS extends ToolSet, + OUTPUT extends Output, +> implements GenerateTextResult { - readonly steps: GenerateTextResult['steps']; + readonly steps: GenerateTextResult['steps']; readonly totalUsage: LanguageModelUsage; private readonly _output: InferCompleteOutput | undefined; constructor(options: { - steps: GenerateTextResult['steps']; + steps: GenerateTextResult['steps']; output: InferCompleteOutput | undefined; totalUsage: LanguageModelUsage; }) { @@ -1400,7 +1417,7 @@ function asToolCalls(content: Array) { })); } -function asContent({ +function asContent>({ content, toolCalls, toolOutputs, diff --git a/packages/ai/src/generate-text/prepare-step.ts b/packages/ai/src/generate-text/prepare-step.ts index 8ba9901b631d..3dcab84207c0 100644 --- a/packages/ai/src/generate-text/prepare-step.ts +++ b/packages/ai/src/generate-text/prepare-step.ts @@ -1,6 +1,5 @@ import { Context, - ContextRegistry, ModelMessage, ProviderOptions, SystemModelMessage, @@ -23,7 +22,7 @@ import { ToolSet } from './tool-set'; * If you return undefined (or for undefined settings), the settings from the outer level will be used. */ export type PrepareStepFunction< - CONTEXT extends Partial = ContextRegistry, + CONTEXT extends Context, TOOLS extends ToolSet = ToolSet, > = (options: { /** @@ -49,7 +48,7 @@ export type PrepareStepFunction< /** * The context passed via the experimental_context setting (experimental). */ - experimental_context: Context; + experimental_context: CONTEXT; }) => | PromiseLike> | PrepareStepResult; @@ -59,7 +58,7 @@ export type PrepareStepFunction< * allowing per-step overrides of model, tools, or messages. */ export type PrepareStepResult< - CONTEXT extends Partial = ContextRegistry, + CONTEXT extends Context, TOOLS extends ToolSet = ToolSet, > = | { @@ -96,7 +95,7 @@ export type PrepareStepResult< * Changing the context will affect the context in this step * and all subsequent steps. */ - experimental_context?: Context; + experimental_context?: CONTEXT; /** * Additional provider-specific options for this step. diff --git a/packages/ai/src/generate-text/step-result.ts b/packages/ai/src/generate-text/step-result.ts index 94d7a9e92fd2..9b8bf2c09c1e 100644 --- a/packages/ai/src/generate-text/step-result.ts +++ b/packages/ai/src/generate-text/step-result.ts @@ -1,4 +1,4 @@ -import { ContextRegistry, ReasoningPart } from '@ai-sdk/provider-utils'; +import { Context, ReasoningPart } from '@ai-sdk/provider-utils'; import { CallWarning, FinishReason, @@ -22,10 +22,7 @@ import { ToolSet } from './tool-set'; /** * The result of a single step in the generation process. */ -export type StepResult< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = { +export type StepResult = { /** * Zero-based index of this step. */ @@ -91,7 +88,7 @@ export type StepResult< /** * The tool calls that were made during the generation. */ - readonly toolCalls: Array>; + readonly toolCalls: Array>; /** * The static tool calls that were made in the last step. @@ -106,7 +103,7 @@ export type StepResult< /** * The results of the tool calls. */ - readonly toolResults: Array>; + readonly toolResults: Array>; /** * The static tool results that were made in the last step. @@ -168,8 +165,10 @@ export type StepResult< readonly providerMetadata: ProviderMetadata | undefined; }; -export class DefaultStepResult - implements StepResult +export class DefaultStepResult< + CONTEXT extends Context, + TOOLS extends ToolSet, +> implements StepResult { readonly stepNumber: StepResult['stepNumber']; readonly model: StepResult['model']; diff --git a/packages/ai/src/generate-text/stop-condition.ts b/packages/ai/src/generate-text/stop-condition.ts index a9438b923fc1..cdcb27734501 100644 --- a/packages/ai/src/generate-text/stop-condition.ts +++ b/packages/ai/src/generate-text/stop-condition.ts @@ -1,7 +1,11 @@ +import { Context } from '@ai-sdk/provider-utils'; import { StepResult } from './step-result'; import { ToolSet } from './tool-set'; -export type StopCondition = (options: { +export type StopCondition< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +> = (options: { steps: Array>; }) => PromiseLike | boolean; @@ -16,11 +20,14 @@ export function hasToolCall(toolName: string): StopCondition { ) ?? false; } -export async function isStopConditionMet({ +export async function isStopConditionMet< + CONTEXT extends Context, + TOOLS extends ToolSet, +>({ stopConditions, steps, }: { - stopConditions: Array>; + stopConditions: Array>; steps: Array>; }): Promise { return ( diff --git a/packages/ai/src/generate-text/tool-call-repair-function.ts b/packages/ai/src/generate-text/tool-call-repair-function.ts index d747916c6ff0..49f6f7e62983 100644 --- a/packages/ai/src/generate-text/tool-call-repair-function.ts +++ b/packages/ai/src/generate-text/tool-call-repair-function.ts @@ -1,4 +1,5 @@ import { JSONSchema7, LanguageModelV4ToolCall } from '@ai-sdk/provider'; +import { Context } from '@ai-sdk/provider-utils'; import { InvalidToolInputError } from '../error/invalid-tool-input-error'; import { NoSuchToolError } from '../error/no-such-tool-error'; import { ModelMessage, SystemModelMessage } from '../prompt'; @@ -17,7 +18,10 @@ import { ToolSet } from './tool-set'; * @param options.inputSchema - A function that returns the JSON Schema for a tool. * @param options.error - The error that occurred while parsing the tool call. */ -export type ToolCallRepairFunction = (options: { +export type ToolCallRepairFunction< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +> = (options: { system: string | SystemModelMessage | Array | undefined; messages: ModelMessage[]; toolCall: LanguageModelV4ToolCall; diff --git a/packages/ai/src/generate-text/tool-call.ts b/packages/ai/src/generate-text/tool-call.ts index 59698ce0b550..831f93e3db25 100644 --- a/packages/ai/src/generate-text/tool-call.ts +++ b/packages/ai/src/generate-text/tool-call.ts @@ -10,16 +10,19 @@ type BaseToolCall = { providerMetadata?: ProviderMetadata; }; -export type StaticToolCall = ValueOf<{ - [NAME in keyof TOOLS]: BaseToolCall & { - toolName: NAME & string; - input: TOOLS[NAME] extends Tool ? PARAMETERS : never; - dynamic?: false | undefined; - invalid?: false | undefined; - error?: never; - title?: string; - }; -}>; +export type StaticToolCall = ToolSet> = + ValueOf<{ + [NAME in keyof TOOLS]: BaseToolCall & { + toolName: NAME & string; + input: TOOLS[NAME] extends Tool + ? PARAMETERS + : never; + dynamic?: false | undefined; + invalid?: false | undefined; + error?: never; + title?: string; + }; + }>; export type DynamicToolCall = BaseToolCall & { toolName: string; @@ -42,6 +45,6 @@ export type DynamicToolCall = BaseToolCall & { error?: unknown; }; -export type TypedToolCall = +export type TypedToolCall = ToolSet> = | StaticToolCall | DynamicToolCall; From 7ad6fd7fdfdf149e86bf56276361eeb785dcf5a2 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 16:40:31 +0100 Subject: [PATCH 07/41] y --- .../ai-functions/src/test/typed-context.ts | 6 +- .../ai/src/generate-text/callback-events.ts | 29 +++----- packages/ai/src/generate-text/content-part.ts | 16 ++--- .../src/generate-text/generate-text-result.ts | 4 +- .../ai/src/generate-text/generate-text.ts | 68 +++++++++---------- packages/ai/src/generate-text/step-result.ts | 64 +++++++++-------- .../ai/src/generate-text/stop-condition.ts | 12 +--- packages/ai/src/generate-text/tool-call.ts | 25 +++---- packages/ai/src/generate-text/tool-set.ts | 5 +- packages/provider-utils/src/types/tool.ts | 2 +- 10 files changed, 107 insertions(+), 124 deletions(-) diff --git a/examples/ai-functions/src/test/typed-context.ts b/examples/ai-functions/src/test/typed-context.ts index dd53f7cd0bf2..4446294f8c8d 100644 --- a/examples/ai-functions/src/test/typed-context.ts +++ b/examples/ai-functions/src/test/typed-context.ts @@ -6,13 +6,13 @@ interface Tool { export type ToolSet = Record>; -function executeTool({ +function executeTool>({ tools, toolName, context, }: { - tools: ToolSet; - toolName: keyof ToolSet; + tools: TOOLS; + toolName: keyof TOOLS; context: CONTEXT; }) { return tools[toolName].execute(context); diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index 3df6de677145..697a711002d3 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -30,8 +30,7 @@ export interface CallbackModelInfo { * Called when the generation operation begins, before any LLM calls. */ export interface OnStartEvent< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -132,7 +131,7 @@ export interface OnStartEvent< */ export interface OnStepStartEvent< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -168,7 +167,7 @@ export interface OnStepStartEvent< readonly activeTools: Array | undefined; /** Array of results from previous steps (empty for first step). */ - readonly steps: ReadonlyArray>; + readonly steps: ReadonlyArray>; /** Additional provider-specific options for this step. */ readonly providerOptions: ProviderOptions | undefined; @@ -219,10 +218,7 @@ export interface OnStepStartEvent< * * Called when a tool execution begins, before the tool's `execute` function is invoked. */ -export interface OnToolCallStartEvent< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> { +export interface OnToolCallStartEvent { /** Zero-based index of the current step where this tool call occurs. */ readonly stepNumber: number | undefined; @@ -254,10 +250,7 @@ export interface OnToolCallStartEvent< * Called when a tool execution completes, either successfully or with an error. * Uses a discriminated union on the `success` field. */ -export type OnToolCallFinishEvent< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = { +export type OnToolCallFinishEvent = { /** Zero-based index of the current step where this tool call occurred. */ readonly stepNumber: number | undefined; @@ -309,8 +302,8 @@ export type OnToolCallFinishEvent< */ export type OnStepFinishEvent< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = StepResult; + TOOLS extends ToolSet, +> = StepResult; /** * Event passed to the `onFinish` callback. @@ -320,10 +313,10 @@ export type OnStepFinishEvent< */ export type OnFinishEvent< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = StepResult & { + TOOLS extends ToolSet, +> = StepResult & { /** Array containing results from all steps in the generation. */ - readonly steps: StepResult[]; + readonly steps: StepResult[]; /** Aggregated token usage across all steps. */ readonly totalUsage: LanguageModelUsage; @@ -335,7 +328,7 @@ export type OnFinishEvent< * * @default undefined */ - experimental_context: unknown; + experimental_context: CONTEXT; /** Identifier from telemetry settings for grouping related operations. */ readonly functionId: string | undefined; diff --git a/packages/ai/src/generate-text/content-part.ts b/packages/ai/src/generate-text/content-part.ts index df1e9b1eb90a..8575387a179b 100644 --- a/packages/ai/src/generate-text/content-part.ts +++ b/packages/ai/src/generate-text/content-part.ts @@ -1,29 +1,25 @@ -import { ContextRegistry } from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { Source } from '../types/language-model'; import { GeneratedFile } from './generated-file'; -import { ToolApprovalRequestOutput } from './tool-approval-request-output'; import { ReasoningOutput } from './reasoning-output'; +import { ToolApprovalRequestOutput } from './tool-approval-request-output'; import { TypedToolCall } from './tool-call'; import { TypedToolError } from './tool-error'; import { TypedToolResult } from './tool-result'; import { ToolSet } from './tool-set'; -export type ContentPart< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = +export type ContentPart = | { type: 'text'; text: string; providerMetadata?: ProviderMetadata } | ReasoningOutput | ({ type: 'source' } & Source) | { type: 'file'; file: GeneratedFile; providerMetadata?: ProviderMetadata } // different because of GeneratedFile object - | ({ type: 'tool-call' } & TypedToolCall & { + | ({ type: 'tool-call' } & TypedToolCall & { providerMetadata?: ProviderMetadata; }) - | ({ type: 'tool-result' } & TypedToolResult & { + | ({ type: 'tool-result' } & TypedToolResult & { providerMetadata?: ProviderMetadata; }) - | ({ type: 'tool-error' } & TypedToolError & { + | ({ type: 'tool-error' } & TypedToolError & { providerMetadata?: ProviderMetadata; }) - | ToolApprovalRequestOutput; + | ToolApprovalRequestOutput; diff --git a/packages/ai/src/generate-text/generate-text-result.ts b/packages/ai/src/generate-text/generate-text-result.ts index f2311ba68ab9..6f37f8d69eaf 100644 --- a/packages/ai/src/generate-text/generate-text-result.ts +++ b/packages/ai/src/generate-text/generate-text-result.ts @@ -1,4 +1,3 @@ -import { Context } from '@ai-sdk/provider-utils'; import { CallWarning, FinishReason, ProviderMetadata } from '../types'; import { Source } from '../types/language-model'; import { LanguageModelRequestMetadata } from '../types/language-model-request-metadata'; @@ -24,8 +23,7 @@ import { ToolSet } from './tool-set'; * It contains the generated text, the tool calls that were made during the generation, and the results of the tool calls. */ export interface GenerateTextResult< - CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output, > { /** diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 494ef59459bf..3a312235ac43 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -114,11 +114,10 @@ type GenerateTextIncludeSettings = { * @param event - The event object containing generation configuration. */ export type GenerateTextOnStartCallback< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output = Output, > = ( - event: OnStartEvent, + event: OnStartEvent, ) => PromiseLike | void; /** @@ -179,7 +178,7 @@ export type GenerateTextOnToolCallFinishCallback< export type GenerateTextOnStepFinishCallback< CONTEXT extends Context, TOOLS extends ToolSet = ToolSet, -> = (event: OnStepFinishEvent) => Promise | void; +> = (event: OnStepFinishEvent) => Promise | void; /** * Callback that is set using the `onFinish` option. @@ -193,7 +192,7 @@ export type GenerateTextOnStepFinishCallback< export type GenerateTextOnFinishCallback< CONTEXT extends Context, TOOLS extends ToolSet = ToolSet, -> = (event: OnFinishEvent) => PromiseLike | void; +> = (event: OnFinishEvent) => PromiseLike | void; /** * Generate a text and call tools for a given prompt using a language model. @@ -373,11 +372,7 @@ export async function generateText< * Callback that is called when the generateText operation begins, * before any LLM calls are made. */ - experimental_onStart?: GenerateTextOnStartCallback< - CONTEXT, - NoInfer, - OUTPUT - >; + experimental_onStart?: GenerateTextOnStartCallback, OUTPUT>; /** * Callback that is called when a step (LLM call) begins, @@ -406,12 +401,12 @@ export async function generateText< /** * Callback that is called when each step (LLM call) is finished, including intermediate steps. */ - onStepFinish?: GenerateTextOnStepFinishCallback>; + onStepFinish?: GenerateTextOnStepFinishCallback>; /** * Callback that is called when all steps are finished and the response is complete. */ - onFinish?: GenerateTextOnFinishCallback>; + onFinish?: GenerateTextOnFinishCallback>; /** * Context that is passed into tool execution. @@ -450,9 +445,9 @@ export async function generateText< _internal?: { generateId?: IdGenerator; }; - }): Promise> { + }): Promise> { const model = resolveLanguageModel(modelArg); - const createGlobalTelemetry = getGlobalTelemetryIntegration(); + const createGlobalTelemetry = getGlobalTelemetryIntegration(); const stopConditions = asArray(stopWhen); const totalTimeoutMs = getTotalTimeoutMs(timeout); @@ -527,7 +522,7 @@ export async function generateText< onStart, globalTelemetry.onStart as | undefined - | GenerateTextOnStartCallback, + | GenerateTextOnStartCallback, ], }); @@ -1072,23 +1067,24 @@ export async function generateText< const stepNumber = steps.length; - const currentStepResult: StepResult = new DefaultStepResult({ - stepNumber, - model: stepModelInfo, - functionId: telemetry?.functionId, - metadata: telemetry?.metadata as - | Record - | undefined, - experimental_context, - content: stepContent, - finishReason: currentModelResponse.finishReason.unified, - rawFinishReason: currentModelResponse.finishReason.raw, - usage: asLanguageModelUsage(currentModelResponse.usage), - warnings: currentModelResponse.warnings, - providerMetadata: currentModelResponse.providerMetadata, - request: stepRequest, - response: stepResponse, - }); + const currentStepResult: StepResult = + new DefaultStepResult({ + stepNumber, + model: stepModelInfo, + functionId: telemetry?.functionId, + metadata: telemetry?.metadata as + | Record + | undefined, + experimental_context, + content: stepContent, + finishReason: currentModelResponse.finishReason.unified, + rawFinishReason: currentModelResponse.finishReason.raw, + usage: asLanguageModelUsage(currentModelResponse.usage), + warnings: currentModelResponse.warnings, + providerMetadata: currentModelResponse.providerMetadata, + request: stepRequest, + response: stepResponse, + }); logWarnings({ warnings: currentModelResponse.warnings ?? [], @@ -1201,7 +1197,7 @@ export async function generateText< onFinish, globalTelemetry.onFinish as | undefined - | GenerateTextOnFinishCallback, + | GenerateTextOnFinishCallback, ], }); @@ -1292,14 +1288,14 @@ class DefaultGenerateTextResult< CONTEXT extends Context, TOOLS extends ToolSet, OUTPUT extends Output, -> implements GenerateTextResult +> implements GenerateTextResult { - readonly steps: GenerateTextResult['steps']; + readonly steps: StepResult[]; readonly totalUsage: LanguageModelUsage; private readonly _output: InferCompleteOutput | undefined; constructor(options: { - steps: GenerateTextResult['steps']; + steps: StepResult[]; output: InferCompleteOutput | undefined; totalUsage: LanguageModelUsage; }) { diff --git a/packages/ai/src/generate-text/step-result.ts b/packages/ai/src/generate-text/step-result.ts index 9b8bf2c09c1e..6574053e66a5 100644 --- a/packages/ai/src/generate-text/step-result.ts +++ b/packages/ai/src/generate-text/step-result.ts @@ -22,7 +22,10 @@ import { ToolSet } from './tool-set'; /** * The result of a single step in the generation process. */ -export type StepResult = { +export type StepResult< + CONTEXT extends Context, + TOOLS extends ToolSet, +> = { /** * Zero-based index of this step. */ @@ -53,12 +56,12 @@ export type StepResult = { * * Experimental (can break in patch releases). */ - readonly experimental_context: unknown; + readonly experimental_context: CONTEXT; /** * The content that was generated in the last step. */ - readonly content: Array>; + readonly content: Array>; /** * The generated text. @@ -170,19 +173,22 @@ export class DefaultStepResult< TOOLS extends ToolSet, > implements StepResult { - readonly stepNumber: StepResult['stepNumber']; - readonly model: StepResult['model']; - readonly functionId: StepResult['functionId']; - readonly metadata: StepResult['metadata']; - readonly experimental_context: StepResult['experimental_context']; - readonly content: StepResult['content']; - readonly finishReason: StepResult['finishReason']; - readonly rawFinishReason: StepResult['rawFinishReason']; - readonly usage: StepResult['usage']; - readonly warnings: StepResult['warnings']; - readonly request: StepResult['request']; - readonly response: StepResult['response']; - readonly providerMetadata: StepResult['providerMetadata']; + readonly stepNumber: StepResult['stepNumber']; + readonly model: StepResult['model']; + readonly functionId: StepResult['functionId']; + readonly metadata: StepResult['metadata']; + readonly experimental_context: StepResult< + CONTEXT, + TOOLS + >['experimental_context']; + readonly content: StepResult['content']; + readonly finishReason: StepResult['finishReason']; + readonly rawFinishReason: StepResult['rawFinishReason']; + readonly usage: StepResult['usage']; + readonly warnings: StepResult['warnings']; + readonly request: StepResult['request']; + readonly response: StepResult['response']; + readonly providerMetadata: StepResult['providerMetadata']; constructor({ stepNumber, @@ -199,19 +205,19 @@ export class DefaultStepResult< response, providerMetadata, }: { - stepNumber: StepResult['stepNumber']; - model: StepResult['model']; - functionId: StepResult['functionId']; - metadata: StepResult['metadata']; - experimental_context: StepResult['experimental_context']; - content: StepResult['content']; - finishReason: StepResult['finishReason']; - rawFinishReason: StepResult['rawFinishReason']; - usage: StepResult['usage']; - warnings: StepResult['warnings']; - request: StepResult['request']; - response: StepResult['response']; - providerMetadata: StepResult['providerMetadata']; + stepNumber: StepResult['stepNumber']; + model: StepResult['model']; + functionId: StepResult['functionId']; + metadata: StepResult['metadata']; + experimental_context: StepResult['experimental_context']; + content: StepResult['content']; + finishReason: StepResult['finishReason']; + rawFinishReason: StepResult['rawFinishReason']; + usage: StepResult['usage']; + warnings: StepResult['warnings']; + request: StepResult['request']; + response: StepResult['response']; + providerMetadata: StepResult['providerMetadata']; }) { this.stepNumber = stepNumber; this.model = model; diff --git a/packages/ai/src/generate-text/stop-condition.ts b/packages/ai/src/generate-text/stop-condition.ts index cdcb27734501..239e8d77ff63 100644 --- a/packages/ai/src/generate-text/stop-condition.ts +++ b/packages/ai/src/generate-text/stop-condition.ts @@ -2,10 +2,7 @@ import { Context } from '@ai-sdk/provider-utils'; import { StepResult } from './step-result'; import { ToolSet } from './tool-set'; -export type StopCondition< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = (options: { +export type StopCondition = (options: { steps: Array>; }) => PromiseLike | boolean; @@ -20,14 +17,11 @@ export function hasToolCall(toolName: string): StopCondition { ) ?? false; } -export async function isStopConditionMet< - CONTEXT extends Context, - TOOLS extends ToolSet, ->({ +export async function isStopConditionMet({ stopConditions, steps, }: { - stopConditions: Array>; + stopConditions: Array>; steps: Array>; }): Promise { return ( diff --git a/packages/ai/src/generate-text/tool-call.ts b/packages/ai/src/generate-text/tool-call.ts index 831f93e3db25..59698ce0b550 100644 --- a/packages/ai/src/generate-text/tool-call.ts +++ b/packages/ai/src/generate-text/tool-call.ts @@ -10,19 +10,16 @@ type BaseToolCall = { providerMetadata?: ProviderMetadata; }; -export type StaticToolCall = ToolSet> = - ValueOf<{ - [NAME in keyof TOOLS]: BaseToolCall & { - toolName: NAME & string; - input: TOOLS[NAME] extends Tool - ? PARAMETERS - : never; - dynamic?: false | undefined; - invalid?: false | undefined; - error?: never; - title?: string; - }; - }>; +export type StaticToolCall = ValueOf<{ + [NAME in keyof TOOLS]: BaseToolCall & { + toolName: NAME & string; + input: TOOLS[NAME] extends Tool ? PARAMETERS : never; + dynamic?: false | undefined; + invalid?: false | undefined; + error?: never; + title?: string; + }; +}>; export type DynamicToolCall = BaseToolCall & { toolName: string; @@ -45,6 +42,6 @@ export type DynamicToolCall = BaseToolCall & { error?: unknown; }; -export type TypedToolCall = ToolSet> = +export type TypedToolCall = | StaticToolCall | DynamicToolCall; diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index 061410448768..3d7b8a9acd42 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -1,6 +1,6 @@ import { Context, Tool } from '@ai-sdk/provider-utils'; -export type ToolSet = Record< +export type ToolSet = Record< string, ( | Tool @@ -17,3 +17,6 @@ export type ToolSet = Record< | 'needsApproval' > >; + +export type InferContextFromToolSet = + TOOLS extends ToolSet ? CONTEXT : never; diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index d7745e029767..bb07f386ab39 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -62,7 +62,7 @@ export type ToolNeedsApprovalFunction = ( * * Experimental (can break in patch releases). */ - experimental_context?: CONTEXT; + experimental_context: CONTEXT; }, ) => boolean | PromiseLike; From 9c2d1fd153e28b1e83f49d19c98111f30ca81457 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 16:57:42 +0100 Subject: [PATCH 08/41] x --- .../ai/src/generate-text/generate-text.ts | 2 +- packages/ai/src/generate-text/tool-set.ts | 10 ++-- .../ai/src/telemetry/telemetry-integration.ts | 7 ++- .../src/provider-tool-factory.ts | 56 +++++++++---------- .../provider-utils/src/types/execute-tool.ts | 4 +- .../provider-utils/src/types/tool.test-d.ts | 20 +++---- packages/provider-utils/src/types/tool.ts | 46 +++++++-------- 7 files changed, 74 insertions(+), 71 deletions(-) diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 3a312235ac43..85f74a9df44e 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -250,7 +250,7 @@ export type GenerateTextOnFinishCallback< */ export async function generateText< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output = Output, >({ model: modelArg, diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index 3d7b8a9acd42..89a2658f862f 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -3,13 +3,13 @@ import { Context, Tool } from '@ai-sdk/provider-utils'; export type ToolSet = Record< string, ( - | Tool - | Tool - | Tool - | Tool + | Tool + | Tool + | Tool + | Tool ) & Pick< - Tool, + Tool, | 'execute' | 'onInputAvailable' | 'onInputStart' diff --git a/packages/ai/src/telemetry/telemetry-integration.ts b/packages/ai/src/telemetry/telemetry-integration.ts index 11d4eff8026b..3958728d9bdd 100644 --- a/packages/ai/src/telemetry/telemetry-integration.ts +++ b/packages/ai/src/telemetry/telemetry-integration.ts @@ -1,3 +1,4 @@ +import { Context } from '@ai-sdk/provider-utils'; import type { OnFinishEvent, OnStartEvent, @@ -16,9 +17,9 @@ import { Listener } from '../util/notify'; */ export interface TelemetryIntegration { onStart?: Listener>; - onStepStart?: Listener>; + onStepStart?: Listener>; onToolCallStart?: Listener>; onToolCallFinish?: Listener>; - onStepFinish?: Listener>; - onFinish?: Listener>; + onStepFinish?: Listener>; + onFinish?: Listener>; } diff --git a/packages/provider-utils/src/provider-tool-factory.ts b/packages/provider-utils/src/provider-tool-factory.ts index 73590059e9e9..9766b664a530 100644 --- a/packages/provider-utils/src/provider-tool-factory.ts +++ b/packages/provider-utils/src/provider-tool-factory.ts @@ -8,14 +8,14 @@ export type ProviderToolFactory< ARGS extends object, > = ( options: ARGS & { - execute?: ToolExecuteFunction; - needsApproval?: Tool['needsApproval']; - toModelOutput?: Tool['toModelOutput']; - onInputStart?: Tool['onInputStart']; - onInputDelta?: Tool['onInputDelta']; - onInputAvailable?: Tool['onInputAvailable']; + execute?: ToolExecuteFunction; + needsApproval?: Tool['needsApproval']; + toModelOutput?: Tool['toModelOutput']; + onInputStart?: Tool['onInputStart']; + onInputDelta?: Tool['onInputDelta']; + onInputAvailable?: Tool['onInputAvailable']; }, -) => Tool; +) => Tool; export function createProviderToolFactory< CONTEXT extends Context, @@ -38,14 +38,14 @@ export function createProviderToolFactory< onInputAvailable, ...args }: ARGS & { - execute?: ToolExecuteFunction; + execute?: ToolExecuteFunction; outputSchema?: FlexibleSchema; - needsApproval?: Tool['needsApproval']; - toModelOutput?: Tool['toModelOutput']; - onInputStart?: Tool['onInputStart']; - onInputDelta?: Tool['onInputDelta']; - onInputAvailable?: Tool['onInputAvailable']; - }): Tool => + needsApproval?: Tool['needsApproval']; + toModelOutput?: Tool['toModelOutput']; + onInputStart?: Tool['onInputStart']; + onInputDelta?: Tool['onInputDelta']; + onInputAvailable?: Tool['onInputAvailable']; + }): Tool => tool({ type: 'provider', id, @@ -68,14 +68,14 @@ export type ProviderToolFactoryWithOutputSchema< ARGS extends object, > = ( options: ARGS & { - execute?: ToolExecuteFunction; - needsApproval?: Tool['needsApproval']; - toModelOutput?: Tool['toModelOutput']; - onInputStart?: Tool['onInputStart']; - onInputDelta?: Tool['onInputDelta']; - onInputAvailable?: Tool['onInputAvailable']; + execute?: ToolExecuteFunction; + needsApproval?: Tool['needsApproval']; + toModelOutput?: Tool['toModelOutput']; + onInputStart?: Tool['onInputStart']; + onInputDelta?: Tool['onInputDelta']; + onInputAvailable?: Tool['onInputAvailable']; }, -) => Tool; +) => Tool; export function createProviderToolFactoryWithOutputSchema< CONTEXT extends Context, @@ -112,13 +112,13 @@ export function createProviderToolFactoryWithOutputSchema< onInputAvailable, ...args }: ARGS & { - execute?: ToolExecuteFunction; - needsApproval?: Tool['needsApproval']; - toModelOutput?: Tool['toModelOutput']; - onInputStart?: Tool['onInputStart']; - onInputDelta?: Tool['onInputDelta']; - onInputAvailable?: Tool['onInputAvailable']; - }): Tool => + execute?: ToolExecuteFunction; + needsApproval?: Tool['needsApproval']; + toModelOutput?: Tool['toModelOutput']; + onInputStart?: Tool['onInputStart']; + onInputDelta?: Tool['onInputDelta']; + onInputAvailable?: Tool['onInputAvailable']; + }): Tool => tool({ type: 'provider', id, diff --git a/packages/provider-utils/src/types/execute-tool.ts b/packages/provider-utils/src/types/execute-tool.ts index 68961ccb9c34..8d9add4183a2 100644 --- a/packages/provider-utils/src/types/execute-tool.ts +++ b/packages/provider-utils/src/types/execute-tool.ts @@ -2,12 +2,12 @@ import { isAsyncIterable } from '../is-async-iterable'; import { Context } from './context'; import { ToolExecuteFunction, ToolExecutionOptions } from './tool'; -export async function* executeTool({ +export async function* executeTool({ execute, input, options, }: { - execute: ToolExecuteFunction; + execute: ToolExecuteFunction; input: INPUT; options: ToolExecutionOptions; }): AsyncGenerator< diff --git a/packages/provider-utils/src/types/tool.test-d.ts b/packages/provider-utils/src/types/tool.test-d.ts index 8a3e9d1fba96..cc80184f7c7d 100644 --- a/packages/provider-utils/src/types/tool.test-d.ts +++ b/packages/provider-utils/src/types/tool.test-d.ts @@ -14,7 +14,7 @@ describe('tool type', () => { }); expectTypeOf(aTool).toEqualTypeOf< - Tool + Tool<{ number: number }, never, Context> >(); expectTypeOf(aTool.execute).toEqualTypeOf(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); @@ -28,7 +28,7 @@ describe('tool type', () => { inputSchema: null as unknown as FlexibleSchema, }); - expectTypeOf(aTool).toEqualTypeOf>(); + expectTypeOf(aTool).toEqualTypeOf>(); expectTypeOf(aTool.execute).toEqualTypeOf(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); expectTypeOf(aTool.inputSchema).toEqualTypeOf>(); @@ -81,10 +81,10 @@ describe('tool type', () => { }); expectTypeOf(aTool).toEqualTypeOf< - Tool + Tool<{ number: number }, 'test', Context> >(); expectTypeOf(aTool.execute).toMatchTypeOf< - ToolExecuteFunction | undefined + ToolExecuteFunction<{ number: number }, 'test', Context> | undefined >(); expectTypeOf(aTool.execute).not.toEqualTypeOf(); expectTypeOf(aTool.inputSchema).toEqualTypeOf< @@ -101,10 +101,10 @@ describe('tool type', () => { }); expectTypeOf(aTool).toEqualTypeOf< - Tool + Tool<{ number: number }, 'test', Context> >(); expectTypeOf(aTool.execute).toEqualTypeOf< - ToolExecuteFunction | undefined + ToolExecuteFunction<{ number: number }, 'test', Context> | undefined >(); expectTypeOf(aTool.inputSchema).toEqualTypeOf< FlexibleSchema<{ number: number }> @@ -182,7 +182,7 @@ describe('tool type', () => { expectTypeOf(options).toEqualTypeOf<{ toolCallId: string; messages: ModelMessage[]; - experimental_context?: Context | undefined; + experimental_context: Context; }>(); return true; }, @@ -195,7 +195,7 @@ describe('tool type', () => { options: { toolCallId: string; messages: ModelMessage[]; - experimental_context?: Context | undefined; + experimental_context: Context; }, ) => boolean | PromiseLike) | undefined @@ -211,7 +211,7 @@ describe('tool type', () => { expectTypeOf(options).toEqualTypeOf<{ toolCallId: string; messages: ModelMessage[]; - experimental_context?: Context | undefined; + experimental_context: Context; }>(); return true; }, @@ -224,7 +224,7 @@ describe('tool type', () => { options: { toolCallId: string; messages: ModelMessage[]; - experimental_context?: Context | undefined; + experimental_context: Context; }, ) => boolean | PromiseLike) | undefined diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index bb07f386ab39..ac3b9fa65e67 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -66,7 +66,7 @@ export type ToolNeedsApprovalFunction = ( }, ) => boolean | PromiseLike; -export type ToolExecuteFunction = ( +export type ToolExecuteFunction = ( input: INPUT, options: ToolExecutionOptions, ) => AsyncIterable | PromiseLike | OUTPUT; @@ -80,9 +80,9 @@ type NeverOptional = 0 extends 1 & N : T; type ToolOutputProperties< - CONTEXT extends Context, INPUT, OUTPUT, + CONTEXT extends Context, > = NeverOptional< OUTPUT, | { @@ -93,7 +93,7 @@ type ToolOutputProperties< * @args is the input of the tool call. * @options.abortSignal is a signal that can be used to abort the tool call. */ - execute: ToolExecuteFunction; + execute: ToolExecuteFunction; outputSchema?: FlexibleSchema; } @@ -111,9 +111,9 @@ type ToolOutputProperties< * The tool can also contain an optional execute function for the actual execution function of the tool. */ export type Tool< - CONTEXT extends Context = any, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any, + CONTEXT extends Context = any, > = { /** * An optional description of what the tool does. @@ -158,7 +158,7 @@ export type Tool< | boolean | ToolNeedsApprovalFunction< [INPUT] extends [never] ? unknown : INPUT, - CONTEXT + NoInfer >; /** @@ -175,7 +175,7 @@ export type Tool< * Only called when the tool is used in a streaming context. */ onInputStart?: ( - options: ToolExecutionOptions, + options: ToolExecutionOptions>, ) => void | PromiseLike; /** @@ -183,7 +183,9 @@ export type Tool< * Only called when the tool is used in a streaming context. */ onInputDelta?: ( - options: { inputTextDelta: string } & ToolExecutionOptions, + options: { inputTextDelta: string } & ToolExecutionOptions< + NoInfer + >, ) => void | PromiseLike; /** @@ -193,9 +195,9 @@ export type Tool< onInputAvailable?: ( options: { input: [INPUT] extends [never] ? unknown : INPUT; - } & ToolExecutionOptions, + } & ToolExecutionOptions>, ) => void | PromiseLike; -} & ToolOutputProperties & { +} & ToolOutputProperties> & { /** * Optional conversion function that maps the tool result to an output that can be used by the language model. * @@ -284,18 +286,18 @@ export type InferToolOutput> = * Helper function for inferring the execute args of a tool. */ // Note: overload order is important for auto-completion -export function tool( - tool: Tool, -): Tool; -export function tool( - tool: Tool, -): Tool; -export function tool( - tool: Tool, -): Tool; +export function tool( + tool: Tool, +): Tool; +export function tool( + tool: Tool, +): Tool; +export function tool( + tool: Tool, +): Tool; export function tool( - tool: Tool, -): Tool; + tool: Tool, +): Tool; export function tool(tool: any): any { return tool; } @@ -308,7 +310,7 @@ export function dynamicTool(tool: { title?: string; providerOptions?: ProviderOptions; inputSchema: FlexibleSchema; - execute: ToolExecuteFunction; + execute: ToolExecuteFunction; /** * Optional conversion function that maps the tool result to an output that can be used by the language model. @@ -336,7 +338,7 @@ export function dynamicTool(tool: { * Whether the tool needs approval before it can be executed. */ needsApproval?: boolean | ToolNeedsApprovalFunction; -}): Tool & { +}): Tool & { type: 'dynamic'; } { return { ...tool, type: 'dynamic' }; From aabce40976dfcc223fbfcb7796ed101c4ab1aa4d Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 17:00:48 +0100 Subject: [PATCH 09/41] a --- packages/provider-utils/src/types/execute-tool.ts | 2 +- packages/provider-utils/src/types/tool.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/provider-utils/src/types/execute-tool.ts b/packages/provider-utils/src/types/execute-tool.ts index 8d9add4183a2..41f83de3721c 100644 --- a/packages/provider-utils/src/types/execute-tool.ts +++ b/packages/provider-utils/src/types/execute-tool.ts @@ -9,7 +9,7 @@ export async function* executeTool({ }: { execute: ToolExecuteFunction; input: INPUT; - options: ToolExecutionOptions; + options: ToolExecutionOptions>; }): AsyncGenerator< { type: 'preliminary'; output: OUTPUT } | { type: 'final'; output: OUTPUT } > { diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index ac3b9fa65e67..f6bd56d9dd4f 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -273,14 +273,14 @@ export type Tool< /** * Infer the input type of a tool. */ -export type InferToolInput> = - TOOL extends Tool ? INPUT : never; +export type InferToolInput = + TOOL extends Tool ? INPUT : never; /** * Infer the output type of a tool. */ -export type InferToolOutput> = - TOOL extends Tool ? OUTPUT : never; +export type InferToolOutput = + TOOL extends Tool ? OUTPUT : never; /** * Helper function for inferring the execute args of a tool. From 50c741f5cd21fa2c7484d8e7be91e74fe9d4d0e8 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 17:13:42 +0100 Subject: [PATCH 10/41] x --- .../ai/src/generate-text/callback-events.ts | 6 +-- .../ai/src/generate-text/execute-tool-call.ts | 2 +- .../ai/src/generate-text/generate-text.ts | 48 +++++++++++-------- packages/ai/src/generate-text/tool-set.ts | 17 ++++--- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index 697a711002d3..c0b7c9d79f61 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -131,7 +131,7 @@ export interface OnStartEvent< */ export interface OnStepStartEvent< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -302,7 +302,7 @@ export type OnToolCallFinishEvent = { */ export type OnStepFinishEvent< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, > = StepResult; /** @@ -313,7 +313,7 @@ export type OnStepFinishEvent< */ export type OnFinishEvent< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, > = StepResult & { /** Array containing results from all steps in the generation. */ readonly steps: StepResult[]; diff --git a/packages/ai/src/generate-text/execute-tool-call.ts b/packages/ai/src/generate-text/execute-tool-call.ts index 80a311f0c90d..a52391a49356 100644 --- a/packages/ai/src/generate-text/execute-tool-call.ts +++ b/packages/ai/src/generate-text/execute-tool-call.ts @@ -29,7 +29,7 @@ import { TypedToolError } from './tool-error'; */ export async function executeToolCall< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, >({ toolCall, tools, diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 85f74a9df44e..b356c8b11e03 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -131,10 +131,15 @@ export type GenerateTextOnStartCallback< */ export type GenerateTextOnStepStartCallback< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, > = ( - event: OnStepStartEvent, + event: OnStepStartEvent< + NoInfer, + TOOLS, + OUTPUT, + GenerateTextIncludeSettings + >, ) => PromiseLike | void; /** @@ -146,8 +151,7 @@ export type GenerateTextOnStepStartCallback< * @param event - The event object containing tool call information. */ export type GenerateTextOnToolCallStartCallback< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, > = (event: OnToolCallStartEvent) => PromiseLike | void; /** @@ -163,8 +167,7 @@ export type GenerateTextOnToolCallStartCallback< * @param event - The event object containing tool call result information. */ export type GenerateTextOnToolCallFinishCallback< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, > = (event: OnToolCallFinishEvent) => PromiseLike | void; /** @@ -177,7 +180,7 @@ export type GenerateTextOnToolCallFinishCallback< */ export type GenerateTextOnStepFinishCallback< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, > = (event: OnStepFinishEvent) => Promise | void; /** @@ -191,7 +194,7 @@ export type GenerateTextOnStepFinishCallback< */ export type GenerateTextOnFinishCallback< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, > = (event: OnFinishEvent) => PromiseLike | void; /** @@ -250,7 +253,7 @@ export type GenerateTextOnFinishCallback< */ export async function generateText< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output = Output, >({ model: modelArg, @@ -274,7 +277,7 @@ export async function generateText< prepareStep = experimental_prepareStep, experimental_repairToolCall: repairToolCall, experimental_download: download, - experimental_context = {} as CONTEXT, + experimental_context = {} as NoInfer, experimental_include: include, _internal: { generateId = originalGenerateId } = {}, experimental_onStart: onStart, @@ -356,12 +359,15 @@ export async function generateText< /** * @deprecated Use `prepareStep` instead. */ - experimental_prepareStep?: PrepareStepFunction>; + experimental_prepareStep?: PrepareStepFunction< + NoInfer, + NoInfer + >; /** * Optional function that you can use to provide different settings for a step. */ - prepareStep?: PrepareStepFunction>; + prepareStep?: PrepareStepFunction, NoInfer>; /** * A function that attempts to repair a tool call that failed to parse. @@ -379,7 +385,7 @@ export async function generateText< * before the provider is called. */ experimental_onStepStart?: GenerateTextOnStepStartCallback< - CONTEXT, + NoInfer, NoInfer, OUTPUT >; @@ -401,12 +407,15 @@ export async function generateText< /** * Callback that is called when each step (LLM call) is finished, including intermediate steps. */ - onStepFinish?: GenerateTextOnStepFinishCallback>; + onStepFinish?: GenerateTextOnStepFinishCallback< + NoInfer, + NoInfer + >; /** * Callback that is called when all steps are finished and the response is complete. */ - onFinish?: GenerateTextOnFinishCallback>; + onFinish?: GenerateTextOnFinishCallback, NoInfer>; /** * Context that is passed into tool execution. @@ -1227,10 +1236,7 @@ export async function generateText< } } -async function executeTools< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, ->({ +async function executeTools({ toolCalls, tools, tracer, @@ -1286,7 +1292,7 @@ async function executeTools< class DefaultGenerateTextResult< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output, > implements GenerateTextResult { @@ -1413,7 +1419,7 @@ function asToolCalls(content: Array) { })); } -function asContent>({ +function asContent({ content, toolCalls, toolOutputs, diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index 89a2658f862f..3abaa47fd0c0 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -1,15 +1,15 @@ import { Context, Tool } from '@ai-sdk/provider-utils'; -export type ToolSet = Record< +export type ToolSet = Record< string, ( - | Tool - | Tool - | Tool - | Tool + | Tool + | Tool + | Tool + | Tool ) & Pick< - Tool, + Tool, | 'execute' | 'onInputAvailable' | 'onInputStart' @@ -18,5 +18,8 @@ export type ToolSet = Record< > >; +export type InferContextFromTool = + TOOL extends Tool ? CONTEXT : never; + export type InferContextFromToolSet = - TOOLS extends ToolSet ? CONTEXT : never; + TOOLS extends ToolSet ? InferContextFromTool : never; From 1427dcc008ff1ed91d6df135584c0c6f16354d1a Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Thu, 5 Mar 2026 17:18:32 +0100 Subject: [PATCH 11/41] Revert "x" This reverts commit 50c741f5cd21fa2c7484d8e7be91e74fe9d4d0e8. --- .../ai/src/generate-text/callback-events.ts | 6 +-- .../ai/src/generate-text/execute-tool-call.ts | 2 +- .../ai/src/generate-text/generate-text.ts | 48 ++++++++----------- packages/ai/src/generate-text/tool-set.ts | 17 +++---- 4 files changed, 32 insertions(+), 41 deletions(-) diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index c0b7c9d79f61..697a711002d3 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -131,7 +131,7 @@ export interface OnStartEvent< */ export interface OnStepStartEvent< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -302,7 +302,7 @@ export type OnToolCallFinishEvent = { */ export type OnStepFinishEvent< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, > = StepResult; /** @@ -313,7 +313,7 @@ export type OnStepFinishEvent< */ export type OnFinishEvent< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, > = StepResult & { /** Array containing results from all steps in the generation. */ readonly steps: StepResult[]; diff --git a/packages/ai/src/generate-text/execute-tool-call.ts b/packages/ai/src/generate-text/execute-tool-call.ts index a52391a49356..80a311f0c90d 100644 --- a/packages/ai/src/generate-text/execute-tool-call.ts +++ b/packages/ai/src/generate-text/execute-tool-call.ts @@ -29,7 +29,7 @@ import { TypedToolError } from './tool-error'; */ export async function executeToolCall< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, >({ toolCall, tools, diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index b356c8b11e03..85f74a9df44e 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -131,15 +131,10 @@ export type GenerateTextOnStartCallback< */ export type GenerateTextOnStepStartCallback< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, OUTPUT extends Output = Output, > = ( - event: OnStepStartEvent< - NoInfer, - TOOLS, - OUTPUT, - GenerateTextIncludeSettings - >, + event: OnStepStartEvent, ) => PromiseLike | void; /** @@ -151,7 +146,8 @@ export type GenerateTextOnStepStartCallback< * @param event - The event object containing tool call information. */ export type GenerateTextOnToolCallStartCallback< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, > = (event: OnToolCallStartEvent) => PromiseLike | void; /** @@ -167,7 +163,8 @@ export type GenerateTextOnToolCallStartCallback< * @param event - The event object containing tool call result information. */ export type GenerateTextOnToolCallFinishCallback< - TOOLS extends ToolSet = ToolSet, + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, > = (event: OnToolCallFinishEvent) => PromiseLike | void; /** @@ -180,7 +177,7 @@ export type GenerateTextOnToolCallFinishCallback< */ export type GenerateTextOnStepFinishCallback< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet = ToolSet, > = (event: OnStepFinishEvent) => Promise | void; /** @@ -194,7 +191,7 @@ export type GenerateTextOnStepFinishCallback< */ export type GenerateTextOnFinishCallback< CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet = ToolSet, > = (event: OnFinishEvent) => PromiseLike | void; /** @@ -253,7 +250,7 @@ export type GenerateTextOnFinishCallback< */ export async function generateText< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output = Output, >({ model: modelArg, @@ -277,7 +274,7 @@ export async function generateText< prepareStep = experimental_prepareStep, experimental_repairToolCall: repairToolCall, experimental_download: download, - experimental_context = {} as NoInfer, + experimental_context = {} as CONTEXT, experimental_include: include, _internal: { generateId = originalGenerateId } = {}, experimental_onStart: onStart, @@ -359,15 +356,12 @@ export async function generateText< /** * @deprecated Use `prepareStep` instead. */ - experimental_prepareStep?: PrepareStepFunction< - NoInfer, - NoInfer - >; + experimental_prepareStep?: PrepareStepFunction>; /** * Optional function that you can use to provide different settings for a step. */ - prepareStep?: PrepareStepFunction, NoInfer>; + prepareStep?: PrepareStepFunction>; /** * A function that attempts to repair a tool call that failed to parse. @@ -385,7 +379,7 @@ export async function generateText< * before the provider is called. */ experimental_onStepStart?: GenerateTextOnStepStartCallback< - NoInfer, + CONTEXT, NoInfer, OUTPUT >; @@ -407,15 +401,12 @@ export async function generateText< /** * Callback that is called when each step (LLM call) is finished, including intermediate steps. */ - onStepFinish?: GenerateTextOnStepFinishCallback< - NoInfer, - NoInfer - >; + onStepFinish?: GenerateTextOnStepFinishCallback>; /** * Callback that is called when all steps are finished and the response is complete. */ - onFinish?: GenerateTextOnFinishCallback, NoInfer>; + onFinish?: GenerateTextOnFinishCallback>; /** * Context that is passed into tool execution. @@ -1236,7 +1227,10 @@ export async function generateText< } } -async function executeTools({ +async function executeTools< + CONTEXT extends Context, + TOOLS extends ToolSet = ToolSet, +>({ toolCalls, tools, tracer, @@ -1292,7 +1286,7 @@ async function executeTools({ class DefaultGenerateTextResult< CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, OUTPUT extends Output, > implements GenerateTextResult { @@ -1419,7 +1413,7 @@ function asToolCalls(content: Array) { })); } -function asContent({ +function asContent>({ content, toolCalls, toolOutputs, diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index 3abaa47fd0c0..89a2658f862f 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -1,15 +1,15 @@ import { Context, Tool } from '@ai-sdk/provider-utils'; -export type ToolSet = Record< +export type ToolSet = Record< string, ( - | Tool - | Tool - | Tool - | Tool + | Tool + | Tool + | Tool + | Tool ) & Pick< - Tool, + Tool, | 'execute' | 'onInputAvailable' | 'onInputStart' @@ -18,8 +18,5 @@ export type ToolSet = Record< > >; -export type InferContextFromTool = - TOOL extends Tool ? CONTEXT : never; - export type InferContextFromToolSet = - TOOLS extends ToolSet ? InferContextFromTool : never; + TOOLS extends ToolSet ? CONTEXT : never; From 920adb5aac8ffde76ff381fbfb1525656dfea30e Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Fri, 6 Mar 2026 15:50:43 +0100 Subject: [PATCH 12/41] x --- .../ai-functions/src/test/typed-context-2.ts | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 examples/ai-functions/src/test/typed-context-2.ts diff --git a/examples/ai-functions/src/test/typed-context-2.ts b/examples/ai-functions/src/test/typed-context-2.ts new file mode 100644 index 000000000000..25b0f3c429b6 --- /dev/null +++ b/examples/ai-functions/src/test/typed-context-2.ts @@ -0,0 +1,72 @@ +import { FlexibleSchema } from 'ai'; +import { run } from '../lib/run'; +import { z } from 'zod'; + +interface Tool { + inputSchema: FlexibleSchema; + contextSchema: FlexibleSchema; + execute: (input: NoInfer, context: NoInfer) => unknown; +} + +function tool(options: { + inputSchema: FlexibleSchema; + contextSchema: FlexibleSchema; + execute: (input: NoInfer, context: NoInfer) => unknown; +}) { + return options; +} + +type InferToolInput = + TOOL extends Tool ? INPUT : never; +type InferToolContext = + TOOL extends Tool ? CONTEXT : never; + +export type ToolSet = Record>; + +// should be a union of all the context types of the tools +type InferToolSetContext = { + [K in keyof TOOLS]: InferToolContext; +}[keyof TOOLS]; + +function executeTool({ + tools, + toolName, + input, + context, +}: { + tools: TOOLS; + toolName: keyof TOOLS; + input: InferToolInput; + context: InferToolSetContext; +}) { + const tool = tools[toolName]; + return tool.execute(input, context); +} + +run(async () => { + const tool1 = tool({ + inputSchema: z.object({ input1: z.string() }), + contextSchema: z.object({ context1: z.number() }), + execute: async ({ input1 }, { context1 }) => { + console.log(input1, context1); + }, + }); + + const tool2 = tool({ + inputSchema: z.object({ input2: z.number() }), + contextSchema: z.object({ context2: z.string() }), + execute: async ({ input2 }, { context2 }) => { + console.log(input2, context2); + }, + }); + + executeTool({ + tools: { + tool1, + tool2, + }, + toolName: 'tool1', + input: { input1: 'Hello' }, + context: { context1: 1, context2: 'Hello' }, + }); +}); From ba939e152a06f5f65eef64fb93467c4e085083d9 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Fri, 6 Mar 2026 15:57:46 +0100 Subject: [PATCH 13/41] perf --- .../ai-functions/src/test/typed-context-2.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/examples/ai-functions/src/test/typed-context-2.ts b/examples/ai-functions/src/test/typed-context-2.ts index 25b0f3c429b6..25d9e4502afe 100644 --- a/examples/ai-functions/src/test/typed-context-2.ts +++ b/examples/ai-functions/src/test/typed-context-2.ts @@ -23,10 +23,18 @@ type InferToolContext = export type ToolSet = Record>; +type UnionToIntersection = ( + U extends unknown ? (arg: U) => void : never +) extends (arg: infer I) => void + ? I + : never; + // should be a union of all the context types of the tools -type InferToolSetContext = { - [K in keyof TOOLS]: InferToolContext; -}[keyof TOOLS]; +type InferToolSetContext = UnionToIntersection< + { + [K in keyof TOOLS]: InferToolContext; + }[keyof TOOLS] +>; function executeTool({ tools, @@ -37,7 +45,7 @@ function executeTool({ tools: TOOLS; toolName: keyof TOOLS; input: InferToolInput; - context: InferToolSetContext; + context: InferToolSetContext & Record; }) { const tool = tools[toolName]; return tool.execute(input, context); @@ -67,6 +75,6 @@ run(async () => { }, toolName: 'tool1', input: { input1: 'Hello' }, - context: { context1: 1, context2: 'Hello' }, + context: { context1: 1, context2: 'world', somethingElse: 'context' }, }); }); From 509417d363105fb99463f59a5a9d7df3cb878e1b Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:01:52 +0100 Subject: [PATCH 14/41] a --- examples/ai-functions/src/test/typed-context-2.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/ai-functions/src/test/typed-context-2.ts b/examples/ai-functions/src/test/typed-context-2.ts index 25d9e4502afe..818a8ea1966a 100644 --- a/examples/ai-functions/src/test/typed-context-2.ts +++ b/examples/ai-functions/src/test/typed-context-2.ts @@ -36,7 +36,11 @@ type InferToolSetContext = UnionToIntersection< }[keyof TOOLS] >; -function executeTool({ +// TODO prepareStep +function executeTool< + TOOLS extends ToolSet, + CONTEXT extends InferToolSetContext & Record, +>({ tools, toolName, input, @@ -45,7 +49,8 @@ function executeTool({ tools: TOOLS; toolName: keyof TOOLS; input: InferToolInput; - context: InferToolSetContext & Record; + context: CONTEXT; + prepareStep: (context: CONTEXT) => void; }) { const tool = tools[toolName]; return tool.execute(input, context); @@ -76,5 +81,8 @@ run(async () => { toolName: 'tool1', input: { input1: 'Hello' }, context: { context1: 1, context2: 'world', somethingElse: 'context' }, + prepareStep: context => { + console.log(context); + }, }); }); From 8e273175a68e047cc9f2cd5396fa0cec4f5146c9 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:02:15 +0100 Subject: [PATCH 15/41] x --- examples/ai-functions/src/test/typed-context-2.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/ai-functions/src/test/typed-context-2.ts b/examples/ai-functions/src/test/typed-context-2.ts index 818a8ea1966a..143a92f9285f 100644 --- a/examples/ai-functions/src/test/typed-context-2.ts +++ b/examples/ai-functions/src/test/typed-context-2.ts @@ -36,7 +36,6 @@ type InferToolSetContext = UnionToIntersection< }[keyof TOOLS] >; -// TODO prepareStep function executeTool< TOOLS extends ToolSet, CONTEXT extends InferToolSetContext & Record, From 7e9fbd9c5cc9a0ea3b18a8599f5dd8e283d48003 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:05:02 +0100 Subject: [PATCH 16/41] x --- examples/ai-functions/src/test/typed-context-2.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/ai-functions/src/test/typed-context-2.ts b/examples/ai-functions/src/test/typed-context-2.ts index 143a92f9285f..339238bab09b 100644 --- a/examples/ai-functions/src/test/typed-context-2.ts +++ b/examples/ai-functions/src/test/typed-context-2.ts @@ -36,9 +36,12 @@ type InferToolSetContext = UnionToIntersection< }[keyof TOOLS] >; +type ExpandedContext = InferToolSetContext & + Record; + function executeTool< TOOLS extends ToolSet, - CONTEXT extends InferToolSetContext & Record, + CONTEXT extends ExpandedContext, >({ tools, toolName, From c5bc3810d70328e97c767a101e284c3f9b079187 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:06:45 +0100 Subject: [PATCH 17/41] x --- examples/ai-functions/src/test/typed-context-2.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/ai-functions/src/test/typed-context-2.ts b/examples/ai-functions/src/test/typed-context-2.ts index 339238bab09b..e34507f0d7ea 100644 --- a/examples/ai-functions/src/test/typed-context-2.ts +++ b/examples/ai-functions/src/test/typed-context-2.ts @@ -2,13 +2,15 @@ import { FlexibleSchema } from 'ai'; import { run } from '../lib/run'; import { z } from 'zod'; -interface Tool { +type Context = Record; + +interface Tool { inputSchema: FlexibleSchema; contextSchema: FlexibleSchema; execute: (input: NoInfer, context: NoInfer) => unknown; } -function tool(options: { +function tool(options: { inputSchema: FlexibleSchema; contextSchema: FlexibleSchema; execute: (input: NoInfer, context: NoInfer) => unknown; @@ -37,7 +39,7 @@ type InferToolSetContext = UnionToIntersection< >; type ExpandedContext = InferToolSetContext & - Record; + Context; function executeTool< TOOLS extends ToolSet, From c41b21cb5875fc2f0a66fea02a5e991d23317309 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:31:29 +0100 Subject: [PATCH 18/41] x --- .../ai/src/generate-text/callback-events.ts | 23 +++--- .../ai/src/generate-text/execute-tool-call.ts | 6 +- .../ai/src/generate-text/generate-text.ts | 77 ++++++++++--------- packages/ai/src/generate-text/step-result.ts | 66 ++++++++-------- .../ai/src/generate-text/stop-condition.ts | 22 ++++-- .../tool-approval-request-output.ts | 8 +- .../tool-call-repair-function.ts | 6 +- packages/ai/src/generate-text/tool-error.ts | 16 ++-- packages/ai/src/generate-text/tool-set.ts | 25 +++--- .../ai/src/telemetry/telemetry-integration.ts | 6 +- packages/ai/src/util/union-to-intersection.ts | 17 ++++ packages/provider-utils/src/types/index.ts | 3 +- packages/provider-utils/src/types/tool.ts | 8 +- 13 files changed, 155 insertions(+), 128 deletions(-) create mode 100644 packages/ai/src/util/union-to-intersection.ts diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index 697a711002d3..0968b784ee8f 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -1,6 +1,5 @@ import type { LanguageModelV4ToolChoice } from '@ai-sdk/provider'; import type { - Context, ModelMessage, ProviderOptions, SystemModelMessage, @@ -12,7 +11,7 @@ import type { Output } from './output'; import type { StepResult } from './step-result'; import type { StopCondition } from './stop-condition'; import type { TypedToolCall } from './tool-call'; -import type { ToolSet } from './tool-set'; +import type { ExpandedContext, ToolSet } from './tool-set'; /** * Common model information used across callback events. @@ -130,8 +129,8 @@ export interface OnStartEvent< * Each step represents a single LLM invocation. */ export interface OnStepStartEvent< - CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -167,7 +166,7 @@ export interface OnStepStartEvent< readonly activeTools: Array | undefined; /** Array of results from previous steps (empty for first step). */ - readonly steps: ReadonlyArray>; + readonly steps: ReadonlyArray>; /** Additional provider-specific options for this step. */ readonly providerOptions: ProviderOptions | undefined; @@ -301,9 +300,9 @@ export type OnToolCallFinishEvent = { * This is simply the StepResult for that step. */ export type OnStepFinishEvent< - CONTEXT extends Context, - TOOLS extends ToolSet, -> = StepResult; + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> = StepResult; /** * Event passed to the `onFinish` callback. @@ -312,11 +311,11 @@ export type OnStepFinishEvent< * Includes the final step's result along with aggregated data from all steps. */ export type OnFinishEvent< - CONTEXT extends Context, - TOOLS extends ToolSet, -> = StepResult & { + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> = StepResult & { /** Array containing results from all steps in the generation. */ - readonly steps: StepResult[]; + readonly steps: StepResult[]; /** Aggregated token usage across all steps. */ readonly totalUsage: LanguageModelUsage; diff --git a/packages/ai/src/generate-text/execute-tool-call.ts b/packages/ai/src/generate-text/execute-tool-call.ts index 80a311f0c90d..ce9f5ef4ce80 100644 --- a/packages/ai/src/generate-text/execute-tool-call.ts +++ b/packages/ai/src/generate-text/execute-tool-call.ts @@ -12,7 +12,7 @@ import { } from './generate-text'; import { TypedToolCall } from './tool-call'; import { ToolOutput } from './tool-output'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; import { TypedToolResult } from './tool-result'; import { TypedToolError } from './tool-error'; @@ -28,8 +28,8 @@ import { TypedToolError } from './tool-error'; * @returns The tool output (result or error), or undefined if the tool has no execute function. */ export async function executeToolCall< - CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, >({ toolCall, tools, diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 63c575dc75a1..bbe46c70147c 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -89,7 +89,7 @@ import { ToolCallRepairFunction } from './tool-call-repair-function'; import { TypedToolError } from './tool-error'; import { ToolOutput } from './tool-output'; import { TypedToolResult } from './tool-result'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, InferToolSetContext, ToolSet } from './tool-set'; const originalGenerateId = createIdGenerator({ prefix: 'aitxt', @@ -130,11 +130,11 @@ export type GenerateTextOnStartCallback< * @param event - The event object containing step configuration. */ export type GenerateTextOnStepStartCallback< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, OUTPUT extends Output = Output, > = ( - event: OnStepStartEvent, + event: OnStepStartEvent, ) => PromiseLike | void; /** @@ -145,10 +145,9 @@ export type GenerateTextOnStepStartCallback< * * @param event - The event object containing tool call information. */ -export type GenerateTextOnToolCallStartCallback< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = (event: OnToolCallStartEvent) => PromiseLike | void; +export type GenerateTextOnToolCallStartCallback = ( + event: OnToolCallStartEvent, +) => PromiseLike | void; /** * Callback that is set using the `experimental_onToolCallFinish` option. @@ -162,10 +161,9 @@ export type GenerateTextOnToolCallStartCallback< * * @param event - The event object containing tool call result information. */ -export type GenerateTextOnToolCallFinishCallback< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = (event: OnToolCallFinishEvent) => PromiseLike | void; +export type GenerateTextOnToolCallFinishCallback = ( + event: OnToolCallFinishEvent, +) => PromiseLike | void; /** * Callback that is set using the `onStepFinish` option. @@ -176,9 +174,9 @@ export type GenerateTextOnToolCallFinishCallback< * @param stepResult - The result of the step. */ export type GenerateTextOnStepFinishCallback< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = (event: OnStepFinishEvent) => Promise | void; + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> = (event: OnStepFinishEvent) => Promise | void; /** * Callback that is set using the `onFinish` option. @@ -190,9 +188,9 @@ export type GenerateTextOnStepFinishCallback< * @param event - The final result along with aggregated step data. */ export type GenerateTextOnFinishCallback< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = (event: OnFinishEvent) => PromiseLike | void; + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> = (event: OnFinishEvent) => PromiseLike | void; /** * Generate a text and call tools for a given prompt using a language model. @@ -249,8 +247,8 @@ export type GenerateTextOnFinishCallback< * A result object that contains the generated text, the results of the tool calls, and additional information. */ export async function generateText< - CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, OUTPUT extends Output = Output, >({ model: modelArg, @@ -308,8 +306,8 @@ export async function generateText< * @default stepCountIs(1) */ stopWhen?: - | StopCondition> - | Array>>; + | StopCondition, CONTEXT> + | Array, CONTEXT>>; /** * Optional telemetry configuration (experimental). @@ -379,8 +377,8 @@ export async function generateText< * before the provider is called. */ experimental_onStepStart?: GenerateTextOnStepStartCallback< + TOOLS, CONTEXT, - NoInfer, OUTPUT >; @@ -401,12 +399,12 @@ export async function generateText< /** * Callback that is called when each step (LLM call) is finished, including intermediate steps. */ - onStepFinish?: GenerateTextOnStepFinishCallback>; + onStepFinish?: GenerateTextOnStepFinishCallback; /** * Callback that is called when all steps are finished and the response is complete. */ - onFinish?: GenerateTextOnFinishCallback>; + onFinish?: GenerateTextOnFinishCallback; /** * Context that is passed into tool execution. @@ -667,7 +665,7 @@ export async function generateText< > & { response: { id: string; timestamp: Date; modelId: string } }; let clientToolCalls: Array> = []; let clientToolOutputs: Array> = []; - const steps: StepResult[] = []; + const steps: StepResult[] = []; // Track provider-executed tool calls that support deferred results // (e.g., code_execution in programmatic tool calling scenarios). @@ -763,7 +761,7 @@ export async function generateText< onStepStart, globalTelemetry.onStepStart as | undefined - | GenerateTextOnStepStartCallback, + | GenerateTextOnStepStartCallback, ], }); @@ -1082,7 +1080,7 @@ export async function generateText< const stepNumber = steps.length; - const currentStepResult: StepResult = + const currentStepResult: StepResult = new DefaultStepResult({ stepNumber, model: stepModelInfo, @@ -1111,7 +1109,12 @@ export async function generateText< await notify({ event: currentStepResult, - callbacks: [onStepFinish, globalTelemetry.onStepFinish], + callbacks: [ + onStepFinish, + globalTelemetry.onStepFinish as + | undefined + | GenerateTextOnStepFinishCallback, + ], }); } finally { if (stepTimeoutId != null) { @@ -1231,7 +1234,7 @@ export async function generateText< onFinish, globalTelemetry.onFinish as | undefined - | GenerateTextOnFinishCallback, + | GenerateTextOnFinishCallback, ], }); @@ -1262,8 +1265,8 @@ export async function generateText< } async function executeTools< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, >({ toolCalls, tools, @@ -1319,17 +1322,17 @@ async function executeTools< } class DefaultGenerateTextResult< - CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, OUTPUT extends Output, > implements GenerateTextResult { - readonly steps: StepResult[]; + readonly steps: StepResult[]; readonly totalUsage: LanguageModelUsage; private readonly _output: InferCompleteOutput | undefined; constructor(options: { - steps: StepResult[]; + steps: StepResult[]; output: InferCompleteOutput | undefined; totalUsage: LanguageModelUsage; }) { @@ -1447,7 +1450,7 @@ function asToolCalls(content: Array) { })); } -function asContent>({ +function asContent({ content, toolCalls, toolOutputs, diff --git a/packages/ai/src/generate-text/step-result.ts b/packages/ai/src/generate-text/step-result.ts index 6574053e66a5..dee7eeba39ab 100644 --- a/packages/ai/src/generate-text/step-result.ts +++ b/packages/ai/src/generate-text/step-result.ts @@ -17,14 +17,14 @@ import { StaticToolResult, TypedToolResult, } from './tool-result'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; /** * The result of a single step in the generation process. */ export type StepResult< - CONTEXT extends Context, - TOOLS extends ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, > = { /** * Zero-based index of this step. @@ -169,26 +169,26 @@ export type StepResult< }; export class DefaultStepResult< - CONTEXT extends Context, - TOOLS extends ToolSet, -> implements StepResult + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> implements StepResult { - readonly stepNumber: StepResult['stepNumber']; - readonly model: StepResult['model']; - readonly functionId: StepResult['functionId']; - readonly metadata: StepResult['metadata']; + readonly stepNumber: StepResult['stepNumber']; + readonly model: StepResult['model']; + readonly functionId: StepResult['functionId']; + readonly metadata: StepResult['metadata']; readonly experimental_context: StepResult< - CONTEXT, - TOOLS + TOOLS, + CONTEXT >['experimental_context']; - readonly content: StepResult['content']; - readonly finishReason: StepResult['finishReason']; - readonly rawFinishReason: StepResult['rawFinishReason']; - readonly usage: StepResult['usage']; - readonly warnings: StepResult['warnings']; - readonly request: StepResult['request']; - readonly response: StepResult['response']; - readonly providerMetadata: StepResult['providerMetadata']; + readonly content: StepResult['content']; + readonly finishReason: StepResult['finishReason']; + readonly rawFinishReason: StepResult['rawFinishReason']; + readonly usage: StepResult['usage']; + readonly warnings: StepResult['warnings']; + readonly request: StepResult['request']; + readonly response: StepResult['response']; + readonly providerMetadata: StepResult['providerMetadata']; constructor({ stepNumber, @@ -205,19 +205,19 @@ export class DefaultStepResult< response, providerMetadata, }: { - stepNumber: StepResult['stepNumber']; - model: StepResult['model']; - functionId: StepResult['functionId']; - metadata: StepResult['metadata']; - experimental_context: StepResult['experimental_context']; - content: StepResult['content']; - finishReason: StepResult['finishReason']; - rawFinishReason: StepResult['rawFinishReason']; - usage: StepResult['usage']; - warnings: StepResult['warnings']; - request: StepResult['request']; - response: StepResult['response']; - providerMetadata: StepResult['providerMetadata']; + stepNumber: StepResult['stepNumber']; + model: StepResult['model']; + functionId: StepResult['functionId']; + metadata: StepResult['metadata']; + experimental_context: StepResult['experimental_context']; + content: StepResult['content']; + finishReason: StepResult['finishReason']; + rawFinishReason: StepResult['rawFinishReason']; + usage: StepResult['usage']; + warnings: StepResult['warnings']; + request: StepResult['request']; + response: StepResult['response']; + providerMetadata: StepResult['providerMetadata']; }) { this.stepNumber = stepNumber; this.model = model; diff --git a/packages/ai/src/generate-text/stop-condition.ts b/packages/ai/src/generate-text/stop-condition.ts index 239e8d77ff63..8973618a279b 100644 --- a/packages/ai/src/generate-text/stop-condition.ts +++ b/packages/ai/src/generate-text/stop-condition.ts @@ -1,28 +1,34 @@ import { Context } from '@ai-sdk/provider-utils'; import { StepResult } from './step-result'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; -export type StopCondition = (options: { - steps: Array>; +export type StopCondition< + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> = (options: { + steps: Array>; }) => PromiseLike | boolean; -export function stepCountIs(stepCount: number): StopCondition { +export function stepCountIs(stepCount: number): StopCondition { return ({ steps }) => steps.length === stepCount; } -export function hasToolCall(toolName: string): StopCondition { +export function hasToolCall(toolName: string): StopCondition { return ({ steps }) => steps[steps.length - 1]?.toolCalls?.some( toolCall => toolCall.toolName === toolName, ) ?? false; } -export async function isStopConditionMet({ +export async function isStopConditionMet< + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +>({ stopConditions, steps, }: { - stopConditions: Array>; - steps: Array>; + stopConditions: Array>; + steps: Array>; }): Promise { return ( await Promise.all(stopConditions.map(condition => condition({ steps }))) diff --git a/packages/ai/src/generate-text/tool-approval-request-output.ts b/packages/ai/src/generate-text/tool-approval-request-output.ts index b43ac9f11bc2..2880b67e0a78 100644 --- a/packages/ai/src/generate-text/tool-approval-request-output.ts +++ b/packages/ai/src/generate-text/tool-approval-request-output.ts @@ -1,4 +1,3 @@ -import { ContextRegistry } from '@ai-sdk/provider-utils'; import { TypedToolCall } from './tool-call'; import { ToolSet } from './tool-set'; @@ -7,10 +6,7 @@ import { ToolSet } from './tool-set'; * * The tool approval request can be approved or denied in the next tool message. */ -export type ToolApprovalRequestOutput< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = { +export type ToolApprovalRequestOutput = { type: 'tool-approval-request'; /** @@ -21,5 +17,5 @@ export type ToolApprovalRequestOutput< /** * Tool call that the approval request is for. */ - toolCall: TypedToolCall; + toolCall: TypedToolCall; }; diff --git a/packages/ai/src/generate-text/tool-call-repair-function.ts b/packages/ai/src/generate-text/tool-call-repair-function.ts index 49f6f7e62983..d747916c6ff0 100644 --- a/packages/ai/src/generate-text/tool-call-repair-function.ts +++ b/packages/ai/src/generate-text/tool-call-repair-function.ts @@ -1,5 +1,4 @@ import { JSONSchema7, LanguageModelV4ToolCall } from '@ai-sdk/provider'; -import { Context } from '@ai-sdk/provider-utils'; import { InvalidToolInputError } from '../error/invalid-tool-input-error'; import { NoSuchToolError } from '../error/no-such-tool-error'; import { ModelMessage, SystemModelMessage } from '../prompt'; @@ -18,10 +17,7 @@ import { ToolSet } from './tool-set'; * @param options.inputSchema - A function that returns the JSON Schema for a tool. * @param options.error - The error that occurred while parsing the tool call. */ -export type ToolCallRepairFunction< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, -> = (options: { +export type ToolCallRepairFunction = (options: { system: string | SystemModelMessage | Array | undefined; messages: ModelMessage[]; toolCall: LanguageModelV4ToolCall; diff --git a/packages/ai/src/generate-text/tool-error.ts b/packages/ai/src/generate-text/tool-error.ts index da2bca8d2d00..12d0540ef8f7 100644 --- a/packages/ai/src/generate-text/tool-error.ts +++ b/packages/ai/src/generate-text/tool-error.ts @@ -1,17 +1,14 @@ -import { ContextRegistry, InferToolInput } from '@ai-sdk/provider-utils'; +import { InferToolInput } from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { ValueOf } from '../util/value-of'; import { ToolSet } from './tool-set'; -export type StaticToolError< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = ValueOf<{ +export type StaticToolError = ValueOf<{ [NAME in keyof TOOLS]: { type: 'tool-error'; toolCallId: string; toolName: NAME & string; - input: InferToolInput; + input: InferToolInput; error: unknown; providerExecuted?: boolean; providerMetadata?: ProviderMetadata; @@ -32,7 +29,6 @@ export type DynamicToolError = { title?: string; }; -export type TypedToolError< - CONTEXT extends Partial, - TOOLS extends ToolSet = ToolSet, -> = StaticToolError | DynamicToolError; +export type TypedToolError = + | StaticToolError + | DynamicToolError; diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index 89a2658f862f..e6b039a9ddb5 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -1,15 +1,16 @@ -import { Context, Tool } from '@ai-sdk/provider-utils'; +import { Context, InferToolContext, Tool } from '@ai-sdk/provider-utils'; +import { UnionToIntersection } from '../util/union-to-intersection'; -export type ToolSet = Record< +export type ToolSet = Record< string, ( - | Tool - | Tool - | Tool - | Tool + | Tool + | Tool + | Tool + | Tool ) & Pick< - Tool, + Tool, | 'execute' | 'onInputAvailable' | 'onInputStart' @@ -18,5 +19,11 @@ export type ToolSet = Record< > >; -export type InferContextFromToolSet = - TOOLS extends ToolSet ? CONTEXT : never; +export type InferToolSetContext = UnionToIntersection< + { + [K in keyof TOOLS]: InferToolContext; + }[keyof TOOLS] +>; + +export type ExpandedContext = + InferToolSetContext & Context; diff --git a/packages/ai/src/telemetry/telemetry-integration.ts b/packages/ai/src/telemetry/telemetry-integration.ts index 3958728d9bdd..fe4e710581f7 100644 --- a/packages/ai/src/telemetry/telemetry-integration.ts +++ b/packages/ai/src/telemetry/telemetry-integration.ts @@ -17,9 +17,9 @@ import { Listener } from '../util/notify'; */ export interface TelemetryIntegration { onStart?: Listener>; - onStepStart?: Listener>; + onStepStart?: Listener>; onToolCallStart?: Listener>; onToolCallFinish?: Listener>; - onStepFinish?: Listener>; - onFinish?: Listener>; + onStepFinish?: Listener>; + onFinish?: Listener>; } diff --git a/packages/ai/src/util/union-to-intersection.ts b/packages/ai/src/util/union-to-intersection.ts new file mode 100644 index 000000000000..63186a2bde79 --- /dev/null +++ b/packages/ai/src/util/union-to-intersection.ts @@ -0,0 +1,17 @@ +/** + * Converts a union type `U` into an intersection type. + * + * For example: + * type A = { a: number }; + * type B = { b: string }; + * type Union = A | B; + * type Intersection = UnionToIntersection; + * // Intersection is: { a: number } & { b: string } + * + * This is useful when you have a union of object types and need a type with all possible properties. + */ +export type UnionToIntersection = ( + U extends unknown ? (arg: U) => void : never +) extends (arg: infer I) => void + ? I + : never; diff --git a/packages/provider-utils/src/types/index.ts b/packages/provider-utils/src/types/index.ts index c70878137ece..f2b0ac372cc9 100644 --- a/packages/provider-utils/src/types/index.ts +++ b/packages/provider-utils/src/types/index.ts @@ -20,6 +20,7 @@ export type { SystemModelMessage } from './system-model-message'; export { dynamicTool, tool, + type InferToolContext, type InferToolInput, type InferToolOutput, type Tool, @@ -34,8 +35,8 @@ export type { ToolContent, ToolModelMessage } from './tool-model-message'; export type { ToolResult } from './tool-result'; export type { UserContent, UserModelMessage } from './user-model-message'; -import type { ToolExecutionOptions } from './tool'; import type { Context } from './context'; +import type { ToolExecutionOptions } from './tool'; /** * @deprecated Use ToolExecutionOptions instead. diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index f6bd56d9dd4f..67b028cfb93b 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -113,7 +113,7 @@ type ToolOutputProperties< export type Tool< INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any, - CONTEXT extends Context = any, + CONTEXT extends Context = Context, > = { /** * An optional description of what the tool does. @@ -282,6 +282,12 @@ export type InferToolInput = export type InferToolOutput = TOOL extends Tool ? OUTPUT : never; +/** + * Infer the context type of a tool. + */ +export type InferToolContext = + TOOL extends Tool ? CONTEXT : never; + /** * Helper function for inferring the execute args of a tool. */ From c8bac431be0f166bc4d8b77b8f57acf38489b91a Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:35:30 +0100 Subject: [PATCH 19/41] x --- packages/ai/src/generate-text/callback-events.ts | 9 +++++---- packages/ai/src/generate-text/generate-text.ts | 11 ++++++++--- .../ai/src/generate-text/run-tools-transformation.ts | 9 ++++++--- packages/ai/src/telemetry/telemetry-integration.ts | 2 +- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index 0968b784ee8f..e3f45081fc8a 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -30,6 +30,7 @@ export interface CallbackModelInfo { */ export interface OnStartEvent< TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -94,8 +95,8 @@ export interface OnStartEvent< * When the condition is an array, any of the conditions can be met to stop. */ readonly stopWhen: - | StopCondition - | Array> + | StopCondition + | Array> | undefined; /** The output specification for structured outputs, if configured. */ @@ -185,8 +186,8 @@ export interface OnStepStartEvent< * When the condition is an array, any of the conditions can be met to stop. */ readonly stopWhen: - | StopCondition - | Array> + | StopCondition + | Array> | undefined; /** The output specification for structured outputs, if configured. */ diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index bbe46c70147c..b9656f15c38e 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -115,9 +115,10 @@ type GenerateTextIncludeSettings = { */ export type GenerateTextOnStartCallback< TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, OUTPUT extends Output = Output, > = ( - event: OnStartEvent, + event: OnStartEvent, ) => PromiseLike | void; /** @@ -370,7 +371,11 @@ export async function generateText< * Callback that is called when the generateText operation begins, * before any LLM calls are made. */ - experimental_onStart?: GenerateTextOnStartCallback, OUTPUT>; + experimental_onStart?: GenerateTextOnStartCallback< + NoInfer, + CONTEXT, + OUTPUT + >; /** * Callback that is called when a step (LLM call) begins, @@ -520,7 +525,7 @@ export async function generateText< onStart, globalTelemetry.onStart as | undefined - | GenerateTextOnStartCallback, + | GenerateTextOnStartCallback, ], }); diff --git a/packages/ai/src/generate-text/run-tools-transformation.ts b/packages/ai/src/generate-text/run-tools-transformation.ts index 05237710d4bb..2b0f2158cef8 100644 --- a/packages/ai/src/generate-text/run-tools-transformation.ts +++ b/packages/ai/src/generate-text/run-tools-transformation.ts @@ -24,7 +24,7 @@ import { TypedToolCall } from './tool-call'; import { ToolCallRepairFunction } from './tool-call-repair-function'; import { TypedToolError } from './tool-error'; import { TypedToolResult } from './tool-result'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; export type SingleRequestTextStreamPart = // Text blocks: @@ -108,7 +108,10 @@ export type SingleRequestTextStreamPart = | { type: 'error'; error: unknown } | { type: 'raw'; rawValue: unknown }; -export function runToolsTransformation({ +export function runToolsTransformation< + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +>({ tools, generatorStream, tracer, @@ -132,7 +135,7 @@ export function runToolsTransformation({ messages: ModelMessage[]; abortSignal: AbortSignal | undefined; repairToolCall: ToolCallRepairFunction | undefined; - experimental_context: unknown; + experimental_context: CONTEXT; generateId: IdGenerator; stepNumber?: number; model?: { provider: string; modelId: string }; diff --git a/packages/ai/src/telemetry/telemetry-integration.ts b/packages/ai/src/telemetry/telemetry-integration.ts index fe4e710581f7..2ce023d7a46b 100644 --- a/packages/ai/src/telemetry/telemetry-integration.ts +++ b/packages/ai/src/telemetry/telemetry-integration.ts @@ -16,7 +16,7 @@ import { Listener } from '../util/notify'; * Methods can be sync or return a PromiseLike. */ export interface TelemetryIntegration { - onStart?: Listener>; + onStart?: Listener>; onStepStart?: Listener>; onToolCallStart?: Listener>; onToolCallFinish?: Listener>; From 4195a90e4b93ef7a26618beacea2249d832dd4f1 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:38:19 +0100 Subject: [PATCH 20/41] x --- .../ai/src/generate-text/generate-text.ts | 24 ++++++++++--------- packages/ai/src/generate-text/prepare-step.ts | 16 ++++++------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index b9656f15c38e..f46c8c8eaac0 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -4,7 +4,6 @@ import { LanguageModelV4ToolCall, } from '@ai-sdk/provider'; import { - Context, createIdGenerator, getErrorMessage, IdGenerator, @@ -89,7 +88,7 @@ import { ToolCallRepairFunction } from './tool-call-repair-function'; import { TypedToolError } from './tool-error'; import { ToolOutput } from './tool-output'; import { TypedToolResult } from './tool-result'; -import { ExpandedContext, InferToolSetContext, ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; const originalGenerateId = createIdGenerator({ prefix: 'aitxt', @@ -355,12 +354,12 @@ export async function generateText< /** * @deprecated Use `prepareStep` instead. */ - experimental_prepareStep?: PrepareStepFunction>; + experimental_prepareStep?: PrepareStepFunction, CONTEXT>; /** * Optional function that you can use to provide different settings for a step. */ - prepareStep?: PrepareStepFunction>; + prepareStep?: PrepareStepFunction, CONTEXT>; /** * A function that attempts to repair a tool call that failed to parse. @@ -373,8 +372,8 @@ export async function generateText< */ experimental_onStart?: GenerateTextOnStartCallback< NoInfer, - CONTEXT, - OUTPUT + NoInfer, + NoInfer >; /** @@ -382,9 +381,9 @@ export async function generateText< * before the provider is called. */ experimental_onStepStart?: GenerateTextOnStepStartCallback< - TOOLS, - CONTEXT, - OUTPUT + NoInfer, + NoInfer, + NoInfer >; /** @@ -404,12 +403,15 @@ export async function generateText< /** * Callback that is called when each step (LLM call) is finished, including intermediate steps. */ - onStepFinish?: GenerateTextOnStepFinishCallback; + onStepFinish?: GenerateTextOnStepFinishCallback< + NoInfer, + NoInfer + >; /** * Callback that is called when all steps are finished and the response is complete. */ - onFinish?: GenerateTextOnFinishCallback; + onFinish?: GenerateTextOnFinishCallback, NoInfer>; /** * Context that is passed into tool execution. diff --git a/packages/ai/src/generate-text/prepare-step.ts b/packages/ai/src/generate-text/prepare-step.ts index 3dcab84207c0..cf06e5e3c5e7 100644 --- a/packages/ai/src/generate-text/prepare-step.ts +++ b/packages/ai/src/generate-text/prepare-step.ts @@ -6,7 +6,7 @@ import { } from '@ai-sdk/provider-utils'; import { LanguageModel, ToolChoice } from '../types/language-model'; import { StepResult } from './step-result'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; /** * Function that you can use to provide different settings for a step. @@ -22,13 +22,13 @@ import { ToolSet } from './tool-set'; * If you return undefined (or for undefined settings), the settings from the outer level will be used. */ export type PrepareStepFunction< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, > = (options: { /** * The steps that have been executed so far. */ - steps: Array>>; + steps: Array>; /** * The number of the step that is being executed. @@ -50,16 +50,16 @@ export type PrepareStepFunction< */ experimental_context: CONTEXT; }) => - | PromiseLike> - | PrepareStepResult; + | PromiseLike> + | PrepareStepResult; /** * The result type returned by a {@link PrepareStepFunction}, * allowing per-step overrides of model, tools, or messages. */ export type PrepareStepResult< - CONTEXT extends Context, - TOOLS extends ToolSet = ToolSet, + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, > = | { /** From dfdbdc6aec47f0b2d3f6ee949756fada9f8c2ab0 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:39:41 +0100 Subject: [PATCH 21/41] a --- packages/ai/src/generate-text/generate-text-result.ts | 5 +++-- packages/ai/src/generate-text/generate-text.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/ai/src/generate-text/generate-text-result.ts b/packages/ai/src/generate-text/generate-text-result.ts index 6f37f8d69eaf..81f0757400cd 100644 --- a/packages/ai/src/generate-text/generate-text-result.ts +++ b/packages/ai/src/generate-text/generate-text-result.ts @@ -16,7 +16,7 @@ import { StaticToolResult, TypedToolResult, } from './tool-result'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; /** * The result of a `generateText` call. @@ -24,6 +24,7 @@ import { ToolSet } from './tool-set'; */ export interface GenerateTextResult< TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, OUTPUT extends Output, > { /** @@ -151,7 +152,7 @@ export interface GenerateTextResult< * You can use this to get information about intermediate steps, * such as the tool calls or the response headers. */ - readonly steps: Array>; + readonly steps: Array>; /** * The generated structured output. It uses the `output` specification. diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index f46c8c8eaac0..124d422060c3 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -450,7 +450,7 @@ export async function generateText< _internal?: { generateId?: IdGenerator; }; - }): Promise> { + }): Promise> { const model = resolveLanguageModel(modelArg); const createGlobalTelemetry = getGlobalTelemetryIntegration(); const stopConditions = asArray(stopWhen); @@ -1332,7 +1332,7 @@ class DefaultGenerateTextResult< TOOLS extends ToolSet, CONTEXT extends ExpandedContext, OUTPUT extends Output, -> implements GenerateTextResult +> implements GenerateTextResult { readonly steps: StepResult[]; readonly totalUsage: LanguageModelUsage; From 342136df118efe8b7b1c5d58cdedaf2f4d701199 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 14:53:40 +0100 Subject: [PATCH 22/41] a --- .../openai/tool-call-with-context.ts | 10 +++++++++- packages/ai/src/generate-text/tool-set.ts | 18 ++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts b/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts index cae53ba9f0e1..4436ffce9098 100644 --- a/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts +++ b/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts @@ -47,7 +47,15 @@ run(async () => { }, }), }, - experimental_context: { weatherApiKey: '123', calculatorApiKey: '456' }, + experimental_context: { + weatherApiKey: '123', + calculatorApiKey: '456', + somethingElse: 'context', + }, + prepareStep: async ({ experimental_context: context }) => { + console.log(context); + return {}; + }, prompt: 'What is the weather in San Francisco?', }); diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index e6b039a9ddb5..104c96b30162 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -4,13 +4,13 @@ import { UnionToIntersection } from '../util/union-to-intersection'; export type ToolSet = Record< string, ( - | Tool - | Tool - | Tool - | Tool + | Tool + | Tool + | Tool + | Tool ) & Pick< - Tool, + Tool, | 'execute' | 'onInputAvailable' | 'onInputStart' @@ -21,9 +21,11 @@ export type ToolSet = Record< export type InferToolSetContext = UnionToIntersection< { - [K in keyof TOOLS]: InferToolContext; + [K in keyof TOOLS]: InferToolContext>; }[keyof TOOLS] >; -export type ExpandedContext = - InferToolSetContext & Context; +export type ExpandedContext = InferToolSetContext< + NoInfer +> & + Context; From 9921f52a89e794714ca6d7969edd2ba5cc56e47a Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 15:52:03 +0100 Subject: [PATCH 23/41] s --- packages/ai/src/generate-text/stream-text.ts | 158 ++++++++++++------- 1 file changed, 103 insertions(+), 55 deletions(-) diff --git a/packages/ai/src/generate-text/stream-text.ts b/packages/ai/src/generate-text/stream-text.ts index 939d30df27c8..cd8138844c77 100644 --- a/packages/ai/src/generate-text/stream-text.ts +++ b/packages/ai/src/generate-text/stream-text.ts @@ -122,7 +122,7 @@ import { TypedToolCall } from './tool-call'; import { ToolCallRepairFunction } from './tool-call-repair-function'; import { ToolOutput } from './tool-output'; import { StaticToolOutputDenied } from './tool-output-denied'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; const originalGenerateId = createIdGenerator({ prefix: 'aitxt', @@ -154,9 +154,10 @@ export type StreamTextOnErrorCallback = (event: { * * @param stepResult - The result of the step. */ -export type StreamTextOnStepFinishCallback = ( - event: OnStepFinishEvent, -) => PromiseLike | void; +export type StreamTextOnStepFinishCallback< + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> = (event: OnStepFinishEvent) => PromiseLike | void; /** * Callback that is set using the `onChunk` option. @@ -185,20 +186,24 @@ export type StreamTextOnChunkCallback = (event: { * * @param event - The event that is passed to the callback. */ -export type StreamTextOnFinishCallback = ( - event: OnFinishEvent, -) => PromiseLike | void; +export type StreamTextOnFinishCallback< + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> = (event: OnFinishEvent) => PromiseLike | void; /** * Callback that is set using the `onAbort` option. * * @param event - The event that is passed to the callback. */ -export type StreamTextOnAbortCallback = (event: { +export type StreamTextOnAbortCallback< + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +> = (event: { /** * Details for all previously finished steps. */ - readonly steps: StepResult[]; + readonly steps: StepResult[]; }) => PromiseLike | void; /** @@ -217,9 +222,10 @@ type StreamTextIncludeSettings = { requestBody?: boolean }; */ export type StreamTextOnStartCallback< TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = Output, > = ( - event: OnStartEvent, + event: OnStartEvent, ) => PromiseLike | void; /** @@ -233,9 +239,10 @@ export type StreamTextOnStartCallback< */ export type StreamTextOnStepStartCallback< TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = Output, > = ( - event: OnStepStartEvent, + event: OnStepStartEvent, ) => PromiseLike | void; export type StreamTextOnToolCallStartCallback = @@ -293,6 +300,7 @@ export type StreamTextOnToolCallFinishCallback< */ export function streamText< TOOLS extends ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = Output, >({ model, @@ -328,7 +336,7 @@ export function streamText< experimental_onStepStart: onStepStart, experimental_onToolCallStart: onToolCallStart, experimental_onToolCallFinish: onToolCallFinish, - experimental_context, + experimental_context = {} as CONTEXT, experimental_include: include, _internal: { now = originalNow, generateId = originalGenerateId } = {}, ...settings @@ -356,8 +364,8 @@ export function streamText< * @default stepCountIs(1) */ stopWhen?: - | StopCondition> - | Array>>; + | StopCondition, CONTEXT> + | Array, CONTEXT>>; /** * Optional telemetry configuration (experimental). @@ -405,7 +413,7 @@ export function streamText< * @returns An object that contains the settings for the step. * If you return undefined (or for undefined settings), the settings from the outer level will be used. */ - prepareStep?: PrepareStepFunction>; + prepareStep?: PrepareStepFunction, CONTEXT>; /** * A function that attempts to repair a tool call that failed to parse. @@ -455,20 +463,27 @@ export function streamText< * * The usage is the combined usage of all steps. */ - onFinish?: StreamTextOnFinishCallback; + onFinish?: StreamTextOnFinishCallback, NoInfer>; - onAbort?: StreamTextOnAbortCallback; + onAbort?: StreamTextOnAbortCallback, NoInfer>; /** * Callback that is called when each step (LLM call) is finished, including intermediate steps. */ - onStepFinish?: StreamTextOnStepFinishCallback; + onStepFinish?: StreamTextOnStepFinishCallback< + NoInfer, + NoInfer + >; /** * Callback that is called when the streamText operation begins, * before any LLM calls are made. */ - experimental_onStart?: StreamTextOnStartCallback, OUTPUT>; + experimental_onStart?: StreamTextOnStartCallback< + NoInfer, + NoInfer, + NoInfer + >; /** * Callback that is called when a step (LLM call) begins, @@ -476,7 +491,8 @@ export function streamText< */ experimental_onStepStart?: StreamTextOnStepStartCallback< NoInfer, - OUTPUT + NoInfer, + NoInfer >; /** @@ -500,7 +516,7 @@ export function streamText< * * @default undefined */ - experimental_context?: unknown; + experimental_context?: CONTEXT; /** * Settings for controlling what data is included in step results. @@ -533,7 +549,7 @@ export function streamText< stepTimeoutMs != null ? new AbortController() : undefined; const chunkAbortController = chunkTimeoutMs != null ? new AbortController() : undefined; - return new DefaultStreamTextResult({ + return new DefaultStreamTextResult({ model: resolveLanguageModel(model), telemetry, headers, @@ -684,8 +700,11 @@ function createOutputTransformStream< }); } -class DefaultStreamTextResult - implements StreamTextResult +class DefaultStreamTextResult< + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, + OUTPUT extends Output, +> implements StreamTextResult { private readonly _totalUsage = new DelayedPromise< Awaited['usage']> @@ -776,31 +795,51 @@ class DefaultStreamTextResult transforms: Array>; activeTools: Array | undefined; repairToolCall: ToolCallRepairFunction | undefined; - stopConditions: Array>>; + stopConditions: Array, NoInfer>>; output: OUTPUT | undefined; providerOptions: ProviderOptions | undefined; - prepareStep: PrepareStepFunction> | undefined; + prepareStep: + | PrepareStepFunction, NoInfer> + | undefined; includeRawChunks: boolean; now: () => number; generateId: () => string; timeout: TimeoutConfiguration | undefined; stopWhen: - | StopCondition> - | Array>> + | StopCondition, NoInfer> + | Array, NoInfer>> | undefined; originalAbortSignal: AbortSignal | undefined; - experimental_context: unknown; + experimental_context: CONTEXT; download: DownloadFunction | undefined; include: { requestBody?: boolean } | undefined; // callbacks: onChunk: undefined | StreamTextOnChunkCallback; onError: StreamTextOnErrorCallback; - onFinish: undefined | StreamTextOnFinishCallback; - onAbort: undefined | StreamTextOnAbortCallback; - onStepFinish: undefined | StreamTextOnStepFinishCallback; - onStart: undefined | StreamTextOnStartCallback; - onStepStart: undefined | StreamTextOnStepStartCallback; + onFinish: + | undefined + | StreamTextOnFinishCallback, NoInfer>; + onAbort: + | undefined + | StreamTextOnAbortCallback, NoInfer>; + onStepFinish: + | undefined + | StreamTextOnStepFinishCallback, NoInfer>; + onStart: + | undefined + | StreamTextOnStartCallback< + NoInfer, + NoInfer, + NoInfer + >; + onStepStart: + | undefined + | StreamTextOnStepStartCallback< + NoInfer, + NoInfer, + NoInfer + >; onToolCallStart: undefined | StreamTextOnToolCallStartCallback; onToolCallFinish: undefined | StreamTextOnToolCallFinishCallback; }) { @@ -826,7 +865,7 @@ class DefaultStreamTextResult let recordedTotalUsage: LanguageModelUsage | undefined = undefined; let recordedRequest: LanguageModelRequestMetadata = {}; let recordedWarnings: Array = []; - const recordedSteps: StepResult[] = []; + const recordedSteps: StepResult[] = []; // Track provider-executed tool calls that support deferred results // (e.g., code_execution in programmatic tool calling scenarios). @@ -1024,23 +1063,24 @@ class DefaultStreamTextResult }); // Add step information (after response messages are updated): - const currentStepResult: StepResult = new DefaultStepResult({ - stepNumber: recordedSteps.length, - model: modelInfo, - ...callbackTelemetryProps, - experimental_context, - content: recordedContent, - finishReason: part.finishReason, - rawFinishReason: part.rawFinishReason, - usage: part.usage, - warnings: recordedWarnings, - request: recordedRequest, - response: { - ...part.response, - messages: [...recordedResponseMessages, ...stepMessages], - }, - providerMetadata: part.providerMetadata, - }); + const currentStepResult: StepResult = + new DefaultStepResult({ + stepNumber: recordedSteps.length, + model: modelInfo, + ...callbackTelemetryProps, + experimental_context, + content: recordedContent, + finishReason: part.finishReason, + rawFinishReason: part.rawFinishReason, + usage: part.usage, + warnings: recordedWarnings, + request: recordedRequest, + response: { + ...part.response, + messages: [...recordedResponseMessages, ...stepMessages], + }, + providerMetadata: part.providerMetadata, + }); await notify({ event: currentStepResult, @@ -1135,7 +1175,7 @@ class DefaultStreamTextResult onFinish, globalTelemetry.onFinish as | undefined - | StreamTextOnFinishCallback, + | StreamTextOnFinishCallback, NoInfer>, ], }); @@ -1340,7 +1380,11 @@ class DefaultStreamTextResult onStart, globalTelemetry.onStart as | undefined - | StreamTextOnStartCallback, + | StreamTextOnStartCallback< + NoInfer, + NoInfer, + NoInfer + >, ], }); @@ -1622,7 +1666,11 @@ class DefaultStreamTextResult onStepStart, globalTelemetry.onStepStart as | undefined - | StreamTextOnStepStartCallback, + | StreamTextOnStepStartCallback< + NoInfer, + NoInfer, + NoInfer + >, ], }); From d0e70cb380732bf18b24b81dd42216448fa397a5 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 16:00:15 +0100 Subject: [PATCH 24/41] st --- packages/ai/src/agent/agent.ts | 17 ++++--- .../agent/create-agent-ui-stream-response.ts | 7 +-- .../ai/src/agent/create-agent-ui-stream.ts | 5 +- .../agent/pipe-agent-ui-stream-to-response.ts | 5 +- .../ai/src/agent/tool-loop-agent-settings.ts | 48 ++++++++++------- packages/ai/src/agent/tool-loop-agent.ts | 51 ++++++++++++++----- .../src/generate-text/stream-text-result.ts | 5 +- packages/ai/src/generate-text/stream-text.ts | 12 ++--- packages/ai/src/ui/direct-chat-transport.ts | 22 +++++--- 9 files changed, 111 insertions(+), 61 deletions(-) diff --git a/packages/ai/src/agent/agent.ts b/packages/ai/src/agent/agent.ts index 2dbb9ba1c198..c8ce19c941a4 100644 --- a/packages/ai/src/agent/agent.ts +++ b/packages/ai/src/agent/agent.ts @@ -3,7 +3,7 @@ import { GenerateTextResult } from '../generate-text/generate-text-result'; import { Output } from '../generate-text/output'; import { StreamTextTransform } from '../generate-text/stream-text'; import { StreamTextResult } from '../generate-text/stream-text-result'; -import { ToolSet } from '../generate-text/tool-set'; +import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; import { TimeoutConfiguration } from '../prompt/call-settings'; import type { ToolLoopAgentOnFinishCallback, @@ -17,9 +17,11 @@ import type { /** * Parameters for calling an agent. */ -export type AgentCallParameters = ([ +export type AgentCallParameters< CALL_OPTIONS, -] extends [never] + TOOLS extends ToolSet = {}, + CONTEXT extends ExpandedContext = ExpandedContext, +> = ([CALL_OPTIONS] extends [never] ? { options?: never } : { options: CALL_OPTIONS }) & ( @@ -67,12 +69,12 @@ export type AgentCallParameters = ([ /** * Callback that is called when the agent operation begins, before any LLM calls. */ - experimental_onStart?: ToolLoopAgentOnStartCallback; + experimental_onStart?: ToolLoopAgentOnStartCallback; /** * Callback that is called when a step (LLM call) begins, before the provider is called. */ - experimental_onStepStart?: ToolLoopAgentOnStepStartCallback; + experimental_onStepStart?: ToolLoopAgentOnStepStartCallback; /** * Callback that is called before each tool execution begins. @@ -122,6 +124,7 @@ export type AgentStreamParameters< export interface Agent< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = never, > { /** @@ -145,12 +148,12 @@ export interface Agent< */ generate( options: AgentCallParameters, - ): PromiseLike>; + ): PromiseLike>; /** * Streams an output from the agent (streaming). */ stream( options: AgentStreamParameters, - ): PromiseLike>; + ): PromiseLike>; } diff --git a/packages/ai/src/agent/create-agent-ui-stream-response.ts b/packages/ai/src/agent/create-agent-ui-stream-response.ts index a8f6646895c1..d47e3db798cd 100644 --- a/packages/ai/src/agent/create-agent-ui-stream-response.ts +++ b/packages/ai/src/agent/create-agent-ui-stream-response.ts @@ -1,6 +1,6 @@ import { StreamTextTransform, UIMessageStreamOptions } from '../generate-text'; import { Output } from '../generate-text/output'; -import { ToolSet } from '../generate-text/tool-set'; +import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; import { TimeoutConfiguration } from '../prompt/call-settings'; import { createUIMessageStreamResponse } from '../ui-message-stream'; import { UIMessageStreamResponseInit } from '../ui-message-stream/ui-message-stream-response-init'; @@ -29,7 +29,8 @@ import type { ToolLoopAgentOnStepFinishCallback } from './tool-loop-agent-settin export async function createAgentUIStreamResponse< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - OUTPUT extends Output = never, + CONTEXT extends ExpandedContext = ExpandedContext, + OUTPUT extends Output = never, MESSAGE_METADATA = unknown, >({ headers, @@ -38,7 +39,7 @@ export async function createAgentUIStreamResponse< consumeSseStream, ...options }: { - agent: Agent; + agent: Agent; uiMessages: unknown[]; abortSignal?: AbortSignal; timeout?: TimeoutConfiguration; diff --git a/packages/ai/src/agent/create-agent-ui-stream.ts b/packages/ai/src/agent/create-agent-ui-stream.ts index cc8da02822d5..5460b21a94f0 100644 --- a/packages/ai/src/agent/create-agent-ui-stream.ts +++ b/packages/ai/src/agent/create-agent-ui-stream.ts @@ -1,6 +1,6 @@ import { StreamTextTransform, UIMessageStreamOptions } from '../generate-text'; import { Output } from '../generate-text/output'; -import { ToolSet } from '../generate-text/tool-set'; +import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; import { TimeoutConfiguration } from '../prompt/call-settings'; import { InferUIMessageChunk } from '../ui-message-stream'; import { convertToModelMessages } from '../ui/convert-to-model-messages'; @@ -26,6 +26,7 @@ import type { ToolLoopAgentOnStepFinishCallback } from './tool-loop-agent-settin export async function createAgentUIStream< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = never, MESSAGE_METADATA = unknown, >({ @@ -38,7 +39,7 @@ export async function createAgentUIStream< onStepFinish, ...uiMessageStreamOptions }: { - agent: Agent; + agent: Agent; uiMessages: unknown[]; abortSignal?: AbortSignal; timeout?: TimeoutConfiguration; diff --git a/packages/ai/src/agent/pipe-agent-ui-stream-to-response.ts b/packages/ai/src/agent/pipe-agent-ui-stream-to-response.ts index 048921f4bd78..8ed3f80132dd 100644 --- a/packages/ai/src/agent/pipe-agent-ui-stream-to-response.ts +++ b/packages/ai/src/agent/pipe-agent-ui-stream-to-response.ts @@ -1,7 +1,7 @@ import { ServerResponse } from 'node:http'; import { StreamTextTransform, UIMessageStreamOptions } from '../generate-text'; import { Output } from '../generate-text/output'; -import { ToolSet } from '../generate-text/tool-set'; +import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; import { TimeoutConfiguration } from '../prompt/call-settings'; import { pipeUIMessageStreamToResponse } from '../ui-message-stream'; import { UIMessageStreamResponseInit } from '../ui-message-stream/ui-message-stream-response-init'; @@ -29,6 +29,7 @@ import type { ToolLoopAgentOnStepFinishCallback } from './tool-loop-agent-settin export async function pipeAgentUIStreamToResponse< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = never, MESSAGE_METADATA = unknown, >({ @@ -40,7 +41,7 @@ export async function pipeAgentUIStreamToResponse< ...options }: { response: ServerResponse; - agent: Agent; + agent: Agent; uiMessages: unknown[]; abortSignal?: AbortSignal; timeout?: TimeoutConfiguration; diff --git a/packages/ai/src/agent/tool-loop-agent-settings.ts b/packages/ai/src/agent/tool-loop-agent-settings.ts index 8c0e7dd50643..025e81f80ca3 100644 --- a/packages/ai/src/agent/tool-loop-agent-settings.ts +++ b/packages/ai/src/agent/tool-loop-agent-settings.ts @@ -16,7 +16,7 @@ import { Output } from '../generate-text/output'; import { PrepareStepFunction } from '../generate-text/prepare-step'; import { StopCondition } from '../generate-text/stop-condition'; import { ToolCallRepairFunction } from '../generate-text/tool-call-repair-function'; -import { ToolSet } from '../generate-text/tool-set'; +import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; import { CallSettings } from '../prompt/call-settings'; import { Prompt } from '../prompt/prompt'; import { TelemetrySettings } from '../telemetry/telemetry-settings'; @@ -26,13 +26,17 @@ import { AgentCallParameters } from './agent'; export type ToolLoopAgentOnStartCallback< TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = Output, -> = (event: OnStartEvent) => PromiseLike | void; +> = (event: OnStartEvent) => PromiseLike | void; export type ToolLoopAgentOnStepStartCallback< TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = Output, -> = (event: OnStepStartEvent) => PromiseLike | void; +> = ( + event: OnStepStartEvent, +) => PromiseLike | void; export type ToolLoopAgentOnToolCallStartCallback< TOOLS extends ToolSet = ToolSet, @@ -42,13 +46,15 @@ export type ToolLoopAgentOnToolCallFinishCallback< TOOLS extends ToolSet = ToolSet, > = (event: OnToolCallFinishEvent) => PromiseLike | void; -export type ToolLoopAgentOnStepFinishCallback = ( - stepResult: OnStepFinishEvent, -) => Promise | void; +export type ToolLoopAgentOnStepFinishCallback< + TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, +> = (stepResult: OnStepFinishEvent) => Promise | void; -export type ToolLoopAgentOnFinishCallback = ( - event: OnFinishEvent, -) => PromiseLike | void; +export type ToolLoopAgentOnFinishCallback< + TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, +> = (event: OnFinishEvent) => PromiseLike | void; /** * Configuration options for an agent. @@ -56,6 +62,7 @@ export type ToolLoopAgentOnFinishCallback = ( export type ToolLoopAgentSettings< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = never, > = Omit & { /** @@ -92,8 +99,8 @@ export type ToolLoopAgentSettings< * @default stepCountIs(20) */ stopWhen?: - | StopCondition> - | Array>>; + | StopCondition, CONTEXT> + | Array, CONTEXT>>; /** * Optional telemetry configuration (experimental). @@ -114,7 +121,7 @@ export type ToolLoopAgentSettings< /** * Optional function that you can use to provide different settings for a step. */ - prepareStep?: PrepareStepFunction>; + prepareStep?: PrepareStepFunction, CONTEXT>; /** * A function that attempts to repair a tool call that failed to parse. @@ -124,14 +131,19 @@ export type ToolLoopAgentSettings< /** * Callback that is called when the agent operation begins, before any LLM calls. */ - experimental_onStart?: ToolLoopAgentOnStartCallback, OUTPUT>; + experimental_onStart?: ToolLoopAgentOnStartCallback< + NoInfer, + CONTEXT, + NoInfer + >; /** * Callback that is called when a step (LLM call) begins, before the provider is called. */ experimental_onStepStart?: ToolLoopAgentOnStepStartCallback< NoInfer, - OUTPUT + NoInfer, + NoInfer >; /** @@ -169,10 +181,8 @@ export type ToolLoopAgentSettings< * Context that is passed into tool calls. * * Experimental (can break in patch releases). - * - * @default undefined */ - experimental_context?: unknown; + experimental_context?: CONTEXT; /** * Custom download function to use for URLs. @@ -197,7 +207,7 @@ export type ToolLoopAgentSettings< 'onStepFinish' > & Pick< - ToolLoopAgentSettings, + ToolLoopAgentSettings>, | 'model' | 'tools' | 'maxOutputTokens' @@ -219,7 +229,7 @@ export type ToolLoopAgentSettings< >, ) => MaybePromiseLike< Pick< - ToolLoopAgentSettings, + ToolLoopAgentSettings>, | 'model' | 'tools' | 'maxOutputTokens' diff --git a/packages/ai/src/agent/tool-loop-agent.ts b/packages/ai/src/agent/tool-loop-agent.ts index 70206687a7b6..acc3bc97333f 100644 --- a/packages/ai/src/agent/tool-loop-agent.ts +++ b/packages/ai/src/agent/tool-loop-agent.ts @@ -4,10 +4,14 @@ import { Output } from '../generate-text/output'; import { stepCountIs } from '../generate-text/stop-condition'; import { streamText } from '../generate-text/stream-text'; import { StreamTextResult } from '../generate-text/stream-text-result'; -import { ToolSet } from '../generate-text/tool-set'; +import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; import { Prompt } from '../prompt'; import { Agent, AgentCallParameters, AgentStreamParameters } from './agent'; -import { ToolLoopAgentSettings } from './tool-loop-agent-settings'; +import { + ToolLoopAgentOnStartCallback, + ToolLoopAgentOnStepStartCallback, + ToolLoopAgentSettings, +} from './tool-loop-agent-settings'; /** * A tool loop agent is an agent that runs tools in a loop. In each step, @@ -23,14 +27,22 @@ import { ToolLoopAgentSettings } from './tool-loop-agent-settings'; export class ToolLoopAgent< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = never, -> implements Agent +> implements Agent { readonly version = 'agent-v1'; - private readonly settings: ToolLoopAgentSettings; + private readonly settings: ToolLoopAgentSettings< + CALL_OPTIONS, + TOOLS, + CONTEXT, + OUTPUT + >; - constructor(settings: ToolLoopAgentSettings) { + constructor( + settings: ToolLoopAgentSettings, + ) { this.settings = settings; } @@ -54,7 +66,7 @@ export class ToolLoopAgent< options?: CALL_OPTIONS; }): Promise< Omit< - ToolLoopAgentSettings, + ToolLoopAgentSettings, | 'prepareCall' | 'instructions' | 'experimental_onStart' @@ -86,7 +98,12 @@ export class ToolLoopAgent< (await this.settings.prepareCall?.( baseCallArgs as Parameters< NonNullable< - ToolLoopAgentSettings['prepareCall'] + ToolLoopAgentSettings< + CALL_OPTIONS, + TOOLS, + CONTEXT, + OUTPUT + >['prepareCall'] > >[0], )) ?? baseCallArgs; @@ -128,7 +145,7 @@ export class ToolLoopAgent< onFinish, ...options }: AgentCallParameters): Promise< - GenerateTextResult + GenerateTextResult > { return generateText({ ...(await this.prepareCall(options)), @@ -136,11 +153,15 @@ export class ToolLoopAgent< timeout, experimental_onStart: this.mergeCallbacks( this.settings.experimental_onStart, - experimental_onStart, + experimental_onStart as + | ToolLoopAgentOnStartCallback + | undefined, ), experimental_onStepStart: this.mergeCallbacks( this.settings.experimental_onStepStart, - experimental_onStepStart, + experimental_onStepStart as + | ToolLoopAgentOnStepStartCallback + | undefined, ), experimental_onToolCallStart: this.mergeCallbacks( this.settings.experimental_onToolCallStart, @@ -173,7 +194,7 @@ export class ToolLoopAgent< onFinish, ...options }: AgentStreamParameters): Promise< - StreamTextResult + StreamTextResult > { return streamText({ ...(await this.prepareCall(options)), @@ -182,11 +203,15 @@ export class ToolLoopAgent< experimental_transform, experimental_onStart: this.mergeCallbacks( this.settings.experimental_onStart, - experimental_onStart, + experimental_onStart as + | ToolLoopAgentOnStartCallback + | undefined, ), experimental_onStepStart: this.mergeCallbacks( this.settings.experimental_onStepStart, - experimental_onStepStart, + experimental_onStepStart as + | ToolLoopAgentOnStepStartCallback + | undefined, ), experimental_onToolCallStart: this.mergeCallbacks( this.settings.experimental_onToolCallStart, diff --git a/packages/ai/src/generate-text/stream-text-result.ts b/packages/ai/src/generate-text/stream-text-result.ts index bc2840be90f1..07fc05f9d7c5 100644 --- a/packages/ai/src/generate-text/stream-text-result.ts +++ b/packages/ai/src/generate-text/stream-text-result.ts @@ -35,7 +35,7 @@ import { StaticToolResult, TypedToolResult, } from './tool-result'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; export type UIMessageStreamOptions = { /** @@ -108,6 +108,7 @@ export type ConsumeStreamOptions = { */ export interface StreamTextResult< TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, OUTPUT extends Output, > { /** @@ -237,7 +238,7 @@ export interface StreamTextResult< * * Automatically consumes the stream. */ - readonly steps: PromiseLike>>; + readonly steps: PromiseLike>>; /** * Additional request information from the last step. diff --git a/packages/ai/src/generate-text/stream-text.ts b/packages/ai/src/generate-text/stream-text.ts index cd8138844c77..2fdc18603b04 100644 --- a/packages/ai/src/generate-text/stream-text.ts +++ b/packages/ai/src/generate-text/stream-text.ts @@ -541,7 +541,7 @@ export function streamText< now?: () => number; generateId?: IdGenerator; }; - }): StreamTextResult { + }): StreamTextResult { const totalTimeoutMs = getTotalTimeoutMs(timeout); const stepTimeoutMs = getStepTimeoutMs(timeout); const chunkTimeoutMs = getChunkTimeoutMs(timeout); @@ -704,19 +704,19 @@ class DefaultStreamTextResult< TOOLS extends ToolSet, CONTEXT extends ExpandedContext, OUTPUT extends Output, -> implements StreamTextResult +> implements StreamTextResult { private readonly _totalUsage = new DelayedPromise< - Awaited['usage']> + Awaited['usage']> >(); private readonly _finishReason = new DelayedPromise< - Awaited['finishReason']> + Awaited['finishReason']> >(); private readonly _rawFinishReason = new DelayedPromise< - Awaited['rawFinishReason']> + Awaited['rawFinishReason']> >(); private readonly _steps = new DelayedPromise< - Awaited['steps']> + Awaited['steps']> >(); private readonly addStream: ( diff --git a/packages/ai/src/ui/direct-chat-transport.ts b/packages/ai/src/ui/direct-chat-transport.ts index 6611b4ba7d01..60b20fe01cad 100644 --- a/packages/ai/src/ui/direct-chat-transport.ts +++ b/packages/ai/src/ui/direct-chat-transport.ts @@ -1,6 +1,6 @@ import { Output } from '../generate-text/output'; import { UIMessageStreamOptions } from '../generate-text/stream-text-result'; -import { ToolSet } from '../generate-text/tool-set'; +import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; import { UIMessageChunk } from '../ui-message-stream/ui-message-chunks'; import { Agent } from '../agent/agent'; import { ChatTransport } from './chat-transport'; @@ -14,13 +14,14 @@ import { validateUIMessages } from './validate-ui-messages'; export type DirectChatTransportOptions< CALL_OPTIONS, TOOLS extends ToolSet, - OUTPUT extends Output, + CONTEXT extends ExpandedContext, + OUTPUT extends Output, UI_MESSAGE extends UIMessage>, > = { /** * The agent to use for generating responses. */ - agent: Agent; + agent: Agent; /** * Options to pass to the agent when calling it. @@ -49,7 +50,8 @@ export type DirectChatTransportOptions< export class DirectChatTransport< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - OUTPUT extends Output = never, + CONTEXT extends ExpandedContext = ExpandedContext, + OUTPUT extends Output = never, UI_MESSAGE extends UIMessage> = UIMessage< unknown, never, @@ -57,7 +59,7 @@ export class DirectChatTransport< >, > implements ChatTransport { - private readonly agent: Agent; + private readonly agent: Agent; private readonly agentOptions: CALL_OPTIONS | undefined; private readonly uiMessageStreamOptions: Omit< UIMessageStreamOptions, @@ -68,7 +70,13 @@ export class DirectChatTransport< agent, options, ...uiMessageStreamOptions - }: DirectChatTransportOptions) { + }: DirectChatTransportOptions< + CALL_OPTIONS, + TOOLS, + CONTEXT, + OUTPUT, + UI_MESSAGE + >) { this.agent = agent; this.agentOptions = options; this.uiMessageStreamOptions = uiMessageStreamOptions; @@ -98,7 +106,7 @@ export class DirectChatTransport< ...(this.agentOptions !== undefined ? { options: this.agentOptions } : {}), - } as Parameters['stream']>[0]); + } as Parameters['stream']>[0]); // Return the UI message stream return result.toUIMessageStream(this.uiMessageStreamOptions); From eebb671eca5a86328700646e18c0da381f762777 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Mon, 9 Mar 2026 16:03:56 +0100 Subject: [PATCH 25/41] re --- .../ai/src/generate-text/callback-events.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ai/src/generate-text/callback-events.ts b/packages/ai/src/generate-text/callback-events.ts index e3f45081fc8a..add4a7b4ba38 100644 --- a/packages/ai/src/generate-text/callback-events.ts +++ b/packages/ai/src/generate-text/callback-events.ts @@ -29,8 +29,8 @@ export interface CallbackModelInfo { * Called when the generation operation begins, before any LLM calls. */ export interface OnStartEvent< - TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -95,8 +95,8 @@ export interface OnStartEvent< * When the condition is an array, any of the conditions can be met to stop. */ readonly stopWhen: - | StopCondition - | Array> + | StopCondition, CONTEXT> + | Array, CONTEXT>> | undefined; /** The output specification for structured outputs, if configured. */ @@ -130,8 +130,8 @@ export interface OnStartEvent< * Each step represents a single LLM invocation. */ export interface OnStepStartEvent< - TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -218,7 +218,7 @@ export interface OnStepStartEvent< * * Called when a tool execution begins, before the tool's `execute` function is invoked. */ -export interface OnToolCallStartEvent { +export interface OnToolCallStartEvent { /** Zero-based index of the current step where this tool call occurs. */ readonly stepNumber: number | undefined; @@ -250,7 +250,7 @@ export interface OnToolCallStartEvent { * Called when a tool execution completes, either successfully or with an error. * Uses a discriminated union on the `success` field. */ -export type OnToolCallFinishEvent = { +export type OnToolCallFinishEvent = { /** Zero-based index of the current step where this tool call occurred. */ readonly stepNumber: number | undefined; @@ -301,8 +301,8 @@ export type OnToolCallFinishEvent = { * This is simply the StepResult for that step. */ export type OnStepFinishEvent< - TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, > = StepResult; /** @@ -312,8 +312,8 @@ export type OnStepFinishEvent< * Includes the final step's result along with aggregated data from all steps. */ export type OnFinishEvent< - TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, > = StepResult & { /** Array containing results from all steps in the generation. */ readonly steps: StepResult[]; From 61a61c7f5b5755268ae3993ef09dce5f02752480 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Tue, 10 Mar 2026 16:33:08 +0100 Subject: [PATCH 26/41] ex --- .../openai/tool-call-with-context.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts b/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts index 4436ffce9098..2ac1cb6dbb06 100644 --- a/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts +++ b/examples/ai-functions/src/generate-text/openai/tool-call-with-context.ts @@ -5,7 +5,7 @@ import { run } from '../../lib/run'; run(async () => { const result = await generateText({ - model: openai('gpt-4o'), + model: openai('gpt-5-mini'), tools: { weather: tool({ description: 'Get the weather in a location', @@ -15,10 +15,11 @@ run(async () => { contextSchema: z.object({ weatherApiKey: z.string().describe('The API key for the weather API'), }), - execute: async ({ location }, { experimental_context: context }) => { - console.log(context); - - context satisfies { weatherApiKey: string }; + execute: async ( + { location }, + { experimental_context: { weatherApiKey } }, + ) => { + console.log('weather tool api key:', weatherApiKey); return { location, @@ -38,8 +39,11 @@ run(async () => { .string() .describe('The API key for the calculator API'), }), - execute: async ({ expression }, { experimental_context: context }) => { - console.log(context); + execute: async ( + { expression }, + { experimental_context: { calculatorApiKey } }, + ) => { + console.log('calculator tool api key:', calculatorApiKey); return { expression, result: eval(expression), @@ -48,12 +52,12 @@ run(async () => { }), }, experimental_context: { - weatherApiKey: '123', - calculatorApiKey: '456', - somethingElse: 'context', + weatherApiKey: 'weather-123', + calculatorApiKey: 'calculator-456', + somethingElse: 'other-context', }, prepareStep: async ({ experimental_context: context }) => { - console.log(context); + console.log('prepareStep context:', context); return {}; }, prompt: 'What is the weather in San Francisco?', From ec894c2a457d6c31f18e8e679d5e01deff9adb2b Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 12:32:39 +0200 Subject: [PATCH 27/41] types --- .../ai/src/agent/tool-loop-agent.test-d.ts | 14 ++-- ...reate-execute-tools-transformation.test.ts | 24 +++---- .../create-execute-tools-transformation.ts | 9 ++- .../generate-text/execute-tool-call.test.ts | 60 ++++++++-------- .../src/generate-text/generate-text.test.ts | 72 ++++++++++--------- .../ai/src/generate-text/stream-text.test.ts | 70 +++++++++--------- packages/ai/src/generate-text/tool-call.ts | 4 +- .../telemetry/open-telemetry-integration.ts | 5 +- .../src/provider-tool-factory.ts | 12 ++-- 9 files changed, 141 insertions(+), 129 deletions(-) diff --git a/packages/ai/src/agent/tool-loop-agent.test-d.ts b/packages/ai/src/agent/tool-loop-agent.test-d.ts index 86be0b7c0109..c2169b1e02aa 100644 --- a/packages/ai/src/agent/tool-loop-agent.test-d.ts +++ b/packages/ai/src/agent/tool-loop-agent.test-d.ts @@ -11,11 +11,13 @@ import type { ToolLoopAgentOnFinishCallback } from './tool-loop-agent-settings'; describe('ToolLoopAgent', () => { describe('onFinish callback type compatibility', () => { it('should allow StreamTextOnFinishCallback where ToolLoopAgentOnFinishCallback is expected', () => { - const streamTextCallback: StreamTextOnFinishCallback<{}> = - async event => { - const context: unknown = event.experimental_context; - context; - }; + const streamTextCallback: StreamTextOnFinishCallback< + {}, + {} + > = async event => { + const context: unknown = event.experimental_context; + context; + }; expectTypeOf(streamTextCallback).toMatchTypeOf< ToolLoopAgentOnFinishCallback<{}> @@ -29,7 +31,7 @@ describe('ToolLoopAgent', () => { }; expectTypeOf(agentCallback).toMatchTypeOf< - StreamTextOnFinishCallback<{}> + StreamTextOnFinishCallback<{}, {}> >(); }); }); diff --git a/packages/ai/src/generate-text/create-execute-tools-transformation.test.ts b/packages/ai/src/generate-text/create-execute-tools-transformation.test.ts index b68a63d76ae6..69d56e8a194d 100644 --- a/packages/ai/src/generate-text/create-execute-tools-transformation.test.ts +++ b/packages/ai/src/generate-text/create-execute-tools-transformation.test.ts @@ -58,7 +58,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], timeout: undefined, abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }), ); @@ -136,7 +136,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], abortSignal: undefined, timeout: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect( @@ -231,7 +231,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], abortSignal: undefined, timeout: undefined, - experimental_context: undefined, + experimental_context: {}, }), ); @@ -274,7 +274,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], timeout: undefined, abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallStart: async () => { callOrder.push('onToolCallStart'); }, @@ -324,7 +324,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], timeout: undefined, abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, stepNumber: 2, provider: 'test-provider', modelId: 'test-model', @@ -397,7 +397,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], timeout: undefined, abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallFinish: async event => { finishEvents.push(event); }, @@ -454,7 +454,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], timeout: undefined, abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallFinish: async event => { finishEvents.push(event); }, @@ -504,7 +504,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], timeout: undefined, abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallStart: async event => { startEvents.push(event); }, @@ -557,7 +557,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], timeout: undefined, abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallStart: async event => { startEvents.push(event.toolCall.toolCallId); }, @@ -613,7 +613,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], timeout: undefined, abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallStart: async event => { startEvents.push(event); }, @@ -667,7 +667,7 @@ describe('createExecuteToolsTransformation', () => { messages: [], abortSignal: undefined, timeout: undefined, - experimental_context: undefined, + experimental_context: {}, }), ); @@ -753,7 +753,7 @@ describe('createExecuteToolsTransformation', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }), ); diff --git a/packages/ai/src/generate-text/create-execute-tools-transformation.ts b/packages/ai/src/generate-text/create-execute-tools-transformation.ts index f2271ecfad78..736f5886b932 100644 --- a/packages/ai/src/generate-text/create-execute-tools-transformation.ts +++ b/packages/ai/src/generate-text/create-execute-tools-transformation.ts @@ -9,10 +9,13 @@ import { StreamTextOnToolCallStartCallback, } from './stream-text'; import { TypedToolCall } from './tool-call'; -import { ToolSet } from './tool-set'; +import { ExpandedContext, ToolSet } from './tool-set'; import { ModelCallStreamPart } from './stream-model-call'; -export function createExecuteToolsTransformation({ +export function createExecuteToolsTransformation< + TOOLS extends ToolSet, + CONTEXT extends ExpandedContext, +>({ tools, telemetry, callId, @@ -34,7 +37,7 @@ export function createExecuteToolsTransformation({ messages: ModelMessage[]; abortSignal: AbortSignal | undefined; timeout?: TimeoutConfiguration; - experimental_context: unknown; + experimental_context: CONTEXT; generateId: IdGenerator; stepNumber?: number; provider?: string; diff --git a/packages/ai/src/generate-text/execute-tool-call.test.ts b/packages/ai/src/generate-text/execute-tool-call.test.ts index f19ce9febd65..87c29f8dcd74 100644 --- a/packages/ai/src/generate-text/execute-tool-call.test.ts +++ b/packages/ai/src/generate-text/execute-tool-call.test.ts @@ -48,7 +48,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toBeUndefined(); @@ -69,7 +69,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toEqual({ @@ -97,7 +97,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toMatchObject({ @@ -125,7 +125,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toEqual({ @@ -155,7 +155,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toMatchObject({ @@ -229,7 +229,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallStart: async () => { throw new Error('callback error'); }, @@ -357,7 +357,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallFinish: async () => { throw new Error('callback error'); }, @@ -386,7 +386,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallFinish: async () => { throw new Error('callback error'); }, @@ -419,7 +419,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallFinish: async event => { finishEvents.push(event); }, @@ -449,7 +449,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallFinish: async event => { finishEvents.push(event); }, @@ -479,7 +479,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onPreliminaryToolResult: result => { preliminaryResults.push(result); }, @@ -516,7 +516,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onPreliminaryToolResult: result => { preliminaryResults.push(result); }, @@ -611,7 +611,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallFinish: async event => { finishEvents.push(event); }, @@ -645,7 +645,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, executeToolInTelemetryContext, }); @@ -669,7 +669,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toMatchObject({ @@ -694,7 +694,7 @@ describe('executeToolCall', () => { messages: [], abortSignal: undefined, timeout: { toolMs: 5000 }, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toMatchObject({ @@ -722,7 +722,7 @@ describe('executeToolCall', () => { messages: [], abortSignal: undefined, timeout: { toolMs: 5000 }, - experimental_context: undefined, + experimental_context: {}, }); expect(receivedSignal).toBeDefined(); @@ -747,7 +747,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(receivedSignal).toBeUndefined(); @@ -773,7 +773,7 @@ describe('executeToolCall', () => { messages: [], abortSignal: controller.signal, timeout: { toolMs: 5000 }, - experimental_context: undefined, + experimental_context: {}, }); expect(receivedSignal).toBeDefined(); @@ -800,7 +800,7 @@ describe('executeToolCall', () => { messages: [], abortSignal: undefined, timeout: { toolMs: 10000, tools: { testToolMs: 2000 } }, - experimental_context: undefined, + experimental_context: {}, }); expect(receivedSignal).toBeDefined(); @@ -826,7 +826,7 @@ describe('executeToolCall', () => { messages: [], abortSignal: undefined, timeout: { toolMs: 5000, tools: { otherToolMs: 2000 } }, - experimental_context: undefined, + experimental_context: {}, }); expect(receivedSignal).toBeDefined(); @@ -852,7 +852,7 @@ describe('executeToolCall', () => { messages: [], abortSignal: undefined, timeout: { tools: { otherToolMs: 2000 } }, - experimental_context: undefined, + experimental_context: {}, }); expect(receivedSignal).toBeUndefined(); @@ -875,7 +875,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toMatchObject({ @@ -901,7 +901,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toMatchObject({ @@ -920,7 +920,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toBeUndefined(); @@ -941,7 +941,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, }); expect(result).toBeUndefined(); @@ -964,7 +964,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallStart: [ async () => { calls.push('first'); @@ -993,7 +993,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallFinish: [ async () => { calls.push('first'); @@ -1022,7 +1022,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallStart: [ undefined, async () => { @@ -1057,7 +1057,7 @@ describe('executeToolCall', () => { callId: 'test-telemetry-call-id', messages: [], abortSignal: undefined, - experimental_context: undefined, + experimental_context: {}, onToolCallStart: [ async () => { throw new Error('listener error'); diff --git a/packages/ai/src/generate-text/generate-text.test.ts b/packages/ai/src/generate-text/generate-text.test.ts index f2e10805eb71..26afabfd70a9 100644 --- a/packages/ai/src/generate-text/generate-text.test.ts +++ b/packages/ai/src/generate-text/generate-text.test.ts @@ -812,7 +812,9 @@ describe('generateText', () => { describe('options.experimental_onStart', () => { it('should send correct information with text prompt', async () => { - let startEvent!: Parameters[0]; + let startEvent!: Parameters< + GenerateTextOnStartCallback + >[0]; await generateText({ model: new MockLanguageModelV4({ @@ -839,7 +841,9 @@ describe('generateText', () => { }); it('should pass experimental_context', async () => { - let startEvent!: Parameters[0]; + let startEvent!: Parameters< + GenerateTextOnStartCallback + >[0]; await generateText({ model: new MockLanguageModelV4({ @@ -862,7 +866,9 @@ describe('generateText', () => { }); it('should send correct information with system and messages', async () => { - let startEvent!: Parameters[0]; + let startEvent!: Parameters< + GenerateTextOnStartCallback + >[0]; await generateText({ model: new MockLanguageModelV4({ @@ -1365,7 +1371,7 @@ describe('generateText', () => { describe('options.onStepFinish stepNumber', () => { it('should pass stepNumber 0 for a single step', async () => { let stepFinishEvent!: Parameters< - GenerateTextOnStepFinishCallback + GenerateTextOnStepFinishCallback >[0]; await generateText({ @@ -2178,7 +2184,7 @@ describe('generateText', () => { describe('options.onFinish', () => { it('should send correct information', async () => { - let result!: Parameters>[0]; + let result!: Parameters>[0]; await generateText({ model: new MockLanguageModelV4({ @@ -2513,9 +2519,9 @@ describe('generateText', () => { }); describe('options.stopWhen', () => { - let result: GenerateTextResult; - let onFinishResult: Parameters>[0]; - let onStepFinishResults: StepResult[]; + let result: GenerateTextResult; + let onFinishResult: Parameters>[0]; + let onStepFinishResults: StepResult[]; beforeEach(() => { result = undefined as any; @@ -2711,13 +2717,13 @@ describe('generateText', () => { }); describe('2 steps: initial, tool-result with prepareStep', () => { - let result: GenerateTextResult; - let onStepFinishResults: StepResult[]; + let result: GenerateTextResult; + let onStepFinishResults: StepResult[]; let doGenerateCalls: Array; let prepareStepCalls: Array<{ modelId: string; stepNumber: number; - steps: Array>; + steps: Array>; messages: Array; experimental_context: unknown; }>; @@ -3481,10 +3487,10 @@ describe('generateText', () => { }); describe('2 stop conditions', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; let stopConditionCalls: Array<{ number: number; - steps: StepResult[]; + steps: StepResult[]; }>; beforeEach(async () => { @@ -4476,7 +4482,7 @@ describe('generateText', () => { describe('provider-executed tools', () => { describe('two provider-executed tool calls and results', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; beforeEach(async () => { result = await generateText({ @@ -5069,7 +5075,7 @@ describe('generateText', () => { }); describe('tool execution errors', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; beforeEach(async () => { result = await generateText({ @@ -5260,14 +5266,14 @@ describe('generateText', () => { describe('programmatic tool calling', () => { describe('5 steps: code_execution triggers client tool across multiple turns (dice game fixture)', () => { - let result: GenerateTextResult; - let onFinishResult: Parameters>[0]; - let onStepFinishResults: StepResult[]; + let result: GenerateTextResult; + let onFinishResult: Parameters>[0]; + let onStepFinishResults: StepResult[]; let doGenerateCalls: Array; let prepareStepCalls: Array<{ modelId: string; stepNumber: number; - steps: Array>; + steps: Array>; messages: Array; }>; let rollDieExecutions: Array<{ player: string }>; @@ -6598,7 +6604,7 @@ describe('generateText', () => { describe('invalid tool calls', () => { describe('single invalid tool call', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; beforeEach(async () => { result = await generateText({ @@ -6741,7 +6747,7 @@ describe('generateText', () => { describe('tools with preliminary results', () => { describe('single tool with preliminary results', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; beforeEach(async () => { result = await generateText({ @@ -7062,7 +7068,7 @@ describe('generateText', () => { describe('tool execution approval', () => { describe('when a single tool needs approval', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; beforeEach(async () => { result = await generateText({ @@ -7167,7 +7173,7 @@ describe('generateText', () => { }); describe('when a single tool has a needsApproval function', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; let needsApprovalCalls: Array<{ input: any; options: any }> = []; beforeEach(async () => { @@ -7366,9 +7372,9 @@ describe('generateText', () => { }); describe('when a call from a single tool that needs approval is approved', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; let prompts: LanguageModelV4Prompt[]; - let executeFunction: ToolExecuteFunction; + let executeFunction: ToolExecuteFunction; beforeEach(async () => { prompts = []; @@ -7532,7 +7538,7 @@ describe('generateText', () => { }); describe('when a call from a single tool that needs approval is approved and the tool throws', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; let prompts: LanguageModelV4Prompt[]; beforeEach(async () => { @@ -7646,9 +7652,9 @@ describe('generateText', () => { }); describe('when a call from a single tool that needs approval is denied', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; let prompts: LanguageModelV4Prompt[]; - let executeFunction: ToolExecuteFunction; + let executeFunction: ToolExecuteFunction; beforeEach(async () => { prompts = []; @@ -7805,9 +7811,9 @@ describe('generateText', () => { }); describe('when two calls from a single tool that needs approval are approved', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; let prompts: LanguageModelV4Prompt[]; - let executeFunction: ToolExecuteFunction; + let executeFunction: ToolExecuteFunction; beforeEach(async () => { prompts = []; @@ -8010,7 +8016,7 @@ describe('generateText', () => { describe('provider-executed tool (MCP) approval', () => { describe('when a provider-executed tool emits tool-approval-request', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; beforeEach(async () => { result = await generateText({ @@ -8121,7 +8127,7 @@ describe('generateText', () => { }); describe('when a provider-executed tool approval is approved', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; let prompts: LanguageModelV4Prompt[]; beforeEach(async () => { @@ -8307,7 +8313,7 @@ describe('generateText', () => { }); describe('when a provider-executed tool approval is denied', () => { - let result: GenerateTextResult; + let result: GenerateTextResult; let prompts: LanguageModelV4Prompt[]; beforeEach(async () => { diff --git a/packages/ai/src/generate-text/stream-text.test.ts b/packages/ai/src/generate-text/stream-text.test.ts index ce7a7ed67e87..a3ed17513aba 100644 --- a/packages/ai/src/generate-text/stream-text.test.ts +++ b/packages/ai/src/generate-text/stream-text.test.ts @@ -7041,7 +7041,7 @@ describe('streamText', () => { describe('options.onFinish', () => { it('should send correct information', async () => { - let result!: Parameters>[0]; + let result!: Parameters>[0]; const resultObject = streamText({ model: createTestModel({ @@ -8023,9 +8023,9 @@ describe('streamText', () => { }); describe('options.stopWhen', () => { - let result: StreamTextResult; - let onFinishResult: Parameters>[0]; - let onStepFinishResults: StepResult[]; + let result: StreamTextResult; + let onFinishResult: Parameters>[0]; + let onStepFinishResults: StepResult[]; let tracer: MockTracer; let stepInputs: Array; @@ -9344,7 +9344,7 @@ describe('streamText', () => { let prepareStepCalls: Array<{ modelId: string; stepNumber: number; - steps: Array>; + steps: Array>; messages: Array; experimental_context: unknown; }>; @@ -11392,7 +11392,7 @@ describe('streamText', () => { describe('2 stop conditions', () => { let stopConditionCalls: Array<{ number: number; - steps: StepResult[]; + steps: StepResult[]; }>; beforeEach(async () => { @@ -11820,7 +11820,7 @@ describe('streamText', () => { describe('provider-executed tools', () => { describe('single provider-executed tool call and result', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -12455,7 +12455,7 @@ describe('streamText', () => { describe('dynamic tools', () => { describe('single dynamic tool call and result', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -13713,7 +13713,7 @@ describe('streamText', () => { }); describe('tool execution errors', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -16045,7 +16045,7 @@ describe('streamText', () => { }); describe('array output', () => { - let result: StreamTextResult | undefined; + let result: StreamTextResult | undefined; let onFinishResult: | Parameters[0]>['onFinish']>[0] @@ -16796,7 +16796,7 @@ describe('streamText', () => { describe('mixed multi content streaming with interleaving parts', () => { describe('mixed text and reasoning blocks', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -17120,9 +17120,9 @@ describe('streamText', () => { describe('abort signal', () => { describe('basic abort', () => { - let result: StreamTextResult; + let result: StreamTextResult; let onErrorCalls: Array<{ error: unknown }> = []; - let onAbortCalls: Array<{ steps: StepResult[] }> = []; + let onAbortCalls: Array<{ steps: StepResult[] }> = []; beforeEach(() => { onErrorCalls = []; @@ -17301,9 +17301,9 @@ describe('streamText', () => { }); describe('abort in 2nd step', () => { - let result: StreamTextResult; + let result: StreamTextResult; let onErrorCalls: Array<{ error: unknown }> = []; - let onAbortCalls: Array<{ steps: StepResult[] }> = []; + let onAbortCalls: Array<{ steps: StepResult[] }> = []; beforeEach(() => { onErrorCalls = []; @@ -17619,9 +17619,9 @@ describe('streamText', () => { }); describe('abort during tool execution', () => { - let result: StreamTextResult; + let result: StreamTextResult; let onErrorCalls: Array<{ error: unknown }> = []; - let onAbortCalls: Array<{ steps: StepResult[] }> = []; + let onAbortCalls: Array<{ steps: StepResult[] }> = []; beforeEach(() => { onErrorCalls = []; @@ -17906,7 +17906,7 @@ describe('streamText', () => { describe('invalid tool calls', () => { describe('single invalid tool call', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -18203,7 +18203,7 @@ describe('streamText', () => { describe('tools with preliminary results', () => { describe('single tool with preliminary results', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -18574,7 +18574,7 @@ describe('streamText', () => { describe('provider-executed dynamic tools', () => { describe('single provider-executed dynamic tool with input streaming', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -18882,14 +18882,14 @@ describe('streamText', () => { describe('programmatic tool calling', () => { describe('5 steps: code_execution triggers client tool across multiple turns (dice game fixture)', () => { - let result: StreamTextResult; - let onFinishResult: Parameters>[0]; - let onStepFinishResults: StepResult[]; + let result: StreamTextResult; + let onFinishResult: Parameters>[0]; + let onStepFinishResults: StepResult[]; let doStreamCalls: Array; let prepareStepCalls: Array<{ modelId: string; stepNumber: number; - steps: Array>; + steps: Array>; messages: Array; }>; let rollDieExecutions: Array<{ player: string }>; @@ -20788,7 +20788,7 @@ describe('streamText', () => { describe('tool execution approval', () => { describe('when a single tool needs approval', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -21012,7 +21012,7 @@ describe('streamText', () => { }); describe('when a single tool has a needsApproval function', () => { - let result: StreamTextResult; + let result: StreamTextResult; let needsApprovalCalls: Array<{ input: any; options: any }> = []; beforeEach(async () => { @@ -21340,9 +21340,9 @@ describe('streamText', () => { }); describe('when a call from a single tool that needs approval is approved', () => { - let result: StreamTextResult; + let result: StreamTextResult; let prompts: LanguageModelV4Prompt[]; - let executeFunction: ToolExecuteFunction; + let executeFunction: ToolExecuteFunction; beforeEach(async () => { prompts = []; @@ -21644,7 +21644,7 @@ describe('streamText', () => { }); describe('when a call from a single tool that needs approval is approved and the tool throws', () => { - let result: StreamTextResult; + let result: StreamTextResult; let prompts: LanguageModelV4Prompt[]; beforeEach(async () => { @@ -21767,7 +21767,7 @@ describe('streamText', () => { }); describe('when a call from a single tool with preliminary results that needs approval is approved', () => { - let result: StreamTextResult; + let result: StreamTextResult; let prompts: LanguageModelV4Prompt[]; beforeEach(async () => { @@ -22097,9 +22097,9 @@ describe('streamText', () => { }); describe('when a call from a single tool that needs approval is denied', () => { - let result: StreamTextResult; + let result: StreamTextResult; let prompts: LanguageModelV4Prompt[]; - let executeFunction: ToolExecuteFunction; + let executeFunction: ToolExecuteFunction; beforeEach(async () => { prompts = []; @@ -22389,7 +22389,7 @@ describe('streamText', () => { describe('provider-executed tool (MCP) approval', () => { describe('when a provider-executed tool emits tool-approval-request', () => { - let result: StreamTextResult; + let result: StreamTextResult; beforeEach(async () => { result = streamText({ @@ -22624,7 +22624,7 @@ describe('streamText', () => { }); describe('when a provider-executed tool approval is approved', () => { - let result: StreamTextResult; + let result: StreamTextResult; let prompts: LanguageModelV4Prompt[]; beforeEach(async () => { @@ -22813,7 +22813,7 @@ describe('streamText', () => { }); describe('when a provider-executed tool approval is denied', () => { - let result: StreamTextResult; + let result: StreamTextResult; let prompts: LanguageModelV4Prompt[]; beforeEach(async () => { diff --git a/packages/ai/src/generate-text/tool-call.ts b/packages/ai/src/generate-text/tool-call.ts index 59698ce0b550..5c5fa589b32a 100644 --- a/packages/ai/src/generate-text/tool-call.ts +++ b/packages/ai/src/generate-text/tool-call.ts @@ -1,4 +1,4 @@ -import { Tool } from '@ai-sdk/provider-utils'; +import { InferToolInput } from '@ai-sdk/provider-utils'; import { ProviderMetadata } from '../types'; import { ValueOf } from '../util/value-of'; import { ToolSet } from './tool-set'; @@ -13,7 +13,7 @@ type BaseToolCall = { export type StaticToolCall = ValueOf<{ [NAME in keyof TOOLS]: BaseToolCall & { toolName: NAME & string; - input: TOOLS[NAME] extends Tool ? PARAMETERS : never; + input: InferToolInput; dynamic?: false | undefined; invalid?: false | undefined; error?: never; diff --git a/packages/ai/src/telemetry/open-telemetry-integration.ts b/packages/ai/src/telemetry/open-telemetry-integration.ts index 3b4fd00ea77e..89ad5f5e029a 100644 --- a/packages/ai/src/telemetry/open-telemetry-integration.ts +++ b/packages/ai/src/telemetry/open-telemetry-integration.ts @@ -31,7 +31,7 @@ import type { OnToolCallStartEvent, } from '../generate-text/core-events'; import type { Output } from '../generate-text/output'; -import type { ToolSet } from '../generate-text/tool-set'; +import type { ExpandedContext, ToolSet } from '../generate-text/tool-set'; import type { ObjectOnStartEvent, ObjectOnFinishEvent, @@ -115,8 +115,9 @@ function selectAttributes( interface OtelStepStartEvent< TOOLS extends ToolSet = ToolSet, + CONTEXT extends ExpandedContext = ExpandedContext, OUTPUT extends Output = Output, -> extends OnStepStartEvent { +> extends OnStepStartEvent { readonly promptMessages?: LanguageModelV4Prompt; readonly stepTools?: ReadonlyArray>; readonly stepToolChoice?: unknown; diff --git a/packages/provider-utils/src/provider-tool-factory.ts b/packages/provider-utils/src/provider-tool-factory.ts index 9766b664a530..77e63ec5bfd1 100644 --- a/packages/provider-utils/src/provider-tool-factory.ts +++ b/packages/provider-utils/src/provider-tool-factory.ts @@ -3,9 +3,9 @@ import { FlexibleSchema } from './schema'; import { Context } from './types/context'; export type ProviderToolFactory< - CONTEXT extends Context, INPUT, ARGS extends object, + CONTEXT extends Context = {}, > = ( options: ARGS & { execute?: ToolExecuteFunction; @@ -18,16 +18,16 @@ export type ProviderToolFactory< ) => Tool; export function createProviderToolFactory< - CONTEXT extends Context, INPUT, ARGS extends object, + CONTEXT extends Context = {}, >({ id, inputSchema, }: { id: `${string}.${string}`; inputSchema: FlexibleSchema; -}): ProviderToolFactory { +}): ProviderToolFactory { return ({ execute, outputSchema, @@ -62,10 +62,10 @@ export function createProviderToolFactory< } export type ProviderToolFactoryWithOutputSchema< - CONTEXT extends Context, INPUT, OUTPUT, ARGS extends object, + CONTEXT extends Context = {}, > = ( options: ARGS & { execute?: ToolExecuteFunction; @@ -78,10 +78,10 @@ export type ProviderToolFactoryWithOutputSchema< ) => Tool; export function createProviderToolFactoryWithOutputSchema< - CONTEXT extends Context, INPUT, OUTPUT, ARGS extends object, + CONTEXT extends Context = {}, >({ id, inputSchema, @@ -102,7 +102,7 @@ export function createProviderToolFactoryWithOutputSchema< * @default false */ supportsDeferredResults?: boolean; -}): ProviderToolFactoryWithOutputSchema { +}): ProviderToolFactoryWithOutputSchema { return ({ execute, needsApproval, From a966d4594805960af321362f332f1fec17b1e36a Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 12:35:33 +0200 Subject: [PATCH 28/41] tests --- .../__snapshots__/generate-text.test.ts.snap | 24 ++--- .../__snapshots__/stream-text.test.ts.snap | 4 +- .../src/generate-text/generate-text.test.ts | 18 ++-- .../ai/src/generate-text/stream-text.test.ts | 93 ++++++++++--------- 4 files changed, 71 insertions(+), 68 deletions(-) diff --git a/packages/ai/src/generate-text/__snapshots__/generate-text.test.ts.snap b/packages/ai/src/generate-text/__snapshots__/generate-text.test.ts.snap index a67498cf5668..30103ffbd5d0 100644 --- a/packages/ai/src/generate-text/__snapshots__/generate-text.test.ts.snap +++ b/packages/ai/src/generate-text/__snapshots__/generate-text.test.ts.snap @@ -5,7 +5,7 @@ exports[`generateText > options.experimental_onStart > should send correct infor "abortSignal": undefined, "activeTools": undefined, "callId": "test-telemetry-call-id", - "experimental_context": undefined, + "experimental_context": {}, "frequencyPenalty": undefined, "functionId": "test-function", "headers": { @@ -46,7 +46,7 @@ exports[`generateText > options.experimental_onToolCallStart > should be called { "abortSignal": undefined, "callId": "test-telemetry-call-id", - "experimental_context": undefined, + "experimental_context": {}, "functionId": undefined, "messages": [ { @@ -83,7 +83,7 @@ exports[`generateText > options.stopWhen > 2 steps: initial, tool-result > callb ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [], "finishReason": "stop", "functionId": undefined, @@ -177,7 +177,7 @@ exports[`generateText > options.stopWhen > 2 steps: initial, tool-result > callb "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -254,7 +254,7 @@ exports[`generateText > options.stopWhen > 2 steps: initial, tool-result > callb "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -403,7 +403,7 @@ exports[`generateText > options.stopWhen > 2 steps: initial, tool-result > callb "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -480,7 +480,7 @@ exports[`generateText > options.stopWhen > 2 steps: initial, tool-result > callb "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -635,7 +635,7 @@ exports[`generateText > options.stopWhen > 2 steps: initial, tool-result > resul "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -712,7 +712,7 @@ exports[`generateText > options.stopWhen > 2 steps: initial, tool-result > resul "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -1673,7 +1673,7 @@ exports[`generateText > result.steps > should add the reasoning from the model r "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -1774,7 +1774,7 @@ exports[`generateText > result.steps > should contain files 1`] = ` "type": "file", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -1873,7 +1873,7 @@ exports[`generateText > result.steps > should contain sources 1`] = ` "url": "https://example.com/2", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, diff --git a/packages/ai/src/generate-text/__snapshots__/stream-text.test.ts.snap b/packages/ai/src/generate-text/__snapshots__/stream-text.test.ts.snap index c32c3bf645a1..b5603beb4166 100644 --- a/packages/ai/src/generate-text/__snapshots__/stream-text.test.ts.snap +++ b/packages/ai/src/generate-text/__snapshots__/stream-text.test.ts.snap @@ -7,7 +7,7 @@ exports[`streamText > options.experimental_onStart > should send correct informa "abortSignal": undefined, "activeTools": undefined, "callId": "test-telemetry-call-id", - "experimental_context": undefined, + "experimental_context": {}, "frequencyPenalty": undefined, "functionId": "test-function", "headers": undefined, @@ -46,7 +46,7 @@ exports[`streamText > options.experimental_onToolCallStart > should be called wi { "abortSignal": undefined, "callId": "test-telemetry-call-id", - "experimental_context": undefined, + "experimental_context": {}, "functionId": undefined, "messages": [ { diff --git a/packages/ai/src/generate-text/generate-text.test.ts b/packages/ai/src/generate-text/generate-text.test.ts index 26afabfd70a9..042631852e0d 100644 --- a/packages/ai/src/generate-text/generate-text.test.ts +++ b/packages/ai/src/generate-text/generate-text.test.ts @@ -2260,7 +2260,7 @@ describe('generateText', () => { ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [], "finishReason": "stop", "functionId": undefined, @@ -2376,7 +2376,7 @@ describe('generateText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -3605,7 +3605,7 @@ describe('generateText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -3704,7 +3704,7 @@ describe('generateText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -3951,6 +3951,7 @@ describe('generateText', () => { abortSignal: abortController.signal, toolCallId: 'call-1', messages: expect.any(Array), + experimental_context: {}, }, ); }); @@ -4054,6 +4055,7 @@ describe('generateText', () => { abortSignal: expect.any(AbortSignal), toolCallId: 'call-1', messages: expect.any(Array), + experimental_context: {}, }, ); }); @@ -4352,7 +4354,7 @@ describe('generateText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "input": { "value": "value", }, @@ -6868,7 +6870,7 @@ describe('generateText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -7341,7 +7343,7 @@ describe('generateText', () => { "value": "value-needs-approval", }, "options": { - "experimental_context": undefined, + "experimental_context": {}, "messages": [ { "content": "test-input", @@ -7356,7 +7358,7 @@ describe('generateText', () => { "value": "value-no-approval", }, "options": { - "experimental_context": undefined, + "experimental_context": {}, "messages": [ { "content": "test-input", diff --git a/packages/ai/src/generate-text/stream-text.test.ts b/packages/ai/src/generate-text/stream-text.test.ts index a3ed17513aba..344e8cd28303 100644 --- a/packages/ai/src/generate-text/stream-text.test.ts +++ b/packages/ai/src/generate-text/stream-text.test.ts @@ -4968,7 +4968,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -5111,7 +5111,7 @@ describe('streamText', () => { "url": "https://example.com/2", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -5199,7 +5199,7 @@ describe('streamText', () => { "type": "file", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -5309,7 +5309,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -7121,7 +7121,7 @@ describe('streamText', () => { ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [], "finishReason": "stop", "functionId": undefined, @@ -7241,7 +7241,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -7436,7 +7436,7 @@ describe('streamText', () => { ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [], "finishReason": "stop", "functionId": undefined, @@ -7531,7 +7531,7 @@ describe('streamText', () => { "url": "https://example.com/2", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -7698,7 +7698,7 @@ describe('streamText', () => { ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [ DefaultGeneratedFileWithType { "base64Data": "Hello World", @@ -7787,7 +7787,7 @@ describe('streamText', () => { "type": "file", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -8428,7 +8428,7 @@ describe('streamText', () => { ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [], "finishReason": "stop", "functionId": undefined, @@ -8531,7 +8531,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -8615,7 +8615,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -8775,7 +8775,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -8859,7 +8859,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -9037,7 +9037,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -9121,7 +9121,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -10332,7 +10332,7 @@ describe('streamText', () => { ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [], "finishReason": "stop", "functionId": undefined, @@ -10435,7 +10435,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -10519,7 +10519,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -10679,7 +10679,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -10763,7 +10763,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -10937,7 +10937,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -11021,7 +11021,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -11518,7 +11518,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -11628,7 +11628,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -12824,6 +12824,7 @@ describe('streamText', () => { { abortSignal: abortController.signal, toolCallId: 'call-1', + experimental_context: {}, messages: expect.any(Array), }, ); @@ -13451,7 +13452,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "messages": [ { "content": "test-input", @@ -13465,7 +13466,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "inputTextDelta": "{"", "messages": [ { @@ -13480,7 +13481,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "inputTextDelta": "value", "messages": [ { @@ -13495,7 +13496,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "inputTextDelta": "":"", "messages": [ { @@ -13510,7 +13511,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "inputTextDelta": "Spark", "messages": [ { @@ -13525,7 +13526,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "inputTextDelta": "le", "messages": [ { @@ -13540,7 +13541,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "inputTextDelta": " Day", "messages": [ { @@ -13555,7 +13556,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "inputTextDelta": ""}", "messages": [ { @@ -13570,7 +13571,7 @@ describe('streamText', () => { { "options": { "abortSignal": undefined, - "experimental_context": undefined, + "experimental_context": {}, "input": { "value": "Sparkle Day", }, @@ -13865,7 +13866,7 @@ describe('streamText', () => { "type": "tool-error", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -14370,7 +14371,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -14604,7 +14605,7 @@ describe('streamText', () => { ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [], "finishReason": "stop", "functionId": undefined, @@ -14724,7 +14725,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -14954,7 +14955,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -15461,7 +15462,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -15906,7 +15907,7 @@ describe('streamText', () => { ], "dynamicToolCalls": [], "dynamicToolResults": [], - "experimental_context": undefined, + "experimental_context": {}, "files": [], "finishReason": "stop", "functionId": undefined, @@ -15952,7 +15953,7 @@ describe('streamText', () => { "type": "text", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -17048,7 +17049,7 @@ describe('streamText', () => { "type": "reasoning", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, @@ -17439,7 +17440,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "tool-calls", "functionId": undefined, "metadata": undefined, @@ -18494,7 +18495,7 @@ describe('streamText', () => { "type": "tool-result", }, ], - "experimental_context": undefined, + "experimental_context": {}, "finishReason": "stop", "functionId": undefined, "metadata": undefined, From 974866e63ee7e83f002e1354dd1bf11ac59dd3a4 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 12:42:06 +0200 Subject: [PATCH 29/41] types --- .../app/api/chat/human-in-the-loop/utils.ts | 3 +- .../ai-functions/src/lib/print-full-stream.ts | 2 +- .../ai-functions/src/lib/save-raw-chunks.ts | 2 +- examples/mcp/src/image-content/client.ts | 5 +- examples/mcp/src/output-schema/client.ts | 6 +- packages/mcp/src/tool/mcp-client.test.ts | 80 ++++++++++++++----- packages/mcp/src/tool/mcp-client.ts | 4 +- .../openai/src/tool/local-shell.test-d.ts | 3 +- packages/openai/src/tool/web-search.test-d.ts | 2 +- 9 files changed, 74 insertions(+), 33 deletions(-) diff --git a/examples/ai-e2e-next/app/api/chat/human-in-the-loop/utils.ts b/examples/ai-e2e-next/app/api/chat/human-in-the-loop/utils.ts index 561ecf49a683..752f4d99216c 100644 --- a/examples/ai-e2e-next/app/api/chat/human-in-the-loop/utils.ts +++ b/examples/ai-e2e-next/app/api/chat/human-in-the-loop/utils.ts @@ -51,7 +51,7 @@ export async function processToolCalls< executeFunctions: { [K in keyof Tools & keyof ExecutableTools]?: ( args: ExecutableTools[K] extends Tool ? P : never, - context: ToolExecutionOptions, + context: ToolExecutionOptions<{}>, ) => Promise; }, ): Promise { @@ -86,6 +86,7 @@ export async function processToolCalls< result = await toolInstance(part.input, { messages: await convertToModelMessages(messages), toolCallId: part.toolCallId, + experimental_context: {}, }); } else { result = 'Error: No execute function found on tool'; diff --git a/examples/ai-functions/src/lib/print-full-stream.ts b/examples/ai-functions/src/lib/print-full-stream.ts index dc63f0b30c55..a8994ba4e493 100644 --- a/examples/ai-functions/src/lib/print-full-stream.ts +++ b/examples/ai-functions/src/lib/print-full-stream.ts @@ -3,7 +3,7 @@ import { StreamTextResult } from 'ai'; export async function printFullStream({ result, }: { - result: StreamTextResult; + result: StreamTextResult; }) { for await (const chunk of result.fullStream) { switch (chunk.type) { diff --git a/examples/ai-functions/src/lib/save-raw-chunks.ts b/examples/ai-functions/src/lib/save-raw-chunks.ts index 2bb0a0f8662f..57661394945f 100644 --- a/examples/ai-functions/src/lib/save-raw-chunks.ts +++ b/examples/ai-functions/src/lib/save-raw-chunks.ts @@ -5,7 +5,7 @@ export async function saveRawChunks({ result, filename, }: { - result: StreamTextResult; + result: StreamTextResult; filename: string; }) { const rawChunks: unknown[] = []; diff --git a/examples/mcp/src/image-content/client.ts b/examples/mcp/src/image-content/client.ts index c7955957c56e..e36da502074e 100644 --- a/examples/mcp/src/image-content/client.ts +++ b/examples/mcp/src/image-content/client.ts @@ -20,7 +20,10 @@ async function main() { const tool = tools['get-image']; console.log('Calling get-image tool...\n'); - const result = await tool.execute!({}, { messages: [], toolCallId: '1' }); + const result = await tool.execute!( + {}, + { messages: [], toolCallId: '1', experimental_context: {} }, + ); console.log('Raw execute() result (MCP format):'); console.log(JSON.stringify(result, null, 2)); diff --git a/examples/mcp/src/output-schema/client.ts b/examples/mcp/src/output-schema/client.ts index f7ec49b6a7da..9832ae124a6a 100644 --- a/examples/mcp/src/output-schema/client.ts +++ b/examples/mcp/src/output-schema/client.ts @@ -59,7 +59,7 @@ async function main() { const weatherTool = tools['get-weather']; const weatherResult = await weatherTool.execute( { location: 'New York' }, - { messages: [], toolCallId: 'weather-1' }, + { messages: [], toolCallId: 'weather-1', experimental_context: {} }, ); const weather = weatherResult as { @@ -78,7 +78,7 @@ async function main() { const usersTool = tools['list-users']; const usersResult = await usersTool.execute( {}, - { messages: [], toolCallId: 'users-1' }, + { messages: [], toolCallId: 'users-1', experimental_context: {} }, ); const users = usersResult as { @@ -96,7 +96,7 @@ async function main() { const echoTool = tools['echo']; const echoResult = await echoTool.execute( { message: 'Hello, MCP!' }, - { messages: [], toolCallId: 'echo-1' }, + { messages: [], toolCallId: 'echo-1', experimental_context: {} }, ); console.log('Raw result:', JSON.stringify(echoResult, null, 2)); diff --git a/packages/mcp/src/tool/mcp-client.test.ts b/packages/mcp/src/tool/mcp-client.test.ts index 629d785b00cc..7747b0f72377 100644 --- a/packages/mcp/src/tool/mcp-client.test.ts +++ b/packages/mcp/src/tool/mcp-client.test.ts @@ -76,6 +76,7 @@ describe('MCPClient', () => { { messages: [], toolCallId: '1', + experimental_context: {}, }, ), ).toMatchInlineSnapshot(` @@ -135,7 +136,7 @@ describe('MCPClient', () => { // Verify the execute function works const result = await tool.execute( { foo: 'bar' }, - { messages: [], toolCallId: '1' }, + { messages: [], toolCallId: '1', experimental_context: {} }, ); expect(result).toMatchObject({ content: [{ type: 'text', text: 'Mock tool call result' }], @@ -192,8 +193,12 @@ describe('MCPClient', () => { const tools = await client.tools(); const tool = tools['get-image']; - expect(await tool.execute!({}, { messages: [], toolCallId: '1' })) - .toMatchInlineSnapshot(` + expect( + await tool.execute!( + {}, + { messages: [], toolCallId: '1', experimental_context: {} }, + ), + ).toMatchInlineSnapshot(` { "content": [ { @@ -268,8 +273,12 @@ describe('MCPClient', () => { const tools = await client.tools(); const tool = tools['get-text']; - expect(await tool.execute!({}, { messages: [], toolCallId: '1' })) - .toMatchInlineSnapshot(` + expect( + await tool.execute!( + {}, + { messages: [], toolCallId: '1', experimental_context: {} }, + ), + ).toMatchInlineSnapshot(` { "content": [ { @@ -334,8 +343,12 @@ describe('MCPClient', () => { const tools = await client.tools(); const tool = tools['get-mixed']; - expect(await tool.execute!({}, { messages: [], toolCallId: '1' })) - .toMatchInlineSnapshot(` + expect( + await tool.execute!( + {}, + { messages: [], toolCallId: '1', experimental_context: {} }, + ), + ).toMatchInlineSnapshot(` { "content": [ { @@ -411,8 +424,12 @@ describe('MCPClient', () => { const tools = await client.tools(); const tool = tools['get-unknown']; - expect(await tool.execute!({}, { messages: [], toolCallId: '1' })) - .toMatchInlineSnapshot(` + expect( + await tool.execute!( + {}, + { messages: [], toolCallId: '1', experimental_context: {} }, + ), + ).toMatchInlineSnapshot(` { "content": [ { @@ -477,8 +494,12 @@ describe('MCPClient', () => { const tools = await client.tools(); const tool = tools['get-raw']; - expect(await tool.execute!({}, { messages: [], toolCallId: '1' })) - .toMatchInlineSnapshot(` + expect( + await tool.execute!( + {}, + { messages: [], toolCallId: '1', experimental_context: {} }, + ), + ).toMatchInlineSnapshot(` { "isError": false, "toolResult": undefined, @@ -716,6 +737,7 @@ describe('MCPClient', () => { { messages: [], toolCallId: '1', + experimental_context: {}, }, ); @@ -758,7 +780,10 @@ describe('MCPClient', () => { }); const toolCall = tools['mock-tool'].execute; await expect( - toolCall({ bar: 'bar' }, { messages: [], toolCallId: '1' }), + toolCall( + { bar: 'bar' }, + { messages: [], toolCallId: '1', experimental_context: {} }, + ), ).rejects.toThrow(MCPClientError); }); @@ -782,7 +807,10 @@ describe('MCPClient', () => { const toolCall = tools['mock-tool'].execute; try { - await toolCall({ bar: 'bar' }, { messages: [], toolCallId: '1' }); + await toolCall( + { bar: 'bar' }, + { messages: [], toolCallId: '1', experimental_context: {} }, + ); throw new Error('Expected error to be thrown'); } catch (error) { expect(MCPClientError.isInstance(error)).toBe(true); @@ -940,6 +968,7 @@ describe('MCPClient', () => { messages: [], toolCallId: '1', abortSignal: abortController.signal, + experimental_context: {}, }, ), ).rejects.toSatisfy( @@ -1054,6 +1083,7 @@ describe('MCPClient', () => { { messages: [], toolCallId: '1', + experimental_context: {}, }, ); @@ -1091,7 +1121,10 @@ describe('MCPClient', () => { }, }); - const result = await tool.execute({}, { messages: [], toolCallId: '1' }); + const result = await tool.execute( + {}, + { messages: [], toolCallId: '1', experimental_context: {} }, + ); expect(result).toMatchInlineSnapshot(` { "content": [ @@ -1209,7 +1242,7 @@ describe('MCPClient', () => { const result = await tool.execute( { location: 'New York' }, - { messages: [], toolCallId: '1' }, + { messages: [], toolCallId: '1', experimental_context: {} }, ); expectTypeOf>>().toEqualTypeOf<{ @@ -1265,7 +1298,7 @@ describe('MCPClient', () => { const result = await tools['json-tool'].execute( {}, - { messages: [], toolCallId: '1' }, + { messages: [], toolCallId: '1', experimental_context: {} }, ); expect(result).toEqual({ @@ -1319,7 +1352,7 @@ describe('MCPClient', () => { const result = await tool.execute( { input: 'test' }, - { messages: [], toolCallId: '1' }, + { messages: [], toolCallId: '1', experimental_context: {} }, ); expectTypeOf< @@ -1380,7 +1413,10 @@ describe('MCPClient', () => { }); await expect( - tools['bad-output-tool'].execute({}, { messages: [], toolCallId: '1' }), + tools['bad-output-tool'].execute( + {}, + { messages: [], toolCallId: '1', experimental_context: {} }, + ), ).rejects.toThrow(MCPClientError); }); @@ -1426,7 +1462,7 @@ describe('MCPClient', () => { await expect( tools['invalid-json-tool'].execute( {}, - { messages: [], toolCallId: '1' }, + { messages: [], toolCallId: '1', experimental_context: {} }, ), ).rejects.toThrow(MCPClientError); }); @@ -1473,7 +1509,7 @@ describe('MCPClient', () => { await expect( tools['mismatched-json-tool'].execute( {}, - { messages: [], toolCallId: '1' }, + { messages: [], toolCallId: '1', experimental_context: {} }, ), ).rejects.toThrow(MCPClientError); }); @@ -1487,7 +1523,7 @@ describe('MCPClient', () => { const result = await tools['mock-tool'].execute( { foo: 'bar' }, - { messages: [], toolCallId: '1' }, + { messages: [], toolCallId: '1', experimental_context: {} }, ); // With automatic discovery, result is CallToolResult @@ -1568,7 +1604,7 @@ describe('MCPClient', () => { const result = await tools['complex-tool'].execute( {}, - { messages: [], toolCallId: '1' }, + { messages: [], toolCallId: '1', experimental_context: {} }, ); expect(result).toEqual({ diff --git a/packages/mcp/src/tool/mcp-client.ts b/packages/mcp/src/tool/mcp-client.ts index debcaf497b0c..978fb44ed9a7 100644 --- a/packages/mcp/src/tool/mcp-client.ts +++ b/packages/mcp/src/tool/mcp-client.ts @@ -420,7 +420,7 @@ class DefaultMCPClient implements MCPClient { }: { name: string; args: Record; - options?: ToolExecutionOptions; + options?: ToolExecutionOptions<{}>; }): Promise { try { return this.request({ @@ -579,7 +579,7 @@ class DefaultMCPClient implements MCPClient { const execute = async ( args: any, - options: ToolExecutionOptions, + options: ToolExecutionOptions<{}>, ): Promise => { options?.abortSignal?.throwIfAborted(); const result = await self.callTool({ name, args, options }); diff --git a/packages/openai/src/tool/local-shell.test-d.ts b/packages/openai/src/tool/local-shell.test-d.ts index 45b198d660a4..8dc6ef798de7 100644 --- a/packages/openai/src/tool/local-shell.test-d.ts +++ b/packages/openai/src/tool/local-shell.test-d.ts @@ -13,7 +13,8 @@ describe('local-shell tool type', () => { expectTypeOf(localShellTool).toEqualTypeOf< Tool< InferSchema, - InferSchema + InferSchema, + {} > >(); }); diff --git a/packages/openai/src/tool/web-search.test-d.ts b/packages/openai/src/tool/web-search.test-d.ts index fc78329bf87f..8c0b8e86dc62 100644 --- a/packages/openai/src/tool/web-search.test-d.ts +++ b/packages/openai/src/tool/web-search.test-d.ts @@ -7,7 +7,7 @@ describe('web-search tool type', () => { const webSearchTool = webSearch(); expectTypeOf(webSearchTool).toEqualTypeOf< - Tool<{}, InferSchema> + Tool<{}, InferSchema, {}> >(); }); }); From c6cc59f3c901b3f871522703ea03b0b697a65ce3 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 12:49:11 +0200 Subject: [PATCH 30/41] revert --- packages/ai/src/generate-text/tool-call.ts | 4 ++-- packages/ai/src/generate-text/tool-result.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ai/src/generate-text/tool-call.ts b/packages/ai/src/generate-text/tool-call.ts index 5c5fa589b32a..1d72085b9091 100644 --- a/packages/ai/src/generate-text/tool-call.ts +++ b/packages/ai/src/generate-text/tool-call.ts @@ -10,7 +10,7 @@ type BaseToolCall = { providerMetadata?: ProviderMetadata; }; -export type StaticToolCall = ValueOf<{ +export type StaticToolCall = ValueOf<{ [NAME in keyof TOOLS]: BaseToolCall & { toolName: NAME & string; input: InferToolInput; @@ -42,6 +42,6 @@ export type DynamicToolCall = BaseToolCall & { error?: unknown; }; -export type TypedToolCall = +export type TypedToolCall = | StaticToolCall | DynamicToolCall; diff --git a/packages/ai/src/generate-text/tool-result.ts b/packages/ai/src/generate-text/tool-result.ts index 422f3dafb283..406deef0231e 100644 --- a/packages/ai/src/generate-text/tool-result.ts +++ b/packages/ai/src/generate-text/tool-result.ts @@ -3,7 +3,7 @@ import { ProviderMetadata } from '../types'; import { ValueOf } from '../../src/util/value-of'; import { ToolSet } from './tool-set'; -export type StaticToolResult = ValueOf<{ +export type StaticToolResult = ValueOf<{ [NAME in keyof TOOLS]: { type: 'tool-result'; toolCallId: string; @@ -31,6 +31,6 @@ export type DynamicToolResult = { title?: string; }; -export type TypedToolResult = +export type TypedToolResult = | StaticToolResult | DynamicToolResult; From 69fc8178d3db56bdb07fac55064d2c1fbc6efb5f Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 13:12:36 +0200 Subject: [PATCH 31/41] extract infer --- packages/provider-utils/src/types/context.ts | 3 ++ packages/provider-utils/src/types/index.ts | 6 +-- .../src/types/infer-tool-context.test-d.ts | 38 ++++++++++++++++++ .../src/types/infer-tool-context.ts | 7 ++++ .../src/types/infer-tool-input.test-d.ts | 21 ++++++++++ .../src/types/infer-tool-input.ts | 7 ++++ .../src/types/infer-tool-output.test-d.ts | 40 +++++++++++++++++++ .../src/types/infer-tool-output.ts | 7 ++++ packages/provider-utils/src/types/tool.ts | 18 --------- 9 files changed, 126 insertions(+), 21 deletions(-) create mode 100644 packages/provider-utils/src/types/infer-tool-context.test-d.ts create mode 100644 packages/provider-utils/src/types/infer-tool-context.ts create mode 100644 packages/provider-utils/src/types/infer-tool-input.test-d.ts create mode 100644 packages/provider-utils/src/types/infer-tool-input.ts create mode 100644 packages/provider-utils/src/types/infer-tool-output.test-d.ts create mode 100644 packages/provider-utils/src/types/infer-tool-output.ts diff --git a/packages/provider-utils/src/types/context.ts b/packages/provider-utils/src/types/context.ts index 9a1d29a311fd..d595a8e7d3a3 100644 --- a/packages/provider-utils/src/types/context.ts +++ b/packages/provider-utils/src/types/context.ts @@ -1 +1,4 @@ +/** + * A context object that is passed into tool execution. + */ export type Context = Record; diff --git a/packages/provider-utils/src/types/index.ts b/packages/provider-utils/src/types/index.ts index ddda36df5ff7..e896a6e14521 100644 --- a/packages/provider-utils/src/types/index.ts +++ b/packages/provider-utils/src/types/index.ts @@ -16,15 +16,15 @@ export type { export type { Context } from './context'; export type { DataContent } from './data-content'; export { executeTool } from './execute-tool'; +export type { InferToolContext } from './infer-tool-context'; +export type { InferToolInput } from './infer-tool-input'; +export type { InferToolOutput } from './infer-tool-output'; export type { ModelMessage } from './model-message'; export type { ProviderOptions } from './provider-options'; export type { SystemModelMessage } from './system-model-message'; export { dynamicTool, tool, - type InferToolContext, - type InferToolInput, - type InferToolOutput, type Tool, type ToolExecuteFunction, type ToolExecutionOptions, diff --git a/packages/provider-utils/src/types/infer-tool-context.test-d.ts b/packages/provider-utils/src/types/infer-tool-context.test-d.ts new file mode 100644 index 000000000000..e32503d7328e --- /dev/null +++ b/packages/provider-utils/src/types/infer-tool-context.test-d.ts @@ -0,0 +1,38 @@ +import { describe, expectTypeOf, it } from 'vitest'; +import { z } from 'zod/v4'; +import type { InferToolContext } from './infer-tool-context'; +import { tool } from './tool'; +import { Context } from './context'; + +describe('InferToolContext', () => { + it('infers the context type from a tool with contextSchema', () => { + const weatherTool = tool({ + inputSchema: z.object({ + city: z.string(), + }), + contextSchema: z.object({ + userId: z.string(), + role: z.string(), + }), + execute: async () => ({ temperature: 72 }), + }); + + expectTypeOf>().toEqualTypeOf<{ + userId: string; + role: string; + }>(); + }); + + it('infers the generic context type from a tool without contextSchema', () => { + const weatherTool = tool({ + inputSchema: z.object({ + city: z.string(), + }), + execute: async () => ({ temperature: 72 }), + }); + + expectTypeOf< + InferToolContext + >().toEqualTypeOf(); + }); +}); diff --git a/packages/provider-utils/src/types/infer-tool-context.ts b/packages/provider-utils/src/types/infer-tool-context.ts new file mode 100644 index 000000000000..294074e3f096 --- /dev/null +++ b/packages/provider-utils/src/types/infer-tool-context.ts @@ -0,0 +1,7 @@ +import type { Tool } from './tool'; + +/** + * Infer the context type of a tool. + */ +export type InferToolContext> = + TOOL extends Tool ? CONTEXT : never; diff --git a/packages/provider-utils/src/types/infer-tool-input.test-d.ts b/packages/provider-utils/src/types/infer-tool-input.test-d.ts new file mode 100644 index 000000000000..0045b7ac8779 --- /dev/null +++ b/packages/provider-utils/src/types/infer-tool-input.test-d.ts @@ -0,0 +1,21 @@ +import { describe, expectTypeOf, it } from 'vitest'; +import { z } from 'zod/v4'; +import type { InferToolInput } from './infer-tool-input'; +import { tool } from './tool'; + +describe('InferToolInput', () => { + it('infers the input type from a tool with inputSchema', () => { + const weatherTool = tool({ + inputSchema: z.object({ + city: z.string(), + countryCode: z.string().length(2), + }), + execute: async () => ({ temperature: 72 }), + }); + + expectTypeOf>().toEqualTypeOf<{ + city: string; + countryCode: string; + }>(); + }); +}); diff --git a/packages/provider-utils/src/types/infer-tool-input.ts b/packages/provider-utils/src/types/infer-tool-input.ts new file mode 100644 index 000000000000..8ae9c8678f8f --- /dev/null +++ b/packages/provider-utils/src/types/infer-tool-input.ts @@ -0,0 +1,7 @@ +import type { Tool } from './tool'; + +/** + * Infer the input type of a tool. + */ +export type InferToolInput> = + TOOL extends Tool ? INPUT : never; diff --git a/packages/provider-utils/src/types/infer-tool-output.test-d.ts b/packages/provider-utils/src/types/infer-tool-output.test-d.ts new file mode 100644 index 000000000000..10c2a08c1ecd --- /dev/null +++ b/packages/provider-utils/src/types/infer-tool-output.test-d.ts @@ -0,0 +1,40 @@ +import { describe, expectTypeOf, it } from 'vitest'; +import { z } from 'zod/v4'; +import type { InferToolOutput } from './infer-tool-output'; +import { tool } from './tool'; + +describe('InferToolOutput', () => { + it('infers the output type from a tool with execute function', () => { + const weatherTool = tool({ + inputSchema: z.object({ + city: z.string(), + }), + execute: async () => ({ + temperature: 72, + conditions: 'sunny' as const, + }), + }); + + expectTypeOf>().toEqualTypeOf<{ + temperature: number; + conditions: 'sunny'; + }>(); + }); + + it('infers the output type from a tool with outputSchema', () => { + const weatherTool = tool({ + inputSchema: z.object({ + city: z.string(), + }), + outputSchema: z.object({ + temperature: z.number(), + conditions: z.literal('sunny'), + }), + }); + + expectTypeOf>().toEqualTypeOf<{ + temperature: number; + conditions: 'sunny'; + }>(); + }); +}); diff --git a/packages/provider-utils/src/types/infer-tool-output.ts b/packages/provider-utils/src/types/infer-tool-output.ts new file mode 100644 index 000000000000..6401e1e9407d --- /dev/null +++ b/packages/provider-utils/src/types/infer-tool-output.ts @@ -0,0 +1,7 @@ +import type { Tool } from './tool'; + +/** + * Infer the output type of a tool. + */ +export type InferToolOutput> = + TOOL extends Tool ? OUTPUT : never; diff --git a/packages/provider-utils/src/types/tool.ts b/packages/provider-utils/src/types/tool.ts index 663aa0ecd098..ebbe371ffe43 100644 --- a/packages/provider-utils/src/types/tool.ts +++ b/packages/provider-utils/src/types/tool.ts @@ -272,24 +272,6 @@ export type Tool< } ); -/** - * Infer the input type of a tool. - */ -export type InferToolInput = - TOOL extends Tool ? INPUT : never; - -/** - * Infer the output type of a tool. - */ -export type InferToolOutput = - TOOL extends Tool ? OUTPUT : never; - -/** - * Infer the context type of a tool. - */ -export type InferToolContext = - TOOL extends Tool ? CONTEXT : never; - /** * Helper function for inferring the execute args of a tool. */ From 9d2db4861f14fc5ec0fc3b7cc5def2a0e4cc5d29 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 14:57:32 +0200 Subject: [PATCH 32/41] type test --- .../src/generate-text/generate-text.test-d.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/ai/src/generate-text/generate-text.test-d.ts b/packages/ai/src/generate-text/generate-text.test-d.ts index fd7ccd47fabd..d1fb9110da4e 100644 --- a/packages/ai/src/generate-text/generate-text.test-d.ts +++ b/packages/ai/src/generate-text/generate-text.test-d.ts @@ -1,4 +1,5 @@ import { JSONValue } from '@ai-sdk/provider'; +import { tool } from '@ai-sdk/provider-utils'; import { describe, expectTypeOf, it } from 'vitest'; import { z } from 'zod'; import { generateText, Output } from '../generate-text'; @@ -65,4 +66,47 @@ describe('generateText types', () => { expectTypeOf().toEqualTypeOf(); }); }); + + describe('experimental_context', () => { + it('should infer typed experimental_context with one tool context and prepareStep', async () => { + generateText({ + model: new MockLanguageModelV4(), + prompt: 'Hello, world!', + tools: { + weather: tool({ + inputSchema: z.object({ + city: z.string(), + }), + contextSchema: z.object({ + userId: z.string(), + }), + execute: async (_input, { experimental_context }) => { + expectTypeOf(experimental_context).toMatchTypeOf<{ + userId: string; + }>(); + + return 'sunny'; + }, + }), + }, + experimental_context: { + userId: 'test-user', + role: 'admin', + }, + prepareStep: ({ experimental_context }) => { + expectTypeOf(experimental_context).toMatchTypeOf<{ + userId: string; + role: string; + }>(); + + return { + experimental_context: { + userId: experimental_context.userId, + role: experimental_context.role, + }, + }; + }, + }); + }); + }); }); From fef307ae7bb346e464f1ed8d91d5f8e0e1cae27f Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 15:02:19 +0200 Subject: [PATCH 33/41] cs --- .changeset/bright-glasses-happen.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/bright-glasses-happen.md diff --git a/.changeset/bright-glasses-happen.md b/.changeset/bright-glasses-happen.md new file mode 100644 index 000000000000..41930e4f56e5 --- /dev/null +++ b/.changeset/bright-glasses-happen.md @@ -0,0 +1,6 @@ +--- +"@ai-sdk/provider-utils": major +"ai": major +--- + +feat(ai): change type of experimental_context from unknown to generic From 3a4dc0aab3b4625f020044cdc83858164883f462 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 15:05:05 +0200 Subject: [PATCH 34/41] stream example --- .../openai/tool-call-with-context.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 examples/ai-functions/src/stream-text/openai/tool-call-with-context.ts diff --git a/examples/ai-functions/src/stream-text/openai/tool-call-with-context.ts b/examples/ai-functions/src/stream-text/openai/tool-call-with-context.ts new file mode 100644 index 000000000000..41fe8dca9508 --- /dev/null +++ b/examples/ai-functions/src/stream-text/openai/tool-call-with-context.ts @@ -0,0 +1,68 @@ +import { openai } from '@ai-sdk/openai'; +import { streamText, tool } from 'ai'; +import { z } from 'zod'; +import { run } from '../../lib/run'; +import { printFullStream } from '../../lib/print-full-stream'; + +run(async () => { + const result = streamText({ + model: openai('gpt-5-mini'), + tools: { + weather: tool({ + description: 'Get the weather in a location', + inputSchema: z.object({ + location: z.string().describe('The location to get the weather for'), + }), + contextSchema: z.object({ + weatherApiKey: z.string().describe('The API key for the weather API'), + }), + execute: async ( + { location }, + { experimental_context: { weatherApiKey } }, + ) => { + console.log('weather tool api key:', weatherApiKey); + + return { + location, + temperature: 72 + Math.floor(Math.random() * 21) - 10, + }; + }, + }), + calculator: tool({ + description: 'Calculate mathematical expressions', + inputSchema: z.object({ + expression: z + .string() + .describe('The mathematical expression to calculate'), + }), + contextSchema: z.object({ + calculatorApiKey: z + .string() + .describe('The API key for the calculator API'), + }), + execute: async ( + { expression }, + { experimental_context: { calculatorApiKey } }, + ) => { + console.log('calculator tool api key:', calculatorApiKey); + return { + expression, + result: eval(expression), + }; + }, + }), + }, + experimental_context: { + weatherApiKey: 'weather-123', + calculatorApiKey: 'calculator-456', + somethingElse: 'other-context', + }, + prepareStep: async ({ experimental_context: context }) => { + console.log('prepareStep context:', context); + return {}; + }, + prompt: 'What is the weather in San Francisco?', + }); + + await printFullStream({ result }); +}); From 91b1a2f972d1134f81d28e10aa5e3702fc2ecdbf Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 15:15:02 +0200 Subject: [PATCH 35/41] Add tests for UnionToIntersection utility type --- .../src/util/union-to-intersection.test-d.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/ai/src/util/union-to-intersection.test-d.ts diff --git a/packages/ai/src/util/union-to-intersection.test-d.ts b/packages/ai/src/util/union-to-intersection.test-d.ts new file mode 100644 index 000000000000..f2fde5a0fd41 --- /dev/null +++ b/packages/ai/src/util/union-to-intersection.test-d.ts @@ -0,0 +1,29 @@ +import { describe, expectTypeOf, it } from 'vitest'; +import type { UnionToIntersection } from './union-to-intersection'; + +describe('UnionToIntersection', () => { + it('returns never when given no input', () => { + type Result = UnionToIntersection; + + expectTypeOf().toEqualTypeOf(); + }); + + it('returns the same type for a single input', () => { + type Result = UnionToIntersection<{ city: string }>; + + expectTypeOf().toEqualTypeOf<{ + city: string; + }>(); + }); + + it('converts a union of object types into an intersection', () => { + type Result = UnionToIntersection< + { city: string } | { countryCode: string } + >; + + expectTypeOf().toEqualTypeOf<{ + city: string; + countryCode: string; + }>(); + }); +}); From 6e56bd9b5b8eb9b5afc95dc526bfe1fdb9430f6e Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 15:30:08 +0200 Subject: [PATCH 36/41] Add InferToolSetContext type and corresponding tests --- .../infer-tool-set-context.test-d.ts | 65 +++++++++++++++++++ .../generate-text/infer-tool-set-context.ts | 12 ++++ packages/ai/src/generate-text/tool-set.ts | 10 +-- 3 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 packages/ai/src/generate-text/infer-tool-set-context.test-d.ts create mode 100644 packages/ai/src/generate-text/infer-tool-set-context.ts diff --git a/packages/ai/src/generate-text/infer-tool-set-context.test-d.ts b/packages/ai/src/generate-text/infer-tool-set-context.test-d.ts new file mode 100644 index 000000000000..58d92f6dba55 --- /dev/null +++ b/packages/ai/src/generate-text/infer-tool-set-context.test-d.ts @@ -0,0 +1,65 @@ +import { Context, tool } from '@ai-sdk/provider-utils'; +import { describe, expectTypeOf, it } from 'vitest'; +import { z } from 'zod/v4'; +import type { InferToolSetContext } from './infer-tool-set-context'; + +describe('InferToolSetContext', () => { + it('infers the intersection of context types across a tool set', () => { + const tools = { + weather: tool({ + inputSchema: z.object({ + city: z.string(), + }), + contextSchema: z.object({ + userId: z.string(), + }), + }), + forecast: tool({ + inputSchema: z.object({ + days: z.number(), + }), + contextSchema: z.object({ + role: z.string(), + }), + }), + }; + + expectTypeOf>().toMatchObjectType<{ + userId: string; + role: string; + }>(); + }); + + it('infers a single tool context type from a tool set', () => { + const tools = { + weather: tool({ + inputSchema: z.object({ + city: z.string(), + }), + contextSchema: z.object({ + userId: z.string(), + role: z.string(), + }), + }), + }; + + expectTypeOf>().toMatchObjectType<{ + userId: string; + role: string; + }>(); + }); + + it('falls back to the generic context type for tools without contextSchema', () => { + const tools = { + weather: tool({ + inputSchema: z.object({ + city: z.string(), + }), + }), + }; + + expectTypeOf< + InferToolSetContext + >().toMatchObjectType(); + }); +}); diff --git a/packages/ai/src/generate-text/infer-tool-set-context.ts b/packages/ai/src/generate-text/infer-tool-set-context.ts new file mode 100644 index 000000000000..8c549e2d8afc --- /dev/null +++ b/packages/ai/src/generate-text/infer-tool-set-context.ts @@ -0,0 +1,12 @@ +import { InferToolContext } from '@ai-sdk/provider-utils'; +import { UnionToIntersection } from '../util/union-to-intersection'; +import type { ToolSet } from './tool-set'; + +/** + * Infer the context type of a tool set. + */ +export type InferToolSetContext = UnionToIntersection< + { + [K in keyof TOOLS]: InferToolContext>; + }[keyof TOOLS] +>; diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index 104c96b30162..fa2fe650ad7d 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -1,5 +1,5 @@ -import { Context, InferToolContext, Tool } from '@ai-sdk/provider-utils'; -import { UnionToIntersection } from '../util/union-to-intersection'; +import { Context, Tool } from '@ai-sdk/provider-utils'; +import type { InferToolSetContext } from './infer-tool-set-context'; export type ToolSet = Record< string, @@ -19,12 +19,6 @@ export type ToolSet = Record< > >; -export type InferToolSetContext = UnionToIntersection< - { - [K in keyof TOOLS]: InferToolContext>; - }[keyof TOOLS] ->; - export type ExpandedContext = InferToolSetContext< NoInfer > & From 66a0c37ddc081652b0327eeb725925b8f960f095 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 15:36:12 +0200 Subject: [PATCH 37/41] 1 --- packages/ai/src/generate-text/generate-text.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 126462bf2abf..80d47d93223c 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -1195,7 +1195,7 @@ class DefaultGenerateTextResult< private readonly _output: InferCompleteOutput | undefined; constructor(options: { - steps: StepResult[]; + steps: GenerateTextResult['steps']; output: InferCompleteOutput | undefined; totalUsage: LanguageModelUsage; }) { From 251ac1b8648c0e355d1b2f15049ab9ed27fa2b77 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 15:43:17 +0200 Subject: [PATCH 38/41] Refactor context types: Replace ExpandedContext with GenerationContext across multiple files for improved type handling in agent and generation processes. --- packages/ai/src/agent/agent.ts | 7 ++++--- .../agent/create-agent-ui-stream-response.ts | 5 +++-- packages/ai/src/agent/create-agent-ui-stream.ts | 5 +++-- .../agent/pipe-agent-ui-stream-to-response.ts | 5 +++-- .../ai/src/agent/tool-loop-agent-settings.ts | 13 +++++++------ packages/ai/src/agent/tool-loop-agent.ts | 5 +++-- packages/ai/src/generate-text/core-events.ts | 11 ++++++----- .../create-execute-tools-transformation.ts | 5 +++-- .../ai/src/generate-text/execute-tool-call.ts | 5 +++-- .../src/generate-text/generate-text-result.ts | 5 +++-- packages/ai/src/generate-text/generate-text.ts | 17 +++++++++-------- .../ai/src/generate-text/generation-context.ts | 14 ++++++++++++++ packages/ai/src/generate-text/prepare-step.ts | 7 ++++--- packages/ai/src/generate-text/step-result.ts | 7 ++++--- packages/ai/src/generate-text/stop-condition.ts | 7 ++++--- .../ai/src/generate-text/stream-text-result.ts | 5 +++-- packages/ai/src/generate-text/stream-text.ts | 17 +++++++++-------- packages/ai/src/generate-text/tool-set.ts | 8 +------- .../src/telemetry/open-telemetry-integration.ts | 5 +++-- packages/ai/src/ui/direct-chat-transport.ts | 7 ++++--- 20 files changed, 93 insertions(+), 67 deletions(-) create mode 100644 packages/ai/src/generate-text/generation-context.ts diff --git a/packages/ai/src/agent/agent.ts b/packages/ai/src/agent/agent.ts index ceced8c26a83..714496e2a259 100644 --- a/packages/ai/src/agent/agent.ts +++ b/packages/ai/src/agent/agent.ts @@ -3,7 +3,8 @@ import { GenerateTextResult } from '../generate-text/generate-text-result'; import { Output } from '../generate-text/output'; import { StreamTextTransform } from '../generate-text/stream-text'; import { StreamTextResult } from '../generate-text/stream-text-result'; -import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; +import type { GenerationContext } from '../generate-text/generation-context'; +import type { ToolSet } from '../generate-text/tool-set'; import { TimeoutConfiguration } from '../prompt/call-settings'; import type { ToolLoopAgentOnFinishCallback, @@ -20,7 +21,7 @@ import type { export type AgentCallParameters< CALL_OPTIONS, TOOLS extends ToolSet = {}, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, > = ([CALL_OPTIONS] extends [never] ? { options?: never } : { options: CALL_OPTIONS }) & @@ -124,7 +125,7 @@ export type AgentStreamParameters< export interface Agent< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = never, > { /** diff --git a/packages/ai/src/agent/create-agent-ui-stream-response.ts b/packages/ai/src/agent/create-agent-ui-stream-response.ts index 6ee6581c78f6..957bb104111f 100644 --- a/packages/ai/src/agent/create-agent-ui-stream-response.ts +++ b/packages/ai/src/agent/create-agent-ui-stream-response.ts @@ -1,6 +1,7 @@ import { StreamTextTransform, UIMessageStreamOptions } from '../generate-text'; import { Output } from '../generate-text/output'; -import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; +import type { GenerationContext } from '../generate-text/generation-context'; +import type { ToolSet } from '../generate-text/tool-set'; import { TimeoutConfiguration } from '../prompt/call-settings'; import { createUIMessageStreamResponse } from '../ui-message-stream'; import { UIMessageStreamResponseInit } from '../ui-message-stream/ui-message-stream-response-init'; @@ -29,7 +30,7 @@ import type { ToolLoopAgentOnStepFinishCallback } from './tool-loop-agent-settin export async function createAgentUIStreamResponse< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = never, MESSAGE_METADATA = unknown, >({ diff --git a/packages/ai/src/agent/create-agent-ui-stream.ts b/packages/ai/src/agent/create-agent-ui-stream.ts index 39e9d4d39f1d..7ceb950a99ec 100644 --- a/packages/ai/src/agent/create-agent-ui-stream.ts +++ b/packages/ai/src/agent/create-agent-ui-stream.ts @@ -1,6 +1,7 @@ import { StreamTextTransform, UIMessageStreamOptions } from '../generate-text'; import { Output } from '../generate-text/output'; -import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; +import type { GenerationContext } from '../generate-text/generation-context'; +import type { ToolSet } from '../generate-text/tool-set'; import { TimeoutConfiguration } from '../prompt/call-settings'; import { InferUIMessageChunk } from '../ui-message-stream'; import { convertToModelMessages } from '../ui/convert-to-model-messages'; @@ -26,7 +27,7 @@ import type { ToolLoopAgentOnStepFinishCallback } from './tool-loop-agent-settin export async function createAgentUIStream< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = never, MESSAGE_METADATA = unknown, >({ diff --git a/packages/ai/src/agent/pipe-agent-ui-stream-to-response.ts b/packages/ai/src/agent/pipe-agent-ui-stream-to-response.ts index 655b6e0fe1b0..942c0871fbdc 100644 --- a/packages/ai/src/agent/pipe-agent-ui-stream-to-response.ts +++ b/packages/ai/src/agent/pipe-agent-ui-stream-to-response.ts @@ -1,7 +1,8 @@ import { ServerResponse } from 'node:http'; import { StreamTextTransform, UIMessageStreamOptions } from '../generate-text'; import { Output } from '../generate-text/output'; -import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; +import type { GenerationContext } from '../generate-text/generation-context'; +import type { ToolSet } from '../generate-text/tool-set'; import { TimeoutConfiguration } from '../prompt/call-settings'; import { pipeUIMessageStreamToResponse } from '../ui-message-stream'; import { UIMessageStreamResponseInit } from '../ui-message-stream/ui-message-stream-response-init'; @@ -29,7 +30,7 @@ import type { ToolLoopAgentOnStepFinishCallback } from './tool-loop-agent-settin export async function pipeAgentUIStreamToResponse< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = never, MESSAGE_METADATA = unknown, >({ diff --git a/packages/ai/src/agent/tool-loop-agent-settings.ts b/packages/ai/src/agent/tool-loop-agent-settings.ts index dcba514ba66b..5f6cf1cab0db 100644 --- a/packages/ai/src/agent/tool-loop-agent-settings.ts +++ b/packages/ai/src/agent/tool-loop-agent-settings.ts @@ -16,7 +16,8 @@ import { Output } from '../generate-text/output'; import { PrepareStepFunction } from '../generate-text/prepare-step'; import { StopCondition } from '../generate-text/stop-condition'; import { ToolCallRepairFunction } from '../generate-text/tool-call-repair-function'; -import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; +import type { GenerationContext } from '../generate-text/generation-context'; +import type { ToolSet } from '../generate-text/tool-set'; import { CallSettings, TimeoutConfiguration } from '../prompt/call-settings'; import { Prompt } from '../prompt/prompt'; import { TelemetrySettings } from '../telemetry/telemetry-settings'; @@ -26,13 +27,13 @@ import { AgentCallParameters } from './agent'; export type ToolLoopAgentOnStartCallback< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = Output, > = (event: OnStartEvent) => PromiseLike | void; export type ToolLoopAgentOnStepStartCallback< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = Output, > = ( event: OnStepStartEvent, @@ -48,12 +49,12 @@ export type ToolLoopAgentOnToolCallFinishCallback< export type ToolLoopAgentOnStepFinishCallback< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, > = (stepResult: OnStepFinishEvent) => Promise | void; export type ToolLoopAgentOnFinishCallback< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, > = (event: OnFinishEvent) => PromiseLike | void; /** @@ -62,7 +63,7 @@ export type ToolLoopAgentOnFinishCallback< export type ToolLoopAgentSettings< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = never, > = Omit & { /** diff --git a/packages/ai/src/agent/tool-loop-agent.ts b/packages/ai/src/agent/tool-loop-agent.ts index 92ff75573f73..1c6b33569f1e 100644 --- a/packages/ai/src/agent/tool-loop-agent.ts +++ b/packages/ai/src/agent/tool-loop-agent.ts @@ -4,7 +4,8 @@ import { Output } from '../generate-text/output'; import { isStepCount } from '../generate-text/stop-condition'; import { streamText } from '../generate-text/stream-text'; import { StreamTextResult } from '../generate-text/stream-text-result'; -import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; +import type { GenerationContext } from '../generate-text/generation-context'; +import type { ToolSet } from '../generate-text/tool-set'; import { Prompt } from '../prompt'; import { Agent, AgentCallParameters, AgentStreamParameters } from './agent'; import { @@ -27,7 +28,7 @@ import { export class ToolLoopAgent< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = never, > implements Agent { readonly version = 'agent-v1'; diff --git a/packages/ai/src/generate-text/core-events.ts b/packages/ai/src/generate-text/core-events.ts index b4545c1d5029..cd30610e6beb 100644 --- a/packages/ai/src/generate-text/core-events.ts +++ b/packages/ai/src/generate-text/core-events.ts @@ -12,7 +12,8 @@ import type { StepResult } from './step-result'; import type { StopCondition } from './stop-condition'; import { TextStreamPart } from './stream-text-result'; import type { TypedToolCall } from './tool-call'; -import type { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; /** * Common model information used across callback events. @@ -31,7 +32,7 @@ export interface CallbackModelInfo { */ export interface OnStartEvent< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -150,7 +151,7 @@ export interface OnStartEvent< */ export interface OnStepStartEvent< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = Output, INCLUDE = { requestBody?: boolean; responseBody?: boolean }, > { @@ -370,7 +371,7 @@ export interface OnChunkEvent { */ export type OnStepFinishEvent< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, > = StepResult; /** @@ -381,7 +382,7 @@ export type OnStepFinishEvent< */ export type OnFinishEvent< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, > = StepResult & { /** Array containing results from all steps in the generation. */ readonly steps: StepResult[]; diff --git a/packages/ai/src/generate-text/create-execute-tools-transformation.ts b/packages/ai/src/generate-text/create-execute-tools-transformation.ts index 736f5886b932..a1e3e967b43e 100644 --- a/packages/ai/src/generate-text/create-execute-tools-transformation.ts +++ b/packages/ai/src/generate-text/create-execute-tools-transformation.ts @@ -9,12 +9,13 @@ import { StreamTextOnToolCallStartCallback, } from './stream-text'; import { TypedToolCall } from './tool-call'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; import { ModelCallStreamPart } from './stream-model-call'; export function createExecuteToolsTransformation< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, >({ tools, telemetry, diff --git a/packages/ai/src/generate-text/execute-tool-call.ts b/packages/ai/src/generate-text/execute-tool-call.ts index d251b6bc517a..409b75c43478 100644 --- a/packages/ai/src/generate-text/execute-tool-call.ts +++ b/packages/ai/src/generate-text/execute-tool-call.ts @@ -14,7 +14,8 @@ import { TypedToolCall } from './tool-call'; import { TypedToolError } from './tool-error'; import { ToolOutput } from './tool-output'; import { TypedToolResult } from './tool-result'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; /** * Executes a single tool call and manages its lifecycle callbacks. @@ -29,7 +30,7 @@ import { ExpandedContext, ToolSet } from './tool-set'; */ export async function executeToolCall< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, >({ toolCall, tools, diff --git a/packages/ai/src/generate-text/generate-text-result.ts b/packages/ai/src/generate-text/generate-text-result.ts index 5722e5e8e31e..a80bb66df7ab 100644 --- a/packages/ai/src/generate-text/generate-text-result.ts +++ b/packages/ai/src/generate-text/generate-text-result.ts @@ -16,7 +16,8 @@ import { StaticToolResult, TypedToolResult, } from './tool-result'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; /** * The result of a `generateText` call. @@ -24,7 +25,7 @@ import { ExpandedContext, ToolSet } from './tool-set'; */ export interface GenerateTextResult< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, OUTPUT extends Output, > { /** diff --git a/packages/ai/src/generate-text/generate-text.ts b/packages/ai/src/generate-text/generate-text.ts index 80d47d93223c..317b2b7ff977 100644 --- a/packages/ai/src/generate-text/generate-text.ts +++ b/packages/ai/src/generate-text/generate-text.ts @@ -64,6 +64,7 @@ import { executeToolCall } from './execute-tool-call'; import { filterActiveTools } from './filter-active-tool'; import { GenerateTextResult } from './generate-text-result'; import { DefaultGeneratedFile } from './generated-file'; +import type { GenerationContext } from './generation-context'; import { isApprovalNeeded } from './is-approval-needed'; import { Output, text } from './output'; import { InferCompleteOutput } from './output-utils'; @@ -84,7 +85,7 @@ import { ToolCallRepairFunction } from './tool-call-repair-function'; import { TypedToolError } from './tool-error'; import { ToolOutput } from './tool-output'; import { TypedToolResult } from './tool-result'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { ToolSet } from './tool-set'; const originalGenerateId = createIdGenerator({ prefix: 'aitxt', @@ -115,7 +116,7 @@ type GenerateTextIncludeSettings = { */ export type GenerateTextOnStartCallback< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, OUTPUT extends Output = Output, > = ( event: OnStartEvent, @@ -132,7 +133,7 @@ export type GenerateTextOnStartCallback< */ export type GenerateTextOnStepStartCallback< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, OUTPUT extends Output = Output, > = ( event: OnStepStartEvent, @@ -176,7 +177,7 @@ export type GenerateTextOnToolCallFinishCallback = ( */ export type GenerateTextOnStepFinishCallback< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = (event: OnStepFinishEvent) => Promise | void; /** @@ -190,7 +191,7 @@ export type GenerateTextOnStepFinishCallback< */ export type GenerateTextOnFinishCallback< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = (event: OnFinishEvent) => PromiseLike | void; /** @@ -249,7 +250,7 @@ export type GenerateTextOnFinishCallback< */ export async function generateText< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, OUTPUT extends Output = Output, >({ model: modelArg, @@ -1127,7 +1128,7 @@ export async function generateText< async function executeTools< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, >({ toolCalls, tools, @@ -1187,7 +1188,7 @@ async function executeTools< class DefaultGenerateTextResult< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, OUTPUT extends Output, > implements GenerateTextResult { readonly steps: GenerateTextResult['steps']; diff --git a/packages/ai/src/generate-text/generation-context.ts b/packages/ai/src/generate-text/generation-context.ts new file mode 100644 index 000000000000..510b2050f133 --- /dev/null +++ b/packages/ai/src/generate-text/generation-context.ts @@ -0,0 +1,14 @@ +import type { Context } from '@ai-sdk/provider-utils'; +import type { InferToolSetContext } from './infer-tool-set-context'; +import type { ToolSet } from './tool-set'; + +/** + * The context type for a generation call. + * + * It expands the tool set context with the generic context type for + * e.g. prepareStep or telemetry. + */ +export type GenerationContext = InferToolSetContext< + NoInfer +> & + Context; diff --git a/packages/ai/src/generate-text/prepare-step.ts b/packages/ai/src/generate-text/prepare-step.ts index cf06e5e3c5e7..890f934de36c 100644 --- a/packages/ai/src/generate-text/prepare-step.ts +++ b/packages/ai/src/generate-text/prepare-step.ts @@ -6,7 +6,8 @@ import { } from '@ai-sdk/provider-utils'; import { LanguageModel, ToolChoice } from '../types/language-model'; import { StepResult } from './step-result'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; /** * Function that you can use to provide different settings for a step. @@ -23,7 +24,7 @@ import { ExpandedContext, ToolSet } from './tool-set'; */ export type PrepareStepFunction< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = (options: { /** * The steps that have been executed so far. @@ -59,7 +60,7 @@ export type PrepareStepFunction< */ export type PrepareStepResult< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = | { /** diff --git a/packages/ai/src/generate-text/step-result.ts b/packages/ai/src/generate-text/step-result.ts index 399aa1cb3770..dddfe648f61f 100644 --- a/packages/ai/src/generate-text/step-result.ts +++ b/packages/ai/src/generate-text/step-result.ts @@ -23,14 +23,15 @@ import { StaticToolResult, TypedToolResult, } from './tool-result'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; /** * The result of a single step in the generation process. */ export type StepResult< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = { /** * Unique identifier for the generation call this step belongs to. @@ -181,7 +182,7 @@ export type StepResult< export class DefaultStepResult< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > implements StepResult { readonly callId: StepResult['callId']; readonly stepNumber: StepResult['stepNumber']; diff --git a/packages/ai/src/generate-text/stop-condition.ts b/packages/ai/src/generate-text/stop-condition.ts index 7a849fceb802..6527b7f73218 100644 --- a/packages/ai/src/generate-text/stop-condition.ts +++ b/packages/ai/src/generate-text/stop-condition.ts @@ -1,9 +1,10 @@ import { StepResult } from './step-result'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; export type StopCondition< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = (options: { steps: Array>; }) => PromiseLike | boolean; @@ -25,7 +26,7 @@ export function hasToolCall(toolName: string): StopCondition { export async function isStopConditionMet< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, >({ stopConditions, steps, diff --git a/packages/ai/src/generate-text/stream-text-result.ts b/packages/ai/src/generate-text/stream-text-result.ts index a75da8fc0bbd..cbef38b52777 100644 --- a/packages/ai/src/generate-text/stream-text-result.ts +++ b/packages/ai/src/generate-text/stream-text-result.ts @@ -35,7 +35,8 @@ import { StaticToolResult, TypedToolResult, } from './tool-result'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; export type UIMessageStreamOptions = { /** @@ -108,7 +109,7 @@ export type ConsumeStreamOptions = { */ export interface StreamTextResult< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, OUTPUT extends Output, > { /** diff --git a/packages/ai/src/generate-text/stream-text.ts b/packages/ai/src/generate-text/stream-text.ts index 8c77fe47547f..455bbefd6b9f 100644 --- a/packages/ai/src/generate-text/stream-text.ts +++ b/packages/ai/src/generate-text/stream-text.ts @@ -113,7 +113,8 @@ import { TypedToolCall } from './tool-call'; import { ToolCallRepairFunction } from './tool-call-repair-function'; import { ToolOutput } from './tool-output'; import { StaticToolOutputDenied } from './tool-output-denied'; -import { ExpandedContext, ToolSet } from './tool-set'; +import type { GenerationContext } from './generation-context'; +import type { ToolSet } from './tool-set'; const originalGenerateId = createIdGenerator({ prefix: 'aitxt', @@ -152,7 +153,7 @@ export type StreamTextOnErrorCallback = (event: { */ export type StreamTextOnStepFinishCallback< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = (event: OnStepFinishEvent) => PromiseLike | void; /** @@ -185,7 +186,7 @@ export type StreamTextOnChunkCallback = (event: { */ export type StreamTextOnFinishCallback< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = (event: OnFinishEvent) => PromiseLike | void; /** @@ -195,7 +196,7 @@ export type StreamTextOnFinishCallback< */ export type StreamTextOnAbortCallback< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, > = (event: { /** * Details for all previously finished steps. @@ -219,7 +220,7 @@ type StreamTextIncludeSettings = { requestBody?: boolean }; */ export type StreamTextOnStartCallback< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = Output, > = ( event: OnStartEvent, @@ -236,7 +237,7 @@ export type StreamTextOnStartCallback< */ export type StreamTextOnStepStartCallback< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = Output, > = ( event: OnStepStartEvent, @@ -297,7 +298,7 @@ export type StreamTextOnToolCallFinishCallback< */ export function streamText< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = Output, >({ model, @@ -713,7 +714,7 @@ function createOutputTransformStream< class DefaultStreamTextResult< TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, OUTPUT extends Output, > implements StreamTextResult { private readonly _totalUsage = new DelayedPromise< diff --git a/packages/ai/src/generate-text/tool-set.ts b/packages/ai/src/generate-text/tool-set.ts index fa2fe650ad7d..57f40d946f9c 100644 --- a/packages/ai/src/generate-text/tool-set.ts +++ b/packages/ai/src/generate-text/tool-set.ts @@ -1,5 +1,4 @@ -import { Context, Tool } from '@ai-sdk/provider-utils'; -import type { InferToolSetContext } from './infer-tool-set-context'; +import type { Tool } from '@ai-sdk/provider-utils'; export type ToolSet = Record< string, @@ -18,8 +17,3 @@ export type ToolSet = Record< | 'needsApproval' > >; - -export type ExpandedContext = InferToolSetContext< - NoInfer -> & - Context; diff --git a/packages/ai/src/telemetry/open-telemetry-integration.ts b/packages/ai/src/telemetry/open-telemetry-integration.ts index 89ad5f5e029a..f9a156937511 100644 --- a/packages/ai/src/telemetry/open-telemetry-integration.ts +++ b/packages/ai/src/telemetry/open-telemetry-integration.ts @@ -31,7 +31,8 @@ import type { OnToolCallStartEvent, } from '../generate-text/core-events'; import type { Output } from '../generate-text/output'; -import type { ExpandedContext, ToolSet } from '../generate-text/tool-set'; +import type { GenerationContext } from '../generate-text/generation-context'; +import type { ToolSet } from '../generate-text/tool-set'; import type { ObjectOnStartEvent, ObjectOnFinishEvent, @@ -115,7 +116,7 @@ function selectAttributes( interface OtelStepStartEvent< TOOLS extends ToolSet = ToolSet, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = Output, > extends OnStepStartEvent { readonly promptMessages?: LanguageModelV4Prompt; diff --git a/packages/ai/src/ui/direct-chat-transport.ts b/packages/ai/src/ui/direct-chat-transport.ts index d1d5cd6c75ff..52a999944f8a 100644 --- a/packages/ai/src/ui/direct-chat-transport.ts +++ b/packages/ai/src/ui/direct-chat-transport.ts @@ -1,7 +1,8 @@ import { Agent } from '../agent/agent'; import { Output } from '../generate-text/output'; import { UIMessageStreamOptions } from '../generate-text/stream-text-result'; -import { ExpandedContext, ToolSet } from '../generate-text/tool-set'; +import type { GenerationContext } from '../generate-text/generation-context'; +import type { ToolSet } from '../generate-text/tool-set'; import { UIMessageChunk } from '../ui-message-stream/ui-message-chunks'; import { ChatTransport } from './chat-transport'; import { convertToModelMessages } from './convert-to-model-messages'; @@ -14,7 +15,7 @@ import { validateUIMessages } from './validate-ui-messages'; export type DirectChatTransportOptions< CALL_OPTIONS, TOOLS extends ToolSet, - CONTEXT extends ExpandedContext, + CONTEXT extends GenerationContext, OUTPUT extends Output, UI_MESSAGE extends UIMessage>, > = { @@ -50,7 +51,7 @@ export type DirectChatTransportOptions< export class DirectChatTransport< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, - CONTEXT extends ExpandedContext = ExpandedContext, + CONTEXT extends GenerationContext = GenerationContext, OUTPUT extends Output = never, UI_MESSAGE extends UIMessage> = UIMessage< unknown, From 780835f11245c33d43d19961d6ca062bfde0e96d Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 15:46:49 +0200 Subject: [PATCH 39/41] Add tests for GenerationContext type to validate context merging and fallback behavior --- .../generation-context.test-d.ts | 53 +++++++++++++++++++ .../src/generate-text/generation-context.ts | 3 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 packages/ai/src/generate-text/generation-context.test-d.ts diff --git a/packages/ai/src/generate-text/generation-context.test-d.ts b/packages/ai/src/generate-text/generation-context.test-d.ts new file mode 100644 index 000000000000..942581e206a6 --- /dev/null +++ b/packages/ai/src/generate-text/generation-context.test-d.ts @@ -0,0 +1,53 @@ +import { Context, tool } from '@ai-sdk/provider-utils'; +import { describe, expectTypeOf, it } from 'vitest'; +import { z } from 'zod/v4'; +import type { GenerationContext } from './generation-context'; + +describe('GenerationContext', () => { + it('combines inferred tool context with the generic context type', () => { + const tools = { + weather: tool({ + inputSchema: z.object({ + city: z.string(), + }), + contextSchema: z.object({ + userId: z.string(), + }), + }), + forecast: tool({ + inputSchema: z.object({ + days: z.number(), + }), + contextSchema: z.object({ + role: z.string(), + }), + }), + }; + + expectTypeOf>().toEqualTypeOf< + { + userId: string; + } & { + role: string; + } & Context + >(); + + expectTypeOf< + GenerationContext + >().toMatchObjectType(); + }); + + it('falls back to the generic context type when tools have no contextSchema', () => { + const tools = { + weather: tool({ + inputSchema: z.object({ + city: z.string(), + }), + }), + }; + + expectTypeOf< + GenerationContext + >().toMatchObjectType(); + }); +}); diff --git a/packages/ai/src/generate-text/generation-context.ts b/packages/ai/src/generate-text/generation-context.ts index 510b2050f133..7b9a0dce877a 100644 --- a/packages/ai/src/generate-text/generation-context.ts +++ b/packages/ai/src/generate-text/generation-context.ts @@ -6,7 +6,8 @@ import type { ToolSet } from './tool-set'; * The context type for a generation call. * * It expands the tool set context with the generic context type for - * e.g. prepareStep or telemetry. + * e.g. prepareStep or telemetry, + * while keeping the inferred tool set context for autocompletion. */ export type GenerationContext = InferToolSetContext< NoInfer From bc2fbf52343a77aa62e29af1b1841c0ecbe4a5a9 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 15:58:34 +0200 Subject: [PATCH 40/41] test --- .../src/generate-text/generate-text.test-d.ts | 4 +- .../src/generate-text/stream-text.test-d.ts | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/ai/src/generate-text/generate-text.test-d.ts b/packages/ai/src/generate-text/generate-text.test-d.ts index d1fb9110da4e..3bb3dc08a1b0 100644 --- a/packages/ai/src/generate-text/generate-text.test-d.ts +++ b/packages/ai/src/generate-text/generate-text.test-d.ts @@ -81,7 +81,7 @@ describe('generateText types', () => { userId: z.string(), }), execute: async (_input, { experimental_context }) => { - expectTypeOf(experimental_context).toMatchTypeOf<{ + expectTypeOf(experimental_context).toMatchObjectType<{ userId: string; }>(); @@ -94,7 +94,7 @@ describe('generateText types', () => { role: 'admin', }, prepareStep: ({ experimental_context }) => { - expectTypeOf(experimental_context).toMatchTypeOf<{ + expectTypeOf(experimental_context).toMatchObjectType<{ userId: string; role: string; }>(); diff --git a/packages/ai/src/generate-text/stream-text.test-d.ts b/packages/ai/src/generate-text/stream-text.test-d.ts index 3a9afb5228af..824d3a8b0909 100644 --- a/packages/ai/src/generate-text/stream-text.test-d.ts +++ b/packages/ai/src/generate-text/stream-text.test-d.ts @@ -1,4 +1,5 @@ import { JSONValue } from '@ai-sdk/provider'; +import { tool } from '@ai-sdk/provider-utils'; import { describe, expectTypeOf, it } from 'vitest'; import { z } from 'zod'; import { Output, streamText } from '../generate-text'; @@ -197,4 +198,47 @@ describe('streamText types', () => { >(); }); }); + + describe('experimental_context', () => { + it('should infer typed experimental_context with one tool context and prepareStep', async () => { + streamText({ + model: new MockLanguageModelV4(), + prompt: 'Hello, world!', + tools: { + weather: tool({ + inputSchema: z.object({ + city: z.string(), + }), + contextSchema: z.object({ + userId: z.string(), + }), + execute: async (_input, { experimental_context }) => { + expectTypeOf(experimental_context).toMatchObjectType<{ + userId: string; + }>(); + + return 'sunny'; + }, + }), + }, + experimental_context: { + userId: 'test-user', + role: 'admin', + }, + prepareStep: ({ experimental_context }) => { + expectTypeOf(experimental_context).toMatchObjectType<{ + userId: string; + role: string; + }>(); + + return { + experimental_context: { + userId: experimental_context.userId, + role: experimental_context.role, + }, + }; + }, + }); + }); + }); }); From bf081149df2cbb8dd1b33aa4adc4c4a72bc55d21 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Wed, 1 Apr 2026 17:35:29 +0200 Subject: [PATCH 41/41] tf --- .../ai/src/util/union-to-intersection.test-d.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/ai/src/util/union-to-intersection.test-d.ts b/packages/ai/src/util/union-to-intersection.test-d.ts index f2fde5a0fd41..3a94464b5c58 100644 --- a/packages/ai/src/util/union-to-intersection.test-d.ts +++ b/packages/ai/src/util/union-to-intersection.test-d.ts @@ -5,7 +5,7 @@ describe('UnionToIntersection', () => { it('returns never when given no input', () => { type Result = UnionToIntersection; - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); }); it('returns the same type for a single input', () => { @@ -21,9 +21,12 @@ describe('UnionToIntersection', () => { { city: string } | { countryCode: string } >; - expectTypeOf().toEqualTypeOf<{ - city: string; - countryCode: string; - }>(); + expectTypeOf().toEqualTypeOf< + { + city: string; + } & { + countryCode: string; + } + >(); }); });