Skip to content

feat: add support for AI SDK v5 dynamic tools#589

Merged
miurla merged 2 commits intov0.5from
feat/dynamic-tools-support
Jul 29, 2025
Merged

feat: add support for AI SDK v5 dynamic tools#589
miurla merged 2 commits intov0.5from
feat/dynamic-tools-support

Conversation

@miurla
Copy link
Copy Markdown
Owner

@miurla miurla commented Jul 29, 2025

Summary

This PR adds support for AI SDK v5's dynamic tools feature, enabling runtime-defined tools beyond compile-time static tools.

Key Changes

  • SDK Updates: Upgraded to AI SDK v5.0.0-beta.29 and @ai-sdk/react v2.0.0-beta.29
  • Database Schema: Renamed MCP-specific columns to generic dynamic tool columns and added metadata fields
  • Message Handling: Updated message mapping to handle the new dynamic-tool UI parts from AI SDK v5
  • UI Components: Created DynamicToolDisplay component for rendering dynamic tool invocations
  • API Compatibility: Fixed trigger names and addToolResult signature for the new SDK version

What are Dynamic Tools?

Dynamic tools are tools whose input/output types are not known at compile time, including:

  • MCP (Model Context Protocol) tools without schemas
  • User-defined functions at runtime
  • Tools loaded from external sources

Migration Required

After merging, run the database migration to update the schema:

bun run migrate

Or manually apply:

ALTER TABLE parts RENAME COLUMN tool_mcp_input TO tool_dynamic_input;
ALTER TABLE parts RENAME COLUMN tool_mcp_output TO tool_dynamic_output;
ALTER TABLE parts ADD COLUMN tool_dynamic_name varchar(256);
ALTER TABLE parts ADD COLUMN tool_dynamic_type varchar(256);

Breaking Changes

  • Trigger names changed: regenerate-assistant-messageregenerate-message, submit-user-messagesubmit-message
  • addToolResult now requires a tool parameter

Testing

All TypeScript, linting, and build checks pass. The implementation is compatible with existing static tools while adding support for dynamic ones.

- Update AI SDK to v5.0.0-beta.29 and @ai-sdk/react to v2.0.0-beta.29
- Rename MCP-specific columns to generic dynamic tool columns in database schema
- Add tool_dynamic_name and tool_dynamic_type columns for better tool identification
- Update message mapping to handle dynamic-tool UI parts from AI SDK v5
- Create DynamicToolDisplay component for rendering dynamic tool invocations
- Fix trigger names (regenerate-message, submit-message) for new SDK version
- Update addToolResult to include required 'tool' parameter
- Support MCP tools, user-defined functions, and external API tools at runtime
@vercel
Copy link
Copy Markdown

vercel bot commented Jul 29, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
morphic ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 29, 2025 0:59am

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for AI SDK v5's dynamic tools feature, enabling runtime-defined tools beyond compile-time static tools. It includes database schema changes to support dynamic tool metadata, updates to message mapping for the new SDK format, and new UI components for rendering dynamic tool interactions.

  • Upgraded AI SDK packages to v5.0.0-beta.29 with new dynamic tool APIs
  • Refactored database schema from MCP-specific to generic dynamic tool support
  • Updated message handling to support AI SDK v5's dynamic-tool UI parts

Reviewed Changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
package.json Updates AI SDK packages to v5.0.0-beta.29
lib/utils/message-mapping.ts Adds mapping logic for dynamic-tool parts and metadata handling
lib/types/message-persistence.ts Renames MCP types to dynamic tool types
lib/types/ai.ts Adds flexibility for dynamic tools in UITools interface
lib/tools/dynamic.ts New dynamic tool creation utilities
lib/db/schema.ts Renames MCP columns to dynamic tool columns with metadata
lib/db/migrations/0003_rename_mcp_to_dynamic.sql Migration script for schema changes
lib/agents/researcher-with-dynamic-tools.ts New agent supporting dynamic tools
drizzle/meta/_journal.json Migration metadata updates
drizzle/0001_rename_mcp_to_dynamic.sql Drizzle migration file
components/render-message.tsx Adds rendering support for dynamic-tool parts
components/dynamic-tool-display.tsx New component for displaying dynamic tool interactions
components/chat.tsx Updates trigger names and addToolResult signature

const toolInputColumn = `tool_${toolName}_input` as keyof DBMessagePart

return {
const result: any = {
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'any' type reduces type safety. Consider defining a specific interface for the result object or using a more specific type.

Suggested change
const result: any = {
const result: ToolCallResult = {

Copilot uses AI. Check for mistakes.
`tool_${resultToolName}_output` as keyof DBMessagePart

return {
const toolResult: any = {
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'any' type reduces type safety. Consider defining a specific interface for the toolResult object or using a more specific type.

Suggested change
const toolResult: any = {
const toolResult: ToolResult = {

Copilot uses AI. Check for mistakes.

// Dynamic tool parts from AI SDK v5
case 'dynamic-tool':
const dynamicPart = part as any
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'as any' type assertion bypasses type checking. Consider defining a proper interface for dynamic parts or using type guards to ensure type safety.

Copilot uses AI. Check for mistakes.
export function createMCPTool(
toolName: string,
description: string,
mcpClient: any // Replace with actual MCP client type
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'any' type for mcpClient parameter reduces type safety. Consider defining a proper interface for the MCP client.

Suggested change
mcpClient: any // Replace with actual MCP client type
mcpClient: MCPClient // Replace with actual MCP client type

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +52
interface DynamicToolConfig {
name: string
description: string
handler?: (params: any) => Promise<any>
mcpClient?: any // Replace with actual MCP client type
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'any' type for mcpClient reduces type safety. Consider defining a proper interface for the MCP client.

Suggested change
interface DynamicToolConfig {
name: string
description: string
handler?: (params: any) => Promise<any>
mcpClient?: any // Replace with actual MCP client type
interface MCPClient {
connect(): Promise<void>;
disconnect(): Promise<void>;
sendRequest(request: object): Promise<object>;
}
interface DynamicToolConfig {
name: string
description: string
handler?: (params: any) => Promise<any>
mcpClient?: MCPClient // Replace with actual MCP client type

Copilot uses AI. Check for mistakes.
return (
<DynamicToolDisplay
key={`${messageId}-dynamic-tool-${index}`}
part={part as any}
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'as any' type assertion bypasses type checking. Consider defining a proper type for the dynamic tool part or using type guards.

Copilot uses AI. Check for mistakes.
Comment on lines +300 to +319
const allParts = messages.flatMap(m => m.parts || [])

for (const part of allParts) {
const p = part as any
if (p.type === 'tool-call' && p.toolCallId === toolCallId) {
toolName = p.toolName
break
} else if (
p.type?.startsWith('tool-') &&
p.toolCallId === toolCallId
) {
toolName = p.type.substring(5) // Remove 'tool-' prefix
break
} else if (
p.type === 'dynamic-tool' &&
p.toolCallId === toolCallId
) {
toolName = p.toolName
break
}
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flatMap operation creates a new array of all parts on every tool result call. Consider optimizing this by breaking early once the tool is found or caching the parts lookup.

Suggested change
const allParts = messages.flatMap(m => m.parts || [])
for (const part of allParts) {
const p = part as any
if (p.type === 'tool-call' && p.toolCallId === toolCallId) {
toolName = p.toolName
break
} else if (
p.type?.startsWith('tool-') &&
p.toolCallId === toolCallId
) {
toolName = p.type.substring(5) // Remove 'tool-' prefix
break
} else if (
p.type === 'dynamic-tool' &&
p.toolCallId === toolCallId
) {
toolName = p.toolName
break
}
for (const message of messages) {
if (!message.parts) continue;
for (const part of message.parts) {
const p = part as any;
if (p.type === 'tool-call' && p.toolCallId === toolCallId) {
toolName = p.toolName;
break;
} else if (
p.type?.startsWith('tool-') &&
p.toolCallId === toolCallId
) {
toolName = p.type.substring(5); // Remove 'tool-' prefix
break;
} else if (
p.type === 'dynamic-tool' &&
p.toolCallId === toolCallId
) {
toolName = p.toolName;
break;
}
}
if (toolName !== 'unknown') break;

Copilot uses AI. Check for mistakes.
Comment on lines +303 to +317
const p = part as any
if (p.type === 'tool-call' && p.toolCallId === toolCallId) {
toolName = p.toolName
break
} else if (
p.type?.startsWith('tool-') &&
p.toolCallId === toolCallId
) {
toolName = p.type.substring(5) // Remove 'tool-' prefix
break
} else if (
p.type === 'dynamic-tool' &&
p.toolCallId === toolCallId
) {
toolName = p.toolName
Copy link

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'as any' type assertion bypasses type checking. Consider using proper type guards or defining specific interfaces for message parts.

Suggested change
const p = part as any
if (p.type === 'tool-call' && p.toolCallId === toolCallId) {
toolName = p.toolName
break
} else if (
p.type?.startsWith('tool-') &&
p.toolCallId === toolCallId
) {
toolName = p.type.substring(5) // Remove 'tool-' prefix
break
} else if (
p.type === 'dynamic-tool' &&
p.toolCallId === toolCallId
) {
toolName = p.toolName
if (isToolCallPart(part) && part.toolCallId === toolCallId) {
toolName = part.toolName
break
} else if (
isToolTypePart(part) &&
part.toolCallId === toolCallId
) {
toolName = part.type.substring(5) // Remove 'tool-' prefix
break
} else if (
isDynamicToolPart(part) &&
part.toolCallId === toolCallId
) {
toolName = part.toolName

Copilot uses AI. Check for mistakes.
- Add proper TypeScript types for dynamic tools (MCPClient, DynamicToolConfig, DynamicToolPart)
- Replace all 'any' types with proper interfaces throughout codebase
- Add type guards for tool parts (isDynamicToolPart, isToolCallPart, isToolTypePart)
- Optimize tool name lookup in chat.tsx to break early when found
- Cast partial objects to full types where TypeScript requires it
- Improve overall type safety without using 'any' type assertions
@vercel vercel bot requested a deployment to Preview – morphic July 29, 2025 00:59 Abandoned
@miurla miurla merged commit 28f9085 into v0.5 Jul 29, 2025
3 checks passed
@miurla miurla deleted the feat/dynamic-tools-support branch July 29, 2025 01:01
byte-rose pushed a commit to Bastsec/morphic that referenced this pull request Sep 23, 2025
feat: add support for AI SDK v5 dynamic tools
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants