lucidAGENTS
Packages

@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/http

Basic 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 plan

Install 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;
      };
};
OptionDefaultDescription
servicePage{ preset: 'dossier' }Select/configure the public storefront, or use false for API-only.
landingPagetrueDeprecated 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.
idempotencybounded in-memory storeConfigure 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:

MethodRouteCapability
GET/Landing page, unless disabled.
GET/healthHealth status.
GET/entrypointsEntrypoint discovery.
POST/entrypoints/:key/invokeInvoke.
POST/entrypoints/:key/streamSSE stream.
GET/.well-known/agent-card.jsonCanonical Agent Card.
GET/.well-known/agent.jsonLegacy card alias.
GET/.well-known/oasf-record.jsonOASF record or 404.
GET/favicon.svgSVG favicon.

Task routes are added only when a2a() is installed:

MethodRouteCapability
POST/tasksCreate and start a task.
GET/tasksList tasks owned by the access token.
GET/tasks/:taskIdRead an owned task.
POST/tasks/:taskId/cancelCancel an owned task.
GET/tasks/:taskId/subscribeSubscribe 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:

  1. resolves exactly one payment rail for the canonical entrypoint;
  2. verifies SIWX, x402, or MPP credentials in the owning extension;
  3. wins the target-side idempotency claim for an invoke;
  4. admits the request by evaluating and reserving incoming policy capacity;
  5. executes the invoke or admits asynchronous stream/task work;
  6. 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:

  • text for a complete text segment;
  • delta for incremental text;
  • asset for inline or external assets;
  • control for structured control messages;
  • error for 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.

On this page