Why
ExecuteToolFn is typed (toolSlug, input: Record<string, unknown>) — it promises an object. Per #2406, models intermittently emit tool-call input as a JSON string. The llamaindex (input as Record<string, unknown>) and claude-agent-sdk (args passed straight through) providers forwarded model input unchecked, so a stringified payload reached execution as a raw string (Input should be a valid dictionary).
Changes
- llamaindex + claude-agent-sdk: normalize string→object before forwarding —
const normalized = typeof input === 'string' ? JSON.parse(input) : input; — mirroring the guard already shipped in vercel/cloudflare/openai-agents.
- Tests: the existing
openai-agents guard had no string-path test. Backfilled string→object regression tests across all three providers ('{"x":1}' → {x:1}; {x:1} → passthrough).
- Patch changeset for the two providers.
Scope / honesty
- Left
langchain/anthropic/google to in-flight #3438; didn't touch mastra (#3400).
- Lower-severity than the Vercel path (these Zod-validate against the tool schema before execute), so this is a defensive-consistency + coverage fix, not a hotfix.
Refs #2406.