@episcloud/agent
v0.6.0
Published
EpisCloud coding agent SDK — typed tools, permissions, streaming, sub-agents, handoffs, guardrails, structured outputs.
Downloads
1,532
Maintainers
Readme
@episcloud/agent
A TypeScript SDK for building coding agents on top of the EpisCloud AI
platform. Built for the same patterns developers know from other agent
toolkits: a query() async generator, typed tool definitions backed by
Zod, a permission system, hooks, and a streaming result protocol.
npm install @episcloud/agentNode 20+ required.
Quick start
import { query } from '@episcloud/agent';
for await (const msg of query('Review src/auth.ts for OWASP issues', {
apiKey: process.env.EPISCLOUD_API_KEY!, // epis_sk_…
allowedTools: ['Read', 'Glob', 'Grep'], // read-only inspection
})) {
if (msg.type === 'stream_event') process.stdout.write(msg.delta);
if (msg.type === 'tool_use') console.log('\n→', msg.name, msg.input);
if (msg.type === 'result') console.log('\n✓', msg.usage);
}How it works
The SDK calls the EpisCloud Chat API (/v1/chat/completions) over HTTPS
with your epis_sk_ Bearer token. Each tool the agent invokes is run
inside your Node process — the SDK never executes anything server-side.
your code ──► @episcloud/agent ──► paas-ai.episcloud.com
│ │
│ ▼
│ ┌──────────────────┐
│ │ EpisCloud Chat │
│ │ (auth + bill + │
│ │ model loop) │
│ └──────────────────┘
│
▼ runs locally
Read · Glob · Grep · Edit · Write · Bash · …Built-in tools
| Tool | Purpose | Default permission |
|--------------|------------------------------------------------------|--------------------|
| Read | Read file with line offset / limit | allow |
| Write | Create / overwrite a file | ask |
| Edit | Replace exact string in an existing file | ask |
| Glob | Match files by glob (src/**/*.ts) | allow |
| Grep | Search regex across the working directory | allow |
| Bash | Run a shell command | ask |
| TodoWrite | Agent's internal multi-step task tracker | allow |
By default query() enables only the read-only set (Read, Glob,
Grep, TodoWrite). Opt in to mutators explicitly:
query(prompt, {
apiKey: '…',
allowedTools: ['Read', 'Glob', 'Grep', 'Edit', 'Write', 'Bash'],
permissions: { Bash: 'ask', Edit: 'allow', Write: 'allow' },
});Custom tools
import { z } from 'zod';
import { tool, query } from '@episcloud/agent';
const dbQuery = tool({
name: 'db_query',
description: 'Run a read-only SQL query against the analytics DB',
parameters: z.object({
sql: z.string().describe('SELECT only — DDL/DML rejected'),
limit: z.number().int().min(1).max(1000).default(100),
}),
defaultPermission: 'allow',
async execute({ sql, limit }) {
if (!/^\s*select/i.test(sql)) return 'error: only SELECT allowed';
const rows = await runQuery(sql, limit);
return JSON.stringify(rows);
},
});
for await (const msg of query('How many sign-ups last week?', {
apiKey: process.env.EPISCLOUD_API_KEY!,
tools: [dbQuery],
})) {
// …
}Permission modes
Pass permissionMode to set a coarse policy that overrides per-tool
defaults:
| Mode | Effect |
|-----------------------|-------------------------------------------------------------------|
| default | Honor each tool's defaultPermission + your permissions map |
| acceptEdits | Auto-approve file-mutating tools (Write/Edit/MultiEdit) |
| plan | Refuse every tool — model must produce a plan in prose |
| bypassPermissions | Allow everything (CI / unattended only) |
When a tool is gated by ask, the SDK invokes your onToolApproval:
query(prompt, {
apiKey: '…',
onToolApproval: ({ name, input, description }) => {
if (name === 'Bash' && /rm\s+-rf/.test((input as any).command)) {
return 'deny';
}
return 'allow';
},
});Hooks
Hooks fire at lifecycle points and can observe, mutate, or block:
query(prompt, {
apiKey: '…',
hooks: {
PreToolUse: ({ name }) => {
if (name === 'Write') return { permission: 'deny', reason: 'frozen tree' };
return {};
},
PostToolUse: ({ name, result }) => {
metrics.record(name, result.length);
},
UserPromptSubmit: ({ prompt }) => ({
prompt: prompt.replace(/<SECRET>/g, '[REDACTED]'),
}),
},
});Available hooks:
PreToolUse— before a tool runs; can denyPostToolUse— after a tool returnsUserPromptSubmit— before the user prompt reaches the modelStop— when the agent decides to finalize; can force another turnNotification— surface info/warn events to your UI
Cancellation & budgets
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 30_000); // hard 30s cap
const gen = query(prompt, {
apiKey: '…',
signal: ctrl.signal,
maxTurns: 12, // tool-loop iteration cap
maxCostMicroUSD: 100_000, // $0.10 budget
requestTimeoutMs: 60_000, // per model call
});
for await (const msg of gen) {
if (msg.type === 'result' && msg.subtype === 'cancelled') {
console.log('partial result:', msg.result);
}
}Messages
query() yields a discriminated union (SDKMessage). Each message
carries session_id. Branch on .type:
| Type | When |
|----------------|-------------------------------------------------|
| system | Once at start (resolved options, tool catalog) |
| user | Echo of the prompt |
| assistant | One per turn — text + tool calls |
| tool_use | The agent invoked a tool |
| tool_result | The tool returned (success or is_error=true) |
| stream_event | Incremental text/reasoning chunk |
| result | Final — has usage, duration_ms, result |
Error handling
import { InsufficientBalanceError, RateLimitError } from '@episcloud/agent';
try {
for await (const msg of query(prompt, { apiKey: '…' })) { /* … */ }
} catch (err) {
if (err instanceof InsufficientBalanceError) {
promptUserToTopUp();
} else if (err instanceof RateLimitError) {
await sleep((err.retryAfterSec ?? 5) * 1000);
} else {
throw err;
}
}Typed errors: AuthenticationError, InsufficientBalanceError,
RateLimitError, InvalidOptionsError, MaxTurnsExceededError,
BudgetExceededError, ToolExecutionError, ToolDeniedError,
TransportError.
CLI
npm install -g @episcloud/agent
episctl-agent run "refactor src/api/client.ts to use fetch"
episctl-agent chat # interactive REPLCLI looks for ~/.episcloud/agent-settings.json and the
EPISCLOUD_API_KEY env var.
License
MIT
