feat(provider-utils): narrow tool() return when execute is provided#15466
Merged
Conversation
aayush-kapoor
approved these changes
May 20, 2026
lgrammel
approved these changes
May 26, 2026
Contributor
|
🚀 Published in:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
Consumers that define a tool with
executein their own codebase and then call.execute(...)(most often in tests) currently hit a typing gap. The inferred type oftool({ ...with execute... })is the wideTool<INPUT, OUTPUT, CONTEXT>union, whereexecuteisToolExecuteFunction<...> | undefined. That forcesisExecutableToolnarrowing, a!assertion, or?.()chaining at the call site even when the consumer statically knowsexecuteis defined.The SDK already ships the right primitive:
ExecutableTool<TOOL>from@ai-sdk/provider-utils(added in #14516). It just isn't wired intotool()'s own return type. Current options to get a narrowed tool are:isExecutableTool(t)(runtime guard) at every consumption site.as ExecutableTool<...>.tool()in a project-local factory that re-applies the cast.Summary
Add a fifth overload to
tool()that matches calls whose config includes anexecutefunction. That overload returnsExecutableTool<Tool<INPUT, OUTPUT, CONTEXT>>instead of the widerTool<INPUT, OUTPUT, CONTEXT>. Calls withoutexecutefall through to the existing four overloads unchanged.This is non-breaking:
return tool;.ExecutableTool<Tool<I, O, C>>isTool<I, O, C> & { execute: NonNullable<...> }. Strictly assignable to the old return; anything that took aTool<...>keeps compiling.t.executenarrows fromToolExecuteFunction<...> | undefinedtoToolExecuteFunction<...>. Existing runtime null checks become provably-true but still compile.The existing type tests in
tool.test-d.tscodified the wider contract. This PR updates the three affected assertions to expect the narrower one.Effect at a call site:
Manual Verification
pnpm type-check:fullpasses.pnpm exec vitest --typecheck.onlyinpackages/provider-utils: 26 type-test files, 184 assertions, all pass.pnpm exec vitest --typecheck.onlyinpackages/ai: 30 type-test files, 504 assertions, all pass.pnpm --filter '@ai-sdk/provider-utils' test:node: 65 test files, 606 tests, all pass.pnpm check(lint + format) clean.Open question for maintainers
This is small enough to land standalone, but #14516 deliberately introduced
ExecutableToolandisExecutableToolas additive primitives and chose not to wire them intotool()itself. Was that intentional?If you'd rather keep
tool()'s return wide and expose narrowing as an opt-in, happy to close this and instead propose:executableTool()factory exported from@ai-sdk/provider-utils(symmetric withisExecutableTool/ExecutableTool).content/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdxcoveringisExecutableToolfor the runtime-discovered case (MCP etc).Marking this as a draft until you've had a chance to weigh in.
Checklist
Related
ExecutableToolandisExecutableTool.Toolinto a 4-variant union.