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

Edge & Runtimes

Why the core is Web-APIs-only, the guaranteed-safe /edge subpath, the Node-only subpaths, and per-runtime notes for Workers, Vercel Edge, Deno, and Bun.

@deuz-sdk/core runs on Web APIs only. There are no node:* imports, no Buffer, and no process; clock and id sources are replaceable seams with documented Web-API host defaults. The same build therefore ships to Node, Cloudflare Workers, Vercel Edge, Deno, Bun, and the browser. Use the /edge subpath when you want a compile-time guarantee that nothing Node-only sneaks into your edge bundle.

What "edge-safe" means here

The core touches only APIs that exist in every modern JS runtime: fetch, Web Streams (ReadableStream/TransformStream), TextEncoder/TextDecoder, WebCrypto, timers, and atob/btoa. Anything stateful or non-deterministic used by inference is replaceable through the single Dependencies seam — including the HTTP transport, clock, id generation, logging, tracing, and key resolution. When omitted, the clock and id seams use the host's Date.now() and crypto.randomUUID().

The following are banned in ordinary core code by lint, and the build fails if they appear without a narrowly documented default:

BannedUse instead
node:* imports (node:fs, node:crypto, …)Web APIs, or move the code to a …/node subpath
BufferUint8Array / TextEncoder
process (incl. process.env)inject config; read env at the app layer
__dirname / __filenamenot available at the edge
Date.now()deps.clock.now() (or an explicitly injectable helper default)
Math.random()inject randomness via Dependencies
crypto.randomUUID() / crypto.getRandomValues()deps.generateId()
console.*deps.logger

Because the core reads no environment variables, you read your API key at the app layer and pass it into the provider factory. The factory never calls process.env itself.

import { createAnthropic } from '@deuz-sdk/core/anthropic';

// App layer reads env (or a Workers/Deno binding) and passes it in.
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });

The /edge subpath

@deuz-sdk/core/edge re-exports the guaranteed Web-APIs-only subset. Every release bundles representative root, /edge, and provider consumers with esbuild's browser platform and fails if the graph reaches a Node-only module. The browser/jsdom suite then exercises the Responses stream with Web Streams and a custom base URL.

It exports exactly:

ExportKind
streamChat, generateText, generateObject, streamObjectfunctions
stepCountIs, hasToolCall, totalTokensExceed, costExceedsfunctions (stop conditions)
createClient, resolveDependenciesfunctions
agentToolfunction
AgentToolDeftype
anthropicWebSearch, openaiWebSearch, googleSearchfunctions (server tools)
DeuzClienttype
DeuzError, NoObjectGeneratedErrorerror classes
createInMemorySessionStore, resumeFromCheckpoint, resumeStreamFromCheckpoint, serializeCheckpoint, deserializeCheckpoint, createApprovalSignerfunctions (durable runtime, 1.5)
CheckpointNotFoundErrorerror class (1.5)
ResumeOptions, SignedApprovalPayload, CreateApprovalSignerOptions, ApprovalSignertypes (1.5)
everything from the canonical types (Message, Part, Usage, StreamPart, LanguageModel, CommonCallOptions, AgentCheckpoint, SessionStore, …)export type *
edge bundle (guaranteed safe)
import { streamChat } from '@deuz-sdk/core/edge';
import type { StreamPart, Message } from '@deuz-sdk/core/edge';

What /edge deliberately does not re-export: the provider factories (createAnthropic, …), embed/embedMany, the full error subclass hierarchy (RateLimitError, TimeoutError, …), pricing, and middleware. Those are not Node-only — they are just kept off the minimal /edge surface. The provider factories are themselves edge-safe, so import them from their own subpath alongside /edge:

import { streamChat } from '@deuz-sdk/core/edge';
import { createAnthropic } from '@deuz-sdk/core/anthropic';

The root entry (@deuz-sdk/core) is also edge-safe and exports a larger surface (the error hierarchy, pricing, middleware, embed). The difference is contractual, not technical: /edge is the narrow, locked promise. Use it when you want the bundler to fail loudly if a Node-only path is ever pulled in transitively.

Node-only subpaths

A few feature surfaces genuinely need the filesystem, a child process, or a Node-only peer package. These live behind dedicated …/node subpaths that are exempt from the edge-safety lint and reach Node APIs through lazy import() so that importing the core never pulls them in. Do not import these from an edge runtime.

SubpathNeedsWhy it is Node-only
@deuz-sdk/core/rag/nodelazy unpdf / mammoth / xlsx peersPDF / DOCX / XLSX parsing (optional peer packages)
@deuz-sdk/core/skills/nodelazy node:fs/promises, node:pathreads SKILL.md files from disk
@deuz-sdk/core/memory/markdownlazy node:fs/promises, node:pathObsidian-style markdown vault on disk
@deuz-sdk/core/mcp/stdiolazy @modelcontextprotocol/sdk + child processspawns a stdio MCP server
@deuz-sdk/core/mcp/nodelazy node:fs/promises, node:http0600 token file + a loopback OAuth redirect listener
@deuz-sdk/core/stores/sqlitelazy node:sqlite (or an injected handle)a database file
@deuz-sdk/core/stores/redislazy redis peer (or an injected client)a TCP client
@deuz-sdk/core/stores/postgreslazy pg peer (or an injected client)a TCP client

The edge-safe counterparts of these features stay on the Web-APIs-only path: @deuz-sdk/core/rag (text/markdown/CSV parsing in core), @deuz-sdk/core/skills (parse SKILL.md you already loaded), @deuz-sdk/core/memory (the cosine vector backend), and @deuz-sdk/core/mcp (HTTP/SSE transport). Use those at the edge and reserve the …/node subpaths for your Node server.

The two store packs that take an injected client (stores/redis, stores/postgres) still import a Node-only module path, so they stay on this list — but the seam itself (RedisClientLike, PgClientLike) is a plain object, which is how an HTTP-driver-backed store reaches an edge runtime: implement the seam against your driver and keep it in your own module.

2.0's edge-safe additions: handoff() plus promptInjectionGuardrail / maxOutputLength are re-exported from @deuz-sdk/core/edge (all three are pure construction — no clock, no randomness, no I/O), and the @deuz-sdk/core/guardrails subpath itself imports types only. @deuz-sdk/core/speech, /transcription and /video are edge-safe too: /speech and /transcription are deps.fetch and nothing else, and /video adds only deps.clock.setTimeout for its poll — never an ambient timer. compactMessages ships from the root entry rather than /edge, and is pure unless you hand it a summarizer.

Per-runtime notes

The core needs nothing injected to run anywhere that provides fetch and Web Streams — streamChat({ model, messages }) works with zero deps. The notes below cover where you read the API key.

RuntimeNotes
Cloudflare Workersfetch/Web Streams/WebCrypto are built in. There is no process.env; read the key from the env binding passed to your handler and pass it to the factory. Import from /edge for the bundle guarantee.
Vercel EdgeWeb APIs are available; process.env is populated at build/deploy time, so process.env.ANTHROPIC_API_KEY! works. Set export const runtime = 'edge' on the route.
DenoNative fetch/Web Streams. Read the key with Deno.env.get('ANTHROPIC_API_KEY') and pass it in. Import via npm:@deuz-sdk/core specifiers.
Bunfetch/Web Streams are built in and process.env works. The Node-only subpaths also work under Bun if you need them.

You normally do not need to inject deps at all. The injection seam exists for testing and for advanced needs (a custom fetch, a distributed breakerStore, a keyProvider, a logger). See Dependencies for the full list.

Example: a Cloudflare Worker chat endpoint

A streaming endpoint that reads the key from the Worker env binding and returns the canonical text stream as an SSE-friendly Response. streamChat returns synchronously and its body is a Web ReadableStream, so it maps straight onto the Workers Response.

worker.ts
import { streamChat } from '@deuz-sdk/core/edge';
import { createAnthropic } from '@deuz-sdk/core/anthropic';

interface Env {
  ANTHROPIC_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { prompt } = (await request.json()) as { prompt: string };

    const anthropic = createAnthropic({ apiKey: env.ANTHROPIC_API_KEY });

    const result = streamChat({
      model: anthropic('claude-opus-4-8'),
      messages: [{ role: 'user', content: prompt }],
    });

    // Project the canonical text deltas into a Web ReadableStream.
    const encoder = new TextEncoder();
    const body = new ReadableStream<Uint8Array>({
      async start(controller) {
        try {
          for await (const chunk of result.textStream) {
            controller.enqueue(encoder.encode(chunk));
          }
        } catch (err) {
          controller.error(err);
          return;
        }
        controller.close();
      },
    });

    return new Response(body, {
      headers: { 'content-type': 'text/plain; charset=utf-8' },
    });
  },
};

Returning the Deuz UI wire from the edge

toDeuzStreamResponse (from @deuz-sdk/core/ui) is edge-safe and turns a StreamChatResult into the versioned Deuz-protocol SSE Response in one call — ideal for a Worker or Vercel Edge route feeding a Deuz UI client.

route.ts
import { streamChat } from '@deuz-sdk/core/edge';
import { createAnthropic } from '@deuz-sdk/core/anthropic';
import { toDeuzStreamResponse } from '@deuz-sdk/core/ui';

export async function POST(request: Request): Promise<Response> {
  const { messages } = (await request.json()) as {
    messages: { role: 'user' | 'assistant'; content: string }[];
  };

  const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });

  const result = streamChat({
    model: anthropic('claude-opus-4-8'),
    messages,
  });

  return toDeuzStreamResponse(result);
}

See also

On this page