@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.
| Parameter | Type | Description |
|---|---|---|
message | string | Error description |
opts.provider | string | Provider name |
opts.statusCode | number? | HTTP status code |
opts.retryable | boolean? | Whether to retry (default false) |
opts.cause | Error? | 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.
| Type | Description |
|---|---|
ToolSchemaMap | Record<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 |
UnknownToolCallComplete | Completion 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>
| Parameter | Type | Default | Description |
|---|---|---|---|
fn | () => Promise<T> | — | The async operation to retry |
options.maxRetries | number | 3 | Maximum number of retry attempts |
options.baseDelayMs | number | 1000 | Base delay for exponential backoff |
options.maxDelayMs | number | 30000 | Maximum delay cap |
options.shouldRetry | (error: unknown) => boolean | checks error.retryable | Predicate 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
}
| Parameter | Type | Default | Description |
|---|---|---|---|
failureThreshold | number | 5 | Failures within windowMs before opening |
resetTimeoutMs | number | 30000 | Time in open state before probing (half-open) |
halfOpenMaxAttempts | number | 3 | Max concurrent probes in half-open state |
windowMs | number | 60000 | Sliding window for counting failures |
onStateChange | (from, to) => void | — | Callback on state transitions |
shouldCount | (error) => boolean | checks retryable | Predicate 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
}
| Member | Description |
|---|---|
execute(fn) | Runs fn if the circuit is closed or half-open. Throws ElsiumError with code PROVIDER_ERROR if open. |
state | Current state. Accessing this may trigger an open → half-open transition if resetTimeoutMs has elapsed. |
failureCount | Number 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
}
| Member | Description |
|---|---|
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. |
size | Number 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[]
}
| Member | Description |
|---|---|
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. |
policies | List 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.
| Type | Description |
|---|---|
DeclarativePolicySetConfig | { bundle: PolicyBundle; evaluator?: PolicyEvaluator; strict?: boolean } |
DeclarativePolicySet | { evaluate(req); load(bundle); exportBundle(); verify(bundle?); evaluatorName } |
PolicyBundle | { apiVersion; documents: PolicyDocument[]; defaultEffect: 'allow' | 'deny' } |
PolicyDocument | A single { apiVersion; kind: 'Policy'; metadata; spec } rule |
PolicySpec | effect, subjects?, actions, resources?, when?, priority? |
AuthorizationRequest | { principal; action; resource?; context? } |
EvaluationResult | { decision: 'allow' | 'deny'; reason; matchedPolicy? } |
ConditionExpression | One of 8 ops: eq, ne, gt, lt, gte, lte, in, matches |
MatchPattern | Exact 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.
| Type | Description |
|---|---|
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.
| Type | Description |
|---|---|
TraceRecorderConfig | { agentId?; traceId?; clock?: () => number } |
TraceRecorder | recordStep(...), 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.
| Type | Description |
|---|---|
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
}
| Parameter | Type | Default | Description |
|---|---|---|---|
drainTimeoutMs | number | 30000 | Max time to wait for in-flight operations to finish |
signals | string[] | ['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
}
| Member | Description |
|---|---|
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. |
inFlight | Number of currently tracked operations. |
isShuttingDown | true 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.
| Type | Description |
|---|---|
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.
| Type | Description |
|---|---|
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.
| Type | Description |
|---|---|
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.
| Type | Description |
|---|---|
KeyRegistryConfig | { trustRoots?: [...]; clock?: () => number } |
KeyRegistry | add, 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
| Type | Description |
|---|---|
WriteOnceStore | put(key, value), get(key), has(key), list(prefix?) (async iterable) |
WriteReceipt | { key; hash; size; writtenAt } |
FileWriteOnceStoreConfig | { dir: string; fsync?: boolean } (fsync defaults to true) |
WriteOnceConflictError | Thrown 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.
| Type | Description |
|---|---|
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.
| Type | Description |
|---|---|
CapabilityVerifierConfig | { resolver: PublicKeyResolver; clock?; revocationStore? } |
CapabilityVerifier | { verifyToken(token); verifyTokenAsync(token) } |
TokenVerificationResult | { valid; signatureValid; withinValidityWindow; reason?; detail? } |
CapabilityToken types
| Type | Description |
|---|---|
CapabilityToken | { version; tokenId; issuer; subject; capabilities; dataClasses?; budget?; validity; signature } |
AgentCapability | Union 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[] } |
CapabilityCheckReason | Denial 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.
| Type | Description |
|---|---|
RevocationStore | revoke(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.
| Origin | Meaning |
|---|---|
trusted | Operator-authored: system prompts, hardcoded config |
model | LLM output. Derived from whatever was in context, so it cannot launder untrusted input into trusted standing |
untrusted | Everything 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
| Layer | API | Sink |
|---|---|---|
| Gateway | flowMiddleware | llm:<provider> |
| Tools | withFlowControl | tool:<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.