Deuz SDK 2.0 is out — stores, guardrails, handoffs, and zero-config MCP. What is new in 2.0
Deuz SDK

Introduction

A pure, web-first, multi-provider TypeScript AI SDK with zero runtime dependencies and one canonical streaming wire.

@deuz-sdk/core is a from-scratch TypeScript AI SDK that talks to Anthropic, OpenAI, xAI Grok, Google Gemini, Vertex AI, Azure, Bedrock and Yunwu through one canonical streaming protocol. It depends on no other AI SDK, ships zero runtime dependencies, and runs anywhere fetch runs — Node, Deno, Bun, and Vercel/Cloudflare Edge.

Reach for it when you want full control over the wire, deterministic and replayable behaviour in tests, and no vendor lock-in at the streaming or UI layer. Reach for something else if you want a hosted agent platform, a prompt IDE, or a batteries-included RAG-as-a-service — this is a library, and everything stateful is a seam you fill.

60 seconds to your first result

1. install
npm init -y && npm pkg set type=module   # ESM, so top-level await works
npm install @deuz-sdk/core
2. put a key in your environment
export ANTHROPIC_API_KEY=sk-ant-...
3. hello.ts
import { generateText } from '@deuz-sdk/core';
import { createAnthropic } from '@deuz-sdk/core/anthropic';

// The SDK core never reads process.env — you read the key, you pass it in.
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });

const { text, usage, finishReason } = await generateText({
  model: anthropic('claude-opus-4-8'),
  prompt: 'Say hello in exactly three words.',
});

console.log(text);
console.log(finishReason, `${usage.inputTokens} in / ${usage.outputTokens} out`);
4. run it
node --experimental-strip-types hello.ts   # Node 22.6+
# or: npx tsx hello.ts

Swap generateText for streamChat and you get tokens as they arrive:

stream.ts
import { streamChat } from '@deuz-sdk/core';

// Returns SYNCHRONOUSLY. No request has been sent yet.
const res = streamChat({
  model: anthropic('claude-opus-4-8'),
  prompt: 'Write a haiku about TypeScript.',
});

// The request starts here, on the first pull.
for await (const chunk of res.textStream) process.stdout.write(chunk);

console.log('\n', await res.usage);

That call did more than it looks like: the request was normalized to a canonical message shape, retried up to twice if the provider failed before the first byte, guarded by a time-to-first-token and a total timeout, and normalized back into a canonical delta stream before your for await saw a single character.

Pick the row that matches what you are building. Each is two or three pages, not the whole site.

You are buildingRead, in order
A chatbot with a UIQuickstartUI streamingReact hooks
A tool-using agentDefining toolsThe tool loopcreateAgent
A coding agentToolsCompute sandboxCoding agent cookbook
A long-running / autonomous agentAutonomyCompactionDurable runtime
Structured data extractiongenerateObjectstreamObject
Retrieval over your own documentsRAGEmbeddingsMemory
Something that must not lose statePersistent storesChat persistenceDurable runtime
Something users can attackRequest validationGuardrailsClient tools
A multi-agent systemSub-agentsHandoffs
A port from another SDKMigrating from the Vercel AI SDK

Whatever you build, two pages pay for themselves early: Error handling (the taxonomy you branch on) and Dependencies & clients (how you make any of it testable).

The mental model

Three ideas explain almost every design decision in this SDK. If you internalize them, the API stops having surprises.

1. One canonical line

Every request is normalized to a canonical Message[] / Part[] shape before it hits a wire, and every response is normalized to a canonical delta stream (StreamPart) before any orchestration or consumer sees it. Adapters never hand a provider's raw bytes to a caller.

Request:  canonical Message[]/Part[]  ->  adapter (one of 4 wires)  ->  upstream fetch
Response: upstream SSE  ->  robust parser  ->  CANONICAL DELTA STREAM
          (text-delta | reasoning-delta | tool-call-delta | source | finish)
          ->  inference orchestration (retry / timeout / tool-loop)
          ->  (a) canonical stream to the consumer   (b) versioned Deuz UI wire

Without this normalization, abort, retry-after-first-byte, multi-wire merging, and typed UI events are all impossible — you cannot resume or reinterpret bytes you already forwarded. The practical consequence for you: application code that switches on part.type works identically on Anthropic, OpenAI, Gemini and every OpenAI-compatible host. Keep a default case in that switch — StreamPart is an open union and new variants are added additively.

2. One seam for everything non-deterministic

The core touches only Web APIs. Everything stateful or ambient — HTTP, the clock, logging, tracing, metering, the circuit breaker, API keys, id generation — is injected through a single Dependencies object, with sensible defaults applied for you.

deterministic-by-construction.ts
import { generateText } from '@deuz-sdk/core';
import { createMockModel } from '@deuz-sdk/core/testing';

// No network, no wall clock, no randomness: the same test result every run.
const { text } = await generateText({
  model: createMockModel({ responses: [{ text: 'hello' }] }),
  prompt: 'hi',
});

That one seam is why Date.now(), Math.random(), process.env and console.* are lint errors inside the core. It is also why the same build runs unchanged on Cloudflare Workers: there is nothing ambient left to be missing. See Dependencies & clients.

3. streamChat returns synchronously and never throws (the "G2" rule)

streamChat and streamObject do no async work in the call body. They hand you a result object immediately; the network pump starts lazily on the first access of any output. A failure — including a missing API key — never becomes a synchronous throw. It arrives as an error part on fullStream, a throwing textStream, and rejected usage / finishReason promises.

const res = streamChat({ model, prompt: 'hi' }); // never throws, sends nothing yet
for await (const part of res.fullStream) {
  if (part.type === 'error') { /* the only failure channel */ }
}

Two consequences worth knowing before you hit them:

  • You can construct and pass around a StreamChatResult with zero I/O cost — handy for pre-binding a client or building a response wrapper.
  • If nobody reads the stream, nothing runs — no request, no onFinish, no persistence. That is what consume() is for, and it is the single most common serverless mistake.

The buffered calls (generateText, generateObject, embed) are ordinary promises and do reject. Wrap those in try/catch; wrap the streaming ones in an error-part check.

Why it exists

Many AI integrations become hard to test because runtime state, provider-specific stream shapes, and UI wires leak into app code. @deuz-sdk/core keeps those seams explicit: dependencies are injected, provider streams are normalized, and the UI wire is owned by the SDK.

PrincipleWhat it means in practice
Pure coreNo environment-variable or console coupling; inference consumes replaceable seams for clock, ids, logging, metering, circuit breaker, and keys. Documented host defaults remain injectable, so tests are deterministic and replayable.
Edge-safeOnly Web APIs (fetch, Web Streams, TextDecoder, WebCrypto, atob/btoa). node:* and Buffer are forbidden by lint; Node-only code lives in dedicated …/node subpaths.
Zero depsThe chat core ships nothing in dependencies. Heavy or stateful things are optional peers or injected seams.
No vendor lockOur own canonical delta stream and our own versioned UI wire. The SDK never proxies a provider's raw SSE bytes to your client.
Secrets never leakAPI keys are masked in every log, error, and span path. This is a regression-tested invariant — see secret redaction.

New in 2.0

Almost everything below is additive — no existing type gained a required field, every 1.9 call still compiles, and every stored checkpoint still loads. Four loud breaks are listed on What is new in 2.0: NotImplementedError is gone, two unions gained members, the streaming compaction part always carries trigger, and generateObject / streamObject now refuse mcp / guardrails / doneWhen.

  • Persistent stores — SQLite, Redis and Postgres packs behind the memory / chat / session / run seams → Persistent stores
  • Guardrails — pass / block / rewrite on the run's input, each tool call, and the final answer → Guardrails
  • Handoffshandoff() transfers the whole run to another agent, history and all → Handoffs
  • Zero-config MCP, plus OAuth 2.0, sampling, roots, reconnect and a connection pool → MCP
  • compactMessages(), a rolling summary, and automatic context-overflow recovery → Compaction
  • Speech, transcription and videoSpeech · Transcription · Video
  • Eight more providers, two of them keyless → Local models · OpenAI-compat hosts
  • runtimeContext — request-scoped facts travel with the call instead of a closure per request

The whole surface, including what is still missing, is on one page: What is new in 2.0. The previous release's page is still there too: What is new in 1.9.

Feature overview

AreaWhat you getPage
Streaming chatLazy, never-throwing canonical stream with usage and finish-reason promisesstreamChat
Text generationBuffered single-turn or multi-step textgenerateText
Structured outputSchema-typed objects (Standard Schema or JSON Schema) with auto json/tool strategygenerateObject
Structured output (streaming)Progressive DeepPartial<T> via partialObjectStream with a validated final objectstreamObject
Embeddingsembed / embedMany with auto-batching and concurrency capsEmbeddings
Agentic toolsParallel, self-healing tool loop with runaway guardsTools and Tool loop
Memorymem0 extract/reconcile pipeline over a vector or Obsidian-markdown storeMemory
RAGMIME sniff, chunkers, dense + lexical (BM25) hybrid retrievalRAG
SkillsSKILL.md parser with progressive disclosureSkills
MCPZero-config servers in the loop, OAuth 2.0, sampling, roots (HTTP/SSE edge-safe; stdio Node-only)MCP
Guardrails (2.0)onInput / onToolCall / onOutput — pass, block or rewrite, reported on the streamGuardrails
Handoffs (2.0)Transfer the run to another agent, history and allHandoffs
Persistent stores (2.0)SQLite / Redis / Postgres behind the four storage seamsPersistent stores
Autonomy (1.8)Workspace memory, CodeAct sandbox, planner/verifier, browser tools, background runs, live plan/activity feedAutonomy
Image generationSync OpenAI-compatible generation, async Midjourney, Yunwu relayImage generation
Speech / transcription (2.0)generateSpeech (OpenAI, ElevenLabs) and transcribe (OpenAI, Deepgram)Speech · Transcription
Video (2.0)Async submit → poll → download over the OpenAI Videos shapeVideo
UI streamingtoDeuzStreamResponse (server) and readDeuzStream (client)UI streaming
React hooksuseChat (client-tool round-trips + approval pauses + live plan/activity) and useObject over the Deuz UI wireReact hooks
MiddlewarewrapModel with logging, caching, PII redaction, injection guardMiddleware
PricingOptional token-to-USD cost tablePricing

Supported providers

All providers normalize to the same canonical stream, so application code is provider-agnostic.

ProviderSubpathNotes
Anthropic@deuz-sdk/core/anthropicMessages API (/v1/messages)
OpenAI@deuz-sdk/core/openaiChat Completions and Responses API + openaiEmbedding
Azure OpenAI@deuz-sdk/core/azureDeployment URL + api-key / Entra Bearer
Amazon Bedrock@deuz-sdk/core/bedrockMantle OpenAI-compat (Bearer API key; no SigV4 SDK)
xAI Grok@deuz-sdk/core/xaiOpenAI-compatible wire
Google Gemini@deuz-sdk/core/googleCompat (createGoogle) and native generateContent (createGoogleNative) + googleEmbedding
Vertex AI@deuz-sdk/core/vertexClaude and Gemini on Vertex with OAuth2 Bearer auth
Voyage AI@deuz-sdk/core/voyageEmbeddings
Yunwu@deuz-sdk/core/yunwuUnified relay — chat, image, embed at /v1, Midjourney at the root
OpenAI-compat hosts@deuz-sdk/core/providersMistral, DeepSeek, Qwen, Kimi (createKimi / Moonshot), Groq, Together, OpenRouter, Cerebras, Fireworks, GLM, MiniMax + 2.0's Perplexity, Cohere, DeepInfra, NVIDIA NIM, SambaNova, Hyperbolic + Azure/Bedrock re-exports + createProviderRegistry
Local models (2.0)@deuz-sdk/core/providersOllama and LM Studio — keyless, localhost by default

That is 29 built-in provider ids across four chat wires. A provider factory returns a tiny LanguageModel descriptor; the model registry is the single source of truth for per-model capabilities (vision, tools, reasoning, structured output, caching, native PDF, audio, context window). Unknown model slugs do not throw — they fall back to conservative defaults and record a warning, so new model releases work without a code change.

Quality bar

  • Over 1,800 tests across 113 files (vitest with golden-replay SSE fixtures and deterministic mock models — no real network), plus a separate type-contract lock run with --typecheck
  • tsc strict (moduleResolution: "Bundler", verbatimModuleSyntax, noUncheckedIndexedAccess)
  • eslint with edge-safety enforced, publint --strict, and attw (Are The Types Wrong) all green
  • Dual ESM + CJS build with .d.ts for every subpath export

Next steps

  • Installation — runtimes, optional peers, and how to pick a subpath
  • Quickstart — a complete working example end to end
  • streamChat — the streaming entry point in depth

On this page