Creating an Adapter
Ce contenu n’est pas encore disponible dans votre langue.
Build a custom adapter to connect AImetier to any agent runtime.
Two Paths
Section titled “Two Paths”| Built-in | External Plugin | |
|---|---|---|
| Source | Inside aimetier-fork |
Separate npm package |
| Distribution | Ships with AImetier | Independent npm publish |
| UI parser | Static import | Dynamic load from API |
| Registration | Edit 3 registries | Auto-loaded at startup |
| Best for | Core adapters, contributors | Third-party adapters, internal tools |
For most cases, build an external adapter plugin. It’s cleaner, independently versioned, and doesn’t require modifying AImetier’s source. See External Adapters for the full guide.
The rest of this page covers the shared internals that both paths use.
Package Structure
Section titled “Package Structure”packages/adapters/<name>/ # built-in ── or ──my-adapter/ # external plugin package.json tsconfig.json src/ index.ts # Shared metadata server/ index.ts # Server exports (createServerAdapter) execute.ts # Core execution logic parse.ts # Output parsing test.ts # Environment diagnostics ui/ index.ts # UI exports (built-in only) parse-stdout.ts # Transcript parser (built-in only) build-config.ts # Config builder ui-parser.ts # Self-contained UI parser (external — see [UI Parser Contract](/adapters/adapter-ui-parser)) cli/ index.ts # CLI exports format-event.ts # Terminal formatterStep 1: Root Metadata
Section titled “Step 1: Root Metadata”src/index.ts is imported by all three consumers. Keep it dependency-free.
export const type = "my_agent"; // snake_case, globally uniqueexport const label = "My Agent (local)";export const models = [ { id: "model-a", label: "Model A" },];export const agentConfigurationDoc = `# my_agent configurationUse when: ...Don't use when: ...Core fields: ...`;
// Required for external adapters (plugin-loader convention)export { createServerAdapter } from "./server/index.js";Step 2: Server Execute
Section titled “Step 2: Server Execute”src/server/execute.ts is the core. It receives an AdapterExecutionContext and returns an AdapterExecutionResult.
Key responsibilities:
- Read config using safe helpers (
asString,asNumber, etc.) from@aimetier/adapter-utils/server-utils - Build environment with
buildAImetierEnv(agent)plus context vars - Resolve session state from
runtime.sessionParams - Render prompt with
renderTemplate(template, data) - Spawn the process with
runChildProcess()or call viafetch() - Parse output for usage, costs, session state, errors
- Handle unknown session errors (retry fresh, set
clearSession: true)
Available Helpers
Section titled “Available Helpers”| Helper | Source | Purpose |
|---|---|---|
runChildProcess(cmd, opts) |
@aimetier/adapter-utils/server-utils |
Spawn with timeout, grace, streaming |
buildAImetierEnv(agent) |
@aimetier/adapter-utils/server-utils |
Inject AIMETIER_* env vars |
renderTemplate(tpl, data) |
@aimetier/adapter-utils/server-utils |
{{variable}} substitution |
asString(v) |
@aimetier/adapter-utils |
Safe config value extraction |
asNumber(v) |
@aimetier/adapter-utils |
Safe number extraction |
AdapterExecutionContext
Section titled “AdapterExecutionContext”interface AdapterExecutionContext { runId: string; agent: { id: string; companyId: string; name: string; adapterConfig: unknown }; runtime: { sessionId: string | null; sessionParams: Record<string, unknown> | null }; config: Record<string, unknown>; // agent's adapterConfig context: Record<string, unknown>; // task, wake reason, etc. onLog: (stream: "stdout" | "stderr", chunk: string) => Promise<void>; onMeta?: (meta: AdapterInvocationMeta) => Promise<void>; onSpawn?: (meta: { pid: number; startedAt: string }) => Promise<void>;}AdapterExecutionResult
Section titled “AdapterExecutionResult”interface AdapterExecutionResult { exitCode: number | null; signal: string | null; timedOut: boolean; errorMessage?: string | null; usage?: { inputTokens: number; outputTokens: number }; sessionParams?: Record<string, unknown> | null; // persist across heartbeats sessionDisplayId?: string | null; provider?: string | null; model?: string | null; costUsd?: number | null; clearSession?: boolean; // set true to force fresh session on next wake}Step 3: Environment Test
Section titled “Step 3: Environment Test”src/server/test.ts validates the adapter config before running.
Return structured diagnostics:
| Level | Meaning | Effect |
|---|---|---|
error |
Invalid or unusable setup | Blocks execution |
warn |
Non-blocking issue | Shown with yellow indicator |
info |
Successful check | Shown in test results |
export async function testEnvironment( ctx: AdapterEnvironmentTestContext,): Promise<AdapterEnvironmentTestResult> { return { adapterType: ctx.adapterType, status: "pass", // "pass" | "warn" | "fail" checks: [ { level: "info", message: "CLI v1.2.0 detected", code: "cli_detected" }, { level: "warn", message: "No API key found", hint: "Set ANTHROPIC_API_KEY", code: "no_key" }, ], testedAt: new Date().toISOString(), };}Step 4: UI Module (Built-in Only)
Section titled “Step 4: UI Module (Built-in Only)”For built-in adapters registered in AImetier’s source:
parse-stdout.ts— converts stdout lines toTranscriptEntry[]for the run viewerbuild-config.ts— converts form values toadapterConfigJSON- Config fields React component in
ui/src/adapters/<name>/config-fields.tsx
For external adapters, use a self-contained ui-parser.ts instead. See the UI Parser Contract.
Step 5: CLI Module
Section titled “Step 5: CLI Module”format-event.ts — pretty-prints stdout for aimetier run --watch using picocolors.
export function formatStdoutEvent(line: string, debug: boolean): void { if (line.startsWith("[tool-done]")) { console.log(chalk.green(` ✓ ${line}`)); } else { console.log(` ${line}`); }}Step 6: Register (Built-in Only)
Section titled “Step 6: Register (Built-in Only)”Add the adapter to all three registries:
server/src/adapters/registry.tsui/src/adapters/registry.tscli/src/adapters/registry.ts
For external adapters, registration is automatic — the plugin loader handles it.
Session Persistence
Section titled “Session Persistence”If your agent runtime supports conversation continuity across heartbeats:
- Return
sessionParamsfromexecute()(e.g.,{ sessionId: "abc123" }) - Read
runtime.sessionParamson the next wake to resume - Optionally implement a
sessionCodecfor validation and display
export const sessionCodec: AdapterSessionCodec = { deserialize(raw) { /* validate raw session data */ }, serialize(params) { /* serialize for storage */ }, getDisplayId(params) { /* human-readable session label */ },};Capability Flags
Section titled “Capability Flags”Adapters can declare what “local” capabilities they support by setting optional fields on the ServerAdapterModule. The server and UI use these flags to decide which features to enable for agents using the adapter (instructions bundle editor, skills sync, JWT auth, etc.).
| Flag | Type | Default | What it controls |
|---|---|---|---|
supportsLocalAgentJwt |
boolean |
false |
Whether heartbeat generates a local JWT for the agent |
supportsInstructionsBundle |
boolean |
false |
Managed instructions bundle (AGENTS.md) — server-side resolution + UI editor |
instructionsPathKey |
string |
"instructionsFilePath" |
The adapterConfig key that holds the instructions file path |
requiresMaterializedRuntimeSkills |
boolean |
false |
Whether runtime skill entries must be written to disk before execution |
These flags are exposed via GET /api/adapters in a capabilities object, along with a derived supportsSkills flag (true when listSkills or syncSkills is defined).
Example
Section titled “Example”export function createServerAdapter(): ServerAdapterModule { return { type: "my_k8s_adapter", execute: myExecute, testEnvironment: myTestEnvironment, listSkills: myListSkills, syncSkills: mySyncSkills,
// Capability flags supportsLocalAgentJwt: true, supportsInstructionsBundle: true, instructionsPathKey: "instructionsFilePath", requiresMaterializedRuntimeSkills: true, };}With these flags set, the AImetier UI will automatically show the instructions bundle editor, skills management tab, and working directory field for agents using this adapter — no AImetier source changes required.
If capability flags are not set, the server falls back to legacy hardcoded lists for built-in adapter types. External adapters that omit the flags will default to false for all capabilities.
Skills Injection
Section titled “Skills Injection”Make AImetier skills discoverable to your agent runtime without writing to the agent’s working directory:
- Best: tmpdir + flag — create tmpdir, symlink skills, pass via CLI flag, clean up after
- Acceptable: global config dir — symlink to the runtime’s global plugins directory
- Acceptable: env var — point a skills path env var at the repo’s
skills/directory - Last resort: prompt injection — include skill content in the prompt template
Security
Section titled “Security”- Treat agent output as untrusted (parse defensively, never execute)
- Inject secrets via environment variables, not prompts
- Configure network access controls if the runtime supports them
- Always enforce timeout and grace period
- The UI parser module runs in a browser sandbox — zero runtime imports, no side effects
Next Steps
Section titled “Next Steps”- External Adapters — build a standalone adapter plugin
- UI Parser Contract — ship a custom run-log parser
- How Agents Work — the heartbeat lifecycle