DeepSeek provider
Standalone — usable in any Node project, no
@warlock.js/corerequired.
@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.
Install
Section titled “Install”npm install @warlock.js/ai @warlock.js/ai-deepseekyarn add @warlock.js/ai @warlock.js/ai-deepseekpnpm add @warlock.js/ai @warlock.js/ai-deepseekConstruct
Section titled “Construct”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",});First call
Section titled “First call”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);}Use as a model
Section titled “Use as a model”deepseek.model({ name: "deepseek-chat" }); // V3 lineage, non-thinkingdeepseek.model({ name: "deepseek-reasoner" }); // R1 lineage, reasoning-capabledeepseek.model({ name: "deepseek-chat", temperature: 0.2 });Returns a ModelContract you pass straight into any agent, workflow, or
supervisor.
Models
Section titled “Models”| Model | Notes |
|---|---|
deepseek-chat | Non-thinking surface (V3 lineage). Fast, cheap, broad coverage. Reasoning off. |
deepseek-reasoner | Thinking surface (R1 lineage). Emits a reasoning channel. Reasoning on. |
deepseek-v4-flash | Newer flash tier. Reasoning off by default. |
deepseek-v4-pro | Newer 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.
Capabilities
Section titled “Capabilities”The wrapper injects DeepSeek’s own inference so the right flags are set even though the model names aren’t OpenAI names:
| Flag | Default |
|---|---|
reasoning | Inferred 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. |
vision | false for every DeepSeek model — no DeepSeek chat model documents image input (mid-2026). |
structuredOutput | true (inherited from the wrapped OpenAI model), unless responseFormat is set to a loose mode. |
promptCaching | true (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 flagdeepseek.model({ name: "deepseek-reasoner", reasoning: false }); // suppress itReasoning effort
Section titled “Reasoning effort”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-trueawait model.complete(messages, { reasoning: { effort: "high" } }); // → reasoning_effort: "high"Inherited from the OpenAI adapter
Section titled “Inherited from the OpenAI adapter”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.
Pricing and usage
Section titled “Pricing and usage”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.
Embeddings and images
Section titled “Embeddings and images”deepseek.embedder({...}) and deepseek.image({...}) delegate to the wrapped
OpenAI adapter.
See also
Section titled “See also”- 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-deepseekskill.
Provider body options and token estimates
Section titled “Provider body options and token estimates”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.