External Adapters
AImetier supports external adapter plugins that can be installed from npm packages or local directories. External adapters work exactly like built-in adapters — they execute agents, parse output, and render transcripts — but they live in their own package and don’t require changes to AImetier’s source code.
Built-in vs External
Section titled “Built-in vs External”| Built-in | External | |
|---|---|---|
| Source location | Inside aimetier-fork/packages/adapters/ |
Separate npm package or local directory |
| Registration | Hardcoded in three registries | Loaded at startup via plugin system |
| UI parser | Static import at build time | Dynamically loaded from API (see UI Parser) |
| Distribution | Ships with AImetier | Published to npm or linked via file: |
| Updates | Requires AImetier release | Independent versioning |
Quick Start
Section titled “Quick Start”Minimal Package Structure
Section titled “Minimal Package Structure”my-adapter/ package.json tsconfig.json src/ index.ts # Shared metadata (type, label, models) server/ index.ts # createServerAdapter() factory execute.ts # Core execution logic parse.ts # Output parsing test.ts # Environment diagnostics ui-parser.ts # Self-contained UI transcript parserpackage.json
Section titled “package.json”{ "name": "my-aimetier-adapter", "version": "1.0.0", "type": "module", "license": "MIT", "aimetier": { "adapterUiParser": "1.0.0" }, "exports": { ".": "./dist/index.js", "./server": "./dist/server/index.js", "./ui-parser": "./dist/ui-parser.js" }, "files": ["dist"], "scripts": { "build": "tsc" }, "dependencies": { "@aimetier/adapter-utils": "^2026.325.0", "picocolors": "^1.1.0" }, "devDependencies": { "@types/node": "^22.0.0", "typescript": "^5.7.0" }}Key fields:
| Field | Purpose |
|---|---|
exports["."] |
Entry point — must export createServerAdapter |
exports["./ui-parser"] |
Self-contained UI parser module (optional but recommended) |
aimetier.adapterUiParser |
Contract version for the UI parser ("1.0.0") |
files |
Limits what gets published — only dist/ |
tsconfig.json
Section titled “tsconfig.json”{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "dist", "rootDir": "src", "declaration": true, "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src"]}Server Module
Section titled “Server Module”The plugin loader calls createServerAdapter() from your package root. This function must return a ServerAdapterModule.
src/index.ts
Section titled “src/index.ts”export const type = "my_adapter"; // snake_case, globally uniqueexport const label = "My Agent (local)";
export const models = [ { id: "model-a", label: "Model A" },];
export const agentConfigurationDoc = `# my_adapter configurationUse when: ...Don't use when: ...`;
// Required by plugin-loader conventionexport { createServerAdapter } from "./server/index.js";src/server/index.ts
Section titled “src/server/index.ts”import type { ServerAdapterModule } from "@aimetier/adapter-utils";import { type, models, agentConfigurationDoc } from "../index.js";import { execute } from "./execute.js";import { testEnvironment } from "./test.js";
export function createServerAdapter(): ServerAdapterModule { return { type, execute, testEnvironment, models, agentConfigurationDoc, };}src/server/execute.ts
Section titled “src/server/execute.ts”The core execution function. Receives an AdapterExecutionContext and returns an AdapterExecutionResult.
import type { AdapterExecutionContext, AdapterExecutionResult,} from "@aimetier/adapter-utils";
import { runChildProcess, buildAImetierEnv, renderTemplate,} from "@aimetier/adapter-utils/server-utils";
export async function execute( ctx: AdapterExecutionContext,): Promise<AdapterExecutionResult> { const { config, agent, runtime, context, onLog, onMeta } = ctx;
// 1. Read config with safe helpers const cwd = String(config.cwd ?? "/tmp"); const command = String(config.command ?? "my-agent"); const timeoutSec = Number(config.timeoutSec ?? 300);
// 2. Build environment with AImetier vars injected const env = buildAImetierEnv(agent);
// 3. Render prompt template const prompt = config.promptTemplate ? renderTemplate(String(config.promptTemplate), { agentId: agent.id, agentName: agent.name, companyId: agent.companyId, runId: ctx.runId, taskId: context.taskId ?? "", taskTitle: context.taskTitle ?? "", }) : "Continue your work.";
// 4. Spawn process const result = await runChildProcess(command, { args: [prompt], cwd, env, timeout: timeoutSec * 1000, graceMs: 10_000, onStdout: (chunk) => onLog("stdout", chunk), onStderr: (chunk) => onLog("stderr", chunk), });
// 5. Return structured result return { exitCode: result.exitCode, timedOut: result.timedOut, // Include session state for persistence sessionParams: { /* ... */ }, };}Available Helpers from @aimetier/adapter-utils
Section titled “Available Helpers from @aimetier/adapter-utils”| Helper | Purpose |
|---|---|
runChildProcess(command, opts) |
Spawn a child process with timeout, grace period, and streaming callbacks |
buildAImetierEnv(agent) |
Inject AIMETIER_* environment variables |
renderTemplate(template, data) |
{{variable}} substitution in prompt templates |
asString(v), asNumber(v), asBoolean(v) |
Safe config value extraction |
src/server/test.ts
Section titled “src/server/test.ts”Validates the adapter configuration before running. Returns structured diagnostics.
import type { AdapterEnvironmentTestContext, AdapterEnvironmentTestResult,} from "@aimetier/adapter-utils";
export async function testEnvironment( ctx: AdapterEnvironmentTestContext,): Promise<AdapterEnvironmentTestResult> { const checks = [];
// Example: check CLI is installed checks.push({ level: "info", message: "My Agent CLI v1.2.0 detected", code: "cli_detected", });
// Example: check working directory const cwd = String(ctx.config.cwd ?? ""); if (!cwd.startsWith("/")) { checks.push({ level: "error", message: `Working directory must be absolute: "${cwd}"`, hint: "Use /home/user/project or /workspace", code: "invalid_cwd", }); }
return { adapterType: ctx.adapterType, status: checks.some(c => c.level === "error") ? "fail" : "pass", checks, testedAt: new Date().toISOString(), };}Check levels:
| Level | Meaning | Effect |
|---|---|---|
info |
Informational | Shown in test results |
warn |
Non-blocking issue | Shown with yellow indicator |
error |
Blocks execution | Prevents agent from running |
Installation
Section titled “Installation”From npm
Section titled “From npm”# Via the AImetier UI# Settings → Adapters → Install from npm → "my-aimetier-adapter"
# Or via APIcurl -X POST http://localhost:3102/api/adapters \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"packageName": "my-aimetier-adapter"}'From local directory
Section titled “From local directory”curl -X POST http://localhost:3102/api/adapters \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"localPath": "/home/user/my-adapter"}'Local adapters are symlinked into AImetier’s adapter directory. Changes to the source are picked up on server restart.
Via adapter-plugins.json
Section titled “Via adapter-plugins.json”For development, you can also edit ~/.aimetier/adapter-plugins.json directly:
[ { "packageName": "my-aimetier-adapter", "localPath": "/home/user/my-adapter", "type": "my_adapter", "installedAt": "2026-03-30T12:00:00.000Z" }]Optional: Session Persistence
Section titled “Optional: Session Persistence”If your agent runtime supports sessions (conversation continuity across heartbeats), implement a session codec:
import type { AdapterSessionCodec } from "@aimetier/adapter-utils";
export const sessionCodec: AdapterSessionCodec = { deserialize(raw) { if (typeof raw !== "object" || raw === null) return null; const r = raw as Record<string, unknown>; return r.sessionId ? { sessionId: String(r.sessionId) } : null; }, serialize(params) { return params?.sessionId ? { sessionId: String(params.sessionId) } : null; }, getDisplayId(params) { return params?.sessionId ? String(params.sessionId) : null; },};Include it in createServerAdapter():
return { type, execute, testEnvironment, sessionCodec, /* ... */ };Optional: Skills Sync
Section titled “Optional: Skills Sync”If your agent runtime supports skills/plugins, implement listSkills and syncSkills:
return { type, execute, testEnvironment, async listSkills(ctx) { return { adapterType: ctx.adapterType, supported: true, mode: "ephemeral", desiredSkills: [], entries: [], warnings: [], }; }, async syncSkills(ctx, desiredSkills) { // Install desired skills into the runtime return { /* same shape as listSkills */ }; },};Optional: Model Detection
Section titled “Optional: Model Detection”If your runtime has a local config file that specifies the default model:
async function detectModel() { // Read ~/.my-agent/config.yaml or similar return { model: "anthropic/claude-sonnet-4", provider: "anthropic", source: "~/.my-agent/config.yaml", candidates: ["anthropic/claude-sonnet-4", "openai/gpt-4o"], };}
return { type, execute, testEnvironment, detectModel: () => detectModel() };Publishing
Section titled “Publishing”npm run buildnpm publishOther AImetier users can then install your adapter by package name from the UI or API.
Security
Section titled “Security”- Treat agent output as untrusted — parse defensively, never
eval()agent output - Inject secrets via environment variables, not in prompts
- Configure network access controls if the runtime supports them
- Always enforce timeout and grace period — don’t let agents run forever
- The UI parser module runs in a browser sandbox — it must have zero runtime imports and no side effects
Next Steps
Section titled “Next Steps”- UI Parser Contract — add a custom run-log parser so the UI renders your adapter’s output correctly
- Creating an Adapter — full walkthrough of adapter internals
- How Agents Work — understand the heartbeat lifecycle your adapter serves