Minimal primitive for configuring and calling AI inference endpoints. 4 API formats. 200 LOC. Zero deps.
npm i @acoyfellow/ai-connectA shared layer for products that let users BYO inference. Handles:
- AES-GCM encryption of API keys for DB at-rest
- Dispatch to 4 request/response shapes (Anthropic, OpenAI Chat, OpenAI Responses, Gemini)
- Pre-flight
testConnectionthat actually hits the endpoint
- Not a storage layer (you bring D1/Postgres/whatever)
- Not a UI kit (you bring your own form)
- Not a model catalog (you list your own)
import type { AIConnection } from "@acoyfellow/ai-connect";
const conn: AIConnection = {
id: "abc",
userId: "user-1",
displayName: "Opus 4.6",
provider: "anthropic",
endpoint: "https://api.anthropic.com/v1/messages",
model: "claude-opus-4-6",
apiKeyEncrypted: "<encrypted>",
maxContextTokens: 200_000,
tags: [],
isDefault: true,
createdAt: Date.now(),
updatedAt: Date.now(),
};import { encryptKey } from "@acoyfellow/ai-connect";
const blob = await encryptKey("sk-ant-api03-xxx", env.BETTER_AUTH_SECRET);
// Store blob in your DB as apiKeyEncryptedimport { callModel } from "@acoyfellow/ai-connect";
const result = await callModel({
connection: conn,
secret: env.BETTER_AUTH_SECRET,
messages: [
{ role: "system", content: "Be terse." },
{ role: "user", content: "What's 2+2?" },
],
options: { maxTokens: 64, temperature: 0.3 },
});
// result.content, result.usage, result.rawimport { testConnection } from "@acoyfellow/ai-connect";
const test = await testConnection({
connection: conn,
secret: env.BETTER_AUTH_SECRET,
});
// { ok: true, preview: "4", durationMs: 423 }
// or { ok: false, error: "Invalid API key", durationMs: 120 }| provider | endpoint default | auth header |
|---|---|---|
anthropic |
https://api.anthropic.com/v1/messages |
x-api-key |
openai-chat |
https://api.openai.com/v1/chat/completions |
Authorization: Bearer |
openai-responses |
https://api.openai.com/v1/responses |
Authorization: Bearer |
gemini |
https://generativelanguage.googleapis.com/v1beta/models |
x-goog-api-key |
All endpoints are overridable via connection.endpoint. Use Cloudflare AI Gateway URLs, custom proxies, etc.
- AES-GCM, 256-bit key derived from your secret via PBKDF2 (100k iterations, SHA-256)
- Static salt
acoyfellow/ai-connect/v1— rotating it would break all existing ciphertexts - Random 12-byte IV per encryption, prepended to ciphertext
- Output is base64 of
iv || ciphertext - Wrong secret throws during decrypt
Web Crypto (crypto.subtle) + fetch. Works on:
- Cloudflare Workers
- Node 18+
- Bun
- Deno
- Modern browsers (for client-side scenarios, though keys should ideally decrypt server-side)
- filepath — primary consumer
- cloudshell — parallel consumer
MIT