Skip to content

Migrating to 0.11

0.11 is additive with one required action: bump your Effect beta. The bulk of the internal diff is the mechanical ripple of that bump; the public surface is otherwise source-compatible.

Everything else is new: a DeepResearch capability with four providers, native grounding through provider-hosted tools on OpenAI / Gemini / Anthropic, and a @effect-uai/ai-sdk package that serves a Vercel AI SDK useChat frontend from an effect-uai loop.

If you hit a 0.11 compile error that looks like an older rename (tool arrays, Tool.streaming, durationSeconds, executeAll, Outcome), you are crossing an earlier breaking release. Apply the 0.10 page and the ones before it first.

Required: bump Effect

The effect peer dependency moves from a pin to a range:

BeforeAfter
4.0.0-beta.57>=4.0.0-beta.94 <5.0.0

Upgrade your app to effect@4.0.0-beta.94 or newer:

Terminal window
pnpm add effect@">=4.0.0-beta.94 <5.0.0"

That is the whole migration. effect-uai’s own API did not rename anything; run pnpm typecheck after the bump and fix any Effect-level beta changes in your own code (the effect betas move fast between .57 and .94).

What’s new: DeepResearch (additive)

Submit a question, a provider runs a long-running background research job (many web searches over minutes), and you collect one cited report. It is the provider-hosted counterpart to the hand-rolled deep-research agent.

import { researchStream } from "@effect-uai/core/DeepResearch"
import * as Items from "@effect-uai/core/Items"
import * as Turn from "@effect-uai/core/Turn"
researchStream({ history: [Items.userText(question)] }).pipe(
Stream.runForEach((event) =>
Match.value(event).pipe(
Match.tag("WebSearchCall", (e) => Console.log(` · ${e.status}`)),
Match.tag("TextDelta", (e) => write(e.text)),
Match.tag("TurnComplete", (e) => printSources(Turn.citations(e.turn))),
Match.orElse(() => Effect.void),
),
),
)

The stream terminates in TurnComplete, whose turn is a plain Turn (project it with Turn.assistantText / Turn.citations / Turn.decodeStructured); it is the same value the blocking research(request) and the detached collect(ref) return. Every provider is modeled as a background job, so the surface is uniform:

  • research(request) submits and polls to the terminal Turn.
  • researchStream(request) submits and forwards live TurnEvents.
  • submit / status / collect / streamFrom / cancel are the detached path. A ResearchJobRef is serializable { _tag, provider, id } data, so you can submit now and collect from a later process.

Four providers register the generic DeepResearch tag plus a provider-typed tag for the narrowed model / knobs:

  • @effect-uai/responses/OpenAIDeepResearch (o3-deep-research, real streaming).
  • @effect-uai/google/GoogleDeepResearch (Gemini Interactions, real streaming).
  • @effect-uai/perplexity/PerplexityDeepResearch (sonar-deep-research, poll-only with a synthesized progress stream).
  • @effect-uai/exa/ExaDeepResearch (exa-research, poll-only, with a provider-typed outputSchema for structured JSON output).

@effect-uai/core/Job (the generic poll-driven job primitive) and @effect-uai/core/Citation (the provider-agnostic source / span model) ship alongside. See native deep research.

What’s new: native grounding (additive)

Provider-hosted tools now render end to end. Put a provider tool in a Toolkit next to your function tools and the adapter maps it to the model’s native tools entry, so the model can search the web, ground against Google Search, run code, or read files with no extra loop wiring.

import * as Toolkit from "@effect-uai/core/Toolkit"
import { webSearchTool } from "@effect-uai/responses/ResponsesTools"
const toolkit = Toolkit.make(myFunctionTool, webSearchTool())
// pass `toolkit` to streamTurn as usual; OpenAI runs the search itself

Each provider ships its hosted tools:

  • @effect-uai/responses/ResponsesTools: webSearchTool, codeInterpreterTool, fileSearchTool.
  • @effect-uai/google/GeminiTools: googleSearchTool, urlContextTool, codeExecutionTool.
  • @effect-uai/anthropic/AnthropicTools: webSearchTool, codeExecutionTool.

A provider tool the target adapter cannot render (a foreign provider, or an unrecognized config) fails a typed AiError.Unsupported rather than being silently dropped. Tool.isProviderTool / Tool.providerToolsOf are the new core helpers that partition provider tools out of a toolkit. See native grounding.

What’s new: AI SDK compatibility (additive)

@effect-uai/ai-sdk bridges a Vercel AI SDK frontend to an effect-uai loop. Keep your @ai-sdk/react useChat client as it is and serve it from your loop instead of streamText:

import * as Messages from "@effect-uai/ai-sdk/Messages"
import * as UIMessageStream from "@effect-uai/ai-sdk/UIMessageStream"
const history = Messages.decodeMessages(messages) // UIMessage[] -> HistoryItem[]
const sse = UIMessageStream.toUIMessageStream(agent(history)) // InteractionEvent -> SSE.Event

decodeMessages decodes the posted UIMessage[] into HistoryItems; toUIMessageStream projects the loop’s InteractionEvent stream onto the AI SDK UI Message Stream protocol (v1) as SSE.Events. The package owns no HTTP layer, so it drops into any server. See examples/ai-sdk-next for a full Next.js chat route.

This package was published at 0.10.0 outside the release train; from 0.11 it is fixed to the shared version like every other package.

Behavior changes (no rewrite)

  • Items.UrlCitation (and the Annotation view) widened to the provider-agnostic citation shape: start_index / end_index are now optional and cited_text / marker were added. Reads stay compatible (number becomes number | undefined); a provider populates whichever anchor it has.
  • Tool.make and Tool.provider accept an Effect Schema directly as inputSchema (adapted internally, Input still inferred). Wrapping in Tool.fromEffectSchema still works and is unchanged.
  • Toolkit.namespace now preserves a tool’s typed error E and requirement R through the prefixing rewrite.
  • @effect-uai/core/SSE and @effect-uai/core/JSONL are now backed by Effect’s unstable/encoding primitives; framing across chunk boundaries is unchanged in contract, more correct in edge cases.
  • Mistral no longer synthesizes a TurnComplete for a truncated or failed stream, so a halted turn surfaces as a failure instead of a bogus completion.

Migration order

  1. Bump your app to effect@>=4.0.0-beta.94.
  2. Run pnpm typecheck and fix any Effect-beta changes in your own code.

Everything under “What’s new” is additive: add the layer (or the tool, or the package) and your capability-tag code resolves.