agentslist
v0.3.1
Published
Stable abstraction layer over CLI coding agents (Claude Code, Codex, Gemini CLI, OpenCode)
Maintainers
Readme
agentslist
Stable abstraction layer over CLI coding agents (Claude Code, Codex CLI, Gemini CLI, OpenCode). Detects installed agents, resolves capabilities, executes prompts, and normalizes output — all through a single, agent-agnostic API.
Install
npm install agentslist agentslist-dbRequires Node.js >= 18. The agentslist-db package provides the compatibility database consumed at runtime.
Quick Start
import { run } from "agentslist";
const result = await run({
prompt: "Refactor this function to use async/await",
working_dir: "/path/to/project",
output: { format: "json" },
});
console.log(result.status); // "success"
console.log(result.output.text); // Agent's text response
console.log(result.output.json); // Parsed JSON (when format is "json")API
detect(options?): Promise<DetectedAgent[]>
Find all installed CLI agents by running detection commands from the database.
import { detect } from "agentslist";
const agents = await detect();
// [
// {
// id: "claude-code",
// binary: "claude",
// version: "2.1.7",
// path: "/usr/local/bin/claude",
// env_authenticated: true
// },
// { id: "gemini-cli", binary: "gemini", version: "0.27.2", ... }
// ]Options:
| Field | Type | Description |
|-------|------|-------------|
| db_path | string | Custom path to the DB directory. Defaults to bundled database. |
Returns: DetectedAgent[] — sorted by id. Empty array if no agents are found.
plan(request, options?): Promise<ExecutionPlan>
Resolve an execution request into a concrete execution plan without running it. Useful for previewing what CLI command would be generated.
import { plan } from "agentslist";
const p = await plan({
prompt: "Add tests for the auth module",
model: "claude-sonnet-4-5-20250929",
output: { format: "json" },
max_turns: 5,
});
console.log(p.binary); // "claude"
console.log(p.args); // ["--print", "--output-format", "json", "--model", ...]
console.log(p.env); // { ... }
console.log(p.compatibility_report.overall); // "full"Options:
| Field | Type | Description |
|-------|------|-------------|
| agent | string | Force a specific agent by id ("claude-code", "codex", "gemini-cli", "opencode"). Auto-selects first detected agent if omitted. |
| db_path | string | Custom database path. |
Returns: ExecutionPlan
interface ExecutionPlan {
agent_id: string;
binary: string;
args: string[];
env: Record<string, string>;
stdin?: string;
output_parser: string; // "plain_text" | "json_object" | "ndjson_stream"
compatibility_report: CompatibilityReport;
model_resolution?: ModelResolution; // Present when request.model is specified
protocol?: {
type: ProtocolType;
features: string[];
};
}When request.model is specified, the resolver validates the token against the agent's model bindings, resolves aliases (e.g. "opus" to "claude-opus-4-6"), and applies fallback chains for blocked models. The result is captured in model_resolution.
run(request, options?): Promise<ExecutionResult>
Execute the full pipeline end-to-end: detect agent, resolve plan, spawn process, parse output.
import { run } from "agentslist";
const result = await run({
prompt: "Write a hello world in Rust",
working_dir: process.cwd(),
timeout_seconds: 60,
});
if (result.status === "success") {
console.log(result.output.text);
}Options:
| Field | Type | Description |
|-------|------|-------------|
| agent | string | Force a specific agent. |
| db_path | string | Custom database path. |
| timeout_ms | number | Override timeout in milliseconds. Overrides request.timeout_seconds. |
Returns: ExecutionResult
interface ExecutionResult {
agent: { id: string; version: string };
status: "success" | "error" | "timeout" | "budget_exceeded" | "turn_limit";
exit_code: number;
output: {
text?: string; // Extracted text (via json_response_path or plain stdout)
json?: unknown; // Parsed JSON object (when output format is json)
raw: string; // Raw stdout
};
compatibility: CompatibilityReport;
duration_ms: number;
resolved_command: {
binary: string;
args: string[];
env: Record<string, string>;
};
}check(request, options?): Promise<CompatibilityReport>
Check whether an agent supports the requested capabilities without executing anything.
import { check, summarize } from "agentslist";
const report = await check({
prompt: "...",
output: { format: "json", schema: { type: "object" } },
session: { resume_id: "abc-123" },
});
console.log(report.overall); // "full" | "degraded" | "unsupported" | "unknown"
console.log(summarize(report)); // Human-readable summaryReturns: CompatibilityReport
interface CompatibilityReport {
overall: "full" | "degraded" | "unsupported" | "unknown";
details: CapabilityDetail[];
}
interface CapabilityDetail {
capability: CapabilityId; // e.g. "output.json", "session.resume"
status: "supported" | "degraded" | "unsupported" | "unknown";
message?: string; // Explanation for non-supported statuses
fallback_used?: boolean;
}spawn(request, options?): Promise<AgentSession>
Start an interactive session with streaming events and follow-up messaging.
import { spawn } from "agentslist";
const session = await spawn(
{ prompt: "Review this codebase for security issues" },
{ agent: "claude-code", cwd: "/path/to/project" },
);
for await (const event of session.events) {
switch (event.type) {
case "message":
process.stdout.write(event.content);
break;
case "tool_call":
console.log(`Tool: ${event.name}`, event.input);
break;
case "approval_request":
await session.respond(event.id, { behavior: "allow" });
break;
case "cost_update":
console.log(`Cost: $${event.cost_usd.toFixed(4)}`);
break;
case "error":
console.error(event.message);
break;
}
}
const result = await session.wait();SpawnOptions:
| Field | Type | Description |
|-------|------|-------------|
| agent | string | Force a specific agent. |
| db_path | string | Custom database path. |
| cwd | string | Working directory for the spawned process. |
| env | Record<string, string> | Additional environment variables. |
| cancel_grace_ms | number | Grace period before SIGKILL after cancel. Default: 5000. |
AgentSession interface:
| Member | Type | Description |
|--------|------|-------------|
| events | AsyncIterable<AgentEvent> | Normalized event stream. |
| send(message) | Promise<void> | Send a follow-up message. |
| respond(requestId, response) | Promise<void> | Respond to an approval request. |
| cancel() | Promise<void> | Gracefully cancel (SIGTERM, then SIGKILL after grace period). |
| wait() | Promise<ExecutionResult> | Wait for process exit. |
| sessionId | string \| null | Protocol-provided session ID (readonly). |
| pid | number \| undefined | Child process PID (readonly). |
AgentEvent types:
| Type | Key Fields | Description |
|------|-----------|-------------|
| message | content, partial | Text output from the agent. |
| thought | content | Internal reasoning (when exposed). |
| tool_call | id, name, input | Agent invoked a tool. |
| tool_result | id, output | Tool returned a result. |
| approval_request | id, tool_name, input | Agent requests permission. Call session.respond() to allow/deny. |
| session_start | session_id | Session ID assigned by the agent. |
| file_change | path, diff? | A file was modified. |
| plan | steps | Agent's execution plan. |
| context_usage | tokens_used, tokens_limit? | Token usage update. |
| cost_update | cost_usd, tokens_in, tokens_out | Cost tracking data. |
| done | result? | Agent finished. |
| error | message | An error occurred. |
| raw | data | Unparsed protocol data. |
listModels(options?): Promise<ModelListResult>
List all models available for a specific agent and version, grouped by status.
import { listModels } from "agentslist";
const models = await listModels({ agent: "claude-code" });
console.log(models.agent_id); // "claude-code"
console.log(models.default_model); // "claude-sonnet-4-5-20250929"
console.log(models.selection_mode); // "allowlist" | "passthrough"
console.log(models.supported); // ModelBindingWithCatalog[]
console.log(models.deprecated); // ModelBindingWithCatalog[]Options:
| Field | Type | Description |
|-------|------|-------------|
| agent | string | Agent id. Auto-selects first detected agent if omitted. |
| version | string | Override agent version. Defaults to detected version. |
| db_path | string | Custom database path. |
Returns: ModelListResult
interface ModelListResult {
agent_id: string;
version: string;
default_model?: string;
selection_mode: "allowlist" | "passthrough";
unknown_model_behavior: "passthrough" | "error" | "unknown";
supported: ModelBindingWithCatalog[];
preview: ModelBindingWithCatalog[];
deprecated: ModelBindingWithCatalog[];
blocked: ModelBindingWithCatalog[];
}
interface ModelBindingWithCatalog {
binding: ModelBinding;
catalog?: ModelCatalogEntry; // Catalog metadata (provider, family, lifecycle)
}checkModel(token, options?): Promise<ModelCheckResult>
Check if a model token is compatible with a specific agent and version. Resolves aliases and reports status.
import { checkModel } from "agentslist";
const result = await checkModel("opus", { agent: "claude-code" });
console.log(result.resolution.status); // "matched"
console.log(result.resolution.resolved_model_id); // "claude-opus-4-6"
console.log(result.summary); // 'Model "claude-opus-4-6" is supported'
// Deprecated model
const dep = await checkModel("claude-3-5-haiku-20241022", { agent: "claude-code" });
console.log(dep.resolution.status); // "matched_deprecated"
console.log(dep.resolution.warnings); // [{ type: "deprecated", message: "..." }]
console.log(dep.resolution.suggestions); // ["claude-sonnet-4-5-20250929", ...]Options:
| Field | Type | Description |
|-------|------|-------------|
| agent | string | Agent id. Auto-selects first detected agent if omitted. |
| version | string | Override agent version. |
| db_path | string | Custom database path. |
Returns: ModelCheckResult
interface ModelCheckResult {
resolution: ModelResolution;
summary: string; // Human-readable description
}
interface ModelResolution {
status: "matched" | "matched_deprecated" | "matched_preview"
| "fallback" | "passthrough" | "blocked" | "no_model";
requested_token?: string;
resolved_model_id?: string;
effective_token?: string; // The token passed to --model flag
default_model?: string; // Agent's default model for reference
warnings: ModelWarning[];
suggestions: string[]; // Alternative model IDs
}
interface ModelWarning {
type: "deprecated" | "preview" | "blocked" | "unknown_token" | "fallback" | "lifecycle";
message: string;
}findAgentsForModel(modelId, options?): Promise<CrossAgentModelInfo[]>
Find which agents support a given canonical model ID.
import { findAgentsForModel } from "agentslist";
const agents = await findAgentsForModel("claude-opus-4-6");
// [
// {
// agent_id: "claude-code",
// agent_name: "Claude Code",
// status: "supported",
// accepted_tokens: ["opus", "claude-opus-4-6"],
// is_default: false
// }
// ]Options:
| Field | Type | Description |
|-------|------|-------------|
| db_path | string | Custom database path. |
Returns: CrossAgentModelInfo[]
interface CrossAgentModelInfo {
agent_id: string;
agent_name: string;
status: "supported" | "deprecated" | "blocked" | "preview";
accepted_tokens: string[];
is_default: boolean;
}ExecutionRequest
The full request shape accepted by run(), plan(), check(), and spawn():
interface ExecutionRequest {
// --- Prompt ---
prompt: string; // The main prompt text
prompt_file?: string; // Read prompt from file instead
piped_input?: string; // Data piped to stdin
system_prompt?: {
strategy?: "replace" | "append"; // Replace or append to default system prompt
content?: string; // Inline system prompt
file?: string; // System prompt from file
append_file?: string; // Additional system prompt file (appended)
};
// --- Media ---
images?: string[]; // Image file paths
file_attach?: string[]; // Files to attach
// --- Context ---
context?: {
include_files?: string[]; // Specific files to include
include_globs?: string[]; // Glob patterns to include
exclude_globs?: string[]; // Glob patterns to exclude
};
// --- Output ---
output?: {
format?: "text" | "json" | "stream_json";
schema?: Record<string, unknown>; // JSON Schema for structured output
file?: string; // Write output to file
diff?: boolean; // Show diffs instead of full files
markdown_render?: boolean; // Render markdown in output
};
// --- Model ---
model?: string; // Model identifier
fallback_model?: string; // Fallback if primary unavailable
reasoning_effort?: "off" | "low" | "medium" | "high" | "max";
// --- Execution Control ---
sandbox?: "none" | "read_only" | "workspace_write" | "full_access";
approval_bypass?: boolean; // Skip all approval prompts
approval_mode?: "default" | "auto_edit" | "plan" | "full_bypass";
max_turns?: number;
max_budget_usd?: number;
timeout_seconds?: number;
working_dir?: string;
additional_dirs?: string[];
debug?: boolean;
quiet?: boolean;
dry_run?: boolean;
plan_mode?: boolean;
config_file?: string;
// --- Environment ---
env?: Record<string, string>; // Extra environment variables
provider?: {
api_base?: string;
api_key?: string; // Passed via env, never in CLI args
name?: string;
};
// --- Session ---
session?: {
resume_id?: string; // Resume a previous session
continue_last?: boolean; // Continue the most recent session
session_id?: string; // Explicit session ID
fork?: boolean; // Fork from an existing session
list?: boolean; // List available sessions
export_path?: string; // Export session to file
import_path?: string; // Import session from file
};
// --- Tools ---
tools?: {
allowed?: string[]; // Restrict to these tools
disallowed?: string[]; // Block these tools
};
mcp?: {
config_path?: string; // MCP server config file
strict?: boolean;
};
}Lower-Level API
The high-level functions (detect, plan, run, check, spawn) compose these building blocks. You can use them directly for advanced scenarios.
Detector
import { detectAll, detectAgent, isAvailable } from "agentslist";
const agents = await detectAll(db); // All installed agents
const claude = await detectAgent("claude-code", db); // Specific agent or null
const hasClaude = await isAvailable("claude-code", db);Resolver
Pure function — no I/O. Maps ExecutionRequest + DetectedAgent + AgentDB to ExecutionPlan.
import { resolve } from "agentslist";
import type { ResolveOptions } from "agentslist";
const plan = resolve(request, agent, db);
// Protocol mode: use the agent's streaming protocol flags instead of oneshot flags
const plan = resolve(request, agent, db, { mode: "protocol" });Executor
Spawns the CLI process and collects raw output.
import { execute } from "agentslist";
const raw = await execute(plan, {
cwd: "/path/to/project",
timeout_ms: 30_000,
});
console.log(raw.stdout);
console.log(raw.stderr);
console.log(raw.exit_code); // number
console.log(raw.timed_out); // boolean
console.log(raw.duration_ms); // numberParser
Normalizes raw CLI output into ParsedOutput.
import { parse } from "agentslist";
const output = parse(rawOutput, plan, versionRange?.output_parsing);
console.log(output.text); // Extracted text
console.log(output.json); // Parsed JSON (if applicable)
console.log(output.raw); // Raw stdoutThree parsing strategies are selected automatically based on the execution plan:
| Strategy | When | Behavior |
|----------|------|----------|
| plain_text | Default / text output | Trims stdout |
| json_object | output.format: "json" | Parses JSON, extracts text via json_response_path |
| ndjson_stream | output.format: "stream_json" | Parses newline-delimited JSON events, finds the final event |
Reporter
import { preflightReport, isFullySupported, summarize } from "agentslist";
const report = preflightReport(request, agent, db);
const ok = isFullySupported(request, agent, db); // boolean
const text = summarize(report); // Human-readable stringProtocol Parsers
Normalize agent-specific streaming protocols into AgentEvent:
import {
getProtocolParser,
createClaudeSdkParser,
createCodexJsonrpcParser,
createAcpParser,
createPlainTextParser,
} from "agentslist";| Protocol | Agent | Description |
|----------|-------|-------------|
| claude_sdk | Claude Code | Structured JSON events |
| codex_app_server | Codex CLI | JSON-RPC 2.0 over stdio |
| codex_jsonrpc | Codex CLI | Legacy JSON-RPC |
| acp | — | Agent Communication Protocol |
| plain_text | Fallback | Raw line-by-line stdout |
Resolver Helpers (Browser-Safe)
Exported as raw .ts — no Node.js dependencies. Safe for browser/playground use.
import {
resolveCapability,
computeOverallCompatibility,
capabilityIdToDbKey,
dbKeyToCapabilityId,
pushFlagValue,
pushBooleanFlag,
pushMultiValues,
} from "agentslist/resolver-helpers";Model Helpers (Browser-Safe)
Pure functions for model resolution. Exported as raw .ts — no Node.js dependencies.
import {
resolveModel,
resolveModelToken,
applyModelFallback,
findBindingByToken,
findCatalogEntry,
generateModelWarnings,
listModelBindings,
findAgentsForModel,
summarizeModelResolution,
} from "agentslist/model-helpers";| Function | Description |
|----------|-------------|
| resolveModel(token, fallback, models, catalog) | Full resolution pipeline with fallback chain. |
| resolveModelToken(token, models, catalog) | Resolve a single token against bindings. |
| applyModelFallback(resolution, fallback, models, catalog) | Apply fallback when primary is blocked. |
| findBindingByToken(token, bindings) | Find a binding that accepts a given token. |
| findCatalogEntry(modelId, catalog) | Look up a model in the catalog by ID. |
| generateModelWarnings(binding, catalog, token, models) | Generate warnings for a binding. |
| listModelBindings(models, catalog) | Group bindings by status with catalog info. |
| findAgentsForModel(modelId, agentModelsMap) | Cross-agent lookup (sync, takes a Map). |
| summarizeModelResolution(resolution) | Human-readable summary string. |
Errors
All errors extend AgentslistError:
| Error | When |
|-------|------|
| AgentslistError | Base class for all library errors. |
| AgentNotFoundError | Agent id not in the database. Properties: agentId. |
| AgentNotInstalledError | Agent binary not found on the system. Properties: agentId, binary. |
| VersionNotMatchedError | Detected version has no matching range in the DB. Properties: agentId, version. |
| DBLoadError | Failed to load the compatibility database. Properties: path. |
| ExecutionError | Process spawn or execution failure. Properties: exitCode?, stderr?. |
| ParseError | Output parsing failed. Properties: rawOutput?. |
import { run, AgentNotInstalledError, ExecutionError } from "agentslist";
try {
await run({ prompt: "...", working_dir: "." }, { agent: "codex" });
} catch (err) {
if (err instanceof AgentNotInstalledError) {
console.error(`${err.binary} is not installed`);
} else if (err instanceof ExecutionError) {
console.error(`Execution failed (exit ${err.exitCode}): ${err.stderr}`);
}
}Export Paths
// Main API — all functions, classes, types
import { detect, plan, run, check, spawn, AgentDB } from "agentslist";
// Model API
import { listModels, checkModel, findAgentsForModel } from "agentslist";
// Browser-safe helpers — no Node.js dependencies
import { resolveCapability, computeOverallCompatibility } from "agentslist/resolver-helpers";
import { resolveModel, resolveModelToken, listModelBindings } from "agentslist/model-helpers";
// Types only
import type {
ExecutionRequest, ExecutionPlan, ExecutionResult, AgentEvent,
ModelResolution, ModelCatalogEntry, ModelBinding, ModelCompatRange,
} from "agentslist/types";License
Apache License 2.0. See the LICENSE file for details.
