feat: provider abstraction layer + Grok API support

This commit is contained in:
Yeachan-Heo
2026-04-01 04:10:46 +00:00
parent cbc0a83059
commit 2a0f4b677a
8 changed files with 1547 additions and 999 deletions

View File

@@ -12,8 +12,9 @@ use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use api::{
resolve_startup_auth_source, AnthropicClient, AuthSource, ContentBlockDelta, InputContentBlock,
InputMessage, MessageRequest, MessageResponse, OutputContentBlock,
detect_provider_kind, max_tokens_for_model, resolve_model_alias, resolve_startup_auth_source,
AnthropicClient, AuthSource, ContentBlockDelta, InputContentBlock, InputMessage,
MessageRequest, MessageResponse, OutputContentBlock, ProviderClient, ProviderKind,
StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock,
};
@@ -35,13 +36,6 @@ use serde_json::json;
use tools::{execute_tool, mvp_tool_specs, ToolSpec};
const DEFAULT_MODEL: &str = "claude-opus-4-6";
fn max_tokens_for_model(model: &str) -> u32 {
if model.contains("opus") {
32_000
} else {
64_000
}
}
const DEFAULT_DATE: &str = "2026-03-31";
const DEFAULT_OAUTH_CALLBACK_PORT: u16 = 4545;
const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -288,15 +282,6 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
}
}
fn resolve_model_alias(model: &str) -> &str {
match model {
"opus" => "claude-opus-4-6",
"sonnet" => "claude-sonnet-4-6",
"haiku" => "claude-haiku-4-5-20251213",
_ => model,
}
}
fn normalize_allowed_tools(values: &[String]) -> Result<Option<AllowedToolSet>, String> {
if values.is_empty() {
return Ok(None);
@@ -980,7 +965,7 @@ struct LiveCli {
allowed_tools: Option<AllowedToolSet>,
permission_mode: PermissionMode,
system_prompt: Vec<String>,
runtime: ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>,
runtime: ConversationRuntime<ProviderRuntimeClient, CliToolExecutor>,
session: SessionHandle,
}
@@ -1920,11 +1905,11 @@ fn build_runtime(
emit_output: bool,
allowed_tools: Option<AllowedToolSet>,
permission_mode: PermissionMode,
) -> Result<ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>, Box<dyn std::error::Error>>
) -> Result<ConversationRuntime<ProviderRuntimeClient, CliToolExecutor>, Box<dyn std::error::Error>>
{
Ok(ConversationRuntime::new_with_features(
session,
AnthropicRuntimeClient::new(model, enable_tools, emit_output, allowed_tools.clone())?,
ProviderRuntimeClient::new(model, enable_tools, emit_output, allowed_tools.clone())?,
CliToolExecutor::new(allowed_tools, emit_output),
permission_policy(permission_mode),
system_prompt,
@@ -1978,26 +1963,33 @@ impl runtime::PermissionPrompter for CliPermissionPrompter {
}
}
struct AnthropicRuntimeClient {
struct ProviderRuntimeClient {
runtime: tokio::runtime::Runtime,
client: AnthropicClient,
client: ProviderClient,
model: String,
enable_tools: bool,
emit_output: bool,
allowed_tools: Option<AllowedToolSet>,
}
impl AnthropicRuntimeClient {
impl ProviderRuntimeClient {
fn new(
model: String,
enable_tools: bool,
emit_output: bool,
allowed_tools: Option<AllowedToolSet>,
) -> Result<Self, Box<dyn std::error::Error>> {
let model = resolve_model_alias(&model).to_string();
let client = match detect_provider_kind(&model) {
ProviderKind::Anthropic => ProviderClient::from_model_with_anthropic_auth(
&model,
Some(resolve_cli_auth_source()?),
)?,
ProviderKind::Xai | ProviderKind::OpenAi => ProviderClient::from_model(&model)?,
};
Ok(Self {
runtime: tokio::runtime::Runtime::new()?,
client: AnthropicClient::from_auth(resolve_cli_auth_source()?)
.with_base_url(api::read_base_url()),
client,
model,
enable_tools,
emit_output,
@@ -2016,7 +2008,7 @@ fn resolve_cli_auth_source() -> Result<AuthSource, Box<dyn std::error::Error>> {
})?)
}
impl ApiClient for AnthropicRuntimeClient {
impl ApiClient for ProviderRuntimeClient {
#[allow(clippy::too_many_lines)]
fn stream(&mut self, request: ApiRequest) -> Result<Vec<AssistantEvent>, RuntimeError> {
let message_request = MessageRequest {
@@ -2911,6 +2903,9 @@ mod tests {
assert_eq!(resolve_model_alias("opus"), "claude-opus-4-6");
assert_eq!(resolve_model_alias("sonnet"), "claude-sonnet-4-6");
assert_eq!(resolve_model_alias("haiku"), "claude-haiku-4-5-20251213");
assert_eq!(resolve_model_alias("grok"), "grok-3");
assert_eq!(resolve_model_alias("grok-mini"), "grok-3-mini");
assert_eq!(resolve_model_alias("grok-2"), "grok-2");
assert_eq!(resolve_model_alias("claude-opus"), "claude-opus");
}