External Adapters
Ce contenu n’est pas encore disponible dans votre langue.
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