Environment
- Positron 2026.08.1, Windows 11.26200.9445, Posit Assistant extension 1.4.1
- Provider: DeepSeek, BYOK direct API (baseUrl https://api.deepseek.com)
- Affected model: deepseek-flash
Summary
- The built-in DeepSeek model metadata matches only legacy model ids, so
deepseek-flashfalls through to a 128K fallback, and both the displayed window
and the auto-compaction threshold are wrong. - The context limit is enforced client-side before the request is sent, so a stale
local limit rejects the request instead of letting the provider answer. - The token count shown in the status bar and the count that triggers auto-compaction
come from different sources; the trigger can silently stop firing, allowing a
conversation to grow past the provider's limit and become unusable.
Issue 1: deepseek-flash resolves to a 128K fallback context window
DeepSeek's API documentation lists CONTEXT LENGTH 1M and MAX OUTPUT 384K for
deepseek-flash (and for deepseek-v4-pro). In Posit Assistant the token counter
reports a 128K window, auto-compaction fires at ~98,000 tokens, and conversations
that previously exceeded 128K could not be reopened.
Cause: the built-in DeepSeek metadata table in dist/extension.js matches only these
patterns:
/^deepseek-v4-pro/ { maxInputTokens: 1e6, maxOutputTokens: 384e3, ... }
/^deepseek-v4-flash/ { maxInputTokens: 1e6, maxOutputTokens: 384e3, ... }
/^deepseek-reasoner/ { maxInputTokens: 1e6, maxOutputTokens: 384e3, ... }
/^deepseek-chat/ { maxInputTokens: 1e6, maxOutputTokens: 384e3, ... }
fallback { family: "deepseek", maxInputTokens: 128e3,
maxOutputTokens: 8192, ... }
DeepSeek has renamed the flash model to deepseek-flash; deepseek-v4-flash survives
only as a legacy alias and is no longer returned by the model list. deepseek-flash
therefore matches no pattern and inherits the 128K / 8192 fallback. There is no
deepseek-flash literal anywhere in the shipped dist bundle.
Two distinct fields are involved, which is why a partial fix is misleading:
maxContextLengthdrives the displayed window.maxInputTokensdrives the hard input limit, hence the auto-compaction
threshold = maxInputTokens - autoCompactTokenBuffer (default 30,000).
Setting onlymaxContextLengthraised the display to 1M while compaction stayed at
128,000 - 30,000 = 98,000.
Cost impact: deepseek-v4-pro still matches its pattern and works natively at 1M, so
it was the only model available for long sessions. DeepSeek's published pricing makes
pro roughly 4x the cost of flash on uncached input and about 3x on output, so this
defect forced materially more expensive model usage.
This also is not fixable by refreshing the model list. No remote model catalog exists:
the only remote fetches are the settings and providers JSON schemas and manifest.json.
A refresh re-queries the provider's /models endpoint for model ids only, while context
and output limits are computed locally from the compiled table above.
Issue 2: the limit is enforced client-side, so a stale limit behaves as a hard failure
Before sending, the client computes an effective input limit from local metadata and
compares it against the conversation's token count, with no buffer:
let i = oB({ modelId, providerId, capabilityFacts, autoCompactTokenBuffer: 0 })
.effectiveInputLimit;
if (!i) return true;
if (count < i) return true; // otherwise refuse
Because the local limit is stale (128K against the provider's 1M), the client is
stricter than the provider, and the request is never sent. The refusal uses a frontend
string: "The conversation is too long for this model's context window. Try starting a
new conversation or using a model with a larger context."
A provider-side error would at least be truthful and recoverable; a stale local gate
leaves the user no way through. Note also that the token count compared here is itself
derived, not authoritative for the provider's own accounting.
Issue 3: the displayed token count and the auto-compaction trigger diverge
The status-bar counter is fed by the current turn's request usage, whereas the
auto-compaction trigger reads the conversation store's per-node count, via:
getCurrentPromptTokenCountById(e) {
let r = this.getNodeById(e);
return !r || Xo(r) || r.compactedToolCallIds.length > 0
? null
: r.message.lmMessage.some(n => n.role === "assistant")
? this.getTokenCountById(e)
: this.getCurrentPromptTokenCountById(r.parentId);
}
Two consequences:
a) When the active-path leaf has a non-empty compactedToolCallIds (i.e. after
micro-compaction), the function returns null. The trigger reads
if (!c || c < s) return false; — so null silently disables auto-compaction.
b) Otherwise it recurses to the parent node, so it can report an ancestor's older,
smaller count than the total the user sees displayed.
Observed: on three occasions [DATES] the displayed count was around 300,000 and
auto-compaction never fired, while the prompt actually sent to DeepSeek exceeded the
model's 1,000,000-token limit. The request was refused and, because the conversation
already exceeded the limit, /compact could not run either — it requires a model call —
so the conversation was lost.
Recovery note for documentation: /microcompact runs locally and makes no model call, so
it remains the only way to reduce a conversation that is already over the provider's
limit. It should be surfaced in the over-limit error message, since /compact is
guaranteed to fail at that point. (Ironically, using it then sets compactedToolCallIds
and suppresses the auto-compaction trigger, per (a) above — so auto-compaction and
mic ro-compaction currently interfere with each other.)
Current workaround in ~/.posit/ai/providers.json
"deepseek": {
"baseUrl": "https://api.deepseek.com",
"models": { "overrides": { "deepseek-flash": {
"maxContextLength": 1000000,
"maxInputTokens": 1000000,
"maxOutputTokens": 384000 } } }
}
This moves the displayed window to 1M and the auto-compaction threshold to
1,000,000 - 30,000 = 970,000, which confirms the diagnosis. It is a local workaround and
still does not address Issue 3.
Requests
- Add
deepseek-flashto the built-in DeepSeek metadata and re-check the other
DeepSeek ids against the provider's current model list. - Treat the locally cached limit as advisory: when a request exceeds it but not the
provider's actual limit, send it and surface the provider's error, rather than
refusing locally. - Reconcile the two counts so the value shown to the user and the value the
auto-compaction trigger reads share one source. - Do not let
compactedToolCallIdssuppress the auto-compaction trigger. - Document that /microcompact is the only local, model-free recovery once a
conversation exceeds the provider limit, and surface it in the over-limit error.