Migrating to 0.18
0.18 has one required action: upgrade Effect to 4.0 stable. effect-uai’s own API renames nothing.
If you hit a compile error that looks like an effect-uai rename, you are crossing an earlier breaking release. Apply the 0.16 page and the ones before it first.
Required: upgrade Effect
The effect peer dependency moves to the stable line:
| Before | After |
|---|---|
>=4.0.0-rc.111 <5.0.0 | >=4.0.1 <5.0.0 |
pnpm add effect@">=4.0.1 <5.0.0"Upgrade the @effect/* platform packages you use (@effect/platform-node,
@effect/platform-bun, @effect/vitest, …) to 4.0.1 alongside it. This is
not optional: effect-uai now imports effect/http, effect/socket and the
other modules 4.0 moved out of effect/unstable/*, so a release candidate no
longer resolves.
Effect 4.0 changes you may hit in your own code
None of these are effect-uai changes, but they come with the bump:
| Effect rc | Effect 4.0 |
|---|---|
effect/unstable/http | effect/http (likewise every unstable/* path) |
Encoding.encodeBase64(bytes) | Base64.encode(bytes) from effect/encoding |
Encoding.decodeBase64(text) | Base64.decode(text) from effect/encoding |
Config.redacted("KEY") | Config.Redacted("KEY") |
Config.string, Config.port, … | Config.String, Config.Port, … |
Config.option, Config.orElse and Config.withDefault keep their names.
If you use Effect’s Socket directly: runString and the closeCodeIsError
option are gone. Read with Stream.fromPull(Socket.readerString(socket)), and
note that every close now fails the reader, 1000 included.
@effect-uai/core/WebSocketSession exports isCleanClose for exactly that
check:
import { Stream } from "effect"import * as Socket from "effect/socket/Socket"import { isCleanClose } from "@effect-uai/core/WebSocketSession"
const frames = Stream.fromPull(Socket.readerString(socket)).pipe( Stream.scoped, Stream.catchIf(isCleanClose, () => Stream.empty),)Behavior change: realtime streams fail when the connection drops
The realtime transcribers and synthesizers (OpenAI, ElevenLabs, Inworld, Mistral) used to end their output stream quietly whatever happened to the socket. Now:
- A dropped connection fails the stream with
AiError. A clean server close (1000, 1001, 1005) still ends it normally. - A failing input stream fails the output stream with that error, where it used to be swallowed.
Both were already part of the declared AiError | E error type. If you
handle errors on these streams, there is nothing to change: you now receive
the failures you were already prepared for. If you relied on the stream
always ending cleanly, add the handling you need:
import { Stream } from "effect"import * as Transcriber from "@effect-uai/core/Transcriber"
mic.frames.pipe( Transcriber.streamTranscriptionFrom(request), Stream.catchTag("Unavailable", () => Stream.empty), // treat a drop as the end)What’s new (additive, no migration needed)
@effect-uai/browser:CdpConnectConfig.headersis sent on the CDP WebSocket handshake, for endpoints that want a bearer token (obscura 0.2.3+ withOBSCURA_CDP_TOKEN). Needs Node or Bun.@effect-uai/discord: gateway resumes send the last sequence number seen, so Discord replays the events missed during a reconnect. They used to sendnull.@effect-uai/core/testing/FakeWebSocket:greetedandawaitSent(count)let a test wait for the client instead of sleeping.