Skip to content

Hostile-provider hardening: guard NaN/Infinity/negative on API usage fields + computeThresholds #4350

Description

@LaZzyMan

Scope: follow-up to #4345 (auto-compaction three-tier ladder).

Several integers from upstream sources can be hostile (provider bug, OpenAI-compat proxy returning null/NaN, misconfigured override) and poison the gate arithmetic:

Failure modes

  1. usageMetadata.{prompt,candidates,cached,total}TokenCount can be NaN / Infinity / negative on hostile providers. The streaming response handler captures these unguarded. Subsequent lastPromptTokenCount + NaN >= hard is always false → hard-rescue silently disabled; Infinity >= hard always true → hard-rescue fires every send.

  2. computeThresholds(window) has no input validation. If contextWindowSize: NaN (proxy returning null), NaN - SUMMARY_RESERVE = NaN, all four thresholds become NaN, every tokens >= NaN is false → the entire three-tier ladder silently disables.

Both are catastrophic (silent compression disable) but require non-default provider misbehavior.

Proposed fix

1. Coerce helper for API usage fields

function coerceUsageCount(value: unknown): number {
  return typeof value === 'number' && Number.isFinite(value) && value >= 0
    ? value
    : 0;
}

Route all 4 API-capture sites in geminiChat.ts through it:

  • usageMetadata.promptTokenCount
  • usageMetadata.totalTokenCount
  • usageMetadata.candidatesTokenCount
  • usageMetadata.cachedContentTokenCount

Number.isFinite(-1) is true, so the >= 0 is also needed (not just isFinite).

2. computeThresholds input guard

export function computeThresholds(window: number): CompactionThresholds {
  if (!Number.isFinite(window) || window <= 0) {
    return {
      warn: Number.POSITIVE_INFINITY,
      auto: Number.POSITIVE_INFINITY,
      hard: Number.POSITIVE_INFINITY,
      effectiveWindow: 0,
    };
  }
  // ... existing math
}

Returning Infinity (rather than 0) means tokens < Infinity always true → gate falls through to NOOP-equivalent path, instead of tokens < 0 always false latching at fired-state.

Related

Worked out in R11.1 + R12.1 + R12.2 of PR #4168 (archived at tag pr-4168-archive-pre-revert). Pure follow-up — both are defensive guards on edge-case inputs, no behavioral change for any provider returning sane values.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions