From e31504492ae2c75c68fe2d657f33540869b000c7 Mon Sep 17 00:00:00 2001 From: Duyet Le Date: Tue, 20 Oct 2026 16:33:31 +0700 Subject: [PATCH 1/2] test(claude): assert the launch env declares the model context window Claude Code only knows Anthropic model ids, so a third-party id opens on its built-in 200k default. The CLI resolves the model's real window from /v1/models but never hands it over, so minimax/minimax-m3.1-flash (1M) reports 200k in /context while the website says 1M. The test asserts the launch env declares CLAUDE_CODE_MAX_CONTEXT_TOKENS for a concrete model, leaves it unset for an unknown window and for a virtual preset, and still declares a window below 200k. Co-authored-by: Duyet Le Co-authored-by: duyetbot Co-Authored-By: Claude Code --- src/spawn.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/spawn.rs b/src/spawn.rs index b23b5da..d844e99 100644 --- a/src/spawn.rs +++ b/src/spawn.rs @@ -1194,6 +1194,48 @@ mod tests { } } + fn claude_env(model: &str, context_window: Option) -> BTreeMap { + let tool = builtin("claude").unwrap(); + build_tool_env(BuildToolEnvInput { + tool_name: "claude", + tool: &tool, + profile: &profile(), + api_key: "sk-ar-v1-secret", + model, + effort: None, + context_window, + min_context: None, + model_map: None, + }) + } + + #[test] + fn claude_declares_the_model_context_window() { + // WHY: Claude Code only knows Anthropic model ids, so a third-party id + // opens on its 200k default unless the CLI declares the real window. + assert_eq!( + claude_env("minimax/minimax-m3.1-flash", Some(1_000_000)) + .get("CLAUDE_CODE_MAX_CONTEXT_TOKENS") + .map(String::as_str), + Some("1000000") + ); + // An unknown window is not a promise Claude Code should size a session to. + assert!(!claude_env("minimax/minimax-m3.1-flash", None) + .contains_key("CLAUDE_CODE_MAX_CONTEXT_TOKENS")); + // A virtual preset picks its hop per request, and `anyrouter/auto[1m]` + // already carries the suffix Claude Code reads, so a catalog cap would + // shrink that session. + assert!(!claude_env("anyrouter/auto", Some(200_000)) + .contains_key("CLAUDE_CODE_MAX_CONTEXT_TOKENS")); + // A known window below 200k is still declared, so every surface agrees. + assert_eq!( + claude_env("minimax/minimax-m3.1-flash", Some(128_000)) + .get("CLAUDE_CODE_MAX_CONTEXT_TOKENS") + .map(String::as_str), + Some("128000") + ); + } + #[test] fn claude_explicit_alias_beats_pinned_model() { let tool = builtin("claude").unwrap(); From 9482c4d34ac42be3c2421d5e531d7fdab8ccf814 Mon Sep 17 00:00:00 2001 From: Duyet Le Date: Tue, 20 Oct 2026 16:36:28 +0700 Subject: [PATCH 2/2] fix(claude): declare the model context window to Claude Code Claude Code 2.1.304 keys its model metadata on Anthropic model ids. A third-party id is an unknown model, so it opens on the built-in 200,000-token default and minimax/minimax-m3.1-flash reported 200k in /context while the catalog and the website say 1,000,000. The CLI already resolved the window from /v1/models and carried it on BuildToolEnvInput.context_window, but never handed it over. It set CLAUDE_CODE_AUTO_COMPACT_WINDOW, which is the auto-compact threshold, not the window. The launch env now declares CLAUDE_CODE_MAX_CONTEXT_TOKENS from the resolved window. An unknown window stays unset, and a virtual preset keeps its own window because its hop is picked per request and anyrouter/auto[1m] already carries the suffix Claude Code reads. Co-authored-by: Duyet Le Co-authored-by: duyetbot Co-Authored-By: Claude Code --- src/spawn.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/spawn.rs b/src/spawn.rs index d844e99..59ff8da 100644 --- a/src/spawn.rs +++ b/src/spawn.rs @@ -450,6 +450,15 @@ pub fn build_tool_env(input: BuildToolEnvInput<'_>) -> BTreeMap if claude_wants_auto_compact(input.model, input.min_context, input.context_window) { env.insert("CLAUDE_CODE_AUTO_COMPACT_WINDOW".into(), "1000000".into()); } + // Claude Code sizes a session from its own model table, which only knows + // Anthropic ids, so a third-party id lands on the 200k default. Declare + // the window the catalog resolved. A virtual preset picks its hop per + // request and carries its own `[1m]` suffix, so it keeps its own window. + if !is_virtual_preset(input.model) { + if let Some(window) = input.context_window.filter(|n| *n > 0) { + env.insert("CLAUDE_CODE_MAX_CONTEXT_TOKENS".into(), window.to_string()); + } + } // Label each picker entry with its role; otherwise four identical IDs // all render as "Custom model". for (key, value) in [