@lucid-agents/http
Fetch-native handlers, canonical routes, authorization, and SSE streaming.
The HTTP extension turns a completed agent into a transport-neutral HTTP runtime. It owns the canonical route plan, Fetch-native handlers, entrypoint authorization transaction, target-side idempotency, request validation, and SSE response format used by every framework adapter.
Installation
bun add @lucid-agents/core @lucid-agents/httpBasic usage
import { createAgent } from '@lucid-agents/core';
import { http } from '@lucid-agents/http';
const agent = await createAgent({
name: 'my-agent',
version: '1.0.0',
})
.use(http({ basePath: '/api/agent' }))
.addEntrypoint({
key: 'echo',
async handler({ input }) {
return { output: input };
},
})
.build();
agent.http.basePath; // '/api/agent'
agent.http.handlers; // Fetch-native Request -> Response handlers
agent.http.routes; // canonical route and capability planInstall domain extensions such as payments(), mpp(), identity(), and a2a() before HTTP when convenient. The extension kernel also honors HTTP's declared ordering constraints when call-site order differs.
Configuration
type HttpExtensionOptions = {
servicePage?: ServiceUiConfig | false;
/** @deprecated Use servicePage: false. */
landingPage?: boolean;
basePath?: string;
idempotency?:
| false
| {
store?: HttpIdempotencyStore;
inProgressTtlMs?: number;
retentionMs?: number;
maxEntries?: number;
};
};| Option | Default | Description |
|---|---|---|
servicePage | { preset: 'dossier' } | Select/configure the public storefront, or use false for API-only. |
landingPage | true | Deprecated compatibility switch for the landing route. |
basePath | '' | Normalize and prefix every canonical route. The prefix is also advertised in the Agent Card's HTTP interface. |
idempotency | bounded in-memory store | Configure target-side invoke deduplication, inject a durable store, or explicitly disable it. |
const agent = await createAgent(meta)
.use(
http({
basePath: '/api/agent',
servicePage: false,
idempotency: {
store: durableIdempotencyStore,
inProgressTtlMs: 15 * 60_000,
retentionMs: 24 * 60 * 60_000,
},
})
)
.build();The default idempotency store is bounded and process-local. Multi-instance deployments should inject an atomic durable HttpIdempotencyStore.
Public storefront
Hono and Express render a minimal endpoint directory as static, read-only HTML. Each invoke and stream operation appears with its HTTP path, payment method, network, and price. The page contains no schemas, raw Agent Card JSON, client JavaScript, or browser invocation controls.
UI-capable CLI projects generate a root service-ui.config.ts:
import { defineServiceUi } from '@lucid-agents/http/service-ui';
export default defineServiceUi({
preset: 'console', // dossier | folio | console
tokens: {
colors: { accent: '#DFFF45' },
fonts: { body: ['Instrument Sans', 'Aptos', 'sans-serif'] },
},
});Only bounded semantic color/font tokens are supported. Runtime validation rejects unknown keys, non-hex colors, unsafe font values/URLs, and primary contrast failures. Next and TanStack UI consume the same preset resolver and public model and render the same read-only table. The built-in presets use Lucid's Ink/Paper/Citron foundation and a compact footer attribution while keeping the agent's name and offering primary.
Fetch-native handlers
Handlers accept a standard Web Request and return a Promise<Response>:
const response = await agent.http.handlers.invoke(
new Request('https://agent.example/api/agent/entrypoints/echo/invoke', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input: { value: 1 } }),
}),
{ key: 'echo' }
);AgentHttpHandlers contains health, entrypoints, manifest, oasf, favicon, optional landing, invoke, stream, tasks, listTasks, getTask, cancelTask, and subscribeTask. Framework adapters bind these handlers; they do not call entrypoint implementations directly.
The manifest handler builds by request origin. Compatibility routes should delegate to it instead of caching a manifest independently.
Canonical route plan
agent.http.routes is the only route definition consumed by Hono and Express and is exposed to TanStack and generated adapters. Each item contains a stable ID, method, path, parameter names, and handle(request, params).
With an empty base path, the plan includes:
| Method | Route | Capability |
|---|---|---|
GET | / | Landing page, unless disabled. |
GET | /health | Health status. |
GET | /entrypoints | Entrypoint discovery. |
POST | /entrypoints/:key/invoke | Invoke. |
POST | /entrypoints/:key/stream | SSE stream. |
GET | /.well-known/agent-card.json | Canonical Agent Card. |
GET | /.well-known/agent.json | Legacy card alias. |
GET | /.well-known/oasf-record.json | OASF record or 404. |
GET | /favicon.svg | SVG favicon. |
Task routes are added only when a2a() is installed:
| Method | Route | Capability |
|---|---|---|
POST | /tasks | Create and start a task. |
GET | /tasks | List tasks owned by the access token. |
GET | /tasks/:taskId | Read an owned task. |
POST | /tasks/:taskId/cancel | Cancel an owned task. |
GET | /tasks/:taskId/subscribe | Subscribe to owned task updates. |
basePath prefixes every route in the plan. Task ownership and the Task-Access-Token contract are documented in the A2A package.
Authorization transaction
Invoke, stream, and task creation all call one authorization gate. It:
- resolves exactly one payment rail for the canonical entrypoint;
- verifies SIWX, x402, or MPP credentials in the owning extension;
- wins the target-side idempotency claim for an invoke;
- admits the request by evaluating and reserving incoming policy capacity;
- executes the invoke or admits asynchronous stream/task work;
- finalizes settlement and accounting, or releases a pre-admission reservation on failure.
If x402 and MPP are both installed, every priced entrypoint must set paymentProtocol: 'x402' | 'mpp'. An auth-only entrypoint requires an enabled SIWX payments runtime. These invariants are checked whenever an entrypoint is registered, including after build.
authorizeEntrypointRequest is available for custom transports. After successful verification, call authorization.admit(). Only admitted work may execute. Call the admission's finalize(response) exactly once, or abort() if the transport abandons the operation before it has a response.
Invoke requests and idempotency
POST /entrypoints/echo/invoke
Content-Type: application/json
Idempotency-Key: echo:customer-42:request-2026-07
{"input":{"value":1}}A successful response has the canonical shape:
{
"run_id": "7f3fbb8e-0dd7-4a92-a014-67fd40ba1574",
"status": "succeeded",
"output": { "value": 1 },
"usage": { "total_tokens": 10 },
"model": "example-model"
}An Idempotency-Key must contain 20–256 characters. It is scoped to the entrypoint and bound to the body, ambient authorization context, and freshly verified payment/SIWX subject. The same completed request is replayed with Idempotency-Replayed: true; concurrent duplicates and conflicting reuse return 409.
Verification happens before the claim, while payment-policy reservation and settlement happen only after a new claim is won. If response persistence fails after execution or irreversible settlement, the runtime retains the claim and returns 503 so a retry cannot execute or charge twice.
SSE streaming
A streaming entrypoint receives its validated context and an emit function:
.addEntrypoint({
key: 'tokens',
stream: async ({ input }, emit) => {
await emit({
kind: 'text',
text: String(input),
mime: 'text/plain',
});
return {
output: { done: true },
status: 'succeeded',
};
},
})The runtime writes a run-start envelope, assigns runId, sequence, and timestamp fields to emitted envelopes, and finishes with run-end. A handler failure emits both an error envelope and a failed run-end.
User-emittable envelopes are:
textfor a complete text segment;deltafor incremental text;assetfor inline or external assets;controlfor structured control messages;errorfor a domain error.
Low-level transports can use createSSEStream and writeSSE:
const response = createSSEStream(({ write, close }) => {
write({
event: 'delta',
data: JSON.stringify({ kind: 'delta', delta: 'Hello' }),
});
close();
});Exports
export {
http,
createAgentRoutePlan,
invoke,
invokeHandler,
stream,
authorizeEntrypointRequest,
createInMemoryHttpIdempotencyStore,
HttpIdempotencyCapacityError,
createSSEStream,
writeSSE,
} from '@lucid-agents/http';Transport contracts such as HttpExtensionOptions, AgentHttpRuntime, AgentHttpRoute, AgentHttpHandlers, stream envelopes, and idempotency-store types are defined in @lucid-agents/types/http.