Expose authenticated local AI Agents through familiar LLM API interfaces.
Agent2API is an application host for local Agent APIs. It mounts llm-mimic-surface on its HTTP server, adapts the protocol-neutral request to the Agent2API core, resolves an Agent/model target, renders the complete message history as a deterministic prompt, and delegates execution to @headless-core/core.
OpenAI SDK
│
▼
Agent2API
│
▼
headless_core
│
▼
Authenticated local Agent CLI
Agent2API never logs in to an Agent, reads credentials, refreshes tokens, or implements a provider API client. The selected Codex, Claude Code, Grok, or Agy CLI must already be installed and authenticated by the user.
OpenAI / Anthropic / Gemini / Custom API
│
▼
Agent2API HTTP Host
│
▼
LLMMimicSurface Plugin
│
▼
Agent2API Backend Adapter
│
▼
Canonical Agent IR + Runner
│
▼
Pluggable Agent Backend
│
▼
HeadlessCore Adapter
│
▼
Authenticated Agent CLI
The dependency direction is intentional:
server ─────→ llm-mimic-surface
│
└────→ Agent2API Backend Adapter ─────→ core ←───── backends/*
corehas no OpenAI, Fastify, or headless_core dependency.llm-mimic-surfacehas no headless_core dependency.backends/headless-corehas no OpenAI dependency.- Chat Completions and Responses use the same Canonical request and backend path.
Agent2API owns the Fastify instance, authentication, logging, configuration, listen address, and lifecycle. LLMMimicSurface registers the external protocol routes and owns their HTTP/SSE wire formats.
- Node.js 20 or newer
- npm
- At least one supported Agent CLI installed and already authenticated
Agent2API does not require an Agent provider API key. A dummy key is sufficient for OpenAI SDK clients unless optional HTTP access authentication is enabled.
The package is publish-ready but is not yet published to the npm registry. Run it from a source checkout:
npm install
npm run build
node dist/cli/main.js serve --config ./examples/agent2api.config.jsonAfter the package is published, global installation will be:
npm install -g agent2api
agent2api serve --config ./agent2api.config.jsonThe default bind address is 127.0.0.1:8080. A minimal config is:
{
"backends": {
"headless-core": {
"cwd": ".",
"timeoutMs": 120000
}
}
}cwd, timeout, model routing, and HTTP access settings are server configuration only. API request bodies cannot select a working directory, environment, binary path, CLI argument, or shell command.
Implemented endpoints:
GET /v1/modelsPOST /v1/chat/completionsPOST /v1/responsesGET /health
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "dummy",
baseURL: "http://127.0.0.1:8080/v1"
});
const completion = await client.chat.completions.create({
model: "codex/default",
messages: [
{ role: "system", content: "You are helpful." },
{ role: "user", content: "Hello" }
]
});
console.log(completion.choices[0]?.message.content);Responses API:
const response = await client.responses.create({
model: "codex/default",
input: "Explain this repository in one sentence."
});Both endpoints support the request field stream: true over SSE (streaming: true is not the
OpenAI-compatible field). Because headless_core progress can contain process state, stdout, or
stderr, it is never exposed as assistant text. The current implementation is completion-buffered
streaming: it opens the SSE connection immediately, sends heartbeat comments while the Agent runs,
then emits the final returned output as one text delta, a finish event, and [DONE]. It does not
reduce time-to-first-text compared with a non-streaming request. Client disconnects propagate
through AbortSignal to headless_core.
For streaming requests, HTTP 200 OK means that the SSE connection was established, not that Agent
execution succeeded. A model-resolution or execution failure discovered afterward is sent as an SSE
error event followed by [DONE]. True incremental text streaming can be added when a backend exposes
a semantically safe assistant-output event such as output.delta; raw progress/stdout/stderr will
not be treated as assistant output.
Canonical model IDs use <agent>/<model>:
codex/defaultclaude/default,claude/sonnet,claude/opus,claude/haikugrok/defaultagy/default
An optional final reasoning-effort suffix is also supported:
<agent>/<model>/<effort>
codex/gpt-5.6-luna/low
The recognized suffix vocabulary comes from headless_core: default, low, medium, high,
xhigh, and max. Individual Agents do not necessarily support every value; actual acceptance is
delegated to the selected backend/CLI. A suffix of default, or omitting the suffix entirely, leaves
reasoning effort unspecified. If the request body also contains reasoning.effort, the explicit body
value takes precedence. Recognized effort words in the final path segment are reserved and are
removed from the model name before execution.
Agy exposes Gemini and other models through model slugs that may already contain an effort tier. Use the whole slug as the model portion:
agy/gemini-3.7-flash-low # correct: Agy model slug
agy/gemini-3.7-flash/low # rejected: generic effort suffix is not supported for Agy
gemini/gemini-3.7-flash # rejected: `gemini` is not a HeadlessCore Agent provider
This is Gemini-model execution through the authenticated Agy CLI. It does not add a Gemini external protocol adapter or a direct Gemini API connection.
model: "default" remains unchanged when sent to headless_core, so the CLI's own default model is used without a provider model flag.
Models are resolved by ModelRegistry, not by the OpenAI adapter. Explicit models.entries are
optional: an unregistered canonical ID such as codex/gpt-5.6-luna is dynamically routed to the
HeadlessCore backend, and headless_core/the authenticated CLI determines whether it is actually
available. The /v1/models response remains a discoverable catalog of configured/default entries;
it is not an exhaustive list of every model a local CLI may accept.
Aliases are also optional and may point directly to a dynamic canonical ID:
{
"models": {
"aliases": {
"default": "codex/default",
"smart": "claude/opus",
"luna": "codex/gpt-5.6-luna",
"luna-low": "codex/gpt-5.6-luna/low"
}
}
}The OpenAI request field is decoded into Canonical IR first:
{
"model": "codex/default",
"reasoning": { "effort": "high" }
}The HeadlessCore backend later forwards the canonical effort to headless.run(). Capability
validation rejects reasoning on a model/Agent that does not support it. In the current HeadlessCore
adapter, Agy does not accept the generic reasoning.effort field; choose an Agy model slug such as
gemini-3.7-flash-low instead.
This protects Client → Agent2API. It is unrelated to Agent CLI authentication.
{
"server": {
"host": "127.0.0.1",
"port": 8080,
"auth": { "bearerToken": "local-secret" }
}
}Clients then send Authorization: Bearer local-secret. /health remains unauthenticated. Do not bind to a non-loopback interface without appropriate host/network controls and an HTTP bearer token.
Requests are logged as structured records containing request ID, protocol, resolved model, backend, Agent, duration, and status. Prompt text, Agent output, authentication information, and environment variables are not logged by default. Ignored compatibility parameters are logged by parameter name only, never by value.
Agent2API explicitly returns unsupported_feature instead of silently dropping unsupported input:
- OpenAI external tools,
tool_calls, and parallel tool calls - image, audio, and file input
- embeddings, image generation, and audio generation
previous_response_idand persistent conversations- remote MCP
An Agent CLI may use its own internal tools. That does not expose OpenAI tool calling to an API client.
Unknown compatibility parameters that do not select one of these explicit features—such as
temperature, top_p, or a client-specific extension—are accepted by the external protocol
surface where compatible and are not forwarded as execution controls to headless_core. Explicit
tools, unsupported media, files, and persistent-conversation features remain rejected.
headless_core:
- runs local AI Agent CLIs
- owns provider-specific CLI arguments
- owns timeout and abort process handling
- maps reasoning effort
- handles model candidates
- classifies execution failures
llm-mimic-surface:
- registers OpenAI-compatible routes on the Agent2API Fastify host
- parses external requests into its protocol-neutral boundary contract
- serializes HTTP responses, API errors, and SSE events
- propagates client disconnects through
AbortSignal
Agent2API:
- owns the HTTP server, authentication, logging, configuration, and lifecycle
- registers LLMMimicSurface on that server
- defines Canonical Agent IR and events
- resolves configured models and aliases
- converts full message history into a prompt
- converts responses and streaming events
- normalizes API-level errors
Agent2API does not implement authentication/OAuth for Agent CLIs, credential storage or rotation, direct AI Provider API calls, billing, a database, persistent conversations, a web UI, Agent logic, or CLI process spawning.
npm run typecheck
npm test
npm run build
npm pack --dry-runThe normal test suite uses FakeAgentBackend; Codex, Claude Code, Grok, and Agy are not required in CI. Real-Agent E2E tests, if added, should be opt-in with AGENT2API_E2E=1.
MIT