Repository navigation
v0.4.0
Durable sessions, provider retries, and three hook-reliability fixes. Nothing is removed and no existing signature changes, but three behaviours an existing consumer observes do change — see Upgrading from 0.3.x below.
Upgrading from 0.3.x
- A closed socket no longer erases the conversation. The WebSocket close handler used to call
sessionStore.delete(sessionId)unconditionally, so behind a durable store every conversation was written and then destroyed the moment the customer closed their tab.deletenow has no engine-internal caller and is purely your erasure primitive. To restore the old behaviour, passonDisconnect: sessionId => store.delete(sessionId)— but do not point that atMongoSessionStore, wheredeletedestroys the document. afterModelResponsenow reports the whole turn's usage. It receivestotalUsage, the sum across every provider call in the turn, rather than the last round alone. Billing and audit consumers will see totals rise on tool-heavy turns; that is the corrected figure.onErrornow fires for rejections that are notErrorinstances. A non-Errorvalue is wrapped in anErrorwhosecauseis the original, so a hook typed againstErrorkeeps compiling and a hook that wants the raw value can reach it.SessionStoremembers are nowAwaitable<T>. A synchronous store needs no change.- New optional peer dependency:
mongodb, loaded only ifMongoSessionStoreactually connects.
Added
MongoSessionStore— a durable session store, exported asMongoSessionStoreandcreateMongoSessionStore. Takes a collection, a client with a database name, or a connection URL; nothing reads the environment for you. No credential is ever written:authHeaders,authorization,cookieandhostare stripped at any depth, and the samestripCredentialKeysis exported for stores you write yourself.SessionStore.save(session)— the write signal a durable store needs, called once per processed user message from the same path that runsafterSession, so it fires for a turn that ended in a provider error as well as one that succeeded. What it throws is logged and the turn continues: a database outage is not a reason for the agent to stop answering.SessionStore.evict(sessionId)and a boundedInMemorySessionStore, which now takes{maxAgeMs}(eight hours by default). There is no timer — a library-owned interval would keep your process alive.withRetry(provider, policy)— wraps anyAIProviderwith per-attempt retry and two separate deadlines. Retries stop once the first chunk has been handed off, because streamed output cannot be un-sent; a stalled stream surfaces as anAIErrorwith codeTIMEOUT. Defaults:maxAttempts: 3,baseDelayMs: 500,maxDelayMs: 5000,firstChunkTimeoutMs: 30000,idleChunkTimeoutMs: 30000, exponential backoff with full jitter.HalEngineConfig.resilience?: RetryPolicy— when present,createHalEnginewraps the configured provider; when absent, behaviour is unchanged.
Fixed
onErrorandafterSessionnow run whenbeforeSessionthrows. The call sat above thetry, so a failure during session setup skipped both hooks — and leaked whateverbeforeSessionhad allocated on exactly the path where the allocation failed.
Full detail, including the entries not repeated here, is in CHANGELOG.md.