Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions rust/crates/api/src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,16 @@ pub fn metadata_for_model(model: &str) -> Option<ProviderMetadata> {
default_base_url: openai_compat::DEFAULT_OPENAI_BASE_URL,
});
}
// Groq compound systems and Groq-namespaced model ids are served through
// the OpenAI-compatible transport selected by OPENAI_BASE_URL.
if canonical.starts_with("groq/") {
return Some(ProviderMetadata {
provider: ProviderKind::OpenAi,
auth_env: "OPENAI_API_KEY",
base_url_env: "OPENAI_BASE_URL",
default_base_url: openai_compat::DEFAULT_OPENAI_BASE_URL,
});
}
// Alibaba DashScope compatible-mode endpoint. Routes qwen/* and bare
// qwen-* model names (qwen-max, qwen-plus, qwen-turbo, qwen-qwq, etc.)
// to the OpenAI-compat client pointed at DashScope's /compatible-mode/v1.
Expand Down Expand Up @@ -666,6 +676,13 @@ pub fn model_token_limit(model: &str) -> Option<ModelTokenLimit> {
max_output_tokens: 16_384,
context_window_tokens: 256_000,
}),
// Groq Llama 4 models accept tool calling but cap completions below
// Claw's 64k fallback heuristic. Encode the provider limit so prompts
// fit inside Groq's request validation and TPM budget.
"llama-4-scout-17b-16e-instruct" => Some(ModelTokenLimit {
max_output_tokens: 8_192,
context_window_tokens: 131_072,
}),
"qwen-max" => Some(ModelTokenLimit {
max_output_tokens: 8_192,
context_window_tokens: 131_072,
Expand All @@ -674,6 +691,14 @@ pub fn model_token_limit(model: &str) -> Option<ModelTokenLimit> {
max_output_tokens: 8_192,
context_window_tokens: 131_072,
}),
// Groq compound systems are exposed through an OpenAI-compatible API
// but cap completions at 8,192 output tokens. Without this metadata
// Claw uses its 64k fallback heuristic and the provider rejects the
// request before generation starts.
"compound" | "compound-mini" => Some(ModelTokenLimit {
max_output_tokens: 8_192,
context_window_tokens: 131_072,
}),
_ => None,
}
}
Expand Down Expand Up @@ -1080,6 +1105,18 @@ mod tests {
assert_eq!(kind, ProviderKind::OpenAi);
}

#[test]
fn groq_prefix_routes_to_openai_not_anthropic() {
let meta = super::metadata_for_model("groq/compound")
.expect("groq/ prefix must resolve to OpenAI-compatible metadata");
assert_eq!(meta.provider, ProviderKind::OpenAi);
assert_eq!(meta.auth_env, "OPENAI_API_KEY");
assert_eq!(meta.base_url_env, "OPENAI_BASE_URL");

let kind = detect_provider_kind("groq/compound");
assert_eq!(kind, ProviderKind::OpenAi);
}

#[test]
fn qwen_prefix_routes_to_dashscope_not_anthropic() {
// User request from Discord #clawcode-get-help: web3g wants to use
Expand Down Expand Up @@ -1379,6 +1416,27 @@ mod tests {
);
}

#[test]
fn returns_context_window_metadata_for_groq_compound_models() {
let compound =
model_token_limit("groq/compound").expect("groq/compound should have token limits");
assert_eq!(compound.max_output_tokens, 8_192);
assert_eq!(compound.context_window_tokens, 131_072);

let compound_mini = model_token_limit("groq/compound-mini")
.expect("groq/compound-mini should have token limits");
assert_eq!(compound_mini.max_output_tokens, 8_192);
assert_eq!(compound_mini.context_window_tokens, 131_072);
}

#[test]
fn returns_context_window_metadata_for_groq_llama4_scout() {
let scout = model_token_limit("groq/meta-llama/llama-4-scout-17b-16e-instruct")
.expect("groq llama 4 scout should have token limits");
assert_eq!(scout.max_output_tokens, 8_192);
assert_eq!(scout.context_window_tokens, 131_072);
}

#[test]
fn preflight_blocks_oversized_requests_for_kimi_models() {
let request = MessageRequest {
Expand Down
24 changes: 21 additions & 3 deletions rust/crates/rusty-claude-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12624,22 +12624,32 @@ fn resolve_cli_auth_source_for_cwd() -> Result<AuthSource, api::ApiError> {
resolve_startup_auth_source(|| Ok(None))
}

fn model_supports_local_tools(model: &str) -> bool {
!matches!(
api::resolve_model_alias(model)
.rsplit('/')
.next()
.unwrap_or(model),
"compound" | "compound-mini"
)
}

impl ApiClient for AnthropicRuntimeClient {
#[allow(clippy::too_many_lines)]
fn stream(&mut self, request: ApiRequest) -> Result<Vec<AssistantEvent>, RuntimeError> {
if let Some(progress_reporter) = &self.progress_reporter {
progress_reporter.mark_model_phase();
}
let is_post_tool = request_ends_with_tool_result(&request);
let enable_local_tools = self.enable_tools && model_supports_local_tools(&self.model);
let message_request = MessageRequest {
model: self.model.clone(),
max_tokens: max_tokens_for_model(&self.model),
messages: convert_messages(&request.messages),
system: (!request.system_prompt.is_empty()).then(|| request.system_prompt.join("\n\n")),
tools: self
.enable_tools
tools: enable_local_tools
.then(|| filter_tool_specs(&self.tool_registry, self.allowed_tools.as_ref())),
tool_choice: self.enable_tools.then_some(ToolChoice::Auto),
tool_choice: enable_local_tools.then_some(ToolChoice::Auto),
stream: true,
reasoning_effort: self.reasoning_effort.clone(),
..Default::default()
Expand Down Expand Up @@ -19437,6 +19447,14 @@ UU conflicted.rs",
}
}

#[test]
fn groq_compound_disables_local_tools() {
assert!(!super::model_supports_local_tools("groq/compound"));
assert!(!super::model_supports_local_tools("groq/compound-mini"));
assert!(super::model_supports_local_tools("sonnet"));
assert!(super::model_supports_local_tools("llama-3.1-8b-instant"));
}

#[test]
fn stub_commands_absent_from_repl_completions() {
let candidates =
Expand Down