Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
1febce0
1
lgrammel Mar 5, 2026
672f5ed
2
lgrammel Mar 5, 2026
eaf33e2
proof of concept
lgrammel Mar 5, 2026
d67d969
s
lgrammel Mar 5, 2026
9f48c00
x
lgrammel Mar 5, 2026
37d2b0a
xx
lgrammel Mar 5, 2026
7ad6fd7
y
lgrammel Mar 5, 2026
9c2d1fd
x
lgrammel Mar 5, 2026
aabce40
a
lgrammel Mar 5, 2026
50c741f
x
lgrammel Mar 5, 2026
1427dcc
Revert "x"
lgrammel Mar 5, 2026
920adb5
x
lgrammel Mar 6, 2026
ba939e1
perf
lgrammel Mar 6, 2026
11de8ed
Merge branch 'main' into lg/GJysF7Eo
lgrammel Mar 9, 2026
509417d
a
lgrammel Mar 9, 2026
8e27317
x
lgrammel Mar 9, 2026
7e9fbd9
x
lgrammel Mar 9, 2026
c5bc381
x
lgrammel Mar 9, 2026
c41b21c
x
lgrammel Mar 9, 2026
c8bac43
x
lgrammel Mar 9, 2026
4195a90
x
lgrammel Mar 9, 2026
dfdbdc6
a
lgrammel Mar 9, 2026
342136d
a
lgrammel Mar 9, 2026
9921f52
s
lgrammel Mar 9, 2026
d0e70cb
st
lgrammel Mar 9, 2026
eebb671
re
lgrammel Mar 9, 2026
61a61c7
ex
lgrammel Mar 10, 2026
df04f31
Merge branch 'main' into lg/GJysF7Eo
lgrammel Apr 1, 2026
ec894c2
types
lgrammel Apr 1, 2026
a966d45
tests
lgrammel Apr 1, 2026
974866e
types
lgrammel Apr 1, 2026
c6cc59f
revert
lgrammel Apr 1, 2026
69fc817
extract infer
lgrammel Apr 1, 2026
9d2db48
type test
lgrammel Apr 1, 2026
fef307a
cs
lgrammel Apr 1, 2026
3a4dc0a
stream example
lgrammel Apr 1, 2026
91b1a2f
Add tests for UnionToIntersection utility type
lgrammel Apr 1, 2026
6e56bd9
Add InferToolSetContext type and corresponding tests
lgrammel Apr 1, 2026
66a0c37
1
lgrammel Apr 1, 2026
251ac1b
Refactor context types: Replace ExpandedContext with GenerationContex…
lgrammel Apr 1, 2026
780835f
Add tests for GenerationContext type to validate context merging and …
lgrammel Apr 1, 2026
bc2fbf5
test
lgrammel Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/bright-glasses-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ai-sdk/provider-utils": major
"ai": major
---

feat(ai): change type of experimental_context from unknown to generic
3 changes: 2 additions & 1 deletion examples/ai-e2e-next/app/api/chat/human-in-the-loop/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function processToolCalls<
executeFunctions: {
[K in keyof Tools & keyof ExecutableTools]?: (
args: ExecutableTools[K] extends Tool<infer P> ? P : never,
context: ToolExecutionOptions,
context: ToolExecutionOptions<{}>,
) => Promise<any>;
},
): Promise<HumanInTheLoopUIMessage[]> {
Expand Down Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,61 @@ 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',
inputSchema: z.object({
location: z.string().describe('The location to get the weather for'),
}),
execute: async ({ location }, { experimental_context: context }) => {
const typedContext = context as { weatherApiKey: string }; // or use type validation library

console.log(typedContext);
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 {};
},
experimental_context: { weatherApiKey: '123' },
prompt: 'What is the weather in San Francisco?',
});

Expand Down
2 changes: 1 addition & 1 deletion examples/ai-functions/src/lib/print-full-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StreamTextResult } from 'ai';
export async function printFullStream({
result,
}: {
result: StreamTextResult<any, any>;
result: StreamTextResult<any, any, any>;
}) {
for await (const chunk of result.fullStream) {
switch (chunk.type) {
Expand Down
2 changes: 1 addition & 1 deletion examples/ai-functions/src/lib/save-raw-chunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export async function saveRawChunks({
result,
filename,
}: {
result: StreamTextResult<any, any>;
result: StreamTextResult<any, any, any>;
filename: string;
}) {
const rawChunks: unknown[] = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -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 });
});
92 changes: 92 additions & 0 deletions examples/ai-functions/src/test/typed-context-2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { FlexibleSchema } from 'ai';
import { run } from '../lib/run';
import { z } from 'zod';

type Context = Record<string, unknown>;

interface Tool<INPUT = any, CONTEXT extends Context = Context> {
inputSchema: FlexibleSchema<INPUT>;
contextSchema: FlexibleSchema<CONTEXT>;
execute: (input: NoInfer<INPUT>, context: NoInfer<CONTEXT>) => unknown;
}

function tool<INPUT, CONTEXT extends Context>(options: {
inputSchema: FlexibleSchema<INPUT>;
contextSchema: FlexibleSchema<CONTEXT>;
execute: (input: NoInfer<INPUT>, context: NoInfer<CONTEXT>) => unknown;
}) {
return options;
}

type InferToolInput<TOOL extends Tool> =
TOOL extends Tool<infer INPUT, any> ? INPUT : never;
type InferToolContext<TOOL extends Tool> =
TOOL extends Tool<any, infer CONTEXT> ? CONTEXT : never;

export type ToolSet = Record<string, Tool<any, any>>;

type UnionToIntersection<U> = (
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<TOOLS extends ToolSet> = UnionToIntersection<
{
[K in keyof TOOLS]: InferToolContext<TOOLS[K]>;
}[keyof TOOLS]
>;

type ExpandedContext<TOOLS extends ToolSet> = InferToolSetContext<TOOLS> &
Context;

function executeTool<
TOOLS extends ToolSet,
CONTEXT extends ExpandedContext<TOOLS>,
>({
tools,
toolName,
input,
context,
}: {
tools: TOOLS;
toolName: keyof TOOLS;
input: InferToolInput<TOOLS[typeof toolName]>;
context: CONTEXT;
prepareStep: (context: CONTEXT) => void;
}) {
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: 'world', somethingElse: 'context' },
prepareStep: context => {
console.log(context);
},
});
});
42 changes: 42 additions & 0 deletions examples/ai-functions/src/test/typed-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { run } from '../lib/run';

interface Tool<CONTEXT> {
execute: (context: CONTEXT) => Promise<void>;
}

export type ToolSet<CONTEXT> = Record<string, Tool<CONTEXT>>;

function executeTool<CONTEXT, TOOLS extends ToolSet<CONTEXT>>({
tools,
toolName,
context,
}: {
tools: TOOLS;
toolName: keyof TOOLS;
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 },
});
});
5 changes: 4 additions & 1 deletion examples/mcp/src/image-content/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
6 changes: 3 additions & 3 deletions examples/mcp/src/output-schema/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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));
Expand Down
Loading
Loading