npm package: @lucid-dreams/agent-auth
Description: Agent authentication runtime primitives
Latest version: 0.2.24

# Agent Runtime Module Scaffold

## Context & Goals
- Build the `@lucid-dreams/agent-auth` runtime around the challenge → exchange → refresh loop while hiding protocol details from consumers.
- Lean on the generated HTTP layer exported from `packages/sdk/src/index.ts` so every network call stays in sync with the server OpenAPI contract.
- Deliver a structure that downstream teams can implement module-by-module without having to re-derive responsibilities.

## Runtime Package Layout
```
src/
  runtime/
    agent-runtime.ts
    agent-auth-client.ts
    agent-api-client.ts
    token-manager.ts
    storage/
      memory-adapter.ts
      file-adapter.ts
      types.ts
    wallet/
      base-connector.ts
      coinbase-connector.ts
      privy-connector.ts
      local-key-connector.ts
    transport/
      http-transport.ts
      undici-transport.ts
      fetch-transport.ts
    events.ts
    errors.ts
    logger.ts
    config-loader.ts
```
- The runtime owns orchestration and abstractions; all HTTP contracts flow through the generated SDK under `@lucid-dreams/sdk`.
- CLI and agent-kit consumers import only the public surface inside `src/runtime`.

## Generated SDK Integration
- `packages/sdk/src/index.ts` re-exports `sdk.gen` (request builders) and `types.gen` (request/response types), so the runtime always imports from `@lucid-dreams/sdk` root.
- The runtime currently relies on the following request builders:
  - **Auth**: `postV1AuthAgentsByAgentRefChallenge`, `postV1AuthAgentsByAgentRefExchange`, `postV1AuthAgentsByAgentRefRefresh`.
  - **Agents**: `getV1Agents`, `getV1AgentsByAgentRef`.
  - **Wallet**: `postV1AgentsByAgentRefWalletSignMessage`, `postV1AgentsByAgentRefWalletSignTypedData`, `postV1AgentsByAgentRefWalletSignTransaction`, `postV1AgentsByAgentRefWalletSendTransaction`.
- See `src/runtime/__tests__/sdk-contract.test.ts` for a regression test that asserts these helpers remain available and keep their expected request shapes.
- To regenerate the SDK after OpenAPI changes:
  1. `pnpm --filter @lucid-dreams/sdk run openapi-ts`
  2. `pnpm --filter @lucid-dreams/sdk run build`
  3. Re-run `bun test` (at minimum `packages/agent-auth`) to ensure the runtime still compiles.

## Module Specifications

### AgentRuntime (`src/runtime/agent-runtime.ts`)
- **Purpose**: High-level façade that bootstraps config, authenticates on demand, and keeps tokens fresh.
- **Public Surface**:
  - `static fromConfig(options?: FromConfigOptions): Promise<AgentRuntime>`
  - `static load(options?: LoadOptions): Promise<AgentRuntimeLoadResult>`
  - `authenticate(): Promise<AuthenticatedSession>`
  - `ensureAccessToken(): Promise<string>`
  - `on(event: AgentAuthEvent, handler: EventHandler): () => void`
  - `shutdown(): Promise<void>` (optional lifecycle hook to stop background refresh).
- **Collaborators**:
  - `ConfigLoader` for config hydration.
  - `AgentAuthClient` for challenge/exchange/refresh.
  - `AgentApiClient` for authenticated API calls.
  - `TokenManager` for caching and refresh scheduling.
  - `Logger` + `events.ts` for observability.
- **Generated SDK Touchpoints**: Delegates to `AgentAuthClient`, which is the only module talking to `@lucid-dreams/sdk/auth`.
- **Implementation Notes**:
  - Start background refresh on first successful `authenticate`.
  - Emit `Authenticated`, `TokenRefreshed`, `RefreshFailed`, `CredentialRevoked`, `ReauthenticationRequired` events.
  - Provide escape hatches to override storage, transport, wallet connector, and logger via constructor options.
- **Status**: Implemented `AgentRuntime.fromConfig` with configurable transport/auth/token wiring, background refresh, and event hooks, plus `AgentRuntime.load` convenience wrapper that composes the config loader (`src/runtime/agent-runtime.ts`, `src/runtime/__tests__/agent-runtime.test.ts`).

### AgentAuthClient (`src/runtime/agent-auth-client.ts`)
- **Purpose**: Typed wrapper over `/challenge`, `/exchange`, `/refresh` endpoints.
- **Public Surface**:
  - `requestChallenge(payload: ChallengeRequest): Promise<ChallengeResponse>`
  - `exchange(payload: ExchangeRequest): Promise<ExchangeResponse>`
  - `refresh(payload: RefreshRequest): Promise<RefreshResponse>`
- **Collaborators**:
  - Accepts an `HttpTransport` instance for network execution.
  - Uses `WalletConnector` to sign challenge payloads (if runtime delegates signing here).
  - Returns typed data using interfaces from `@lucid-dreams/sdk/types`.
- **Generated SDK Touchpoints**:
  - Calls `auth.challenge`, `auth.exchange`, `auth.refresh` exported from `sdk.gen`.
  - Re-exports relevant TypeScript types (`ChallengePayload`, etc.) so consumers do not import the generated files directly.
- **Implementation Notes**:
  - Inject `baseUrl`, `clientId`, `agentId`, and telemetry metadata through constructor options.
  - Translate HTTP errors into discriminated union errors defined in `errors.ts`.

### AgentApiClient (`src/runtime/agent-api-client.ts`)
- **Purpose**: Authenticated convenience layer over protected agent APIs.
- **Public Surface** (held behind typed namespaces):
  - `listAgents(params?): Promise<AgentsListResponse>`
  - `sendTransaction(payload): Promise<SendTransactionResponse>`
  - Additional generated calls as coverage grows.
- **Collaborators**:
  - Consumes the generated SDK groups (`agents.*`, `transactions.*`, etc.).
  - Accepts a callback `getAccessToken(): Promise<string>` to attach bearer tokens lazily.
  - Uses shared `HttpTransport` for retries/telemetry.
- **Generated SDK Touchpoints**:
  - Import relevant groups from `@lucid-dreams/sdk` rather than reimplementing fetch.
  - Provide thin wrappers that enrich errors/events before re-throwing.
- **Implementation Notes**:
  - Surfacing coverage: optionally track which generated endpoints remain unwrapped.
  - Expose only stable APIs; experimental ones live under `experimental/` namespace.
- **Status**: Agent API client scaffolded with `listAgents`, `getAgent`, `signMessage`, and `sendTransaction`, leveraging the shared transport + access-token callback with unit coverage (`src/runtime/agent-api-client.ts`, `src/runtime/__tests__/agent-api-client.test.ts`).

### TokenManager (`src/runtime/token-manager.ts`)
- **Purpose**: Persist access + refresh tokens, schedule refresh, dedupe concurrent refresh requests.
- **Public Surface**:
  - `getCachedAccessToken(): CachedToken | null`
  - `setTokens(tokens: TokenBundle): void`
  - `scheduleRefresh(trigger: () => Promise<TokenBundle>): CancelHandle`
  - `withRefreshLock<T>(fn: () => Promise<T>): Promise<T>` (single-flight helper).
- **Collaborators**:
  - `StorageAdapter` for persistence.
  - `Logger` + `events` for refresh telemetry.
- **Implementation Notes**:
  - Default refresh window: 30s prior to `exp`.
  - Detect nonce/jti changes to guard against replayed refresh handles.
  - Provide serialization format for disk persistence and document it.

### Storage Adapters (`src/runtime/storage/*`)
- **Purpose**: Abstract persistence for refresh handles and metadata.
- **Interfaces**:
  - `StorageAdapter#get(key: string): Promise<StoredValue | null>`
  - `StorageAdapter#set(key: string, value: StoredValue): Promise<void>`
  - `StorageAdapter#clear(key: string): Promise<void>`
- **Default Implementations**:
  - `MemoryStorageAdapter` — in-process map.
  - `FileStorageAdapter` — JSON file with advisory locking to survive restarts.
- **Extensibility**: Document how to supply custom adapters (Redis, AWS Secrets Manager) via `AgentRuntime.fromConfig` options.

### Wallet Connectors (`src/runtime/wallet/*`)
- **Purpose**: Provide signing capabilities for different execution environments.
- **Interface (`base-connector.ts`)**:
  - Extends the runtime `ChallengeSigner` interface with metadata helpers (`getWalletMetadata()`, `getAddress()`, `supportsCaip2()`).
  - Offers shared utilities for challenge normalization, stable JSON stringification, payload encoding detection, and signature extraction.
- **Concrete Connectors**:
  - `ServerOrchestratorWalletConnector` — delegates signing to the hosted server orchestrator via the agent wallet endpoints.
  - `LocalEoaWalletConnector` — wraps local EOA signers (Node or browser) and supports message + typed data flows.
  - `CoinbaseManagedWalletConnector` (stub) — placeholder for Coinbase managed wallet orchestration.
  - `PrivyEip191Connector` (stub) — placeholder for Privy managed wallet signing.
- **Generated SDK Touchpoints**: Connectors interact with `@lucid-dreams/sdk` wallet endpoints when delegating signature requests.
- **Implementation Notes**: Guardrails for CAIP-2 chain matching, payload normalization, and metadata propagation are covered by dedicated unit tests.

### HttpTransport (`src/runtime/transport/*`)
- **Purpose**: Normalise HTTP execution across runtimes.
- **Interface**:
  - `request<T>(options: HttpRequestOptions): Promise<HttpResponse<T>>`
- **Default Implementations**:
  - `undici-transport.ts` for Node/Bun.
  - `fetch-transport.ts` for browser/edge environments.
- **Status**: Fetch-based transport implemented with timeout handling, JSON helpers, and a fetch-compatible façade (`asFetch()`); Undici factory reuses the fetch transport while allowing optional dispatcher injection.
- **Responsibilities**:
  - Handle retries, backoff, timeout, and instrumentation hooks.
  - Provide hook for injecting headers (user agent, telemetry metadata).
- **Generated SDK Touchpoints**: The generated SDK accepts a `fetch` implementation; we should pass our wrapped transport to ensure consistent behaviour.

### Logger & Events (`src/runtime/logger.ts`, `src/runtime/events.ts`)
- **Logger**: Accept Pino-compatible logger; default to lightweight structured logger with redacted secrets.
- **Events**:
  - Define `AgentAuthEvent` enum and payload contracts.
  - Provide subscription helpers used by `AgentRuntime`.
  - Emit events from `AgentAuthClient`, `TokenManager`, and `AgentApiClient` as relevant.

### Config Loader (`src/runtime/config-loader.ts`)
- **Purpose**: Merge configuration sources for the runtime.
- **Responsibilities**:
  - Load `.lucid-agent.json` generated by CLI.
  - Overlay environment variables (`LUCID_AGENT_*`).
  - Accept runtime overrides (custom transport, storage adapter, wallet connector, logger).
  - Validate schema using zod/types exported from `@lucid-dreams/sdk` where available.
- **Status**: Loader implemented with file/env/override precedence, sensible defaults, unit coverage, and now exposed through `AgentRuntime.load` for a one-call bootstrap that returns the resolved config and source metadata (`src/runtime/config-loader.ts`, `src/runtime/agent-runtime.ts`, `src/runtime/__tests__/config-loader.test.ts`, `src/runtime/__tests__/agent-runtime.test.ts`).

### Errors (`src/runtime/errors.ts`)
- **Purpose**: Centralised discriminated union for runtime failures.
- **Shape**:
  - `AgentRuntimeError` union (`CredentialRevoked`, `RefreshFailed`, `ChallengeFailed`, `TransportError`, `ConfigurationError`, `CoverageMismatch`).
  - Include helper type guards (`isCredentialRevoked(error)` etc.).
  - Map generated SDK error responses into these unions.
- **Status**: Client-facing error taxonomy extended for auth + agent API flows (`AgentAuthClientError`, `AgentApiClientError`, `TokenManagerError`) with contextual metadata surfaced to callers.

## Cross-Cutting Concerns
- **Telemetry**: Optional OpenTelemetry spans named `agent.auth.challenge`, `agent.auth.exchange`, `agent.auth.refresh`; attach HTTP status, retry count, latency.
- **Debug Mode**: Toggle via `LUCID_AGENT_DEBUG=1` to emit verbose logs without leaking tokens.
- **Edge Compatibility**: Document required polyfills (`crypto.subtle`, `TextEncoder`) and how transports select runtime-specific implementations.
- **Testing**:
  - Contract tests: mock generated SDK responses to verify token lifecycle scenarios.
  - Storage adapter tests: concurrency + persistence.
  - Integration smoke: run against local stack using the generated SDK to guarantee parity.
- **CLI Alignment**: CLI should boot the runtime via `AgentRuntime.fromConfig()` and rely on `AgentApiClient` for admin operations.

## Usage Examples

### Quick start with `createWallet`
```ts
import { createWallet } from "@lucid-dreams/agent-auth";

const wallet = await createWallet({
  // Omitting `config` lets createWallet read standard LUCID_* environment variables
  events: {
    authenticated: (event) => {
      console.log("agent authenticated", event.accessToken);
    },
  },
});

await wallet.ensureAccessToken();

const signed = await wallet.signMessage({
  message: "gm",
});

console.log("signature", signed.signed);

await wallet.shutdown();
```

### Integrate with `viem`
```ts
import {
  createWallet,
  createViemAccount,
} from "@lucid-dreams/agent-auth";
import {
  createPublicClient,
  createWalletClient,
  http,
  parseEther,
} from "viem";
import { base } from "viem/chains";

const lucidWallet = await createWallet();

// Ensure the wallet has an address (authenticate once if using the server orchestrator).
const account = await createViemAccount(lucidWallet, { chainId: base.id });

const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
});

const hash = await walletClient.sendTransaction({
  account,
  to: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  value: parseEther("0.01"),
});

console.log("submitted transaction", hash);
```

> Want a runnable script? Check `example/viem.ts` for a full workflow that wires
> environment-driven configuration into a viem wallet client, signs messages,
> typed data, and demonstrates optional transaction signing.

### Bootstrap with explicit config
```ts
import {
  AgentRuntime,
  MemoryStorageAdapter,
} from "@lucid-dreams/agent-auth";

const runtime = await AgentRuntime.fromConfig({
  config: {
    baseUrl: "https://lucid.daydream.systems",
    agentRef: process.env.LUCID_AGENT_REF!,
    credentialId: process.env.LUCID_AGENT_CREDENTIAL!,
    refreshToken: process.env.LUCID_AGENT_REFRESH_TOKEN,
    scopes: ["agents.read"],
  },
  wallet: {
    signer: myWalletConnector, // e.g. new ServerOrchestratorWalletConnector({...})
  },
  storage: new MemoryStorageAdapter(), // swap in a durable adapter in production
});

runtime.on("tokenRefreshed", (event) => {
  console.log("tokens rotated", event.expiresAt.toISOString());
});

const agents = await runtime.api.listAgents();
console.log("agent count", agents.items.length);

await runtime.shutdown();
```

### Bootstrap from `.lucid-agent.json`
```ts
import { AgentRuntime } from "@lucid-dreams/agent-auth";

const { runtime, config, sourcePath } = await AgentRuntime.load({
  wallet: { signer: myWalletConnector },
  loader: {
    // optional overrides; defaults align with CLI output
    cwd: process.cwd(),
  },
});

console.log("loaded agent config from", sourcePath ?? "environment only");
console.log("authenticated agent ref", config.agentRef);

const session = await runtime.ensureAccessToken();
console.log("active scopes", session.scopes);
```

### Use the server wallet connector
```ts
import {
  AgentRuntime,
  MemoryStorageAdapter,
  ServerOrchestratorWalletConnector,
} from "@lucid-dreams/agent-auth";

const serverWallet = new ServerOrchestratorWalletConnector({
  baseUrl:
    process.env.LUCID_SERVER_URL ??
    process.env.LUCID_BASE_URL ??
    "https://lucid.daydream.systems",
  agentRef: process.env.LUCID_AGENT_REF!,
});

if (process.env.LUCID_SERVER_ACCESS_TOKEN) {
  serverWallet.setAccessToken(process.env.LUCID_SERVER_ACCESS_TOKEN);
}

const runtime = await AgentRuntime.fromConfig({
  config: {
    baseUrl:
      process.env.LUCID_BASE_URL ?? "https://lucid.daydream.systems",
    agentRef: process.env.LUCID_AGENT_REF!,
    credentialId: process.env.LUCID_AGENT_CREDENTIAL!,
    refreshToken: process.env.LUCID_AGENT_REFRESH_TOKEN,
  },
  wallet: { signer: serverWallet },
  storage: new MemoryStorageAdapter(),
});

// Keep the orchestrator token in sync with runtime refresh events
runtime.on("authenticated", (event) => {
  serverWallet.setAccessToken(event.accessToken);
});
runtime.on("tokenRefreshed", (event) => {
  serverWallet.setAccessToken(event.accessToken);
});
```

### Sign a transaction
```ts
const agentRef = process.env.LUCID_AGENT_REF ?? config.agentRef; // reuse the agent ref from your loaded config

const signed = await runtime.api.signTransaction(
  {
    caip2: "eip155:8453",
    params: {
      transaction: {
        to: "0x0000000000000000000000000000000000000000",
        value: "0x0",
        data: "0x",
      },
    },
    authorization_context: {
      reason: "lucid.example.sign-transaction",
    },
  },
  { agentRef }
);

console.log("signed transaction", signed.signed);
```

### Customize transport and storage
```ts
import {
  AgentRuntime,
  createFetchTransport,
  MemoryStorageAdapter,
} from "@lucid-dreams/agent-auth";

const resolvedConfig = {
  baseUrl: "https://lucid.daydream.systems",
  agentRef: "agent-123",
  credentialId: "cred-123",
};

const runtime = await AgentRuntime.fromConfig({
  config: resolvedConfig,
  wallet: { signer: myWalletConnector },
  transport: createFetchTransport({
    baseUrl: resolvedConfig.baseUrl,
    fetch: myInstrumentedFetch,
    defaultHeaders: {
      "x-lucid-sdk-version": "agent-auth/0.1.0",
    },
  }),
  storage: new MemoryStorageAdapter(),
});
```

## Implementation Checklist
- [x] Implemented `AgentAuthClient` via generated SDK helpers with unit coverage (`src/runtime/agent-auth-client.ts`, `src/runtime/__tests__/agent-auth-client.test.ts`).
- [x] Added `AgentApiClient` coverage for initial agent + wallet calls with unit tests (`src/runtime/agent-api-client.ts`, `src/runtime/__tests__/agent-api-client.test.ts`).
- [ ] Finalise generated SDK namespace mappings (`auth.*`, `agents.*`, etc.) and document them here.
- [ ] Create runtime module files with exported interfaces matching the above specs. (TokenManager + storage adapters + HTTP transport + AgentApiClient + AgentRuntime + ConfigLoader scaffolded with tests.)
- [x] Implemented config loader with env/file/override precedence and tests (`src/runtime/config-loader.ts`, `src/runtime/__tests__/config-loader.test.ts`).
- [x] Implement `AgentRuntime` bootstrap that wires `ConfigLoader`, `TokenManager`, `AgentAuthClient`, and `AgentApiClient` together.
- [x] Added `AgentRuntime.load` convenience entrypoint that composes the config loader and surfaces resolved metadata (`src/runtime/agent-runtime.ts`, `src/runtime/__tests__/agent-runtime.test.ts`).
- [ ] Provide default storage, transport, wallet connector, and logger implementations with the documented extension points.
- [x] Add DX examples demonstrating how to call the runtime (`const runtime = await AgentRuntime.fromConfig();`).
- [ ] Wire CI to regenerate SDK and run doc/example compilation to catch drift.