Skip to content
Open
Show file tree
Hide file tree
Changes from all 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