Crux
API Reference@use-crux/core

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)

FieldTypeDescription
namestring?Optional stable name for adapters that need named registries. Prompt tool objects still use their object key as canonical.
descriptionstringModel-facing description.
inputZodType?Tool input schema.
parametersZodType?Alias for input, matching AI SDK naming.
contextSchemaZodType?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:

  1. Skill tools: loader tools from skill()
  2. Context tools: from context({ tools }) collected via use
  3. Contributor tools: from contributor() and injected primitives
  4. Blackboard tools: focused tools from blackboard()
  5. 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.

On this page