Skip to content
Agent SDK

Cookbook

Five complete examples: structured extraction, Q&A over internal data, streaming to a frontend, batch runs, file edits with built-in tools.

Five examples, all against the current API.

1. Structured extraction

No tools — just the model and one run():

import { Agent } from '@neoxlabs/sdk';

const agent = new Agent({
  model: 'claude-sonnet-4-6',
  systemPrompt: 'Return JSON only. No commentary.',
});

const { text, usage } = await agent.run(`Extract this résumé as JSON:\n${resume}`);
console.log(JSON.parse(text), usage);

2. Q&A over internal data (read-only tools)

import { Agent, tool } from '@neoxlabs/sdk';
import { z } from 'zod';

const searchOrders = tool({
  name: 'search_orders',
  description: 'Look up orders by customer id; returns the 20 most recent with status and amount. Read-only.',
  schema: z.object({ customerId: z.string() }),
  handler: async ({ customerId }) => db.orders.byCustomer(customerId, { limit: 20 }),
});

const agent = new Agent({
  model: 'claude-sonnet-4-6',
  tools: [searchOrders],
  maxSteps: 12,
});

const res = await agent.run('Did customer C-88 have any failed payments recently?');
console.log(res.text);

3. Stream the run to a frontend

export async function POST(req: Request) {
  const { prompt } = await req.json();
  const agent = new Agent({ model: 'claude-sonnet-4-6', tools });

  const stream = new ReadableStream({
    async start(controller) {
      for await (const ev of agent.stream(prompt)) {
        controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(ev)}\n\n`));
      }
      controller.close();
    },
  });

  return new Response(stream, { headers: { 'Content-Type': 'text/event-stream' } });
}

The client renders text, tool calls and completion by ev.type.

4. Batch work with caps and timeouts

const results = [];
for (const item of items) {
  const agent = new Agent({
    model: 'deepseek-chat',
    tools: [classify],
    maxSteps: 6,                       // per-item cap
    signal: AbortSignal.timeout(60_000),
  });
  const r = await agent.run(item.text);
  results.push({ id: item.id, text: r.text, stopReason: r.stopReason, usage: r.usage });
}

const spent = results.reduce((n, r) => n + r.usage.inputTokens + r.usage.outputTokens, 0);

One Agent per item keeps contexts clean; stopReason flags the rows that need human review.

5. Let the agent touch files (built-in tools)

import { Agent } from '@neoxlabs/sdk';
import { builtinTools } from '@neoxlabs/sdk/tools';

const agent = new Agent({
  model: 'claude-sonnet-4-6',
  tools: [
    ...builtinTools.fs({ root: './src', allowWrite: true }),
    ...builtinTools.shell({ allowedCommands: ['npm'] }),
  ],
  maxSteps: 20,
  // writes and commands are dangerous — approve them one by one
  permission: async ({ tool, input, dangerous }) => {
    if (!dangerous) return { approved: true };
    return { approved: await confirmWithUser(tool, input), reason: 'needs confirmation' };
  },
});

const stream = agent.stream('Add unit tests for untested functions in utils, then run npm test');
for await (const ev of stream) render(ev);
const { stopReason, usage } = await stream.result();

Every fs path is pinned inside root (symlink escapes included); without allowWrite you only get read tools.

Choosing

SituationUse
One question, one answer, no toolsagent.run()
Fixed steps that never changeplain code calling the model — skip the agent
Steps depend on intermediate resultsagent.run() with tools
The process must be visibleagent.stream()
Irreversible side effectsapproval logic inside handler, plus dangerous