How to Create a Custom Agent Adapter in Paperclip AI
To create a custom agent adapter in Paperclip AI, implement the Adapter interface defined in packages/adapter-utils/src/types.ts, export it from a new package under packages/adapters/, and register it in packages/adapter-utils/src/registry.ts.
Paperclip AI treats every LLM and external tool as an adapter that conforms to a strict TypeScript contract. Creating a custom agent adapter allows you to integrate proprietary models or third-party APIs into the Paperclip execution runtime.
What Is an Agent Adapter?
In the paperclipai/paperclip repository, an agent adapter is a thin wrapper that standardizes communication between the orchestration layer and a specific language model or external service. All adapters share a common contract defined in packages/adapter-utils/src/types.ts, ensuring the runtime can dispatch requests without knowing implementation details.
Prerequisites and Project Structure
The repository uses a monorepo workspace structure. Each adapter lives as an independent package under packages/adapters/, such as the reference claude-local adapter.
Before starting, ensure you have:
- Node.js and pnpm installed
- Familiarity with the core types in
packages/adapter-utils/src/types.ts
Step-by-Step Implementation
Step 1: Scaffold the Adapter Package
Create a new directory under packages/adapters/ for your adapter (e.g., my-custom-adapter). Initialize it with a standard package.json that declares the entry point and workspace compatibility:
{
"name": "@paperclipai/my-custom-adapter",
"version": "0.1.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -p ."
}
}
Create src/index.ts to re-export your implementation:
export * from "./my-custom-adapter";
Step 2: Implement the Adapter Interface
The core contract requires an object that implements the Adapter type. In packages/adapter-utils/src/types.ts, this interface mandates specific methods and metadata properties.
Create src/my-custom-adapter.ts with the following structure:
import type {
Adapter,
AdapterConfig,
GenerateRequest,
GenerateResponse,
RunContext,
Env,
} from "@paperclipai/adapter-utils";
export const myCustomAdapter: Adapter = {
name: "my_custom_adapter",
displayName: "My Custom Adapter",
meta: { description: "Connects Paperclip to My-API" },
async init(config: AdapterConfig, env: Env) {
if (!env.MY_API_KEY) {
throw new Error("Missing MY_API_KEY");
}
// Adapter-specific initialization logic
},
async generate(request: GenerateRequest, ctx: RunContext) {
const response = await fetch("https://api.myservice.com/generate", {
method: "POST",
headers: {
Authorization: `Bearer ${ctx.env.MY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt: request.prompt }),
});
const data = await response.json();
return { text: data.output } as GenerateResponse;
},
async shutdown() {
// Optional cleanup logic
},
};
Key requirements:
- name: Unique snake_case identifier used by the registry
- displayName: Human-readable label shown in the UI
- init: Validates configuration and secrets before execution
- generate: The primary method that receives prompts and returns responses
- shutdown: Optional cleanup method called after run completion
Step 3: Register the Adapter
The runtime loads adapters through the adapter-registry located at packages/adapter-utils/src/registry.ts. Import your adapter and add it to the defaultAdapters map:
import { myCustomAdapter } from "@paperclipai/my-custom-adapter";
export const defaultAdapters = {
// ...existing adapters
my_custom_adapter: myCustomAdapter,
};
This registration makes the adapter available to the UI and runtime service layers.
Step 4: Configure Secrets and Authentication
If your adapter requires API keys, declare them in the adapter-auth schema at packages/adapter-utils/src/auth.ts. Follow the pattern used by existing adapters like Codex-Local, which pulls secrets from the Paperclip secret store using environment variable bindings.
Reference your secrets in the init method via the env parameter to validate presence before runtime execution.
Step 5: Write Unit Tests
Place test files adjacent to your implementation using the pattern src/**/*.test.ts. Use the built-in test helpers from packages/adapter-utils/testing and Vitest for assertions:
import { myCustomAdapter } from "./my-custom-adapter";
import { expect, test } from "vitest";
test("generate returns a response", async () => {
const fakeCtx = { env: { MY_API_KEY: "test-key" } } as any;
const resp = await myCustomAdapter.generate({ prompt: "Hello" }, fakeCtx);
expect(resp).toHaveProperty("text");
});
Update the root vitest.config.ts to include your adapter's test files:
export default defineConfig({
test: {
include: ["packages/adapters/**/src/**/*.test.ts"],
},
});
Step 6: Build and Verify
Run the monorepo build commands from the repository root:
pnpm -r typecheck
pnpm -r build
pnpm test
Verify integration by checking that your adapter appears under Agent → Adapter Selection in the UI. The interface queries the /api/agents/:company/adapter-models/:type endpoint, which populates from the registry you updated.
Summary
- Adapter Location: Create new adapters under
packages/adapters/with their ownpackage.jsonandsrc/index.tsentry point. - Core Interface: Implement the
Adaptertype frompackages/adapter-utils/src/types.ts, providingname,displayName,init, andgeneratemethods. - Registration: Import and export your adapter in
packages/adapter-utils/src/registry.tsto make it available to the runtime. - Secrets: Validate required environment variables in the
initmethod and define them inpackages/adapter-utils/src/auth.ts. - Testing: Use Vitest with helpers from
packages/adapter-utils/testingand place tests insrc/**/*.test.tsfiles.
Frequently Asked Questions
What is the minimum code required to create a functional custom adapter?
At minimum, you must export an object conforming to the Adapter interface with a name property, displayName, and a generate method that accepts GenerateRequest and RunContext and returns a Promise<GenerateResponse>. The init method is required by the type system but can be a no-op if your adapter needs no setup.
How does Paperclip AI handle authentication for custom adapters?
Authentication is handled through the Env object passed to the init method. Store sensitive values like API keys in the Paperclip secret store, reference them in packages/adapter-utils/src/auth.ts, and access them via ctx.env or the env parameter in init. The runtime injects these values securely at execution time.
Can I use external npm packages in my custom adapter?
Yes. Since each adapter is a standalone package in the monorepo, you can declare external dependencies in its package.json. Install them using pnpm from the repository root, and they will be available to your adapter code after running pnpm -r build.
Where does the UI fetch the list of available adapters from?
The React UI pulls the adapter list from the /api/agents/:company/adapter-models/:type endpoint, which aggregates entries from the defaultAdapters map exported in packages/adapter-utils/src/registry.ts. Ensure your adapter is registered there and the server is rebuilt for it to appear in the dropdown.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →