@elsium-ai/core

Core types, errors, result pattern, streaming, and infrastructure utilities for ElsiumAI.

Install

npm install @elsium-ai/core

What’s Inside

Category Exports
Types Role, TextContent, ImageContent, ContentPart, ToolCall, ToolResult, Message, TokenUsage, CostBreakdown, StopReason, LLMResponse, StreamEvent, StreamCheckpoint, XRayData, ProviderConfig, CompletionRequest, ToolDefinition, MiddlewareContext, MiddlewareNext, Middleware
Errors ElsiumError, ErrorCode, ErrorDetails
Result Result, Ok, Err, ok(), err(), isOk(), isErr(), unwrap(), unwrapOr(), tryCatch(), tryCatchSync()
Stream ElsiumStream, createStream(), StreamTransformer, ResilientStreamOptions
Logger createLogger(), Logger, LogLevel, LogEntry, LoggerOptions
Config env(), envNumber(), envBool()
Utilities generateId(), generateTraceId(), extractText(), sleep(), retry()
Circuit Breaker createCircuitBreaker(), CircuitBreakerConfig, CircuitBreaker, CircuitState
Request Dedup createDedup(), dedupMiddleware(), DedupConfig, Dedup
Policy Engine createPolicySet(), policyMiddleware(), modelAccessPolicy(), tokenLimitPolicy(), costLimitPolicy(), contentPolicy(), PolicyDecision, PolicyResult, PolicyContext, PolicyRule, PolicyConfig, PolicySet
Shutdown createShutdownManager(), ShutdownConfig, ShutdownManager
Information Flow taint(), createLabel(), joinLabels(), joinAll(), createFlowPolicy(), lethalTrifectaRule(), createFlowTracker(), declassify(), TaintLabel, Tainted, LabelInit, Origin, DataClass, FlowRule, FlowPolicy, FlowTracker, FlowDecision

Types

All type exports are interfaces and type aliases — no runtime cost.

Role

type Role = 'system' | 'user' | 'assistant' | 'tool'

TextContent

interface TextContent {
  type: 'text'
  text: string
}

ImageContent

interface ImageContent {
  type: 'image'
  source:
    | { type: 'base64'; mediaType: string; data: string }
    | { type: 'url'; url: string }
}

ContentPart

type ContentPart = TextContent | ImageContent

ToolCall

interface ToolCall {
  id: string
  name: string
  arguments: Record<string, unknown>
}

ToolResult

interface ToolResult {
  toolCallId: string
  content: string
  isError?: boolean
}

Message

interface Message {
  role: Role
  content: string | ContentPart[]
  name?: string
  toolCalls?: ToolCall[]
  toolResults?: ToolResult[]
  metadata?: Record<string, unknown>
}

TokenUsage

interface TokenUsage {
  inputTokens: number
  outputTokens: number
  totalTokens: number
  cacheReadTokens?: number
  cacheWriteTokens?: number
}

CostBreakdown

interface CostBreakdown {
  inputCost: number
  outputCost: number
  totalCost: number
  currency: 'USD'
}

StopReason

type StopReason = 'end_turn' | 'max_tokens' | 'stop_sequence' | 'tool_use'

LLMResponse

The unified response shape returned by all providers after a completion.

interface LLMResponse {
  id: string
  message: Message
  usage: TokenUsage
  cost: CostBreakdown
  model: string
  provider: string
  stopReason: StopReason
  latencyMs: number
  traceId: string
}

StreamEvent

A discriminated union of all events emitted during streaming.

type StreamEvent =
  | { type: 'text_delta'; text: string }
  | { type: 'tool_call_start'; toolCall: { id: string; name: string } }
  | { type: 'tool_call_delta'; toolCallId: string; arguments: string }
  | { type: 'tool_call_end'; toolCallId: string }
  | { type: 'message_start'; id: string; model: string }
  | { type: 'message_end'; usage: TokenUsage; stopReason: StopReason }
  | { type: 'error'; error: Error }
  | { type: 'checkpoint'; checkpoint: StreamCheckpoint }
  | { type: 'recovery'; partialText: string; error: Error }

StreamCheckpoint

interface StreamCheckpoint {
  id: string
  timestamp: number
  text: string
  tokensSoFar: number
  eventIndex: number
}

XRayData

Full request/response trace data for observability.

interface XRayData {
  traceId: string
  timestamp: number
  provider: string
  model: string
  latencyMs: number
  request: {
    url: string
    method: string
    headers: Record<string, string>
    body: Record<string, unknown>
  }
  response: {
    status: number
    headers: Record<string, string>
    body: Record<string, unknown>
  }
  usage: TokenUsage
  cost: CostBreakdown
}

ProviderConfig

interface ProviderConfig {
  apiKey: string
  baseUrl?: string
  timeout?: number
  maxRetries?: number
}

CompletionRequest

interface CompletionRequest {
  messages: Message[]
  model?: string
  system?: string
  maxTokens?: number
  temperature?: number
  seed?: number
  topP?: number
  stopSequences?: string[]
  tools?: ToolDefinition[]
  schema?: z.ZodType
  stream?: boolean
  metadata?: Record<string, unknown>
  signal?: AbortSignal
}

ToolDefinition

interface ToolDefinition {
  name: string
  description: string
  inputSchema: Record<string, unknown>
}

Middleware Types

interface MiddlewareContext {
  request: CompletionRequest
  provider: string
  model: string
  traceId: string
  startTime: number
  metadata: Record<string, unknown>
}

type MiddlewareNext = (ctx: MiddlewareContext) => Promise<LLMResponse>

type Middleware = (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<LLMResponse>

Errors

ErrorCode

type ErrorCode =
  | 'PROVIDER_ERROR'
  | 'RATE_LIMIT'
  | 'AUTH_ERROR'
  | 'INVALID_REQUEST'
  | 'TIMEOUT'
  | 'NETWORK_ERROR'
  | 'PARSE_ERROR'
  | 'VALIDATION_ERROR'
  | 'TOOL_ERROR'
  | 'BUDGET_EXCEEDED'
  | 'MAX_ITERATIONS'
  | 'STREAM_ERROR'
  | 'CONFIG_ERROR'
  | 'UNKNOWN'

ErrorDetails

interface ErrorDetails {
  code: ErrorCode
  message: string
  provider?: string
  model?: string
  statusCode?: number
  retryable: boolean
  retryAfterMs?: number
  cause?: Error
  metadata?: Record<string, unknown>
}

ElsiumError

Structured error class used throughout the framework. Extends Error with machine-readable fields for error handling, retries, and observability.

class ElsiumError extends Error {
  readonly code: ErrorCode
  readonly provider?: string
  readonly model?: string
  readonly statusCode?: number
  readonly retryable: boolean
  readonly retryAfterMs?: number
  readonly cause?: Error
  readonly metadata?: Record<string, unknown>

  constructor(details: ErrorDetails)
  toJSON(): Record<string, unknown>

  static providerError(message: string, opts: {
    provider: string
    statusCode?: number
    retryable?: boolean
    cause?: Error
  }): ElsiumError

  static rateLimit(provider: string, retryAfterMs?: number): ElsiumError
  static authError(provider: string): ElsiumError
  static timeout(provider: string, timeoutMs: number): ElsiumError
  static validation(message: string, metadata?: Record<string, unknown>): ElsiumError
  static budgetExceeded(spent: number, budget: number): ElsiumError
}

Static Factory Methods

ElsiumError.providerError(message, opts) — Generic provider failure.

ParameterTypeDescription
messagestringError description
opts.providerstringProvider name
opts.statusCodenumber?HTTP status code
opts.retryableboolean?Whether to retry (default false)
opts.causeError?Underlying error

ElsiumError.rateLimit(provider, retryAfterMs?) — Rate limit (429). Always retryable.

ElsiumError.authError(provider) — Authentication failure (401). Not retryable.

ElsiumError.timeout(provider, timeoutMs) — Request timeout. Retryable.

ElsiumError.validation(message, metadata?) — Validation failure. Not retryable.

ElsiumError.budgetExceeded(spent, budget) — Token/cost budget exceeded. Not retryable.

import { ElsiumError } from '@elsium-ai/core'

try {
  await callProvider()
} catch (e) {
  if (e instanceof ElsiumError && e.retryable) {
    // safe to retry
  }
}

// Create specific errors
const err = ElsiumError.rateLimit('anthropic', 5000)
console.log(err.code)           // 'RATE_LIMIT'
console.log(err.retryAfterMs)   // 5000

Result

A type-safe Result pattern for representing success/failure without exceptions.

Types

type Result<T, E = Error> = Ok<T> | Err<E>

interface Ok<T> {
  readonly ok: true
  readonly value: T
}

interface Err<E> {
  readonly ok: false
  readonly error: E
}

ok()

Wraps a value in a success result.

function ok<T>(value: T): Ok<T>

err()

Wraps an error in a failure result.

function err<E>(error: E): Err<E>

isOk()

Type guard that narrows a Result to Ok.

function isOk<T, E>(result: Result<T, E>): result is Ok<T>

isErr()

Type guard that narrows a Result to Err.

function isErr<T, E>(result: Result<T, E>): result is Err<E>

unwrap()

Extracts the value from an Ok, or throws the error from an Err.

function unwrap<T, E>(result: Result<T, E>): T

unwrapOr()

Extracts the value from an Ok, or returns the fallback for an Err.

function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T

tryCatch()

Wraps an async function in a Result. Caught errors are normalized to Error.

function tryCatch<T>(fn: () => Promise<T>): Promise<Result<T, Error>>

tryCatchSync()

Synchronous version of tryCatch.

function tryCatchSync<T>(fn: () => T): Result<T, Error>
import { ok, err, isOk, unwrap, unwrapOr, tryCatch } from '@elsium-ai/core'

// Manual construction
const success = ok(42)
const failure = err(new Error('boom'))

if (isOk(success)) {
  console.log(success.value) // 42
}

// Safe unwrap with fallback
unwrapOr(failure, 0) // 0

// Wrap async operations
const result = await tryCatch(() => fetch('/api/data').then(r => r.json()))
if (isOk(result)) {
  console.log(result.value)
}

Stream

StreamTransformer

A function that transforms a stream of events into another stream of events.

type StreamTransformer = (
  source: AsyncIterable<StreamEvent>,
) => AsyncIterable<StreamEvent>

ResilientStreamOptions

interface ResilientStreamOptions {
  checkpointIntervalMs?: number                          // default: 1000
  maxRetries?: number
  onCheckpoint?: (checkpoint: StreamCheckpoint) => void
  onPartialRecovery?: (text: string, error: Error) => void
}

ElsiumStream

An AsyncIterable<StreamEvent> wrapper with convenience methods for consuming and transforming LLM streams. Supports only a single consumer — iterating twice throws.

class ElsiumStream implements AsyncIterable<StreamEvent> {
  constructor(source: AsyncIterable<StreamEvent>)
}

stream.text()

Returns an AsyncIterable<string> that yields only the text deltas.

text(): AsyncIterable<string>

stream.toText()

Collects all text deltas and returns the full text.

async toText(): Promise<string>

stream.toTextWithTimeout(timeoutMs)

Like toText() but stops collecting after timeoutMs milliseconds. Returns whatever text was collected before the deadline.

async toTextWithTimeout(timeoutMs: number): Promise<string>

stream.toResponse()

Collects the full text, token usage, and stop reason from the stream.

async toResponse(): Promise<{
  text: string
  usage: TokenUsage | null
  stopReason: StopReason | null
}>

stream.pipe(transform)

Creates a new ElsiumStream by piping events through a StreamTransformer.

pipe(transform: StreamTransformer): ElsiumStream

stream.resilient(options?)

Wraps the stream with checkpoint and partial-recovery support. Periodically emits checkpoint events and, on error, emits a recovery event containing whatever text was received before the failure.

resilient(options?: ResilientStreamOptions): ElsiumStream

createStream()

Creates an ElsiumStream from an imperative callback. The emit function pushes events into a buffered async iterable (max 10,000 events).

function createStream(
  executor: (emit: (event: StreamEvent) => void) => Promise<void>,
): ElsiumStream
import { ElsiumStream, createStream } from '@elsium-ai/core'

// Create a stream from an imperative source
const stream = createStream(async (emit) => {
  emit({ type: 'message_start', id: 'msg_1', model: 'claude-sonnet-4-6' })
  emit({ type: 'text_delta', text: 'Hello ' })
  emit({ type: 'text_delta', text: 'world!' })
  emit({ type: 'message_end', usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }, stopReason: 'end_turn' })
})

// Consume as full text
const text = await stream.toText() // "Hello world!"

// Or iterate text deltas
for await (const chunk of stream.text()) {
  process.stdout.write(chunk)
}

// Add resilience with checkpoints
const resilient = stream.resilient({
  checkpointIntervalMs: 500,
  onCheckpoint: (cp) => console.log('checkpoint:', cp.text.length, 'chars'),
})

// Pipe through a transform
const filtered = stream.pipe(async function* (source) {
  for await (const event of source) {
    if (event.type !== 'checkpoint') yield event
  }
})

withToolTypes()

Wraps a raw StreamEvent stream and, as each tool call finishes, emits an extra synthesized tool_call_complete event with parsed, schema-validated arguments. Pass-through: every original event is still yielded first; the completion event follows. schemas is a ToolSchemaMap (Record<string, ZodType>) keyed by tool name — only listed tools get narrowed types.

function withToolTypes<T extends ToolSchemaMap>(
  source: AsyncIterable<StreamEvent>,
  schemas: T,
): AsyncIterable<TypedStreamEvent<T>>

When the buffered arguments parse and validate against the tool’s Zod schema, a TypedToolCallComplete<T> is emitted whose toolCall.name / toolCall.arguments are narrowed per tool (ToolArgs<T, K>). On JSON-parse or validation failure, an UnknownToolCallComplete is emitted instead, carrying the raw string and a parseError: { reason, raw }. Any tool calls still pending when the source ends are flushed at the end.

TypeDescription
ToolSchemaMapRecord<string, ZodType<unknown>> mapping tool name to argument schema
ToolArgs<T, K>Inferred argument type for tool K from its schema
TypedToolCallComplete<T>Completion event with narrowed toolCall per tool name
UnknownToolCallCompleteCompletion event for unparseable/invalid args, with parseError
TypedStreamEvent<T>Union of StreamEvent, TypedToolCallComplete<T>, UnknownToolCallComplete
import { withToolTypes } from '@elsium-ai/core'
import { z } from 'zod'

const schemas = {
  search: z.object({ query: z.string(), limit: z.number().optional() }),
}

for await (const event of withToolTypes(provider.stream(request), schemas)) {
  if (event.type === 'tool_call_complete' && event.toolCall.name === 'search') {
    // event.toolCall.arguments is typed as { query: string; limit?: number }
    if ('parseError' in event) console.warn(event.parseError.reason)
    else await runSearch(event.toolCall.arguments)
  }
}

Logger

LogLevel

type LogLevel = 'debug' | 'info' | 'warn' | 'error'

LogEntry

interface LogEntry {
  level: LogLevel
  message: string
  timestamp: string
  traceId?: string
  data?: Record<string, unknown>
}

LoggerOptions

interface LoggerOptions {
  level?: LogLevel                    // default: 'info'
  pretty?: boolean                    // default: false (JSON single-line)
  context?: Record<string, unknown>   // merged into every entry
}

Logger

interface Logger {
  debug(message: string, data?: Record<string, unknown>): void
  info(message: string, data?: Record<string, unknown>): void
  warn(message: string, data?: Record<string, unknown>): void
  error(message: string, data?: Record<string, unknown>): void
  child(context: Record<string, unknown>): Logger
}

createLogger()

Creates a structured JSON logger. Messages below the configured level are silently dropped. error and warn go to console.error/console.warn; everything else goes to console.log.

function createLogger(options?: LoggerOptions): Logger
import { createLogger } from '@elsium-ai/core'

const logger = createLogger({ level: 'debug', pretty: true })
logger.info('server started', { port: 3000 })
// {"level":"info","message":"server started","timestamp":"...","data":{"port":3000}}

const child = logger.child({ traceId: 'trc_abc123' })
child.warn('slow response', { latencyMs: 4200 })
// includes traceId in every entry

Config

Type-safe environment variable access. All three functions throw an ElsiumError with code CONFIG_ERROR when the variable is missing and no fallback is provided.

env()

Returns a string environment variable, or the fallback, or throws.

function env(name: string, fallback?: string): string

envNumber()

Parses the variable as a finite number. Throws if the value is not a valid finite number.

function envNumber(name: string, fallback?: number): number

envBool()

Parses the variable as a boolean. 'true', '1', and 'yes' (case-insensitive) are truthy; everything else is falsy.

function envBool(name: string, fallback?: boolean): boolean
import { env, envNumber, envBool } from '@elsium-ai/core'

const apiKey = env('ANTHROPIC_API_KEY')               // throws if missing
const port = envNumber('PORT', 3000)                  // 3000 if unset
const debug = envBool('DEBUG', false)                 // false if unset

Utilities

generateId()

Generates a unique ID with an optional prefix, using timestamp + 4 random bytes.

function generateId(prefix?: string): string   // default prefix: 'els'

Returns a string like els_m1abc2d_8f3e1a2b.

generateTraceId()

Generates a trace ID using timestamp + 6 random bytes. Always prefixed with trc_.

function generateTraceId(): string

Returns a string like trc_m1abc2d_8f3e1a2b4c5d.

extractText()

Extracts plain text from a Message.content field, handling both the string and ContentPart[] forms.

function extractText(content: string | { type: string; text?: string }[]): string

sleep()

Returns a promise that resolves after ms milliseconds.

function sleep(ms: number): Promise<void>

retry()

Retries an async function with exponential backoff and jitter. Respects retryAfterMs on errors (e.g., ElsiumError rate limits).

function retry<T>(
  fn: () => Promise<T>,
  options?: {
    maxRetries?: number                         // default: 3
    baseDelayMs?: number                        // default: 1000
    maxDelayMs?: number                         // default: 30000
    shouldRetry?: (error: unknown) => boolean   // default: checks error.retryable
  },
): Promise<T>
ParameterTypeDefaultDescription
fn() => Promise<T>—The async operation to retry
options.maxRetriesnumber3Maximum number of retry attempts
options.baseDelayMsnumber1000Base delay for exponential backoff
options.maxDelayMsnumber30000Maximum delay cap
options.shouldRetry(error: unknown) => booleanchecks error.retryablePredicate to decide whether to retry
import { retry, generateId, generateTraceId, extractText, sleep } from '@elsium-ai/core'

const id = generateId()             // "els_m1abc2d_8f3e1a2b"
const traceId = generateTraceId()   // "trc_m1abc2d_8f3e1a2b4c5d"

// Extract text from either content format
extractText('hello')                             // "hello"
extractText([{ type: 'text', text: 'hello' }])  // "hello"

// Retry with defaults (3 retries, exponential backoff)
const data = await retry(() => fetchFromProvider(), {
  maxRetries: 5,
  shouldRetry: (err) => err instanceof Error,
})

Schema

zodToJsonSchema(schema)

zodToJsonSchema(schema: ZodType): JsonSchema

Converts a Zod schema to JSON Schema format, used for defining tool parameters and structured output.

import { zodToJsonSchema } from '@elsium-ai/core'
import { z } from 'zod'

const paramSchema = z.object({
  query: z.string().describe('Search query'),
  limit: z.number().optional().default(10),
})

const jsonSchema = zodToJsonSchema(paramSchema)

Registry

createRegistry(name)

createRegistry<T>(name: string): Registry<T>

Creates a typed plugin registry for registering and retrieving named components.

import { createRegistry } from '@elsium-ai/core'

const providers = createRegistry<ProviderFactory>('providers')

providers.register('custom', myProviderFactory)
const factory = providers.get('custom')
const all = providers.list() // string[]

Token Management

Export Signature Description
countTokens countTokens(text: string): number Estimate token count for text
createContextManager createContextManager(config: ContextManagerConfig): ContextManager Manage context window budget
import { countTokens, createContextManager } from '@elsium-ai/core'

const tokens = countTokens('Hello, world!')

const ctx = createContextManager({ maxTokens: 4096, strategy: 'truncate' })
const used = ctx.estimateTokens(messages)
const fitted = await ctx.fit(messages)

Circuit Breaker

Monitors failures within a sliding time window and stops sending traffic to a failing service. Automatically recovers via the half-open state.

CircuitState

type CircuitState = 'closed' | 'open' | 'half-open'

State machine: closed (healthy) → open (tripping after threshold failures) → half-open (probing after reset timeout) → closed (on success) or back to open (on failure).

CircuitBreakerConfig

interface CircuitBreakerConfig {
  failureThreshold?: number                                 // default: 5
  resetTimeoutMs?: number                                   // default: 30000
  halfOpenMaxAttempts?: number                               // default: 3
  windowMs?: number                                          // default: 60000
  onStateChange?: (from: CircuitState, to: CircuitState) => void
  shouldCount?: (error: unknown) => boolean                  // default: checks error.retryable
}
ParameterTypeDefaultDescription
failureThresholdnumber5Failures within windowMs before opening
resetTimeoutMsnumber30000Time in open state before probing (half-open)
halfOpenMaxAttemptsnumber3Max concurrent probes in half-open state
windowMsnumber60000Sliding window for counting failures
onStateChange(from, to) => void—Callback on state transitions
shouldCount(error) => booleanchecks retryablePredicate to decide if an error counts as a failure

CircuitBreaker

interface CircuitBreaker {
  execute<T>(fn: () => Promise<T>): Promise<T>
  readonly state: CircuitState
  readonly failureCount: number
  reset(): void
}
MemberDescription
execute(fn)Runs fn if the circuit is closed or half-open. Throws ElsiumError with code PROVIDER_ERROR if open.
stateCurrent state. Accessing this may trigger an open → half-open transition if resetTimeoutMs has elapsed.
failureCountNumber of failures within the current window.
reset()Manually resets to the closed state and clears failure counts.

createCircuitBreaker()

function createCircuitBreaker(config?: CircuitBreakerConfig): CircuitBreaker
import { createCircuitBreaker } from '@elsium-ai/core'

const breaker = createCircuitBreaker({
  failureThreshold: 3,
  resetTimeoutMs: 10_000,
  onStateChange: (from, to) => console.log(`circuit: ${from} -> ${to}`),
})

const result = await breaker.execute(() => callProvider())
console.log(breaker.state)         // 'closed'
console.log(breaker.failureCount)  // 0

Request Dedup

Coalesces identical in-flight requests into a single execution and caches results for a short TTL.

DedupConfig

interface DedupConfig {
  ttlMs?: number       // default: 5000
  maxEntries?: number  // default: 1000
}

Dedup

interface Dedup<T> {
  deduplicate(key: string, fn: () => Promise<T>): Promise<T>
  hashRequest(request: unknown): string
  readonly size: number
  clear(): void
}
MemberDescription
deduplicate(key, fn)Returns a cached result if within TTL, joins an in-flight request if one exists for key, or executes fn.
hashRequest(request)Deterministic SHA-256 hash (first 16 hex chars) of a JSON-serializable object. Handles key ordering.
sizeNumber of cached + in-flight entries (expired entries are evicted on access).
clear()Clears all cached and in-flight entries.

createDedup()

function createDedup<T>(config?: DedupConfig): Dedup<T>

dedupMiddleware()

Returns a Middleware that deduplicates LLM requests based on their messages, model, provider, and key completion parameters.

function dedupMiddleware(config?: DedupConfig): Middleware
import { createDedup, dedupMiddleware } from '@elsium-ai/core'

// Standalone usage
const dedup = createDedup<string>({ ttlMs: 10_000 })
const key = dedup.hashRequest({ model: 'claude-sonnet-4-6', messages: [...] })
const result = await dedup.deduplicate(key, () => expensive())

// As middleware — identical concurrent requests share one API call
const middleware = dedupMiddleware({ ttlMs: 3000 })

Policy Engine

Declarative rules to allow or deny LLM requests before they reach a provider.

PolicyDecision

type PolicyDecision = 'allow' | 'deny'

PolicyResult

interface PolicyResult {
  decision: PolicyDecision
  reason: string
  policyName: string
}

PolicyContext

The evaluation context passed to every policy rule.

interface PolicyContext {
  model?: string
  provider?: string
  actor?: string
  role?: string
  tokenCount?: number
  costEstimate?: number
  requestContent?: string
  metadata?: Record<string, unknown>
}

PolicyRule

type PolicyRule = (ctx: PolicyContext) => PolicyResult

PolicyConfig

interface PolicyConfig {
  name: string
  description?: string
  rules: PolicyRule[]
  mode?: 'all-must-pass' | 'any-must-pass'   // default: 'all-must-pass'
}

PolicySet

interface PolicySet {
  evaluate(ctx: PolicyContext): PolicyResult[]
  addPolicy(policy: PolicyConfig): void
  removePolicy(name: string): void
  readonly policies: string[]
}
MemberDescription
evaluate(ctx)Runs all policy rules and returns an array of denials only (empty = all passed).
addPolicy(policy)Adds a policy at runtime.
removePolicy(name)Removes a policy by name.
policiesList of currently registered policy names.

createPolicySet()

function createPolicySet(policies: PolicyConfig[]): PolicySet

policyMiddleware()

Returns a Middleware that evaluates all policies before forwarding the request. Throws ElsiumError with code VALIDATION_ERROR if any policy denies.

function policyMiddleware(policySet: PolicySet): Middleware

Built-in Policy Factories

modelAccessPolicy()

Restricts requests to a list of allowed models. Supports glob-style trailing wildcards (e.g., 'claude-*').

function modelAccessPolicy(allowedModels: string[]): PolicyConfig

tokenLimitPolicy()

Denies requests whose estimated token count exceeds maxTokens.

function tokenLimitPolicy(maxTokens: number): PolicyConfig

costLimitPolicy()

Denies requests whose estimated cost exceeds maxCost.

function costLimitPolicy(maxCost: number): PolicyConfig

contentPolicy()

Denies requests whose content matches any of the provided regex patterns.

function contentPolicy(blockedPatterns: RegExp[]): PolicyConfig
import {
  createPolicySet,
  policyMiddleware,
  modelAccessPolicy,
  tokenLimitPolicy,
  costLimitPolicy,
  contentPolicy,
} from '@elsium-ai/core'

const policies = createPolicySet([
  modelAccessPolicy(['claude-sonnet-4-6', 'gpt-4o']),
  tokenLimitPolicy(100_000),
  costLimitPolicy(5.00),
  contentPolicy([/password/i, /secret_key/i]),
])

// Check manually
const denials = policies.evaluate({ model: 'unknown-model' })
// [{ decision: 'deny', reason: 'Model "unknown-model" is not in allowed list', policyName: 'model-access' }]

// Or use as middleware
const middleware = policyMiddleware(policies)

Declarative Policy

Data-driven authorization using YAML/JSON-style PolicyDocuments evaluated by a built-in engine. This is the recommended path going forward; the closure-based createPolicySet above is kept during v0.x.

createDeclarativePolicySet()

function createDeclarativePolicySet(config: DeclarativePolicySetConfig): DeclarativePolicySet

Builds a policy set from a PolicyBundle. Validates the bundle at construction — by default throws on error-level verification issues (set strict: false for warnings-only). Deny policies are evaluated before allow policies; within each pass, higher spec.priority documents win. If nothing matches, the bundle’s defaultEffect applies.

TypeDescription
DeclarativePolicySetConfig{ bundle: PolicyBundle; evaluator?: PolicyEvaluator; strict?: boolean }
DeclarativePolicySet{ evaluate(req); load(bundle); exportBundle(); verify(bundle?); evaluatorName }
PolicyBundle{ apiVersion; documents: PolicyDocument[]; defaultEffect: 'allow' | 'deny' }
PolicyDocumentA single { apiVersion; kind: 'Policy'; metadata; spec } rule
PolicySpeceffect, subjects?, actions, resources?, when?, priority?
AuthorizationRequest{ principal; action; resource?; context? }
EvaluationResult{ decision: 'allow' | 'deny'; reason; matchedPolicy? }
ConditionExpressionOne of 8 ops: eq, ne, gt, lt, gte, lte, in, matches
MatchPatternExact string, { in: string[] }, or { regex: string }
VerificationIssue{ document; severity: 'error' | 'warning'; issue }
import { createDeclarativePolicySet } from '@elsium-ai/core'

const policies = createDeclarativePolicySet({
  bundle: {
    apiVersion: 'elsium.policy/v1',
    defaultEffect: 'deny',
    documents: [
      {
        apiVersion: 'elsium.policy/v1',
        kind: 'Policy',
        metadata: { name: 'allow-analysts-cheap-models' },
        spec: {
          effect: 'allow',
          subjects: [{ type: 'role', match: 'analyst' }],
          actions: [{ type: 'model:use' }],
          resources: [{ kind: 'model', id: { in: ['gpt-4o-mini', 'claude-haiku'] } }],
        },
      },
    ],
  },
})

const result = policies.evaluate({
  principal: { type: 'role', id: 'analyst' },
  action: { type: 'model:use' },
  resource: { kind: 'model', id: 'gpt-4o-mini' },
})
// { decision: 'allow', reason: '...', matchedPolicy: 'allow-analysts-cheap-models' }

Related exports: createBuiltinEvaluator() (default PolicyEvaluator), evaluateCondition(expr, ctx) (reuse the 8-operator semantics), verifyBundle(bundle) (standalone validation), and declarativePolicyMiddleware(config) (gateway-side enforcement deriving an AuthorizationRequest from MiddlewareContext).


State Store

Durable key/value store for opaque agent snapshots — used by the agent runtime to pause and resume mid-execution (e.g. while waiting for human approval).

createInMemoryStateStore()

function createInMemoryStateStore<TSnapshot = unknown>(
  config?: InMemoryStateStoreConfig,
): StateStore<TSnapshot>

In-memory StateStore adapter. Evicts oldest entries past maxEntries (default 10,000). The framework ships only this adapter; production users implement the StateStore interface over Redis/Postgres/SQLite/S3.

TypeDescription
StateStore<TSnapshot>save(key, snapshot), load(key), delete(key), optional list(prefix?)
InMemoryStateStoreConfig{ maxEntries?: number } (default 10,000)
import { createInMemoryStateStore } from '@elsium-ai/core'

const store = createInMemoryStateStore<MySnapshot>({ maxEntries: 1000 })
await store.save('run-123', snapshot)
const resumed = await store.load('run-123')

Trace & Replay

Deterministic record-and-replay of agent step execution — used by the agent runtime and @elsium-ai/testing. A recorder captures each step’s input/output; replayFrom re-runs a trace from a chosen point, replaying earlier steps verbatim and executing later ones live (with optional overrides).

createTraceRecorder()

function createTraceRecorder(config?: TraceRecorderConfig): TraceRecorder

Creates a recorder. Call recordStep({ key, input, output, ... }) per step, then finish() to get an immutable AgentTrace.

TypeDescription
TraceRecorderConfig{ agentId?; traceId?; clock?: () => number }
TraceRecorderrecordStep(...), finish(), traceId, steps
TraceStep<TIn, TOut>{ key; input; output; startedAt; durationMs; metadata? }
AgentTrace{ id; agentId?; startedAt; endedAt?; steps: TraceStep[] }

replayFrom()

function replayFrom<TInput, TOutput>(
  trace: AgentTrace,
  options: ReplayFromOptions<TInput, TOutput>,
): Promise<ReplayResult<TInput, TOutput>>

Replays trace. Steps before fromStep (a numeric index or step key) are returned verbatim (source: 'replay'); steps from that point on are run through options.executor (source: 'live'). Per-step overrides can replace an output or transform its input/output.

TypeDescription
ReplayFromOptions<TIn, TOut>{ fromStep: number | string; executor: StepExecutor; overrides? }
StepExecutor<TIn, TOut>({ key, input, originalStep }) => TOut | Promise<TOut>
StepOverride<TIn, TOut>{ kind: 'replace'; output } or { kind: 'transform'; input?; output? }
ReplayedStep<TIn, TOut>TraceStep plus source: 'replay' | 'live' and overridden: boolean
ReplayResult<TIn, TOut>{ traceId; steps: ReplayedStep[]; finalOutput }
import { createTraceRecorder, replayFrom } from '@elsium-ai/core'

const rec = createTraceRecorder({ agentId: 'support-bot' })
rec.recordStep({ key: 'fetch', input: { id: 1 }, output: { name: 'Ada' } })
rec.recordStep({ key: 'summarize', input: { name: 'Ada' }, output: 'Hi Ada' })
const trace = rec.finish()

const result = await replayFrom(trace, {
  fromStep: 'summarize',
  executor: ({ key, input }) => runStep(key, input),
  overrides: { fetch: { kind: 'replace', output: { name: 'Grace' } } },
})

Shutdown Manager

Tracks in-flight operations and drains them before process exit. Automatically registers signal handlers for SIGTERM and SIGINT.

ShutdownConfig

interface ShutdownConfig {
  drainTimeoutMs?: number         // default: 30000
  signals?: string[]              // default: ['SIGTERM', 'SIGINT']
  onDrainStart?: () => void
  onDrainComplete?: () => void
  onForceShutdown?: () => void
}
ParameterTypeDefaultDescription
drainTimeoutMsnumber30000Max time to wait for in-flight operations to finish
signalsstring[]['SIGTERM', 'SIGINT']OS signals that trigger shutdown
onDrainStart() => void—Called when drain begins
onDrainComplete() => void—Called when all operations finish within timeout
onForceShutdown() => void—Called when drain timeout expires

ShutdownManager

interface ShutdownManager {
  trackOperation<T>(fn: () => Promise<T>): Promise<T>
  shutdown(): Promise<void>
  dispose(): void
  readonly inFlight: number
  readonly isShuttingDown: boolean
}
MemberDescription
trackOperation(fn)Executes fn while tracking it as in-flight. Throws ElsiumError with code VALIDATION_ERROR if already shutting down.
shutdown()Initiates graceful shutdown. Waits for in-flight operations up to drainTimeoutMs. Idempotent — multiple calls return the same promise.
dispose()Removes all registered signal handlers. Call this in tests to prevent leaks.
inFlightNumber of currently tracked operations.
isShuttingDowntrue after shutdown() is called.

createShutdownManager()

function createShutdownManager(config?: ShutdownConfig): ShutdownManager
import { createShutdownManager } from '@elsium-ai/core'

const shutdown = createShutdownManager({
  drainTimeoutMs: 10_000,
  onDrainStart: () => console.log('draining...'),
  onDrainComplete: () => console.log('drained, exiting'),
  onForceShutdown: () => console.log('force shutdown!'),
})

// Wrap every request
const result = await shutdown.trackOperation(() => handleRequest())
console.log(shutdown.inFlight) // 0 after completion

// Cleanup in tests
shutdown.dispose()

Crypto

Runtime primitives for governance code: Ed25519 signing/verification, a trusted-key registry, and tamper-evident write-once storage. Built on Node’s node:crypto.

generateEd25519KeyPair()

function generateEd25519KeyPair(): Ed25519KeyPair

Generates a fresh Ed25519 key pair. Returns PEM strings plus the public-key fingerprint.

TypeDescription
Ed25519KeyPair{ privateKey: string; publicKey: string; fingerprint: string } (PEM + SHA-256 hex)

createEd25519Signer()

function createEd25519Signer(opts: {
  privateKey: string | Uint8Array | KeyObject
  keyId: string
}): Signer

Creates a signer from a PKCS#8 private key. sign(payload) returns a Signature with base64url value. Throws CONFIG_ERROR for non-Ed25519 keys or an empty keyId.

TypeDescription
Signer{ keyId; algorithm: 'Ed25519'; fingerprint; sign(payload) => Signature }
Signature{ algorithm: 'Ed25519'; keyId: string; value: string }

createEd25519Verifier()

function createEd25519Verifier(opts: { resolver: PublicKeyResolver }): Verifier

Creates a verifier that resolves public keys by keyId (typically a KeyRegistry). verify(payload, signature) returns a VerifyResult.

TypeDescription
Verifier{ verify(payload, signature) => VerifyResult }
VerifyResult{ valid; keyId?; algorithm?; reason? }
PublicKeyResolver{ resolve(keyId) => KeyObject | undefined }

createKeyRegistry()

function createKeyRegistry(config?: KeyRegistryConfig): KeyRegistry

A PublicKeyResolver over a set of trusted Ed25519 public keys, each with an optional validity window. resolve() returns a key only while valid; isValid(keyId, atTime?) checks the window. Rejects duplicate or reserved (__proto__, constructor, prototype) key IDs.

TypeDescription
KeyRegistryConfig{ trustRoots?: [...]; clock?: () => number }
KeyRegistryadd, get, remove, list, isValid, plus resolve (extends PublicKeyResolver)
TrustedKey{ keyId; publicKey; fingerprint; notBefore?; notAfter?; label?; addedAt }
AddKeyOptions{ notBefore?; notAfter?; label? }

computeKeyFingerprint()

function computeKeyFingerprint(publicKey: KeyObject): string

SHA-256 hex of the SPKI-DER encoding of a public key. Stable identifier for a key.

import {
  generateEd25519KeyPair,
  createEd25519Signer,
  createEd25519Verifier,
  createKeyRegistry,
} from '@elsium-ai/core'

const { privateKey, publicKey } = generateEd25519KeyPair()

const signer = createEd25519Signer({ privateKey, keyId: 'svc-2026' })
const sig = signer.sign('audit-event-123')

const registry = createKeyRegistry()
registry.add('svc-2026', publicKey)

const verifier = createEd25519Verifier({ resolver: registry })
const result = verifier.verify('audit-event-123', sig) // { valid: true, ... }

(Lower-level helpers publicKeyFromPem / privateKeyFromPem parse and validate PEM into KeyObjects.)

Write-Once Stores

Tamper-evident append-only storage. put is write-once: re-writing an existing key throws WriteOnceConflictError. Each put returns a WriteReceipt with the SHA-256 hash of the bytes.

function createInMemoryWriteOnceStore(): WriteOnceStore
function createFileWriteOnceStore(config: FileWriteOnceStoreConfig): WriteOnceStore
TypeDescription
WriteOnceStoreput(key, value), get(key), has(key), list(prefix?) (async iterable)
WriteReceipt{ key; hash; size; writtenAt }
FileWriteOnceStoreConfig{ dir: string; fsync?: boolean } (fsync defaults to true)
WriteOnceConflictErrorThrown when a key already exists
import { createFileWriteOnceStore } from '@elsium-ai/core'

const audit = createFileWriteOnceStore({ dir: './audit-log' })
const receipt = await audit.put('events/001.json', JSON.stringify(event))
// receipt.hash is the SHA-256 of the written bytes

Capability Tokens

OAuth-style scoped, signed tokens that grant AI agents narrowly-defined permissions (tools, LLMs, RAG, MCP) with budgets, data-class constraints, and expiry. Tokens are Ed25519-signed via the Crypto layer and can be delegated and revoked.

createCapabilityIssuer()

function createCapabilityIssuer(config: CapabilityIssuerConfig): CapabilityIssuer

Mints signed CapabilityTokens for a given org, signing with the provided Signer. mint(options) requires a subject and at least one capability; ttlMs defaults to one hour. delegate(parent, options) derives an attenuated child token.

TypeDescription
CapabilityIssuerConfig{ signer: Signer; orgId: string; clock? }
CapabilityIssuer{ orgId; keyId; mint(options); delegate(parent, options) }
MintOptions{ subject; capabilities; dataClasses?; budget?; ttlMs?; expiresAt?; notBefore? }

createCapabilityVerifier()

function createCapabilityVerifier(config: CapabilityVerifierConfig): CapabilityVerifier

Verifies a token’s signature and validity window. verifyToken is synchronous (signature + expiry); verifyTokenAsync additionally checks an optional revocationStore.

TypeDescription
CapabilityVerifierConfig{ resolver: PublicKeyResolver; clock?; revocationStore? }
CapabilityVerifier{ verifyToken(token); verifyTokenAsync(token) }
TokenVerificationResult{ valid; signatureValid; withinValidityWindow; reason?; detail? }

CapabilityToken types

TypeDescription
CapabilityToken{ version; tokenId; issuer; subject; capabilities; dataClasses?; budget?; validity; signature }
AgentCapabilityUnion of ToolCapability, McpCapability, LLMCapability, RagCapability, WorkflowCapability (discriminated by kind)
CapabilitySubject{ agent: string; runId?; parentToken? }
CapabilityIssuerRef{ orgId: string; keyId: string }
CapabilityValidity{ issuedAt; expiresAt; notBefore? }
CapabilityBudget{ maxCost?; maxTokens?; maxCalls? }
CapabilityDataClasses{ allowed?: DataClass[]; denied?: DataClass[] }
CapabilityCheckReasonDenial reason enum (expired, bad-signature, revoked, budget-exceeded, ...)
CapabilityCheckResult{ allowed; reason?; detail?; matchedCapability? }
CAPABILITY_TOKEN_VERSION'elsium-cap/v1' constant
import {
  createCapabilityIssuer,
  createCapabilityVerifier,
  createEd25519Signer,
  createKeyRegistry,
  generateEd25519KeyPair,
} from '@elsium-ai/core'

const { privateKey, publicKey } = generateEd25519KeyPair()
const signer = createEd25519Signer({ privateKey, keyId: 'issuer-1' })

const issuer = createCapabilityIssuer({ signer, orgId: 'acme' })
const token = issuer.mint({
  subject: { agent: 'support-bot' },
  capabilities: [{ kind: 'tool', name: 'search' }],
  budget: { maxCalls: 50 },
  ttlMs: 15 * 60 * 1000,
})

const registry = createKeyRegistry()
registry.add('issuer-1', publicKey)
const verifier = createCapabilityVerifier({ resolver: registry })
const check = verifier.verifyToken(token) // { valid: true, ... }

Capability-check helpers (canCallTool, canCallLLM, canQueryRag, canUseMcp, checkDataClass) and delegateToken evaluate a verified token against a specific request.

createInMemoryRevocationStore()

function createInMemoryRevocationStore(config?: InMemoryRevocationStoreConfig): RevocationStore

In-memory RevocationStore for use with verifyTokenAsync. revoke(tokenId, opts?) is idempotent (returns the existing entry if already revoked). Production users implement RevocationStore over durable storage.

TypeDescription
RevocationStorerevoke(tokenId, opts?), isRevoked(tokenId), getEntry(tokenId), list()
RevocationEntry{ tokenId; revokedAt; reason?; revokedBy? }
InMemoryRevocationStoreConfig{ clock?: () => number }
import { createInMemoryRevocationStore, createCapabilityVerifier } from '@elsium-ai/core'

const revocations = createInMemoryRevocationStore()
await revocations.revoke(token.tokenId, { reason: 'compromised' })

const verifier = createCapabilityVerifier({ resolver: registry, revocationStore: revocations })
const result = await verifier.verifyTokenAsync(token) // { valid: false, reason: 'revoked', ... }

Information-Flow Control

Capability tokens govern the caller: may this agent call this tool? Once a retrieved document or a tool result enters the context, it becomes undifferentiated text and every downstream decision is made blind.

Information-flow control keeps provenance attached to the data. Labels travel with values, join as values merge, and a sink is checked against the accumulated label before anything leaves.

taint() / createLabel()

function taint<T>(value: T, init?: LabelInit): Tainted<T>
function createLabel(init?: LabelInit): TaintLabel

A TaintLabel carries sensitivity classes, an origin, and sources for audit. origin defaults to 'untrusted' — safe by omission.

OriginMeaning
trustedOperator-authored: system prompts, hardcoded config
modelLLM output. Derived from whatever was in context, so it cannot launder untrusted input into trusted standing
untrustedEverything from outside: user input, retrieved documents, tool results, MCP responses

DataClass is the same free-form string type capability tokens use, so a token permitting dataClasses: ['pii'] and a rule denying pii at the network are talking about the same thing.

import { taint } from '@elsium-ai/core'

const key = taint('sk-live-...', { classes: ['secret'], origin: 'trusted', source: 'vault' })

joinLabels() / joinAll()

function joinLabels(a: TaintLabel, b: TaintLabel): TaintLabel
function joinAll(labels: readonly TaintLabel[]): TaintLabel

Least upper bound: classes and sources union, origin becomes the least trusted of the two. Joins are commutative, associative, idempotent, and monotonic — a join is never weaker than either input, so no sequence of merges can wash a taint out. That property is what the guarantee rests on.

createFlowPolicy()

function createFlowPolicy(rules: readonly FlowRule[]): FlowPolicy

Rules are deny-only and evaluated in order; first match wins; no match allows. Deny-only is deliberate — an allow/deny mix forces authors to reason about precedence, and precedence bugs in a security control fail open.

const policy = createFlowPolicy([
  {
    name: 'eu-pii-stays-in-eu',
    sink: ['llm:openai', 'llm:anthropic-us'],
    deny: { hasClasses: ['pii:eu'] },
    reason: 'EU personal data may not reach a US-hosted model',
  },
])

A sink is kind:name — llm:anthropic, tool:send_email, network:api.stripe.com — matched with * globs. Conditions: hasClasses (all of), hasAnyClass (at least one), origin. All present fields must match. A rule with no conditions throws at construction rather than silently blocking every flow to its sink.

lethalTrifectaRule()

function lethalTrifectaRule(options?: { sensitiveClasses?; sinks?; name? }): FlowRule

An agent is exploitable when three things share one context: sensitive data, untrusted content, and an outbound sink. Any two are safe. This rule blocks the combination.

const policy = createFlowPolicy([lethalTrifectaRule()])
// denies ['secret','pii'] + untrusted origin reaching network:* / tool:* / mcp:*

Prompt injection is the trigger, not the vulnerability, so this blocks the channel instead of trying to detect the phrasing.

createFlowTracker()

function createFlowTracker(config: FlowTrackerConfig): FlowTracker

Accumulates provenance across a run — record(label), unwrap(tainted), check(sink), reset(). Trackers are per-run: sharing one across independent requests leaks taint between them.

const tracker = createFlowTracker({ policy, onDeny: (d) => audit.record(d) })

const apiKey = tracker.unwrap(key)           // context: trusted + [secret]
await guardedInvoice.execute({ id: '882' })  // context: untrusted + [secret]
tracker.check('tool:send_email')             // { allowed: false, rule: 'lethal-trifecta', ... }

declassify()

function declassify<T>(tainted: Tainted<T>, options: { to: LabelInit; reason: string; by: string }): Tainted<T>

The only way to weaken a label, and deliberately awkward: explicit call, mandatory reason, recorded actor. label.sources gains declassified-by:<by>, so every hole in the guarantee is greppable.

Enforcement points

LayerAPISink
GatewayflowMiddlewarellm:<provider>
ToolswithFlowControltool:<name> or a custom sink

Part of ElsiumAI

This package is the foundation layer of the ElsiumAI framework. See the Getting Started guide or the Fundamentals for comprehensive coverage of every feature.

License

MIT