npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@trelent/agents

v0.2.10

Published

TypeScript SDK for the Trelent Agent Orchestration API.

Readme

@trelent/agents

TypeScript SDK for the Trelent Agent Orchestration API.

Installation

npm install @trelent/agents
# or
bun add @trelent/agents

Quick Start

Without authentication

import { Client } from "@trelent/agents";

const client = new Client("http://localhost:8000");

const sandboxes = await client.sandboxes.list();
console.log(sandboxes);

const run = await client.runs.create({
  sandbox: "example-agent:latest",
  prompt: "Create hello.txt with a greeting",
});
console.log(run.id, run.status, run.harness.kind);

With authentication

When the API has authentication enabled, provide your OAuth2 client credentials:

import { Client } from "@trelent/agents";

const client = new Client("https://agents.trelent.com", {
  clientId: "your-client-id",
  clientSecret: "your-client-secret",
});

const sandboxes = await client.sandboxes.list();
const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Do something useful",
});

The SDK handles token acquisition automatically — it exchanges your client credentials for a JWT via the API's /token endpoint before making authenticated requests.

If you need to call the token proxy directly, use client.tokens.create(...).

Client Options

const client = new Client(apiUrl, {
  clientId: "...",       // OAuth2 client ID (optional)
  clientSecret: "...",   // OAuth2 client secret (optional)
  scope: "...",          // Override default OAuth2 scope (optional)
  timeoutMs: 10_000,     // Request timeout in ms (default: 10000)
});

Both clientId and clientSecret must be provided together, or both omitted.

Resources

client.sandboxes

const sandboxes = await client.sandboxes.list();
// => RegistrySandbox[] — [{ name: "my-sandbox", tags: ["latest", "v1"] }]

When auth is enabled, sandboxes are automatically filtered to your namespace. Sandbox names are returned without the namespace prefix.

client.tokens

const token = await client.tokens.create({
  client_id: "your-client-id",
  client_secret: "your-client-secret",
  scope: "AgentOrchestrator:runs:list",
});

client.runs

import { HarnessKind, LocalImporter, ObjectStorageDumpExporter, SharedDrive } from "@trelent/agents";

// Create a run
const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Build a web server",
  harness: { kind: HarnessKind.ClaudeCode }, // optional, defaults to Claude Code
  workdir: "/workspace",         // optional, defaults to image WORKDIR or /workspace
  timeout_seconds: 3600,          // optional, default: 3600
  imports: [new LocalImporter("./data")],  // optional
  exports: [new ObjectStorageDumpExporter()], // optional
  shared_drives: [new SharedDrive("my-drive")],  // optional
  secrets: [                               // optional
    { name: "openai", env_var: "OPENAI_API_KEY" },
  ],
});

// List runs (optionally filter by sandbox)
const runs = await client.runs.list();
const filtered = await client.runs.list("my-sandbox:latest");

// Get a specific run
const run = await client.runs.get("run-id");

// Check status
const status = await client.runs.getStatus("run-id");

// Cancel a run
const cancel = await client.runs.cancel("run-id");

// Get checkpoints
const chain = await client.runs.getCheckpointChain("run-id");
const checkpoint = await client.runs.getCheckpoint("run-id", "checkpoint-id");

Run operations

// Fork (resume from checkpoint)
const forked = await run.fork("Continue from where you left off", {
  workdir: "/workspace/project",
  timeout_seconds: 1800,
});

// Refresh run state
await run.refresh();
console.log(run.status);  // updated status

Structured outputs

Pass outputSchema as a Zod schema to get a typed result back. The SDK auto-converts your schema to JSON Schema before sending and re-validates the harness's structured output through it on the way back via run.outputParsed. The type flows through Run<T> so the result is statically typed:

import { z } from "zod";
import { HarnessKind } from "@trelent/agents";

const CalendarEvent = z.object({
  name: z.string(),
  date: z.string(),
  participants: z.array(z.string()),
});

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Alice and Bob are going to a science fair on Friday. Extract the event.",
  harness: { kind: HarnessKind.Codex },
  outputSchema: CalendarEvent,
});

// poll until done, then:
await run.refresh();
const event = run.outputParsed;  // typed as { name; date; participants: string[] } | null
if (event) {
  console.log(event.name, event.date, event.participants);
}

Zod is an optional peer dependency. Install it only if you use Zod schemas: npm install zod@>=3.24 (or bun add zod).

Fetching a typed run later (or from another process). When a different process or a later poll fetches the run, pass the same schema to get() and outputParsed hydrates the same way:

const run = await client.runs.get<z.infer<typeof CalendarEvent>>("run-id", {
  outputSchema: CalendarEvent,
});
const event = run.outputParsed;  // typed

You can also pass a raw JSON Schema object if you don't want to use Zod:

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "...",
  outputSchema: {
    type: "object",
    properties: { name: { type: "string" } },
    required: ["name"],
    additionalProperties: false,
  },
});
// Read the raw object — `outputParsed` is `null` when no Zod schema was provided.
await run.refresh();
console.log(run.result?.structured_output);

Codex (OpenAI) is stricter than Claude. Codex requires every property in required, additionalProperties: false on every object, and rejects patternProperties, format, optional-field keywords, etc. If your schema violates these the API returns a 422 at request time with a JSON-pointer path to the bad node. Claude accepts most schemas as-is.

Streaming Events

Subscribe to real-time events from a run using Server-Sent Events (SSE). Events are streamed as the agent executes, providing live visibility into reasoning, messages, and tool usage.

Basic streaming

import { Client, EventType } from "@trelent/agents";

const client = new Client(apiUrl, { clientId, clientSecret });

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Build a simple web server",
});

// Subscribe to the event stream
const stream = run.subscribe();

for await (const event of stream) {
  switch (event.type) {
    case EventType.MessageDelta:
      process.stdout.write(event.data.delta.text);
      break;
    case EventType.ToolCallStarted:
      console.log(`\nTool: ${event.data.name}`);
      break;
    case EventType.SessionCompleted:
      console.log(`\nCompleted with exit code: ${event.data.exit_code}`);
      break;
  }
}

Subscribing via client

You can also subscribe using the run ID directly:

const stream = client.runs.subscribe("run-abc123");

for await (const event of stream) {
  console.log(event.type, event.seq);
}

Closing the stream

The stream automatically closes when the run completes. To close early:

const stream = run.subscribe();

for await (const event of stream) {
  if (someCondition) {
    stream.close();  // Unsubscribe and close connection
    break;
  }
}

Event types

| Event Type | Description | |------------|-------------| | session.started | Agent session initialized | | session.completed | Session finished (includes exit_code and usage) | | turn.started | New conversation turn began | | turn.completed | Turn finished | | turn.failed | Turn failed with error | | message.started | Assistant message started | | message.delta | Incremental text content | | message.completed | Full message content available | | reasoning.started | Extended thinking started | | reasoning.delta | Incremental reasoning text | | reasoning.completed | Reasoning block finished | | tool_call.started | Tool invocation started (includes name and kind) | | tool_call.input_delta | Streaming tool input JSON | | tool_call.input_complete | Full tool input available | | tool_call.output_delta | Streaming tool output | | tool_call.completed | Tool execution finished (includes exit_code) | | structured_output | Validated structured output (only when run was created with outputSchema; emitted just before session.completed) | | error | Error occurred |

Event structure

Every event has a common envelope:

interface CommonEvent {
  seq: number;           // Sequence number (monotonically increasing)
  timestamp: string;     // ISO 8601 timestamp
  type: EventType;       // Event type discriminator
  data: EventData;       // Type-specific payload
}

client.health()

const health = await client.health();
// => { status: "ok" }

Connectors

Importing local files

import { LocalImporter } from "@trelent/agents";

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Process the data",
  imports: [new LocalImporter("./my-data-dir")],
});

Exporting to S3

import { ObjectStorageDumpExporter } from "@trelent/agents";

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Generate a report",
  exports: [new ObjectStorageDumpExporter("my-bucket", "reports/")],
});

Shared drives

Shared drives mount a user-scoped shared directory into the sandbox at /mnt/shared/<name>. They are specified via a dedicated shared_drives field on the run, separate from import/export connectors.

import { SharedDrive } from "@trelent/agents";

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Read and write files in /mnt/shared/my-drive",
  shared_drives: [new SharedDrive("my-drive")],
});

To mount a subdirectory of the shared drive:

shared_drives: [new SharedDrive("my-drive", "project-a")]

To export a shared drive (or a subdirectory of it) to S3 after the run:

import { SharedDrive, SharedDriveExporter } from "@trelent/agents";

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Generate reports in /mnt/shared/my-drive",
  shared_drives: [new SharedDrive("my-drive")],
  exports: [new SharedDriveExporter("my-drive", "reports")],
});

Secrets

Create a user-scoped secret once. The API stores the value in the deployment's configured secret backend and only stores metadata in the database.

await client.secrets.create("openai", "sk-...");

const secrets = await client.secrets.list();
const secret = await client.secrets.get("openai");
await client.secrets.update("openai", "sk-new");
await client.secrets.delete("openai");

Use secrets in runs by logical name. A string shorthand injects the secret as an environment variable with the same name:

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",
  prompt: "Use the injected secret",
  secrets: ["openai"],
});

For a custom environment variable:

secrets: [{ name: "openai", env_var: "OPENAI_API_KEY" }]

To mount a secret as a file instead of an env var:

secrets: [{ name: "openai", mount: true }]

The runner writes mounted secrets to /mnt/secrets/<secret_name> inside the sandbox. You can also set both env_var and mount: true to expose both forms.

Docker Registry Setup

When auth is enabled, push sandbox images to your user namespace on the registry:

# Login with your OAuth2 credentials
docker login registry.example.com -u <client_id> -p <client_secret>

# Push to your namespace
docker tag my-sandbox:latest registry.example.com/<client_id>/my-sandbox:latest
docker push registry.example.com/<client_id>/my-sandbox:latest

When creating runs via the SDK, just use the sandbox name without the namespace prefix:

const run = await client.runs.create({
  sandbox: "my-sandbox:latest",  // not "<client_id>/my-sandbox:latest"
  prompt: "...",
});

The API resolves the full registry path automatically based on your authenticated identity.

Types

The SDK exports the following types:

import { EventType, HarnessKind, RunStatus, ToolKind } from "@trelent/agents";
import type {
  CancelRunResponse,
  ClaudeCodeHarnessSpec,
  CodexHarnessSpec,
  ClientOptions,
  CommonEvent,
  CreateRunOptions,
  RegistrySandbox,
  RunResult,
  RunStatusResponse,
  CheckpointResponse,
  ChatHistoryEntry,
  FileOutput,
  OutputFile,
  HealthResponse,
  HarnessInfo,
  HarnessSpec,
  ResumeRunOptions,
  RunSecretBinding,
  RunSecretSpec,
  SecretResponse,
  TokenRequest,
  TokenResponse,
  Usage,
  WorkflowIds,
} from "@trelent/agents";