npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

n8n-nodes-gemini-token-telemetry

v0.5.0

Published

Google Gemini chat model for n8n that reports the full token usage the default node discards: cached input, billable input, reasoning tokens and cache hit ratio.

Readme

n8n-nodes-gemini-token-telemetry

A drop-in replacement for n8n's Google Gemini Chat Model node that reports the token usage the default node throws away — most importantly how much of your input came from Gemini's context cache, which is billed at a fraction of the normal input rate.

If you run a long system prompt through an n8n AI Agent, cached input is probably the single largest factor in your bill, and today it is invisible.

The problem

Google's API returns ten usage fields per call. Three reach your execution data. Two independent layers drop the rest:

1. LangChain flattens it. @langchain/google-genai keeps the rich data on the message (generations[0].message.usage_metadata, including input_token_details.cache_read) but builds llmOutput.tokenUsage with only three fields:

llmOutput: { tokenUsage: {
  promptTokens:     usageMetadata.input_tokens,
  completionTokens: usageMetadata.output_tokens,
  totalTokens:      usageMetadata.total_tokens,
} }

2. n8n deletes the message. N8nLlmTracing.handleLLMEnd runs pick(g, ['text','generationInfo']) over the generations — dropping message, and with it usage_metadata. Crucially this happens before tokensUsageParser is called, so a custom parser alone cannot recover anything.

This is not Gemini-specific. n8n's own Anthropic parser reads only input_tokens / output_tokens and ignores cache_read_input_tokens, which Anthropic does return.

What this node does

It subclasses the chat model and enriches llmOutput.tokenUsage inside _generate, before any callback exists — so it does not depend on callback ordering, which is not guaranteed. A matching tokensUsageParser then copies the extra fields into the persisted output.

No patching of n8n. No patching of LangChain. Only two options n8n already accepts per node.

Fields added to tokenUsage

| Field | Meaning | |---|---| | cacheReadTokens | input tokens served from Gemini's context cache | | inputTokensTotal | total input tokens (cached + billable) | | inputTokensBillable | inputTokensTotal - cacheReadTokens | | cacheHitRatio | cacheReadTokens / inputTokensTotal | | outputTokensVisible | candidatesTokenCount — the text the model actually returned | | reasoningTokens | thinking tokens (billed as output) | | reasoningDerived | true when reasoning was derived, not reported | | outputTokensBillable | outputTokensVisible + reasoningTokens | | inputTokensOver200k / cacheReadOver200k | long-context tier counts, when present | | usageSource | gemini-token-telemetry-v1 — proves the value came from this node |

Two deliberate deviations

Reasoning is derived, not reported. Google's thoughtsTokenCount is not mapped by LangChain into any field — but totalTokenCount includes it, so reasoning = total - input - visible output. reasoningDerived: true marks that this was arithmetic, not a provider field.

completionTokens becomes billable output (visible + reasoning), with the original value preserved in outputTokensVisible. Two reasons: it is the correct number for cost, and it prevents total data loss — n8n only persists real usage if (tokenUsage.completionTokens > 0). When Google omits candidatesTokenCount the default behaviour is to discard the real numbers and silently store a tiktoken estimate instead.

Measured

Against a 19,691-token stable prefix, second call (warm cache):

default node    { promptTokens: 19691, completionTokens: 1, totalTokens: 19692 }

this node       { promptTokens: 19691, completionTokens: 489, totalTokens: 20180,
                  cacheReadTokens: 16357, inputTokensTotal: 19691,
                  inputTokensBillable: 3334, cacheHitRatio: 0.8307,
                  outputTokensVisible: 1, reasoningTokens: 488,
                  reasoningDerived: true, outputTokensBillable: 489,
                  usageSource: "gemini-token-telemetry-v1" }

83% of the input was cached and previously invisible. Verified working inside an AI Agent with tool calling, not only in a basic chain.

Cost (optional)

n8n already has a first-class cost slot: its default parser reads tokenUsage.cost ?? tokenUsage.totalCost and propagates it to the tracing attribute llm.cost.total. No provider fills it — the only occurrence of totalCost in the n8n codebase is the line that reads it. This node fills it.

Prices come pre-filled. The package ships a built-in table for the Gemini models, copied from the official pricing page, and each model carries validity windows — so a promotional price switches over on its own date instead of quietly becoming wrong:

| Model (Standard tier) | Input | Output (incl. thinking) | Cache read | |---|---|---|---| | gemini-3.7-flash | 0.75 → 1.50 on 2027-01-01 | 3.75 → 7.50 | 0.075 → 0.15 | | gemini-3.6-flash | 0.75 → 1.50 on 2027-01-01 | 3.75 → 7.50 | 0.075 → 0.15 | | gemini-3.5-flash | 1.50 | 9.00 | 0.15 | | gemini-3.5-flash-lite | 0.30 | 2.50 | 0.03 |

Every cost row carries costPricingSource: builtin:<date the table was copied> or node-parameters when you override. The number always says where it came from and how old it is — that is the whole point of shipping a table instead of a constant.

Override by setting the three prices in the node's Options (per 1M tokens); yours win. A model that is not in the table and has no prices set emits no cost field at all — an absent number beats a zero that looks like a result.

⚠️ There is no pricing API. GET /v1beta/models returns twelve fields and none of them is a price (measured: 53 models, zero price fields), and the Cloud Billing catalog rejects API keys outright. So the table is a copy, and it has a date on it.

⚠️ Standard tier only. Batch is roughly half, Flex and Priority differ. The API does report serviceTier in usageMetadata, but LangChain drops it before it reaches the node, so it cannot be detected — Standard is assumed.

⚠️ The cache figure is the read price. Explicit caching also bills storage per hour, which is not derivable from token counts. Implicit caching — what you get by default — has no storage charge.

| Field | Meaning | |---|---| | cost | total for the call | | costInput / costCachedInput / costOutput | the three components | | costCacheSavings | what the cache saved versus paying full input price | | costCurrency | a label only — no conversion is performed | | costPricingSource | node-parameters |

If you leave the cached-input price at 0, cached tokens are charged at the full input price. That errs upward on purpose, and costCacheSavings then reports 0 rather than inventing a discount.

Measured on two consecutive calls with the same prompt, at 0.30 / 0.075 / 2.50 USD per 1M:

cold cache   cost 0.00712980   cacheReadTokens      0   savings 0
warm cache   cost 0.00349698   cacheReadTokens 16357   savings 0.00368033

The cache halved the cost of an identical call. That is the number the default node cannot show.

The Model field starts empty — on purpose

n8n's frontend strips any parameter whose value equals the node's declared default before sending it. With a "helpful" default like models/gemini-3.6-flash, anyone picking that exact model has their choice silently erased on save. The node keeps working — off the default — and nobody can answer "which model is running?" by looking at the workflow.

That is not hypothetical: it happened in production here, on the node serving 100% of traffic, and the detector meant to catch it was blind because it matched on the old node type.

With default: "", no legitimate choice can collide with the default, so every choice is stored. Leaving it empty raises a clear error naming the fix, instead of calling the API with no model and letting the provider return something confusing.

What's new

0.5.0 — the Model field no longer has a default, so the UI can never erase your choice. 0.4.0 — built-in Gemini price table with validity windows; cost now works out of the box. 0.3.0 — optional cost calculation from user-supplied prices. 0.2.0 — model list is now loaded from the Google API instead of a free-text field. 0.1.1 — added a node icon.

Install

n8n → Settings → Community nodes → Installn8n-nodes-gemini-token-telemetry

Requires N8N_COMMUNITY_PACKAGES_ENABLED=true. In queue mode, also set N8N_REINSTALL_MISSING_PACKAGES=true so workers reinstall the package on boot when their filesystem is ephemeral.

Use the existing Google Gemini(PaLM) API credential. Then point your Agent or Chain at this node instead of the built-in Gemini model node.

Limitations

  • Only the non-streaming path (_generate) is instrumented. Streaming is untested.
  • reasoningTokens is derived; if Google ever stops including thinking in totalTokenCount, it will read zero rather than wrong — but verify before relying on it.
  • The model list is a plain text field, not the dynamic dropdown of the built-in node.
  • Tested against n8n 2.32.7 and @langchain/google-genai 2.1.24.

License

MIT