imoxto

Is for now just a wrapper of https://www.npmjs.com/package/n8n-nodes-deepseek-reasoner

Package Information

Released: this month
Downloads: 9 weekly / 31 monthly
Latest Version: 0.1.6
Author: imoxto

Documentation

A fork of https://www.npmjs.com/package/n8n-nodes-deepseek-reasoner but with cost reporting as i couldnt find a way to get all types of token counts in n8n.

Layout

src/credentials/AscDeepSeekReasonerApi.credentials.ts   credential definition
src/nodes/AscDeepSeekReasoner/AscDeepSeekReasoner.node.ts   node + ChatDeepSeekReasoner chat model
test/unit/                                              offline tests (stubbed fetch)
test/live/                                              tests against the real APIs (DeepSeek and Z.ai)

Fallback model

The node takes an optional Fallback Model alongside Model. Leave it empty and nothing changes.

Set it, and once the configured model has used up its whole retry budget (Max Retries in Options), the same
request is sent again with the fallback model — same credential, same Base URL, same body, only model differs.
That includes thinking, reasoning_effort, the bound tools and the replayed reasoning_content on assistant
turns, so a failover mid-conversation still satisfies DeepSeek's reasoning echo. A fallback that is blank, or that
just repeats the primary, is ignored.

Only a model that has exhausted its retries is abandoned, so a transient blip retries on the model you asked for
rather than silently switching. If the fallback fails too, its error is the one that surfaces.

Which model gets reported

Whatever answered. The provider's own model field is passed through untouched; when a provider omits it, the node
fills in the model it sent. That value reaches n8n through llmOutput.model_name, the generation's
generationInfo.model and the message's response_metadata.model_name.

So the Output Report URL payload always names a real model, and gains two fields next to the response body:

field meaning
model the model the provider says served the request
requested_model the model the node asked for on the successful attempt — the fallback, after a failover
fallback_used true when the fallback answered

Custom Body Fields are merged last, so they still win on a name clash. Only the successful call is reported: a
failed attempt returns no usage, so it is not billable and not posted.

On Max Tokens

On a thinking model max_tokens covers the reasoning too. When the budget runs out mid-thought the API returns
finish_reason: "length", an empty content and a reasoning_content that stops partway. The node used to fall
back to that reasoning as the answer, which leaked the raw chain of thought into the output.

A final answer cut off at max_tokens now follows On Max Tokens in Options. Only the debug setting ever
outputs the reasoning:

setting result
Throw Error the call fails with a message saying whether it ran out while reasoning or partway through the answer; catch it with the Agent's On Error → Continue (using error output)
Return Empty empty answer
Return Partial Content (default) whatever answer text arrived before the cut-off, which may be empty or end mid-sentence
Return Reasoning (Debug) the cut-off reasoning and any partial answer, raw, under labels — for finding out why it ran out

The debug output looks like this:

[Hit the Max Tokens limit (8192) before it finished reasoning]

Reasoning:
The user wants 17*23. I should break it down: 17*20 = 340, then

Partial answer:
...

Tool-call turns are passed back to the agent unchanged whatever the setting. The reasoning is still kept in history
so the next turn echoes it. With the debug setting, a chat memory node stores the debug text as the assistant's
reply, so the next turn of that conversation sees it — use it while investigating, not in production.

Scripts

script what it does
npm run build compile src/ to dist/ and copy node icons
npm run build:compare same, but emits to dist2/ for diffing against the shipped dist/
npm run typecheck type-check sources and tests without emitting
npm test unit tests (no network)
npm run test:live live tests against DeepSeek and Z.ai
npm run test:all both
npm run test:coverage unit tests with V8 coverage into coverage/

Live tests

test/live/ bills real accounts. Every case runs against each provider in
test/live/providers.ts, so both endpoints get identical coverage. Keys go in .env
(see .env.example):

DEEPSEEK_API_KEY=sk-...
Z_API_KEY=...
# optional overrides
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_TEST_MODEL=deepseek-flash
Z_BASE_URL=https://api.z.ai/api/paas/v4
Z_TEST_MODEL=glm-5.3-flash

A provider skips itself when its key is unset, and RUN_LIVE_TESTS=0 forces all of them off.

Provider differences

Both endpoints speak the same dialect (thinking: { type } on the request, reasoning_content on the response),
so the node drives them unmodified — only the credential's Base URL changes. Two behaviours differ, and
providers.ts records them as flags the live suite asserts:

DeepSeek Z.ai (glm-5.3-flash)
Rejects a thinking-mode turn that dropped reasoning_content yes — the reason this node exists no, it is tolerated
Accepts thinking: {type: 'disabled'} yes no — HTTP 400 code 1210, "This model always engages in thinking and cannot be disabled"

So Enable Thinking = off does not work against glm-5.3-flash: the node states the disable explicitly and the
model rejects it. reasoning_effort accepts low, high and max there; anything else is a 400.

DeepSeek renamed its models

GET /models on DeepSeek now lists deepseek-flash and deepseek-v4-pro. The old deepseek-v4-flash still
works, but as an alias: the request succeeds and the response reports model: "deepseek-flash". The node
passes that through rather than echoing the name it sent, so reporting names the model that was actually billed —
providers.ts pins the alias and the live suite asserts it.

The node's Model parameter still defaults to deepseek-v4-flash, which therefore keeps working. Worth
changing to deepseek-flash the next time the node's defaults are revised.

dist vs dist2

src/ was reconstructed from the JavaScript that used to be committed under dist/.
test/unit/build-parity.test.ts recompiles to dist2/ and asserts the emitted
JavaScript still matches that original line for line, apart from an explicit
allow-list of intended changes. The baseline is read from git at commit 67cdf52,
so it keeps working now that dist/ is built rather than committed.

Discussion