Skip to content

Define and grant a JavaScript Tool

Host-process capability

Create one application-owned callable with validated input and safe JSON output, then grant it to an <Agent /> only when the model should choose whether to call it. The workflow injects an application-owned repository into a component, then grants a narrow <Tool /> rather than exposing the repository itself.

  • Node.js >=26;
  • @aml-jsx/sdk, @aml-jsx/sdk/testing, and zod;
  • no credentials or network for this deterministic workflow.
import { Agent, AmlRuntime, defineTool, Tool } from "@aml-jsx/sdk"
import { DeterministicAgentProvider } from "@aml-jsx/sdk/testing"
import { z } from "zod"
class SessionRepository {
async listOrders(): Promise<readonly string[]> {
return ["order-17", "order-29"]
}
}
const provider = new DeterministicAgentProvider({
async respond(request, context) {
const tool = request.tools.find(candidate => candidate.name === "list_session_orders")
if (tool?.kind !== "javascript") {
throw new Error("Session Tool was not granted")
}
const orders = await tool.execute({}, { signal: context.signal, trace: context.trace })
return { text: `orders:` + JSON.stringify(orders) }
},
})
function OrderAgent({ repository }: { repository: SessionRepository }) {
const listOrders = defineTool({
description: "List orders for the active application session.",
execute: async () => await repository.listOrders(),
input: z.object({}),
name: "list_session_orders",
})
return (
<Agent provider={provider}>
<Tool use={listOrders} />
Inspect the active session orders.
</Agent>
)
}
const result = await new AmlRuntime().evaluate(<OrderAgent repository={new SessionRepository()} />)
console.log(result)

Save the complete source as recipe.tsx in a project configured as shown in Getting started, then run it directly:

Terminal
npx vite-node recipe.tsx
orders:["order-17","order-29"]
  1. The application passes SessionRepository into OrderAgent; AML does not expose that object to the provider.
  2. defineTool() returns a typed callable carrying the Tool declaration. Calling listOrders({}) from an active component would run it as application-selected work without exposing it to a model.
  3. <Tool use={listOrders} /> grants the Tool only to its containing <Agent />. A sibling <Agent /> does not receive it.
  4. The deterministic provider receives AML’s low-level execution port and calls .execute(input, context) to simulate a model-selected Tool call. Application components use the callable form instead.
  • Input is validated before execution; output must be stable JSON. Cyclic values, BigInt, class instances, Map, Set, symbols, non-finite numbers, and undefined are not portable Tool results.
  • Duplicate Tool names and runtime allowedTools policy violations fail closed.
  • Forward context.signal into I/O so cancellation can stop work that supports it.
  • Keep capability descriptions narrow and auditable. The model should not receive a Tool merely because the application happens to have the function available.
  • Calling a Tool from application code requires an active AML function component. Defining it at module scope is valid; calling it there is not.