Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
8 changes: 8 additions & 0 deletions .changeset/tidy-searches-coexist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@tanstack/ai-anthropic': patch
'@tanstack/ai-gemini': patch
'@tanstack/ai-openai': patch
'@tanstack/openai-base': patch
---

Classify Anthropic, Gemini, and OpenAI native tools with stable runtime discriminators so ordinary functions can use the same public names without selecting provider-native behavior.
13 changes: 8 additions & 5 deletions packages/ai-anthropic/src/adapters/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { EventType, normalizeSystemPrompts } from '@tanstack/ai'
import { toRunErrorRawEvent } from '@tanstack/ai/adapter-internals'
import { BaseTextAdapter } from '@tanstack/ai/adapters'
import { convertToolsToProviderFormat } from '../tools/tool-converter'
import { getAnthropicProviderToolKind } from '../tools/anthropic-provider-tool'
import {
readCodeExecutionConfig,
readCodeExecutionSkills,
Expand Down Expand Up @@ -171,7 +172,9 @@ export function computeAnthropicBetas(

// Code-execution beta is version-aware: select from the FIRST code_execution
// tool's config type.
const codeExecTool = tools?.find((t) => t.name === 'code_execution')
const codeExecTool = tools?.find(
(tool) => getAnthropicProviderToolKind(tool) === 'code_execution',
)
if (codeExecTool) {
const cfgType = readCodeExecutionConfig(codeExecTool)?.type
// Each code_execution tool version pairs with a specific beta. Known
Expand All @@ -188,9 +191,9 @@ export function computeAnthropicBetas(
// container-lift, which lifts skills from any code_execution tool that
// carries them (not just the first).
const hasSkills = tools?.some(
(t) =>
t.name === 'code_execution' &&
(readCodeExecutionSkills(t)?.length ?? 0) > 0,
(tool) =>
getAnthropicProviderToolKind(tool) === 'code_execution' &&
(readCodeExecutionSkills(tool)?.length ?? 0) > 0,
)
if (hasSkills) betas.add('skills-2025-10-02')

Expand Down Expand Up @@ -574,7 +577,7 @@ export class AnthropicTextAdapter<
// canonical path for skills; `modelOptions.container.skills` is deprecated.
const toolSkills = options.tools
?.map((tool) =>
tool.name === 'code_execution'
getAnthropicProviderToolKind(tool) === 'code_execution'
? readCodeExecutionSkills(tool)
: undefined,
)
Expand Down
70 changes: 70 additions & 0 deletions packages/ai-anthropic/src/tools/anthropic-provider-tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { brandProviderTool } from '@tanstack/ai'
import type { ProviderTool, Tool } from '@tanstack/ai'

const ANTHROPIC_PROVIDER_TOOL_KINDS = {
bash: 'anthropic.bash',
code_execution: 'anthropic.code_execution',
computer_use: 'anthropic.computer_use',
memory: 'anthropic.memory',
text_editor: 'anthropic.text_editor',
web_fetch: 'anthropic.web_fetch',
web_search: 'anthropic.web_search',
} as const

type AnthropicProviderToolKind = keyof typeof ANTHROPIC_PROVIDER_TOOL_KINDS

/**
* Adds a stable runtime discriminator to an Anthropic-native tool.
*
* The core ProviderTool brand is intentionally type-only. Anthropic also needs
* a runtime discriminator because custom functions may use the same public
* names as native tools. Adapter metadata is the repository-wide extension
* point for this plain-data marker; converters remove it from the wire shape.
*/
export function brandAnthropicProviderTool<
T extends ProviderTool<'anthropic', AnthropicProviderToolKind>,
>(tool: Omit<T, '~provider' | '~toolKind'>, toolKind: T['~toolKind']): T {
return brandProviderTool<T>({
...tool,
metadata: {
...tool.metadata,
__kind: ANTHROPIC_PROVIDER_TOOL_KINDS[toolKind],
},
})
}

export function getAnthropicProviderToolKind(
tool: Tool,
): AnthropicProviderToolKind | undefined {
const toolKind = tool.metadata?.['__kind']
switch (toolKind) {
case 'anthropic.bash':
return 'bash'
case 'anthropic.code_execution':
return 'code_execution'
case 'anthropic.computer_use':
return 'computer_use'
case 'anthropic.memory':
return 'memory'
case 'anthropic.text_editor':
return 'text_editor'
case 'anthropic.web_fetch':
return 'web_fetch'
case 'anthropic.web_search':
return 'web_search'
default:
return undefined
}
}

/** Returns adapter metadata without the internal runtime discriminator. */
export function getAnthropicProviderToolMetadata(
tool: Tool,
): Tool['metadata'] {
if (!tool.metadata) {
return undefined
}
const { __kind: _kind, ...metadata } = tool.metadata
void _kind
return metadata
}
20 changes: 13 additions & 7 deletions packages/ai-anthropic/src/tools/bash-tool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { brandProviderTool } from '@tanstack/ai'
import {
brandAnthropicProviderTool,
getAnthropicProviderToolMetadata,
} from './anthropic-provider-tool'
import type {
BetaToolBash20241022,
BetaToolBash20250124,
Expand All @@ -13,14 +16,17 @@ export type BashTool = BashToolConfig
export type AnthropicBashTool = ProviderTool<'anthropic', 'bash'>

export function convertBashToolToAdapterFormat(tool: Tool): BashToolConfig {
const metadata = tool.metadata as BashToolConfig
const metadata = getAnthropicProviderToolMetadata(tool) as BashToolConfig
return metadata
}

export function bashTool(config: BashToolConfig): AnthropicBashTool {
return brandProviderTool<AnthropicBashTool>({
name: 'bash',
description: '',
metadata: config,
})
return brandAnthropicProviderTool<AnthropicBashTool>(
{
name: 'bash',
description: '',
metadata: config,
},
'bash',
)
}
30 changes: 22 additions & 8 deletions packages/ai-anthropic/src/tools/code-execution-tool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { brandProviderTool } from '@tanstack/ai'
import {
brandAnthropicProviderTool,
getAnthropicProviderToolMetadata,
} from './anthropic-provider-tool'
import type {
BetaCodeExecutionTool20250522,
BetaCodeExecutionTool20250825,
Expand Down Expand Up @@ -56,7 +59,11 @@ export function convertCodeExecutionToolToAdapterFormat(
export function readCodeExecutionConfig(
tool: Tool,
): CodeExecutionToolConfig | undefined {
return (tool.metadata as CodeExecutionToolMetadata | undefined)?.config
return (
getAnthropicProviderToolMetadata(tool) as
| CodeExecutionToolMetadata
| undefined
)?.config
}

/**
Expand All @@ -66,7 +73,11 @@ export function readCodeExecutionConfig(
export function readCodeExecutionSkills(
tool: Tool,
): Array<AnthropicContainerSkill> | undefined {
return (tool.metadata as CodeExecutionToolMetadata | undefined)?.skills
return (
getAnthropicProviderToolMetadata(tool) as
| CodeExecutionToolMetadata
| undefined
)?.skills
}

export function codeExecutionTool(
Expand All @@ -88,9 +99,12 @@ export function codeExecutionTool(
config,
...(skills && { skills }),
}
return brandProviderTool<AnthropicCodeExecutionTool>({
name: 'code_execution',
description: '',
metadata,
})
return brandAnthropicProviderTool<AnthropicCodeExecutionTool>(
{
name: 'code_execution',
description: '',
metadata,
},
'code_execution',
)
}
22 changes: 15 additions & 7 deletions packages/ai-anthropic/src/tools/computer-use-tool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { brandProviderTool } from '@tanstack/ai'
import {
brandAnthropicProviderTool,
getAnthropicProviderToolMetadata,
} from './anthropic-provider-tool'
import type {
BetaToolComputerUse20241022,
BetaToolComputerUse20250124,
Expand All @@ -17,16 +20,21 @@ export type AnthropicComputerUseTool = ProviderTool<'anthropic', 'computer_use'>
export function convertComputerUseToolToAdapterFormat(
tool: Tool,
): ComputerUseToolConfig {
const metadata = tool.metadata as ComputerUseToolConfig
const metadata = getAnthropicProviderToolMetadata(
tool,
) as ComputerUseToolConfig
return metadata
}

export function computerUseTool(
config: ComputerUseToolConfig,
): AnthropicComputerUseTool {
return brandProviderTool<AnthropicComputerUseTool>({
name: 'computer',
description: '',
metadata: config,
})
return brandAnthropicProviderTool<AnthropicComputerUseTool>(
{
name: 'computer',
description: '',
metadata: config,
},
'computer_use',
)
}
23 changes: 16 additions & 7 deletions packages/ai-anthropic/src/tools/memory-tool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { brandProviderTool } from '@tanstack/ai'
import {
brandAnthropicProviderTool,
getAnthropicProviderToolMetadata,
} from './anthropic-provider-tool'
import type { BetaMemoryTool20250818 } from '@anthropic-ai/sdk/resources/beta'
import type { ProviderTool, Tool } from '@tanstack/ai'

Expand All @@ -10,17 +13,23 @@ export type MemoryTool = MemoryToolConfig
export type AnthropicMemoryTool = ProviderTool<'anthropic', 'memory'>

export function convertMemoryToolToAdapterFormat(tool: Tool): MemoryToolConfig {
const metadata = tool.metadata as Omit<MemoryToolConfig, 'type'>
const metadata = getAnthropicProviderToolMetadata(tool) as Omit<
MemoryToolConfig,
'type'
>
return {
type: 'memory_20250818',
...metadata,
}
}

export function memoryTool(config?: MemoryToolConfig): AnthropicMemoryTool {
return brandProviderTool<AnthropicMemoryTool>({
name: 'memory',
description: '',
metadata: config,
})
return brandAnthropicProviderTool<AnthropicMemoryTool>(
{
name: 'memory',
description: '',
metadata: config,
},
'memory',
)
}
22 changes: 15 additions & 7 deletions packages/ai-anthropic/src/tools/text-editor-tool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { brandProviderTool } from '@tanstack/ai'
import {
brandAnthropicProviderTool,
getAnthropicProviderToolMetadata,
} from './anthropic-provider-tool'
import type {
ToolTextEditor20250124,
ToolTextEditor20250429,
Expand All @@ -19,7 +22,9 @@ export type AnthropicTextEditorTool = ProviderTool<'anthropic', 'text_editor'>
export function convertTextEditorToolToAdapterFormat(
tool: Tool,
): TextEditorToolConfig {
const metadata = tool.metadata as TextEditorToolConfig
const metadata = getAnthropicProviderToolMetadata(
tool,
) as TextEditorToolConfig
return {
...metadata,
}
Expand All @@ -28,9 +33,12 @@ export function convertTextEditorToolToAdapterFormat(
export function textEditorTool<T extends TextEditorToolConfig>(
config: T,
): AnthropicTextEditorTool {
return brandProviderTool<AnthropicTextEditorTool>({
name: 'str_replace_editor',
description: '',
metadata: config,
})
return brandAnthropicProviderTool<AnthropicTextEditorTool>(
{
name: 'str_replace_editor',
description: '',
metadata: config,
},
'text_editor',
)
}
11 changes: 5 additions & 6 deletions packages/ai-anthropic/src/tools/tool-converter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getAnthropicProviderToolKind } from './anthropic-provider-tool'
import { convertBashToolToAdapterFormat } from './bash-tool'
import { convertCodeExecutionToolToAdapterFormat } from './code-execution-tool'
import { convertComputerUseToolToAdapterFormat } from './computer-use-tool'
Expand Down Expand Up @@ -37,24 +38,22 @@ export function convertToolsToProviderFormat<TTool extends Tool>(
tools: Array<TTool>,
): Array<AnthropicTool> {
return tools.map((tool) => {
const name = tool.name

switch (name) {
switch (getAnthropicProviderToolKind(tool)) {
case 'bash':
return convertBashToolToAdapterFormat(tool)
case 'code_execution':
return convertCodeExecutionToolToAdapterFormat(tool)
case 'computer':
case 'computer_use':
return convertComputerUseToolToAdapterFormat(tool)
case 'memory':
return convertMemoryToolToAdapterFormat(tool)
case 'str_replace_editor':
case 'text_editor':
return convertTextEditorToolToAdapterFormat(tool)
case 'web_fetch':
return convertWebFetchToolToAdapterFormat(tool)
case 'web_search':
return convertWebSearchToolToAdapterFormat(tool)
default:
case undefined:
return convertCustomToolToAdapterFormat(tool)
}
})
Expand Down
23 changes: 16 additions & 7 deletions packages/ai-anthropic/src/tools/web-fetch-tool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { brandProviderTool } from '@tanstack/ai'
import {
brandAnthropicProviderTool,
getAnthropicProviderToolMetadata,
} from './anthropic-provider-tool'
import type { BetaWebFetchTool20250910 } from '@anthropic-ai/sdk/resources/beta'
import type { ProviderTool, Tool } from '@tanstack/ai'

Expand All @@ -12,7 +15,10 @@ export type AnthropicWebFetchTool = ProviderTool<'anthropic', 'web_fetch'>
export function convertWebFetchToolToAdapterFormat(
tool: Tool,
): WebFetchToolConfig {
const metadata = tool.metadata as Omit<WebFetchToolConfig, 'type' | 'name'>
const metadata = getAnthropicProviderToolMetadata(tool) as Omit<
WebFetchToolConfig,
'type' | 'name'
>
return {
name: 'web_fetch',
type: 'web_fetch_20250910',
Expand All @@ -23,9 +29,12 @@ export function convertWebFetchToolToAdapterFormat(
export function webFetchTool(
config?: Omit<WebFetchToolConfig, 'type' | 'name'>,
): AnthropicWebFetchTool {
return brandProviderTool<AnthropicWebFetchTool>({
name: 'web_fetch',
description: '',
metadata: config,
})
return brandAnthropicProviderTool<AnthropicWebFetchTool>(
{
name: 'web_fetch',
description: '',
metadata: config,
},
'web_fetch',
)
}
Loading