Create a custom tool
Add one well-bounded operation with schema validation, policy metadata, progress, and idempotency.
Use a custom tool for one operation that does not need a shared client or lifecycle bundle.
import { jsonSchema, tool } from '@noir-agent/agent'
const createTicket = tool({
description: 'Create one support ticket for the current user.',
input: jsonSchema<{ title: string; body: string }>({
type: 'object',
properties: {
title: { type: 'string', minLength: 1, maxLength: 120 },
body: { type: 'string', minLength: 1, maxLength: 8_000 },
},
required: ['title', 'body'],
additionalProperties: false,
}),
effect: 'write',
approval: 'requester',
retry: 'idempotent',
approvalSummary: ({ title }) => `Create support ticket “${title}”`,
maxOutputBytes: 4_000,
async execute(input, context) {
await context.progress('Creating the ticket…')
return support.createTicket({
...input,
requesterId: context.actor.id,
idempotencyKey: context.idempotencyKey,
signal: context.signal,
})
},
project(ticket) {
return { id: ticket.id, url: ticket.url, status: ticket.status }
},
})Input validation
input.parse must reject malformed model output. jsonSchema creates a schema whose parser validates common JSON Schema constraints. The optional Zod bridge is available from @noir-agent/agent/schema/zod when your project already uses Zod.
Output projection
Provider responses often contain internal IDs, billing fields, raw headers, or nested data irrelevant to the model. Validate the response when correctness matters, then project a small model-facing shape.
Context
ToolContext includes the execution and call IDs, actor, channel, current attachments, previous messages, normalized connector clients, progress delivery, attachment resolution, abort signal, and stable idempotency key.
Registering
tools: { support: { createTicket } }The model sees support.createTicket. Keep descriptions specific about when the operation is appropriate and what it returns. Tool policy and authorization remain in code, not in prose alone.