Deuz SDK

Quickstart

Streaming chat in under five minutes — Node script, Next.js route handler, and a browser client.

@deuz-sdk/core is a pure, web-first, multi-provider AI SDK. A provider factory returns a tiny model descriptor; the free functions (streamChat, generateText, generateObject) take that descriptor plus canonical messages and do the rest. The core never reads process.env — you read your key at the app layer and pass it into the factory.

install
npm install @deuz-sdk/core

Requires Node >= 22. Zero runtime dependencies.

1. Stream chat in a Node/TS script

streamChat returns a StreamChatResult synchronously — the network request starts lazily on first access of any output (textStream, fullStream, usage, or finishReason). The synchronous call itself never throws: a failure surfaces as an error part on fullStream and as rejected usage/finishReason promises. (Iterating textStream rethrows that error mid-loop, so a for await over it should be wrapped in try/catch.)

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

// Read the key at the app layer; the SDK core never touches process.env.
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });

const result = streamChat({
  model: anthropic('claude-opus-4-8'),
  messages: [{ role: 'user', content: 'Write a haiku about TypeScript.' }],
});

// Print tokens as they arrive.
for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

// Resolves once the stream finishes.
const usage = await result.usage;
console.log('\n', usage.inputTokens, '->', usage.outputTokens);

Full copy-paste setup, from an empty folder to a running stream:

terminal
npm init -y
npm install @deuz-sdk/core
npm install -D tsx typescript
# paste the chat.ts above, then:
ANTHROPIC_API_KEY=sk-ant-... npx tsx chat.ts

messages is a canonical Message[]. content is either a string (shorthand for a single text part) or a Part[] for multimodal input. The roles are 'system' | 'user' | 'assistant' | 'tool'.

Common call options

These are shared by streamChat, generateText, and generateObject.

OptionTypeDefaultNotes
modelLanguageModelFrom a provider factory, e.g. anthropic('claude-opus-4-8').
messagesMessage[]Canonical conversation.
signalAbortSignalCancels the request; resolves finishReason: 'aborted' with partial usage.
maxRetriesnumber2Pre-first-byte retries only (exponential backoff + jitter, honors Retry-After).
temperaturenumberSampling temperature.
maxOutputTokensnumberCap on generated tokens.
topPnumberNucleus sampling.
stopSequencesstring[]Stop strings.
effort'none' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'Canonical reasoning effort; each adapter maps or clamps it to its own unit (see note below).
headersRecord<string, string>Extra request headers.
onUsage(usage, meta) => voidFires when usage is known.
onFinish(meta) => voidFires once the call completes.
toolsToolSetPassing tools turns on the agentic loop (see Tool calling).

This table is the common base subset. When you pass tools, the agentic loop unlocks more options — summarized here, each documented in full on Tool calling:

GroupOptions
Agent loopmaxSteps, stopWhen, toolChoice, activeTools, prepareStep, maxToolConcurrency
Agent safetyneedsApproval (per-tool), approveToolCall, approvalResponses
Long contextcompaction — automatic layered compaction
Budget stopstotalTokensExceed / costExceeds in stopWhen
Provider-nativeproviderOptions, promptCaching
Hooks / depsdeps, onUsage, onFinish

effort mapping. xhigh and max are canonical values; each adapter maps or clamps them to the closest provider-supported level. OpenAI clamps maxxhigh; Gemini maps both toward its high/native-budget setting; newer Anthropic models (Opus 4.7+, Sonnet 5, Fable 5) accept them directly on the output_config effort wire. See each provider page for the exact per-model behavior.

Reading the full stream

fullStream yields canonical StreamPart deltas — text, reasoning, tool calls, sources, per-step boundaries, usage, and a terminal finish (or error). The union is open, so always keep a default branch.

full-stream.ts
for await (const part of result.fullStream) {
  switch (part.type) {
    case 'text-delta':
      process.stdout.write(part.text);
      break;
    case 'reasoning-delta':
      // model's thinking tokens
      break;
    case 'finish':
      console.log('\nfinish:', part.finishReason, part.usage.totalTokens);
      break;
    case 'error':
      console.error('stream error:', part.error);
      break;
    default:
      break; // additive variants — ignore unknown types
  }
}

2. Next.js App Router route handler

On the server, pipe the canonical stream straight into toDeuzStreamResponse. It serializes fullStream to Server-Sent Events on our versioned UI wire (x-deuz-stream: v1) — it never proxies a provider's raw bytes. The function is edge-safe.

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

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

export async function POST(req: Request): Promise<Response> {
  const { messages } = await req.json();

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

  return toDeuzStreamResponse(result);
}

toDeuzStreamResponse(result, options?) accepts an optional second argument:

OptionTypeNotes
messageIdstringId sent in the opening start part. Defaults to generateId() then 'deuz-msg'.
generateId() => stringSource for the message id (e.g. deps.generateId).
headersRecord<string, string>Extra response headers merged onto the SSE response.

3. Read the stream in the browser

On the client, pass the fetch Response to readDeuzStream. It yields typed DeuzUIPart objects until the stream closes — switch on part.type.

client.ts
import { readDeuzStream } from '@deuz-sdk/core/ui';

const response = await fetch('/api/chat', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    messages: [{ role: 'user', content: 'Hello!' }],
  }),
});

let text = '';
for await (const part of readDeuzStream(response)) {
  switch (part.type) {
    case 'start':
      // { type: 'start', messageId }
      break;
    case 'text-delta':
      text += part.text;
      break;
    case 'tool-call':
      console.log('tool:', part.toolName, part.input);
      break;
    case 'tool-result':
      console.log('result:', part.toolName, part.output);
      break;
    case 'step-finish':
      // { step, finishReason, usage }
      break;
    case 'finish':
      console.log('done:', part.finishReason, part.usage.totalTokens);
      break;
    case 'error':
      console.error(part.message); // secrets are already redacted
      break;
    default:
      break;
  }
}

The DeuzUIPart types you can receive: start, step-start, step-finish, text-delta, reasoning-delta, tool-input-delta, tool-call, tool-result, source, finish, error. See the UI streaming wire reference for the full shape of each.

4. Buffered alternative — generateText

When you do not need incremental tokens, generateText awaits the whole response and returns it at once.

generate.ts
import { generateText } from '@deuz-sdk/core';
import { createAnthropic } from '@deuz-sdk/core/anthropic';

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

const { text, usage, finishReason } = await generateText({
  model: anthropic('claude-opus-4-8'),
  messages: [{ role: 'user', content: 'One sentence on why pure cores test well.' }],
});

console.log(text, finishReason, usage.totalTokens);

generateText returns a Promise<GenerateTextResult>:

FieldTypeNotes
textstringFinal assistant text.
usageUsageTotals summed across all agentic steps.
finishReasonFinishReason'stop' | 'length' | 'tool_calls' | 'content_filter' | 'error' | 'aborted'.
response{ messages: Message[] }Assistant (and tool) turns to append to history.
stepsStepResult[]Per-step breakdown — present when tools were used.
toolCalls / toolResultsToolCall[] / ToolResult[]Last step's calls/results (convenience).

Add a tools map and a maxSteps greater than 1 to turn this into a multi-step agentic call.

Where to go next

  • Providers — Anthropic, OpenAI, xAI, Gemini (compat + native), Vertex, and Yunwu factories.
  • Tool calling — the agentic loop: parallel execution, self-healing, maxSteps, stopWhen.
  • generateObject — schema-typed structured output with a Standard Schema or raw JSON Schema.
  • streamChat — the full streaming reference: resilience, timeouts, abort, and the canonical StreamPart union.
  • UI streamingtoDeuzStreamResponse and readDeuzStream in depth.

On this page