Tools
SDK-agnostic Crux tool definitions for prompts, contexts, adapters, and runtime profiles.
Use tool() from @use-crux/core/tools when you want an SDK-agnostic Crux tool definition:
import { tool } from "@use-crux/core/tools";
import { z } from "zod";
export const searchDocs = tool({
name: "searchDocs",
description: "Search product documentation.",
input: z.object({
query: z.string(),
}),
contextSchema: z.object({ apiKey: z.string() }),
execute: async ({ query }, { context }) => {
// context is typed as { apiKey: string }
return searchDocumentation(query);
},
});The helper returns a normal ToolDef. It exists to preserve input and output inference and to give adapter/runtime packages a stable API to mirror.
Export tool values from normal TypeScript modules when local tooling should inspect them. The Project
Index discovers statically visible tool(...) definitions from source; do not repeat tools in
crux.config.ts.
API
tool(config)
| Field | Type | Description |
|---|---|---|
name | string? | Optional stable name for adapters that need named registries. Prompt tool objects still use their object key as canonical. |
description | string | Model-facing description. |
input | ZodType? | Tool input schema. |
parameters | ZodType? | Alias for input, matching AI SDK naming. |
contextSchema | ZodType? | Per-tool dependency schema. Calls that compose this tool must pass toolsContext.<toolName>. |
execute | (input, options) => output | Promise<output> | Application code that runs the tool. options.context is present and typed when contextSchema is declared; options.runtimeContext is shared for the run. |
toModelOutput | (args) => ToolModelOutput | Promise<ToolModelOutput> | Optional mapper from raw output to the model-facing output. |
If neither input nor parameters is provided, the tool receives an empty object.
const now = tool({
name: "now",
description: "Return the current timestamp.",
execute: () => ({ timestamp: Date.now() }),
});Tool Context
Use contextSchema for secrets, clients, request metadata, or tenant-scoped
dependencies that should be explicit at the call site instead of hidden in
global state:
const weather = tool({
description: "Get weather.",
input: z.object({ city: z.string() }),
contextSchema: z.object({ apiKey: z.string() }),
execute: async ({ city }, { context, runtimeContext }) =>
fetchWeather(city, context.apiKey, runtimeContext),
});
await generate(answerQuestion, {
model,
toolsContext: { weather: { apiKey: process.env.WEATHER_API_KEY } },
runtimeContext: { tenantId: "tenant_1" },
});toolsContext is keyed by the final tool name. If a composed tool declares
contextSchema, omitting or mis-shaping its context fails before the tool loop
starts, with the tool name in the error. Passing toolsContext for a tool
without contextSchema also fails.
Prompt Usage
Tools can be declared directly on prompts or contributed by contexts:
import { context, prompt } from "@use-crux/core";
import { tool } from "@use-crux/core/tools";
import { z } from "zod";
const projectSearch = context({
id: "project-search",
input: z.object({ projectId: z.string() }),
system: "Use searchProject when project context is required.",
tools: {
searchProject: tool({
description: "Search project material.",
input: z.object({ query: z.string() }),
execute: async ({ query }) => searchProject(query),
}),
},
});
export const answerQuestion = prompt({
id: "answer-question",
input: z.object({ question: z.string() }),
use: [projectSearch],
prompt: ({ input }) => input.question,
});Merge Order and Collisions
Prompt-time tool names must be unique. During prompt resolution, Crux checks collisions across the prompt-owned sources in this order:
- Skill tools: loader tools from
skill() - Context tools: from
context({ tools })collected viause - Contributor tools: from
contributor()and injected primitives - Blackboard tools: focused tools from
blackboard() - Prompt tools: from
prompt({ tools })
If two of these owners contribute the same tool name, resolution throws. The error names both owners so a model cannot silently bind to the wrong implementation.
Call-site tools from generate(prompt, { tools }) and stream(prompt, { tools }) are applied by the adapter after prompt resolution and intentionally override without a collision error. Passing a replacement tool at the call site is the supported override path.
Contexts whose system text was dropped due to a token budget still contribute their tools; tool availability is independent of whether the context's system message fit within the budget.
Runtime Profiles
Runtime packages can mirror this API while adding runtime-specific plumbing. For Convex, use @use-crux/convex/tools in Convex code. It keeps the same authoring shape but calls execute() with { input, ctx, target, runtime } so tools can access the current Convex action context without app-level global wiring.