Skip to main content

Contract library types

All exported from @withboundary/contract.
import type {
  ContractResult,
  ContractAttempt,
  ContractError,
  AttemptDetail,
  FailureCategory,
  Rule,
  RunFn,
  Message,
  ContractConfig,
  ContractOptions,
  ContractSchema,
  DefinedContract,
  RetryOptions,
  RetryPolicy,
  ContractLogger,
  ConsoleLoggerOptions,
  BoundaryLogEvent,
  RuleDefinition,
  RuleIssue,
  SchemaField,
} from "@withboundary/contract";

ContractResult<T>

The return type of every contract call. Discriminated union on ok.
type ContractResult<T> =
  | {
      ok: true
      data: T              // typed, validated, all rules passed
      attempts: number     // total attempts made
      raw: string          // the raw LLM output that was accepted
      durationMS: number   // total wall-clock time
    }
  | {
      ok: false
      error: ContractError
    }

ContractAttempt

Passed to your RunFn on each attempt.
type ContractAttempt = {
  attempt: number          // 1-indexed attempt number
  maxAttempts: number      // from retry config
  instructions: string     // auto-generated prompt from schema
  repairs: Message[]       // repair messages from prior failure (empty on first)
  previousError?: ContractError
  previousCategory?: FailureCategory
}

ContractError

type ContractError = {
  message: string          // human-readable summary
  attempts: AttemptDetail[] // one entry per failed attempt
}

AttemptDetail

type AttemptDetail = {
  raw: string              // raw LLM output
  cleaned: unknown         // parsed/cleaned output
  issues: string[]         // list of violations
  ruleIssues?: RuleIssue[] // structured rule failures, only on RULE_ERROR
  category: FailureCategory
}

FailureCategory

type FailureCategory =
  | "EMPTY_RESPONSE"     // model returned nothing
  | "REFUSAL"            // model refused the task
  | "NO_JSON"            // response contained no JSON
  | "TRUNCATED"          // JSON was cut off
  | "PARSE_ERROR"        // malformed JSON
  | "VALIDATION_ERROR"   // valid JSON, failed schema
  | "RULE_ERROR"         // passed schema, failed rules
  | "RUN_ERROR"          // RunFn threw an error

Rule<T>

interface Rule<T> {
  name: string
  description?: string
  check: (data: T) => boolean | string
  message?: string
  fields?: string[]
}
Rules are named, synchronous checks. check returns true when the data is correct. It returns false or a string when the rule fails. A string return becomes the failure message for that specific attempt; otherwise Boundary uses message when provided. Use stable name values for dashboards, metrics, and ruleIssues. Use description as the human label. Use fields when a rule maps cleanly to one or more output fields.

RunFn

type RunFn = (attempt: ContractAttempt) => Promise<string | null>
Your LLM call function. Return the raw response as a string, or null if the model returned nothing.

Message

type Message = {
  role: string
  content: string
}
Used in attempt.repairs and repair functions.

Configuration types

ContractConfig<T>

type ContractConfig<T> = {
  name: string
  schema: ContractSchema<T>
  rules?: Rule<T>[]
  retry?: RetryOptions
  repairs?: RepairOverrides
  instructions?: { suffix?: string }
  onAttempt?: AttemptHook
  logger?: ContractLogger<T>
  debug?: boolean
  model?: string
}

RetryOptions

type RetryOptions = {
  maxAttempts?: number     // default: 3
  backoff?: "none" | "linear" | "exponential"
  baseMs?: number          // default: 200
}

RepairOverrides

type RepairOverrides = Partial<Record<FailureCategory, RepairFn | false>>
type RepairFn = (detail: AttemptDetail) => Message[]

ContractLogger<T>

Hook into the execution lifecycle. All hooks are optional. Every context object includes contractName and runHandle so one logger can serve multiple contracts and correlate hooks from the same run.
type ContractLogger<T> = {
  onRunStart?: (ctx: {
    contractName: string
    runHandle: string
    maxAttempts: number
    rulesCount: number
    model?: string
    retry: RetryPolicy
    schema?: SchemaField[]
    rules?: RuleDefinition[]
  }) => void

  onAttemptStart?: (ctx: {
    contractName: string
    runHandle: string
    attempt: number
    maxAttempts: number
    instructions: string
    repairs: Array<unknown>
  }) => void

  onRawOutput?: (ctx: {
    contractName: string
    runHandle: string
    attempt: number
    raw: string
  }) => void

  onCleanedOutput?: (ctx: {
    contractName: string
    runHandle: string
    attempt: number
    cleaned: unknown
  }) => void

  onVerifySuccess?: (ctx: {
    contractName: string
    runHandle: string
    attempt: number
    data: T
    durationMs: number
  }) => void

  onVerifyFailure?: (ctx: {
    contractName: string
    runHandle: string
    attempt: number
    category: string
    issues: string[]
    ruleIssues?: RuleIssue[]
    durationMs: number
  }) => void

  onRepairGenerated?: (ctx: {
    contractName: string
    runHandle: string
    attempt: number
    category: string
    repairMessage: string
  }) => void

  onRetryScheduled?: (ctx: {
    contractName: string
    runHandle: string
    attempt: number
    nextAttempt: number
    category: string
    delayMs: number
  }) => void

  onRunSuccess?: (ctx: {
    contractName: string
    runHandle: string
    attempts: number
    data: T
    totalDurationMs: number
  }) => void

  onRunFailure?: (ctx: {
    contractName: string
    runHandle: string
    attempts: number
    category?: string
    message: string
    totalDurationMs: number
  }) => void
}

ConsoleLoggerOptions

type ConsoleLoggerOptions = {
  prefix?: string
  showInstructions?: boolean
  showRepairs?: boolean
  showRawOutput?: boolean
  showCleanedOutput?: boolean
  showSuccessData?: boolean
  maxStringLength?: number
}
See createConsoleLogger.

Engine primitives

Lower-level functions for advanced use cases. All pure and side-effect-free.
FunctionSignatureDescription
clean(raw: string | null | undefined) => unknownExtract and normalize JSON from raw LLM output
verify<T>(data: unknown, schema: ContractSchema<T>, rules?: Rule<T>[]) => ContractResult<T>Validate data against schema and rules. No LLM involved.
classify(raw: string, cleaned: unknown) => FailureCategoryCategorize a failed response
repair(detail: AttemptDetail, overrides?) => Message[] | falseGenerate repair messages for a failed attempt
instructions(schema: ContractSchema<unknown>) => stringGenerate prompt text from a Zod schema
createConsoleLogger(options?) => ContractLogger<T>Create a logger that prints to console
Full reference: Engine primitives.

SDK types

All exported from @withboundary/sdk.
import type {
  BoundaryLogger,
  BoundaryLoggerOptions,
  BoundaryLogEvent,
  BoundaryEnvironment,
  CapturePolicy,
  RedactionOptions,
  Transport,
} from "@withboundary/sdk";
@withboundary/contract also exports a BoundaryLogEvent type for contract-level logger integrations. The SDK’s BoundaryLogEvent below is the hosted ingest event used by createBoundaryLogger, beforeSend, and custom write sinks.

BoundaryLogger<T>

The object returned by createBoundaryLogger. Implements every ContractLogger<T> hook and adds drain controls.
type BoundaryLogger<T = unknown> = ContractLogger<T> & {
  flush: (timeoutMs?: number) => Promise<void>
  shutdown: (timeoutMs?: number) => Promise<void>
}

BoundaryLoggerOptions

Full options shape for createBoundaryLogger. See createBoundaryLogger for a per-field table with defaults.
interface BoundaryLoggerOptions {
  apiKey?: string
  environment?: BoundaryEnvironment
  endpoint?: string
  model?: string
  capture?: Partial<CapturePolicy>
  redact?: RedactionOptions
  batch?: {
    size?: number
    intervalMs?: number
    maxQueueSize?: number
  }
  beforeSend?: (event: BoundaryLogEvent) => BoundaryLogEvent | null
  write?: (events: BoundaryLogEvent[]) => void | Promise<void>
  flushOnExit?: boolean
  onError?: (err: unknown) => void
  fetch?: typeof fetch
}

BoundaryLogEvent

The wire format shipped to the ingest endpoint. A tagged union on okAcceptedEvent (always terminal) or FailedEvent (one per non-terminal attempt plus the terminal one). See BoundaryLogEvent for examples and lifecycle.
interface BoundaryLogEventBase {
  // identity (always sent)
  contractName: string
  environment?: string
  timestamp: string
  runId: string

  // run metadata (always sent)
  attempt: number
  maxAttempts: number
  durationMs: number
  model?: string
  rulesCount?: number
  schema?: SchemaField[]
  rules?: RuleDefinition[]

  // SDK attribution (always sent)
  sdk?: { name: string; version: string; runtime?: string }

  // resolved capture policy stamped on every event
  capture?: CapturePolicy & { redactedFields?: string[] }

  // raw data — gated by capture.inputs / capture.outputs (default off)
  input?: unknown
  output?: unknown
}

interface AcceptedEvent extends BoundaryLogEventBase {
  ok: true
  final: true
}

interface FailedEvent extends BoundaryLogEventBase {
  ok: false
  final: boolean
  // failure attribution — always sent on FailedEvent (no capture flag)
  category: string
  issues: string[]
  ruleFailures?: string[]
  // gated by capture.repairs (default on)
  repairs?: Array<{ role: string; content: string }>
}

type BoundaryLogEvent = AcceptedEvent | FailedEvent

CapturePolicy

interface CapturePolicy {
  inputs: boolean   // user → model data        (default off)
  outputs: boolean  // model → boundary data    (default off)
  repairs: boolean  // boundary → model retries (default on)
}

const DEFAULT_CAPTURE: CapturePolicy = {
  inputs: false,
  outputs: false,
  repairs: true,
}
Failure attribution (category, issues, ruleFailures) and run metadata are always sent on the wire — they are not gated by a capture flag.

RedactionOptions

interface RedactionOptions {
  fields?: string[]
  patterns?: RegExp[]
  custom?: (value: unknown, path: string[]) => unknown
}

BoundaryEnvironment

type BoundaryEnvironment = "production" | "staging" | "development"
The wire format (BoundaryLogEvent.environment) is string so the server accepts future names without an SDK bump; the SDK’s input type is the narrow union to prevent typos like "prod" or "stg" that would fragment dashboards.

Transport

Low-level extension point. Typical users don’t implement this — they pass write instead.
interface Transport {
  send(events: BoundaryLogEvent[]): Promise<void>
}