Define and grant a JavaScript Tool
Host-process capability
Terminal
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.
Prerequisites
Section titled “Prerequisites”- Node.js
>=26; @aml-jsx/sdk,@aml-jsx/sdk/testing, andzod;- no credentials or network for this deterministic workflow.
Complete source
Section titled “Complete source”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)Run it
Section titled “Run it”Save the complete source as recipe.tsx in a project configured as shown in Getting started, then run it directly:
npx vite-node recipe.tsxExpected output
Section titled “Expected output”orders:["order-17","order-29"]How it works
Section titled “How it works”- The application passes
SessionRepositoryintoOrderAgent; AML does not expose that object to the provider. defineTool()returns a typed callable carrying the Tool declaration. CallinglistOrders({})from an active component would run it as application-selected work without exposing it to a model.<Tool use={listOrders} />grants the Tool only to its containing<Agent />. A sibling<Agent />does not receive it.- 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.
Failure and security notes
Section titled “Failure and security notes”- Input is validated before execution; output must be stable JSON. Cyclic values,
BigInt, class instances,Map,Set, symbols, non-finite numbers, andundefinedare not portable Tool results. - Duplicate Tool names and runtime
allowedToolspolicy violations fail closed. - Forward
context.signalinto 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.