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
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@alphaTen 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>) withspec_version,event_id,event_type,occurred_at,emitted_at,source,operation_id,attempt_id,parent_operation_id,trace_context,sequence, anddata. - 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. ErrorInfoshape +CauseCategory(8-value rollup) + staticERROR_TYPE_TO_CATEGORYmap +errorTypeToCauseCategory()resolver.- Nested
CacheStats { provider_cache?, semantic_cache? }plus helpers. RequestFingerprintcanonicalization rules v1 (NFC, LF, sorted keys, 16 allowed request keys) + SHA-256 / HMAC-SHA-256 primitives +computeRequestFingerprinthelper + golden vectors.EvaluationRef+EvaluationTargetdiscriminated union (7 kinds) +EvaluationScorediscriminated union (4 shapes).CapturePolicyshape +DEFAULT_CAPTURE_POLICY(strict, content off) +PERMISSIVE_CAPTURE_POLICY(debug-mode, content on).- Full Zod schema catalog +
eventSchemaFor()factory +anyObservabilityEventSchema. buildEvent,emitLifecycleEvent,emitEvaluation,emitRawhelpers.
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-suppliedCorrelationContext+TraceContext+Baggageinto a port-scoped context that adapters can retrieve viagetObservabilityContext(port). WeakMap-stored.- Three new typed error classes:
CreditExhaustionError— provider returned 402 or an insufficient-balance body. Walk-worthy underdefaultShouldFallback.ProviderMalformed400Error extends BadRequestError— 400 caused by the provider's request-schema drift, not the caller. Walk-worthy.AdapterInternalError— an adapter-internal JS runtime error (aTypeError,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, genericBadRequestError,AdapterInternalError,InvalidImageUrlError, contract errors.- Fixes:
wrapProviderErrornow propagatesmodelIdintoContextWindowExceededErrorandContentPolicyViolationError(TD-LLMP-16); defensivetools: {}default onrunAgent+ local JS runtime errors classified asAdapterInternalError(TD-LLMP-17);attemptValidationRepairnormalizes Unicode confusables (curly hyphens, quotes, and unusual spaces) oninvalid_enum_valueretries (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:
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:
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:
- Update
package.jsonpeer 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).
pnpm install(ornpm install).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:
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,AdapterInternalErrorare 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.runAgentsurface 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.