Skip to content

DeepSeek provider

Standalone — usable in any Node project, no @warlock.js/core required.

@warlock.js/ai-deepseek is the DeepSeek provider adapter for @warlock.js/ai. DeepSeek’s API is OpenAI-compatible on the wire, so this package is a thin wrapper over the OpenAI adapter: it constructs one internal OpenAISDK pinned to https://api.deepseek.com with provider: "deepseek" and delegates everything to it — while injecting DeepSeek’s own capability inference and pricing so the right flags are set even though the model names aren’t OpenAI names.

You get a vendor-neutral ModelContract — the same shape every agent, workflow, and supervisor already speaks.

Terminal window
npm install @warlock.js/ai @warlock.js/ai-deepseek

DeepSeekSDK is a class holding one long-lived wrapped client. Build it once at boot and reuse it everywhere — every model it produces shares the same connection pool, auth, and rate-limit state.

import { DeepSeekSDK } from "@warlock.js/ai-deepseek";
const deepseek = new DeepSeekSDK({ apiKey: process.env.DEEPSEEK_API_KEY! });

apiKey is the only required field. baseURL defaults to https://api.deepseek.com and provider defaults to "deepseek" (it flows through to ModelContract.provider, AgentReport.model.provider, and logs). Override baseURL only for a proxy/gateway, or provider to relabel the upstream:

const deepseek = new DeepSeekSDK({
apiKey: process.env.DEEPSEEK_API_KEY!,
baseURL: "https://my-gateway.internal/deepseek",
provider: "deepseek-proxy",
});

Build a model, hand it to an agent, run it. execute() never throws — failures land in error as a typed AIError.

import { ai } from "@warlock.js/ai";
import { DeepSeekSDK } from "@warlock.js/ai-deepseek";
const deepseek = new DeepSeekSDK({ apiKey: process.env.DEEPSEEK_API_KEY! });
const assistant = ai.agent({
model: deepseek.model({ name: "deepseek-chat" }),
systemPrompt: "You are a concise senior TypeScript engineer.",
});
const { text, usage, error } = await assistant.execute("Why use generics?");
if (error) {
console.warn(error.code, error.category);
} else {
console.log(text, usage.total);
}
deepseek.model({ name: "deepseek-chat" }); // V3 lineage, non-thinking
deepseek.model({ name: "deepseek-reasoner" }); // R1 lineage, reasoning-capable
deepseek.model({ name: "deepseek-chat", temperature: 0.2 });

Returns a ModelContract you pass straight into any agent, workflow, or supervisor.

ModelNotes
deepseek-chatNon-thinking surface (V3 lineage). Fast, cheap, broad coverage. Reasoning off.
deepseek-reasonerThinking surface (R1 lineage). Emits a reasoning channel. Reasoning on.
deepseek-v4-flashNewer flash tier. Reasoning off by default.
deepseek-v4-proNewer quality tier. Reasoning on by default.

DeepSeek announced the legacy deepseek-chat / deepseek-reasoner names retire 2026/07/24 in favor of the deepseek-v4-* family; both name sets are understood by the capability inference. deepseek.model({ name }) accepts any id the upstream serves.

The wrapper injects DeepSeek’s own inference so the right flags are set even though the model names aren’t OpenAI names:

FlagDefault
reasoningInferred from the model name — true for deepseek-reasoner and the *-pro tier; false for deepseek-chat / *-flash. Drives whether the reasoning-effort knob is forwarded.
visionfalse for every DeepSeek model — no DeepSeek chat model documents image input (mid-2026).
structuredOutputtrue (inherited from the wrapped OpenAI model), unless responseFormat is set to a loose mode.
promptCachingtrue (inherited). DeepSeek reports cache hits via usage.cachedTokens, the same shape as OpenAI.

An explicit value always wins over the injected inference:

deepseek.model({ name: "deepseek-chat", reasoning: true }); // force the thinking flag
deepseek.model({ name: "deepseek-reasoner", reasoning: false }); // suppress it

deepseek-reasoner (and the *-pro tier) emit a reasoning channel. When the model is reasoning-capable, reasoning.effort ("low" | "medium" | "high" | "none") maps verbatim to the OpenAI-compatible reasoning_effort param. When capabilities.reasoning is false (e.g. deepseek-chat), the option is dropped — the adapter never forwards it.

const model = deepseek.model({ name: "deepseek-reasoner" }); // reasoning auto-true
await model.complete(messages, { reasoning: { effort: "high" } }); // → reasoning_effort: "high"

Everything on the wire — the Chat Completions request/response shape, the streaming loop, tool-call accumulation, structured-output response_format, multipart message mapping, and error wrapping — is inherited unchanged from the OpenAI adapter against https://api.deepseek.com. Structured output and streaming therefore behave exactly as documented for OpenAI; see the OpenAI provider for the wire-level detail, including the responseFormat override modes.

Every response reports token usage (input, output, total) — on model.complete() and the streaming done chunk too. DeepSeek’s prompt_tokens is cache hits plus misses, so hits surface as cachedTokens inside input (inputIncludesCache: true) and bill at cachedInput. DeepSeek caches automatically and takes no request hint.

The adapter ships built-in DeepSeek pricing defaults (DEEPSEEK_DEFAULT_PRICING, USD per million tokens) from DeepSeek’s pricing page, fetched 2026-10-17, for deepseek-flash, deepseek-v4-flash (its legacy name) and deepseek-v4-pro. DeepSeek bills by time of day — peak hours (01:00–04:00 and 06:00–10:00 UTC, Monday to Friday) cost twice the off-peak rate — so the defaults are the peak rates, an upper bound that keeps USD budgets safe. Opt into DEEPSEEK_OFF_PEAK_PRICING when you run off-peak. deepseek-chat / deepseek-reasoner are no longer on the price list and are unpriced.

import { DEEPSEEK_OFF_PEAK_PRICING, DeepSeekSDK } from "@warlock.js/ai-deepseek";
// Peak-rate defaults: deepseek-flash → { input: 0.3, output: 1.2, cachedInput: 0.006 }
const deepseek = new DeepSeekSDK({ apiKey: process.env.DEEPSEEK_API_KEY ?? "" });
// Off-peak batch jobs:
const offPeak = new DeepSeekSDK({
apiKey: process.env.DEEPSEEK_API_KEY ?? "",
pricing: DEEPSEEK_OFF_PEAK_PRICING,
});

Override per SDK or per model exactly like the OpenAI adapter. Resolution at model() time: per-model pricing > SDK pricing registry > built-in DeepSeek default > undefined.

DeepSeek documents 503 as “Server Overloaded”; it surfaces as ProviderOverloadedError (code: "PROVIDER_OVERLOADED", retryable: true). Every provider error carries status, retryable and billed (false for 401 / 402 / 403, otherwise "unknown").

Need an offline token estimate before a call? deepseek.count(text) returns a fast character-heuristic approximation — good for budgeting and quota guards, not for billing.

deepseek.embedder({...}) and deepseek.image({...}) delegate to the wrapped OpenAI adapter.

  • OpenAI provider — the wrapped adapter; all wire-level behavior applies here.
  • @warlock.js/ai — the agent, tool, and system-prompt surface these models plug into.
  • For provider-specific notes and the latest model ids, see the warlock-js-ai-deepseek skill.

This adapter delegates Chat Completions to the OpenAI adapter, so providerOptions.openai.body adds native Chat request fields. Other provider keys are ignored. Plain-object fields merge one level deep with neutral options; caller-provided keys win, while arrays, scalars and null replace the field. The OpenAI adapter’s protected conversation, tool and transport fields remain protected here too. Send only fields supported by this provider; do not reuse OpenAI-only bodies across a mixed-provider fallback chain.

Models from this adapter have no native countTokens method. Check model.countTokens before using an optional native counting capability; sdk.count(text) is a separate offline estimate.