04 · Extend & Wrap Tools
You do not need to rewrite a tool to add production behavior. The current tool layer supports extension, middleware wrapping, composition, and version tagging.
What you'll learn
extendTool()for input normalization and fallback handlingwrapTool()for middleware-style caching and loggingpipeTools()andversionTool()for composition and versioning
Extend a tool
ts
import { z } from 'zod/v3';
import { extendTool, tool } from 'confused-ai';
const fetchWeather = tool({
name: 'fetch_weather',
description: 'Return a weather summary for a city.',
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, tempC: 22, condition: 'sunny' }),
});
const smartWeather = extendTool(fetchWeather, {
name: 'smart_weather',
beforeExecute: async (params) => {
console.log(`fetching weather for ${params.city}`);
return undefined;
},
transformInput: async (params) => ({ city: params.city.trim() }),
transformOutput: async (output) => ({
city: output.city,
tempC: output.tempC,
condition: output.condition.toUpperCase(),
}),
onError: async (_error, params) => ({
city: params.city,
tempC: -1,
condition: 'UNAVAILABLE',
}),
});
const result = await smartWeather.execute(
{ city: ' Tokyo ' },
{ agentId: 'demo', sessionId: 'demo' },
);
console.log(result);Wrap a tool with middleware
ts
import { z } from 'zod/v3';
import { tool, wrapTool } from 'confused-ai';
const cache = new Map<string, { city: string; tempC: number; condition: string }>();
const fetchWeather = tool({
name: 'fetch_weather',
description: 'Return a weather summary for a city.',
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, tempC: 22, condition: 'sunny' }),
});
const cachedWeather = wrapTool(
fetchWeather,
[
async (params, ctx, next) => {
console.log(`agent=${ctx.agentId} city=${params.city}`);
return next(params, ctx);
},
async (params, ctx, next) => {
const key = params.city.toLowerCase();
const hit = cache.get(key);
if (hit) return hit;
const result = await next(params, ctx);
cache.set(key, result);
return result;
},
],
{ name: 'cached_weather' },
);
const result = await cachedWeather.execute(
{ city: 'Paris' },
{ agentId: 'demo', sessionId: 'demo' },
);
console.log(result);Pipe and version tools
ts
import { z } from 'zod/v3';
import { pipeTools, tool, versionTool } from 'confused-ai';
const fetchUrl = tool({
name: 'fetch_url',
description: 'Return HTML for a URL.',
parameters: z.object({ url: z.string().url() }),
execute: async ({ url }) => ({ body: `<html><body>Read ${url}</body></html>` }),
});
const extractText = tool({
name: 'extract_text',
description: 'Strip HTML tags from a document.',
parameters: z.object({ body: z.string() }),
execute: async ({ body }) => ({ text: body.replace(/<[^>]+>/g, ' ').trim() }),
});
const fetchAndRead = pipeTools(fetchUrl, extractText, {
name: 'fetch_and_read',
description: 'Fetch a URL and return readable text.',
adapter: (result) => ({ body: result.body }),
});
const fetchAndReadV2 = versionTool(fetchAndRead, '2.0', {
changelog: 'Combines fetch and extraction into one tool call.',
});
const result = await fetchAndReadV2.execute(
{ url: 'https://example.com' },
{ agentId: 'demo', sessionId: 'demo' },
);
console.log(result);