Skip to content

Migrating from alpha.27 to alpha.28

Impact: None — fully additive release. Bump peer deps and consume the new surface at your own pace.

Install

bash
pnpm add @llm-ports/core@alpha @llm-ports/observability-contract@alpha \
         @llm-ports/adapter-openai@alpha @llm-ports/adapter-anthropic@alpha \
         @llm-ports/adapter-google@alpha @llm-ports/adapter-ollama@alpha \
         @llm-ports/adapter-vercel@alpha @llm-ports/adapter-codex@alpha \
         @llm-ports/adapter-aider@alpha @llm-ports/capabilities@alpha

Ten publishable packages at 0.1.0-alpha.28. Three of those are net-new packages first published in this release.

What actually changed

In one sentence: the observability data model gets its own standalone package (@llm-ports/observability-contract) so callers can construct and emit conformant events without pulling in the registry; the core gains a scoped-port wrapper (withObservabilityContext) and three new typed error classes; two subprocess-driven agent adapters join the family (Codex, Aider). No public API is removed or renamed.

In slightly more detail:

New package: @llm-ports/observability-contract

Zero peer dependency on @llm-ports/core. Purpose: any consumer (a Registry, a bespoke wrapper, a non-port caller wrapping a subprocess CLI) can construct ObservabilityEvent<TType, TData> values using this package's types and hand them to any ObservabilitySink without importing the registry.

Ships (the full list, so you can see what's now on the shelf):

  • Event envelope (ObservabilityEvent<TType, TData>) with spec_version, event_id, event_type, occurred_at, emitted_at, source, operation_id, attempt_id, parent_operation_id, trace_context, sequence, and data.
  • Correlation model splitting logical operation identity from physical attempt identity (CorrelationContext, ObservabilityContext).
  • ObservabilitySink { emit(event) } interface, noopSink, createCollectingSink().
  • W3C Trace Context + Baggage (string-header form, ≤64 members, ≤8192 bytes).
  • Nanoid-based ID helpers: newEventId, newOperationId, newAttemptId, newEvaluationId.
  • 9 lifecycle event types (llm.operation.started, llm.attempt.started, llm.attempt.completed, llm.attempt.failed, llm.attempt.retry_scheduled, llm.fallback.selected, llm.operation.completed, llm.operation.failed, llm.operation.cancelled) + 4 agent-step event types.
  • ErrorInfo shape + CauseCategory (8-value rollup) + static ERROR_TYPE_TO_CATEGORY map + errorTypeToCauseCategory() resolver.
  • Nested CacheStats { provider_cache?, semantic_cache? } plus helpers.
  • RequestFingerprint canonicalization rules v1 (NFC, LF, sorted keys, 16 allowed request keys) + SHA-256 / HMAC-SHA-256 primitives + computeRequestFingerprint helper + golden vectors.
  • EvaluationRef + EvaluationTarget discriminated union (7 kinds) + EvaluationScore discriminated union (4 shapes).
  • CapturePolicy shape + DEFAULT_CAPTURE_POLICY (strict, content off) + PERMISSIVE_CAPTURE_POLICY (debug-mode, content on).
  • Full Zod schema catalog + eventSchemaFor() factory + anyObservabilityEventSchema.
  • buildEvent, emitLifecycleEvent, emitEvaluation, emitRaw helpers.

Nothing in this package fires from anywhere in the runtime yet. Runtime instrumentation lands in alpha.29.

@llm-ports/core additions

  • withObservabilityContext(port, context) — Proxy-based scoped-port wrapper. Merges caller-supplied CorrelationContext + TraceContext + Baggage into a port-scoped context that adapters can retrieve via getObservabilityContext(port). WeakMap-stored.
  • Three new typed error classes:
    • CreditExhaustionError — provider returned 402 or an insufficient-balance body. Walk-worthy under defaultShouldFallback.
    • ProviderMalformed400Error extends BadRequestError — 400 caused by the provider's request-schema drift, not the caller. Walk-worthy.
    • AdapterInternalError — an adapter-internal JS runtime error (a TypeError, ReferenceError, SyntaxError, or the adapter's own defensive throw). Abort-worthy.
  • defaultShouldFallback(err) policy function — canonical walk-table semantics. Walk-worthy: RateLimitError, ServiceUnavailableError, CreditExhaustionError, ProviderMalformed400Error, ContextWindowExceededError, ContentPolicyViolationError, ImageTooLargeError, ContentBlockUnsupportedError. Abort-worthy: AuthenticationError, generic BadRequestError, AdapterInternalError, InvalidImageUrlError, contract errors.
  • Fixes: wrapProviderError now propagates modelId into ContextWindowExceededError and ContentPolicyViolationError (TD-LLMP-16); defensive tools: {} default on runAgent + local JS runtime errors classified as AdapterInternalError (TD-LLMP-17); attemptValidationRepair normalizes Unicode confusables (curly hyphens, quotes, and unusual spaces) on invalid_enum_value retries (TD-LLMP-18).

New package: @llm-ports/adapter-codex

Subprocess-driven adapter for OpenAI Codex CLI. Runs codex exec --json --cd DIR -m MODEL -s SANDBOX PROMPT as a subprocess and parses codex's line-delimited JSON output. Exposes only runAgent on its port surface; generateText / generateStructured / streamText / streamStructured throw AdapterInternalError (Codex is an agent runtime, not a raw-completion runtime).

providerExtras.codex on RunAgentOptions:

ts
providerExtras: {
  codex: {
    workingDirectory: string;                 // required
    sandbox?: "read-only" | "workspace-write" | "danger-full-access";
    autoApprove?: boolean;                    // --dangerously-bypass-approvals-and-sandbox
    model?: string;                           // -m MODEL
    imageFiles?: string[];                    // -i IMAGE (repeatable)
  }
}

Shape A passthrough governance: the operator supplies codex with its own OpenAI credentials (env vars or ~/.codex/auth.toml). @llm-ports does NOT route codex's LLM traffic; the adapter owns lifecycle observability only.

New package: @llm-ports/adapter-aider

Subprocess-driven adapter for the Aider CLI. Runs aider --no-stream --yes-always --message "<prompt>" [files...] with cwd set to providerExtras.aider.workingDirectory. Same shape: runAgent-only surface, other methods throw. Same Shape A passthrough governance.

providerExtras.aider on RunAgentOptions:

ts
providerExtras: {
  aider: {
    workingDirectory: string;                 // required
    files?: string[];                         // positional file args
    model?: string;                           // --model MODEL
    editFormat?: string;                      // --edit-format FMT
    yesAlways?: boolean;                      // --yes-always (default true)
    verbose?: boolean;                        // --verbose
    mapTokens?: number;                       // --map-tokens N
  }
}

Migration steps

There are no code changes required. The migration is:

  1. Update package.json peer deps for every @llm-ports/* you consume:
    • "@llm-ports/core": "^0.1.0-alpha.28"
    • "@llm-ports/adapter-openai": "^0.1.0-alpha.28" (and any other adapters you use)
    • Add "@llm-ports/observability-contract": "^0.1.0-alpha.28" if you plan to construct observability events (optional).
  2. pnpm install (or npm install).
  3. pnpm build — confirm nothing broke.

That's it.

Consuming the new observability contract

Alpha.28 ships the types but not the emission. Nothing in the Registry fires yet. If you want to emit conformant events from your own code paths right now (e.g. from a custom retry loop, a subprocess-driven agent, or a non-port pipeline), the contract package's buildEvent + emitLifecycleEvent helpers are ready:

ts
import {
  buildEvent,
  createCollectingSink,
  newOperationId,
  type EmitterConfig,
} from "@llm-ports/observability-contract";

const sink = createCollectingSink();
const config: EmitterConfig = {
  sink,
  source: { library: "my-app", library_version: "1.0.0" },
};

const operationId = newOperationId();
const event = buildEvent(
  config,
  "llm.operation.started",
  { operation_id: operationId },
  { task_type: "triage", method: "generateText", provider_chain: ["openai"] },
);
sink.emit(event);

Registry-driven emission (no manual construction) lands in alpha.29.

When to consider adopting

  • You're on a legacy release (alpha.27 or earlier). Bump. Everything is additive.
  • You want the new error classes. CreditExhaustionError, ProviderMalformed400Error, AdapterInternalError are useful for finer-grained failure classification.
  • You have a subprocess-driven agent (Codex, Aider) in your infrastructure. The new adapters give you a uniform LLMPort.runAgent surface for them.
  • You want to build custom observability plumbing today without waiting for the Registry to emit natively (that arrives in alpha.29).

Downgrade / rollback

Trivial: revert the version pins to 0.1.0-alpha.27. Nothing else to undo.

MIT License