Deuz SDK 2.4 è uscito — memoria versionata e compilatore di contesto, supervisore degli obiettivi e scheduler durevole, runner Docker locale e stream di chat durevoli. Novità della 2.4
Deuz SDK
Advanced

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.

Il contenuto delle pagine di documentazione è in inglese. Navigazione, ricerca e interfaccia seguono la lingua scelta.

@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 these values, and the types named below:

ExportKind
streamChat, generateText, generateObject, streamObjectfunctions
stepCountIs, hasToolCall, totalTokensExceed, costExceeds, durationExceedsfunctions (stop conditions)
tool, filePart, imagePartfunctions (typed tools and content parts, 1.9)
agentTool, handofffunctions (sub-agents; handoffs, 2.0)
promptInjectionGuardrail, maxOutputLength, maxToolResultLengthfunctions (guardrails, 2.0; maxToolResultLength, 2.2)
anthropicWebSearch, openaiWebSearch, googleSearchfunctions (server tools)
createClient, resolveDependencies, createAgentfunctions (createAgent, 1.9)
getModelCapabilities, resolveModelProfilefunctions (the capability registry, 1.9; model profiles, 2.3)
createInMemorySessionStore, resumeFromCheckpoint, resumeStreamFromCheckpoint, resumeDeuzChatResponse, serializeCheckpoint, deserializeCheckpoint, createApprovalSignerfunctions (durable runtime, 1.5 and later)
createAssistantTurn, applyUIPart, sealAssistantTurn, assistantMessageFromTurn, clientToolResultMessage, uiFromMessages, canonicalFromUI, userMessageFromInput, filesToImageParts, dropTrailingAssistant, branchBeforeUserMessage, createInMemoryChatStore, serializeChatRecord, deserializeChatRecordfunctions (the chat engine, 1.7 and later)
validateChatRequest, parseDeuzChatRequestfunctions (chat request validation, 1.9)
createCallbackObserver, createMemoryObserver, composeObservers, filterObserver, summarizeRunfunctions (observation, 1.6)
createOtelTracer, createOtelObserver, otelReadyfunctions (the OpenTelemetry bridge, 1.9)
DeuzError, isDeuzError, NoObjectGeneratedError, BreakerOpenError, CheckpointNotFoundError, CheckpointVersionError, UnpricedModelErrorerror classes and the isDeuzError guard (CheckpointVersionError and UnpricedModelError, 2.3)
createInMemoryLeaseProvider, agentRunLeaseKey, findStaleAgentRunseverything @deuz-sdk/core/ops exports (2.2; the last two, 2.3)
evolve, resumeEvolve, createInMemoryPopulationStore, evolveCandidateId, parseEvolveBlocks, parseSearchReplace, applySearchReplace, extractFullRewrite, buildMutationPrompt, EvolvePatchError, EvolveConflictError, EvolveLeaseErroreverything @deuz-sdk/core/evolve exports (2.2)
createScheduler, createInMemoryClaim, parseCron, nextOccurrence, previousOccurrence, occurrencesBetween, handleSignal, verifyGitHubWebhook, verifySlackRequest, verifyHmacSignature, zonedOccurrencesBetween, nextZonedOccurrence, timeZoneOffset, assertTimeZoneeverything @deuz-sdk/core/schedule exports (2.2; the time zone helpers, 2.4)
AgentToolDef, HandoffAgentDef, HandoffOptions, InferToolInput, InferToolOutput, ModelCapabilities, ModelProfile and its parts, DeuzClient, AgentDef, DeuzAgent, AgentCallOptions, AgentObjectCallOptions, ResumeOptions, ResumeDeuzChatOptions, SignedApprovalPayload, CreateApprovalSignerOptions, ApprovalSigner, DeuzErrorJSON, the chat types (UIMessage, ChatRecord, ChatStore, …), DeuzChatRequest, ValidateChatOptions, ValidateChatResult, MemoryObserver, RunSummary, OtelTracerOptions, and the types of /ops, /evolve and /scheduletypes
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
@deuz-sdk/core/ops/redislazy redis peer (or an injected client)a TCP client
@deuz-sdk/core/swarm/redislazy redis peer (or an injected client)a TCP client
@deuz-sdk/core/swarm/sqlite · /ops/sqlite · /evolve/sqlitelazy node:sqlite (or an injected handle)a database file
@deuz-sdk/core/swarm/postgres · /ops/postgresan injected PgClientLikea TCP client
@deuz-sdk/core/mcp/server/stdiolazy @modelcontextprotocol/server + process.stdin / process.stdoutserves MCP over a process's standard streams (2.3)
@deuz-sdk/core/headlesslazy node:fs/promises, node:path, node:processproject instructions, skill folders, the doctor's environment and Node version (2.3 preview)
@deuz-sdk/core/memory/versioned/sqlite · /goal/runtime/sqlitelazy node:sqlite (or an injected handle)a database file (2.4)
@deuz-sdk/core/memory/versioned/postgres · /goal/runtime/postgresa pool with connect() you pass in (pg.Pool or compatible), or the lazy pg peer with connectionStringa TCP client (2.4)
@deuz-sdk/core/compute/dockerlazy node:child_process, the Docker CLI (dockerPath, default docker); its SQLite process store, lazy node:sqlitestarts and inspects local Docker containers (2.4 preview)

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, or RedisScriptClient for the Redis ops and swarm stores, and 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.

The 2.4 subpaths

2.4 adds nine subpaths: four edge-safe, five Node-only (in the table above). npm run verify:runtime bundles a consumer of each edge-safe one with esbuild's browser platform and fails if the graph reaches a Node-only module, and it checks that each Node-only one fails that boundary. None of them is re-exported from /edge; import each from its own subpath.

Edge-safe subpathWhat it holds
@deuz-sdk/core/memory/versionedcreateMemorySession, createVersionedMemoryTools, importLegacyMemory, createInMemoryVersionedMemoryStore, defineAccountedMemoryLLM, the errors and the types. Its SQLite and PostgreSQL stores are /memory/versioned/sqlite and /memory/versioned/postgres
@deuz-sdk/core/contextcompileContext, createContextCompiler (preview), the memory, RAG, transcript and skills sources, createWorkingMemory, applyWorkingMemoryPatch, workingMemoryTools and the token estimator
@deuz-sdk/core/goal/runtimecreateGoalSupervisor, createNativeRunLauncher, createDurableScheduler, createWebhookHandler, toNodeHandler, verifyGoalAcceptance and createInMemoryGoalRuntimeStore. Its SQLite and PostgreSQL stores are /goal/runtime/sqlite and /goal/runtime/postgres
@deuz-sdk/core/skills/learningproposeSkillFromRun (experimental)
  • /goal/runtime. createNativeRunLauncher loads the native executor with a lazy import(), so the entry does not bundle it. createWebhookHandler returns a Fetch handler, (request: Request) => Promise<Response>, that verifies signatures with WebCrypto. toNodeHandler imports nothing from node:http; it adapts that handler to Node's http server or Express when you serve it from Node. The supervisor and the durable scheduler each have start(), a loop on deps.clock timers, and tick(), one pass; on a runtime without a long-lived process, call tick() from a scheduled trigger.
  • Existing edge-safe entries grow. /agent adds createInMemoryAgentRunStoreV3 and the V3 types, /chat the in-memory conditional chat store and transcript index, /ui the in-memory durable stream store and the capability handshake helpers, /schedule (and so /edge) the time zone helpers, and /workspace digestWorkspace, which hashes with WebCrypto. All of it stays edge-safe.
  • Existing Node-only entries grow. /ops/sqlite and /ops/postgres add the V3 run stores, /stores/sqlite and /stores/postgres the conditional chat stores (each with its transcript index) and the durable stream stores, and /stores/redis the Redis durable stream store. Each new store borrows a handle, pool or client you pass in, or opens its own from a path, connection string or URL (lazy node:sqlite, or the pg or redis peer).

The durable stores of every 2.4 feature are on Node-only subpaths, and the edge-safe entries ship only in-memory stores, which live as long as the isolate. The store contracts (VersionedMemoryStore, GoalRuntimeStore, AgentRunStoreV3, ConditionalChatStore, TranscriptIndex, DurableStreamStateStore) are plain interfaces: a store backed by a driver your edge runtime has can implement one in your own module, as the injected-client paragraph above describes for the 2.3 stores.

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

In questa pagina