From 2d48bb6dcaabf0972f01b0c348bb290208a8563d Mon Sep 17 00:00:00 2001 From: NOXX - Commiter Date: Sun, 26 Jul 2026 09:53:06 +0300 Subject: [PATCH] fix(hyperagent): default 1M context for fable/opus/sonnet (#8496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(hyperagent): default 1M context for fable/opus/sonnet HyperAgent Claude-family models (fable, opus, sonnet) were falling through getTokenLimit to the generic 128k default. Agentic tool-loop prompts with large catalogs then failed with context_length_exceeded (~137k tokens). - defaultContextLength + per-model contextLength = 1_000_000 on hyperagent registry - DEFAULT_LIMITS.hyperagent / ha = 1M - Resolve hyperagent/ha (and fable/opus wire ids) before models.dev DB fallback Verified: getTokenLimit('hyperagent','fable-latest') === 1000000; context-manager tests 30/30. * fix(sse): scope hyperagent 1M context fix to the registry, drop unscoped model-name match The step-1b branch in resolveTokenLimit() matched fable/opus/sonnet model name substrings for ANY provider, before the models.dev DB lookup. That collided with anthropic/claude, kiro, windsurf and bluesminds registries, which serve the same Claude model ids (e.g. claude-opus-4.7-max, claude-sonnet-5) with their own accurate per-model contextLength — those were being clobbered to 1M instead of their real (often 200k) limit. The registry-level defaultContextLength added on the hyperagent provider entry already fixes the reported bug (getTokenLimit('hyperagent', ...) === 1_000_000) on its own, scoped correctly by provider. Remove the redundant, unscoped substring branch and add regression coverage for every hyperagent fallback model id plus the cross-provider collision. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --- .../providers/registry/hyperagent/index.ts | 6 ++++ open-sse/services/contextManager.ts | 21 +++++++----- open-sse/services/hyperagentModels.ts | 18 +++++++++- tests/unit/context-manager.test.ts | 33 +++++++++++++++++++ 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/open-sse/config/providers/registry/hyperagent/index.ts b/open-sse/config/providers/registry/hyperagent/index.ts index b271205729..b7c1efe62e 100644 --- a/open-sse/config/providers/registry/hyperagent/index.ts +++ b/open-sse/config/providers/registry/hyperagent/index.ts @@ -4,6 +4,9 @@ import { HYPERAGENT_FALLBACK_MODELS } from "../../../../services/hyperagentModel // HyperAgent (hyperagent.com) — unofficial reverse-engineered web session. // Auth: browser Cookie header. Chat: POST /api/threads/{id}/chat (SSE). // Credits: GET /api/settings/billing/usage → creditData.creditBlocks. +/** Claude-family agent models on HyperAgent — 1M context (not the 128k openai default). */ +const HYPERAGENT_CONTEXT_LENGTH = 1_000_000; + export const hyperagentProvider: RegistryEntry = { id: "hyperagent", alias: "ha", @@ -13,8 +16,11 @@ export const hyperagentProvider: RegistryEntry = { authType: "apikey", authHeader: "cookie", passthroughModels: true, + /** Used by getTokenLimit when model-specific context is missing. */ + defaultContextLength: HYPERAGENT_CONTEXT_LENGTH, models: HYPERAGENT_FALLBACK_MODELS.map((m) => ({ id: m.id, name: m.name, + contextLength: HYPERAGENT_CONTEXT_LENGTH, })), }; diff --git a/open-sse/services/contextManager.ts b/open-sse/services/contextManager.ts index 15b22ce0c9..ef13454cd4 100644 --- a/open-sse/services/contextManager.ts +++ b/open-sse/services/contextManager.ts @@ -15,6 +15,10 @@ const DEFAULT_LIMITS: Record = { openai: 128000, gemini: 1000000, codex: 400000, + // HyperAgent Claude-family agents (fable/opus/sonnet) — 1M default; was falling + // through to 128k and blocking normal agentic tool loops with huge catalogs. + hyperagent: 1_000_000, + ha: 1_000_000, default: 128000, }; @@ -205,6 +209,8 @@ function resolveTokenLimit( const envOverride = getEnvOverride(provider); if (envOverride) return { limit: envOverride, specific: true }; + const lowerModel = (model || "").toLowerCase(); + // 2. Check models.dev synced DB for per-model context limit if (model) { const dbLimit = getModelContextLimit(provider, model); @@ -219,15 +225,14 @@ function resolveTokenLimit( // 4. Check if model name hints at a known limit if (model) { - const lower = model.toLowerCase(); - if (lower.includes("claude")) return { limit: DEFAULT_LIMITS.claude, specific: true }; - if (lower.includes("gemini")) return { limit: DEFAULT_LIMITS.gemini, specific: true }; + if (lowerModel.includes("claude")) return { limit: DEFAULT_LIMITS.claude, specific: true }; + if (lowerModel.includes("gemini")) return { limit: DEFAULT_LIMITS.gemini, specific: true }; if ( - lower.includes("gpt") || - lower.includes("o1") || - lower.includes("o3") || - lower.includes("o4") || - lower.includes("codex") + lowerModel.includes("gpt") || + lowerModel.includes("o1") || + lowerModel.includes("o3") || + lowerModel.includes("o4") || + lowerModel.includes("codex") ) return { limit: DEFAULT_LIMITS.codex, specific: true }; } diff --git a/open-sse/services/hyperagentModels.ts b/open-sse/services/hyperagentModels.ts index b1bbbf9a55..64befa93d3 100644 --- a/open-sse/services/hyperagentModels.ts +++ b/open-sse/services/hyperagentModels.ts @@ -19,40 +19,56 @@ export interface HyperAgentModel { subagent: "fable" | "opus" | "sonnet" | "haiku"; /** Agent runtime for Claude family models. */ runtimeId?: string; + /** Context window for OmniRoute getTokenLimit / compression (Claude-family → 1M). */ + contextLength?: number; } +/** Default context for Fable / Opus / Sonnet on HyperAgent (1M tokens). */ +export const HYPERAGENT_DEFAULT_CONTEXT_LENGTH = 1_000_000; + /** Valid selectable models (live-validated). */ export const HYPERAGENT_FALLBACK_MODELS: HyperAgentModel[] = [ - { id: "fable-latest", name: "Fable 5", subagent: "fable", runtimeId: "claude-agents-sdk" }, + { + id: "fable-latest", + name: "Fable 5", + subagent: "fable", + runtimeId: "claude-agents-sdk", + contextLength: HYPERAGENT_DEFAULT_CONTEXT_LENGTH, + }, { id: "claude-fable-5", name: "Claude Fable 5", subagent: "fable", runtimeId: "claude-agents-sdk", + contextLength: HYPERAGENT_DEFAULT_CONTEXT_LENGTH, }, { id: "opus-latest", name: "Claude Opus Latest", subagent: "opus", runtimeId: "claude-agents-sdk", + contextLength: HYPERAGENT_DEFAULT_CONTEXT_LENGTH, }, { id: "claude-opus-4-8", name: "Claude Opus 4.8", subagent: "opus", runtimeId: "claude-agents-sdk", + contextLength: HYPERAGENT_DEFAULT_CONTEXT_LENGTH, }, { id: "sonnet-latest", name: "Claude Sonnet Latest", subagent: "sonnet", runtimeId: "claude-agents-sdk", + contextLength: HYPERAGENT_DEFAULT_CONTEXT_LENGTH, }, { id: "claude-sonnet-5", name: "Claude Sonnet 5", subagent: "sonnet", runtimeId: "claude-agents-sdk", + contextLength: HYPERAGENT_DEFAULT_CONTEXT_LENGTH, }, ]; diff --git a/tests/unit/context-manager.test.ts b/tests/unit/context-manager.test.ts index e16a16584e..bad89f19ea 100644 --- a/tests/unit/context-manager.test.ts +++ b/tests/unit/context-manager.test.ts @@ -34,6 +34,39 @@ test("getTokenLimit: default fallback", () => { assert.equal(getTokenLimit("unknown"), 128000); }); +// Regression for #8496: hyperagent Claude-family agents (fable/opus/sonnet) must +// resolve to the 1M context window for every fallback model id, driven solely by +// the registry's `defaultContextLength` (open-sse/config/providers/registry/hyperagent) — +// not by a provider-unscoped model-name substring match, which previously collided +// with unrelated providers serving the same Claude model ids (see +// "getTokenLimit: does not force 1M onto non-hyperagent providers" below). +const HYPERAGENT_FALLBACK_MODEL_IDS = [ + "fable-latest", + "claude-fable-5", + "opus-latest", + "claude-opus-4-8", + "sonnet-latest", + "claude-sonnet-5", +]; + +for (const modelId of HYPERAGENT_FALLBACK_MODEL_IDS) { + test(`getTokenLimit: hyperagent/${modelId} resolves to 1M context`, () => { + assert.equal(getTokenLimit("hyperagent", modelId), 1_000_000); + }); + + test(`getTokenLimit: ha (alias)/${modelId} resolves to 1M context`, () => { + assert.equal(getTokenLimit("ha", modelId), 1_000_000); + }); +} + +test("getTokenLimit: does not force 1M onto non-hyperagent providers serving the same model ids", () => { + // windsurf declares an explicit per-model contextLength of 200000 for this exact id — + // a provider-unscoped substring match on "claude-opus-4" would have clobbered it to 1M. + assert.equal(getTokenLimit("windsurf", "claude-opus-4.7-max"), 200000); + // bluesminds likewise pins its own claude-opus-4-5 entry to 200000. + assert.equal(getTokenLimit("bluesminds", "claude-opus-4-5"), 200000); +}); + // ─── compressContext ──────────────────────────────────────────────────────── test("compressContext: returns unchanged if fits", () => {