Skip to content

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

@LaZzyMan

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions