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

@argszero/cordis-plugin-thinking-level-fallback

v0.1.1

Published

Thinking-level fallback for dsh: wraps the llm/stream waterfall and re-issues an auxiliary model call the provider rejected for naming an unsupported thinking level (e.g. Google: "Thinking level MINIMAL is not supported for this model"). On that provider

Readme

@argszero/cordis-plugin-thinking-level-fallback

A model call the provider rejects for naming a thinking level the route never declared, re-issued once with a level the model actually declares — so an automatic compaction is not silently lost to a 400.

The defect (discussion #6478)

On Google's Gemini 3 models, "no reasoning effort" is not "no parameter on the wire". It is a concrete level the deployment never declared:

HTTP 400 INVALID_ARGUMENT
Thinking level MINIMAL is not supported for this model. Please retry with other thinking level.

Two facts combine.

1. The harness omits the reasoning option when nothing resolved an effort.

// packages/llm/llm-pi-ai/src/adapter.ts:119-123
const enabledReasoning: ThinkingLevel | undefined = reasoning === 'off' ? undefined : reasoning
return {
  ...
  ...enabledReasoning === undefined ? {} : { reasoning: enabledReasoning },
}

The omission is deliberate — adapter.ts:171-182 calls it "the seam's way of saying the capability is unavailable, which leaves the surface offering only the provider's default".

2. pi-ai reads an omitted option as "thinking disabled" and spells disabled as a level.

// @earendil-works/[email protected], dist/api/google-generative-ai.js:238-241
if (!options?.reasoning) {
  return stream(model, context, { ...base, thinking: { enabled: false } });
}

// dist/api/google-generative-ai.js:341-354
function getDisabledThinkingConfig(model) {
  // Google docs: Gemini 3.1 Pro cannot disable thinking, and Gemini 3 Flash /
  // Flash-Lite do not support full thinking-off either. For Gemini 3 models, use
  // the lowest supported thinkingLevel ...
  if (isGemini3ProModel(model))   return { thinkingLevel: "LOW" };
  if (isGemini3FlashModel(model)) return { thinkingLevel: "MINIMAL" };
  if (isGemma4Model(model))       return { thinkingLevel: "MINIMAL" };
  return { thinkingBudget: 0 };   // Gemini 2.x disables properly
}

getDisabledThinkingConfig never consults the model's thinkingLevelMap. That map is what the route declares:

models:
  - id: gemini-3.8-flash
    reasoningEfforts:
      high: high          # minimal/low/medium/xhigh/max are pinned to null = unsupported

resolveModelReasoning pins every undeclared level to null on purpose (llm-pi-ai/src/catalog.ts:716-743), so the harness knows minimal is unsupported — and still receives it, because substitution for "disabled" happens below the map. The declaration and the wire disagree.

Which requests resolve no effort?

  • An auxiliary call. compaction-basic/src/summarizer.ts:126-160 copies only provider and model out of the routed request header (agent.session.requestHeader()?.config) and builds the request with purpose: 'compaction' and no reasoningEffort. purpose is documented as the place for exactly this policy — adapters "may map the purpose to ... purpose-specific generation policy" (llm/src/types.ts:453-458) — and llm-deepseek already uses it for session-title (llm-deepseek/src/serialize.ts:84). llm-pi-ai applies no purpose policy at all.
  • An off selection. The adapter converts it to an omitted option, i.e. to the same request.

The failure chain the report produced:

context pressure reaches the compaction threshold
  -> automatic compaction starts
  -> the summary request omits reasoning effort
  -> pi-ai substitutes thinkingLevel MINIMAL
  -> provider rejects it: HTTP 400 INVALID_ARGUMENT
  -> compaction-basic logs a warning and continues the turn
     (compaction-basic/src/index.ts:161-162)
  -> pressure keeps growing
  -> CONTEXT_WINDOW_EXCEEDED
  -> the overflow-recovery compaction is rejected the same way

Install and mount

npm install @argszero/cordis-plugin-thinking-level-fallback

The bundle patch mounts it; no configuration is required:

- insert:
    - id: thinking-level-fallback
      name: '@argszero/cordis-plugin-thinking-level-fallback'

What it does

It wraps the public llm/stream waterfall — the around-dispatch seam every streaming model call passes through, and the only seam that has the request in hand (guard/ cannot reach it: guard/timeout-policy is a per-tool tools/execute deadline).

When an in-scope call ends in a provider rejection naming an unsupported thinking level, it asks llm.resolveModelInfo(provider, model) which levels that exact model declares, picks the nearest one to what the request carried (walking pi-ai's own escalation order upward, then downward — the order clampThinkingLevel uses), and re-issues the call once with it:

compaction call, google/gemini-3.8-flash, declared { off, high }
  attempt 1: no reasoningEffort     -> HTTP 400 "Thinking level MINIMAL is not supported"
  attempt 2: reasoningEffort 'high' -> 200

The failed attempt is discarded whole. Its chunks are buffered and never yielded, so a re-issued call is indistinguishable from a first one at the caller's boundary; a kept attempt starts flowing at its first reader-visible chunk, so token streaming and the durable log are untouched.

Deliberate narrowness

Five conditions must all hold:

| Condition | Why | |---|---| | Purpose is auxiliary (compaction, session-title) by default | Those are the calls the harness builds. A conversation request carries a model-selection choice, and silently exchanging it would answer a question the user did not ask. Add 'conversation' to purposes to extend coverage. | | The terminal frame is an error (never aborted) whose message names a thinking level | Anything else is not this defect. | | The code is not UNSUPPORTED_REASONING_EFFORT | That is the harness' own refusal of a routing effort the model does not declare, raised before any provider I/O, and deliberate: "The request path still refuses, which is where a bad configuration belongs" (llm-pi-ai/src/adapter.ts:139-142). Re-issuing over it would hide a misconfigured route default behind a working request. | | No reader-visible content has streamed (isVisibleChunk) | After that a re-run would duplicate output. | | The exact model declares a level besides off, and it differs from the rejected one | off is the value the adapter translates back into the rejected request, so it is never a candidate; and repeating the same level is not a retry. |

The re-issued request carries a process-local marker read by the same listener, which is what bounds the recursion — not a counter, so the bound survives any copy of the request the harness makes on its way to the adapter.

Configuration

- set:
    - id: thinking-level-fallback
      config:
        enabled: true                          # false = pure pass-through
        purposes: [compaction, session-title]  # + 'conversation' to cover plain calls
        maxRetries: 1                          # re-issues per call
        extraPatterns: []                      # other providers' wording, as regex sources

extraPatterns sources are compiled once at mount, so a bad pattern fails loudly there rather than inside a stream, where the failure would be attributed to the provider. Each source's first capture group is the level the provider named; without a group the plugin still matches, and the fallback is the model's first declared level.

Relationship to other guards

  • @argszero/cordis-plugin-overflow-classifier-guard repairs the classification of an over-large rejection so compaction runs. This one repairs a compaction that ran and was rejected. Both observe llm/stream; neither changes the other's verdict.
  • @argszero/cordis-plugin-turn-budget-guard and -repeat-guard-escalation bound a loop that keeps going. This one is about a call that never gets to run at all.

Scope, honestly

This is a seam-level stopgap, and it is a two-sided one:

  • It only repairs calls in purposes. A conversation request that names an off effort on such a model still fails, unless 'conversation' is enabled.
  • It cannot fix the request before it is sent — llm/stream is dispatched after the adapter has built the request, and the omitted option is an adapter-internal decision. The provider has to say no first.

It becomes a no-op the moment either side is fixed: when the harness resolves an auxiliary call's effort from the routed header it already reads for provider/model, or when pi-ai stops substituting a level the model's thinkingLevelMap marks unsupported.

Compatibility

  • dsh 0.1.3, 0.1.5 and 0.1.6 lines (one peer comparator group per published tuple — a single >=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 range silently excludes the whole 0.1.3 line).
  • Verified against @deepseek-ai/[email protected] and @earendil-works/[email protected] (the version reported in the discussion).

0.1.1 corrected two claims 0.1.0 made and did not honour:

  • The 0.1.2 line was dropped. This plugin imports isVisibleChunk from the harness; it lives in assistant-stream, which first exists in 0.1.3-alpha.2. On 0.1.2-rc.1 the artifact installs and then dies at mount with does not provide an export named 'isVisibleChunk'.
  • The 0.1.6 line was admitted. A comparator only admits a prerelease when some comparator shares that prerelease's major.minor.patch, so >=0.1.5-alpha.1 <0.1.6-0 is not "0.1.5 and later" — it drops every 0.1.6-* release, one npm install after 0.1.6-alpha.2 became the alpha dist-tag.

test/package.test.js now computes admission over the published version list with semver and asserts the admitted set exactly. It previously asserted the shape of the range (three groups naming 0.1.2/0.1.3/0.1.5) — which is how it certified both defects rather than catching them, since it required a 0.1.2 group and could not see an absent 0.1.6 one.

Tests

npm test     # tsc && node --test "test/*.test.js"

32 tests: the reported Google rejection end to end (re-issued, rejected attempt invisible), every narrowness condition above, the level walk, the marker bound, and the manifest/peer-range invariants.

License

MIT