mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-03 16:04:47 +08:00
fix: resolve all post-merge compile errors
- Fix unresolved imports (auto_compaction, AutoCompactionEvent) - Add Thinking/RedactedThinking match arms - Fix workspace.dependencies serde_json - Fix enum exhaustiveness in OutputContentBlock matches - cargo check --workspace passes
This commit is contained in:
@@ -9,7 +9,7 @@ publish.workspace = true
|
|||||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
runtime = { path = "../runtime" }
|
runtime = { path = "../runtime" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json.workspace = true
|
||||||
tokio = { version = "1", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] }
|
tokio = { version = "1", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub use client::{
|
|||||||
resolve_startup_auth_source, MessageStream, OAuthTokenSet, ProviderClient,
|
resolve_startup_auth_source, MessageStream, OAuthTokenSet, ProviderClient,
|
||||||
};
|
};
|
||||||
pub use error::ApiError;
|
pub use error::ApiError;
|
||||||
pub use providers::anthropic::{AnthropicClient, AuthSource};
|
pub use providers::anthropic::{AnthropicClient, AnthropicClient as ApiClient, AuthSource};
|
||||||
pub use providers::openai_compat::{OpenAiCompatClient, OpenAiCompatConfig};
|
pub use providers::openai_compat::{OpenAiCompatClient, OpenAiCompatConfig};
|
||||||
pub use providers::{
|
pub use providers::{
|
||||||
detect_provider_kind, max_tokens_for_model, resolve_model_alias, ProviderKind,
|
detect_provider_kind, max_tokens_for_model, resolve_model_alias, ProviderKind,
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ mod tests {
|
|||||||
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||||
LOCK.get_or_init(|| Mutex::new(()))
|
LOCK.get_or_init(|| Mutex::new(()))
|
||||||
.lock()
|
.lock()
|
||||||
.expect("env lock")
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn temp_config_home() -> std::path::PathBuf {
|
fn temp_config_home() -> std::path::PathBuf {
|
||||||
@@ -674,6 +674,14 @@ mod tests {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cleanup_temp_config_home(config_home: &std::path::Path) {
|
||||||
|
match std::fs::remove_dir_all(config_home) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
|
Err(error) => panic!("cleanup temp dir: {error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sample_oauth_config(token_url: String) -> OAuthConfig {
|
fn sample_oauth_config(token_url: String) -> OAuthConfig {
|
||||||
OAuthConfig {
|
OAuthConfig {
|
||||||
client_id: "runtime-client".to_string(),
|
client_id: "runtime-client".to_string(),
|
||||||
@@ -709,7 +717,7 @@ mod tests {
|
|||||||
let _guard = env_lock();
|
let _guard = env_lock();
|
||||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||||
std::env::remove_var("CLAUDE_CONFIG_HOME");
|
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||||
let error = super::read_api_key().expect_err("missing key should error");
|
let error = super::read_api_key().expect_err("missing key should error");
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
error,
|
error,
|
||||||
@@ -779,7 +787,7 @@ mod tests {
|
|||||||
fn auth_source_from_saved_oauth_when_env_absent() {
|
fn auth_source_from_saved_oauth_when_env_absent() {
|
||||||
let _guard = env_lock();
|
let _guard = env_lock();
|
||||||
let config_home = temp_config_home();
|
let config_home = temp_config_home();
|
||||||
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home);
|
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||||
@@ -794,8 +802,8 @@ mod tests {
|
|||||||
assert_eq!(auth.bearer_token(), Some("saved-access-token"));
|
assert_eq!(auth.bearer_token(), Some("saved-access-token"));
|
||||||
|
|
||||||
clear_oauth_credentials().expect("clear credentials");
|
clear_oauth_credentials().expect("clear credentials");
|
||||||
std::env::remove_var("CLAUDE_CONFIG_HOME");
|
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||||
std::fs::remove_dir_all(config_home).expect("cleanup temp dir");
|
cleanup_temp_config_home(&config_home);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -818,7 +826,7 @@ mod tests {
|
|||||||
fn resolve_saved_oauth_token_refreshes_expired_credentials() {
|
fn resolve_saved_oauth_token_refreshes_expired_credentials() {
|
||||||
let _guard = env_lock();
|
let _guard = env_lock();
|
||||||
let config_home = temp_config_home();
|
let config_home = temp_config_home();
|
||||||
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home);
|
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||||
@@ -842,15 +850,15 @@ mod tests {
|
|||||||
assert_eq!(stored.access_token, "refreshed-token");
|
assert_eq!(stored.access_token, "refreshed-token");
|
||||||
|
|
||||||
clear_oauth_credentials().expect("clear credentials");
|
clear_oauth_credentials().expect("clear credentials");
|
||||||
std::env::remove_var("CLAUDE_CONFIG_HOME");
|
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||||
std::fs::remove_dir_all(config_home).expect("cleanup temp dir");
|
cleanup_temp_config_home(&config_home);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_startup_auth_source_uses_saved_oauth_without_loading_config() {
|
fn resolve_startup_auth_source_uses_saved_oauth_without_loading_config() {
|
||||||
let _guard = env_lock();
|
let _guard = env_lock();
|
||||||
let config_home = temp_config_home();
|
let config_home = temp_config_home();
|
||||||
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home);
|
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||||
@@ -866,15 +874,15 @@ mod tests {
|
|||||||
assert_eq!(auth.bearer_token(), Some("saved-access-token"));
|
assert_eq!(auth.bearer_token(), Some("saved-access-token"));
|
||||||
|
|
||||||
clear_oauth_credentials().expect("clear credentials");
|
clear_oauth_credentials().expect("clear credentials");
|
||||||
std::env::remove_var("CLAUDE_CONFIG_HOME");
|
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||||
std::fs::remove_dir_all(config_home).expect("cleanup temp dir");
|
cleanup_temp_config_home(&config_home);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_startup_auth_source_errors_when_refreshable_token_lacks_config() {
|
fn resolve_startup_auth_source_errors_when_refreshable_token_lacks_config() {
|
||||||
let _guard = env_lock();
|
let _guard = env_lock();
|
||||||
let config_home = temp_config_home();
|
let config_home = temp_config_home();
|
||||||
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home);
|
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||||
@@ -898,15 +906,15 @@ mod tests {
|
|||||||
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
|
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
|
||||||
|
|
||||||
clear_oauth_credentials().expect("clear credentials");
|
clear_oauth_credentials().expect("clear credentials");
|
||||||
std::env::remove_var("CLAUDE_CONFIG_HOME");
|
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||||
std::fs::remove_dir_all(config_home).expect("cleanup temp dir");
|
cleanup_temp_config_home(&config_home);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_saved_oauth_token_preserves_refresh_token_when_refresh_response_omits_it() {
|
fn resolve_saved_oauth_token_preserves_refresh_token_when_refresh_response_omits_it() {
|
||||||
let _guard = env_lock();
|
let _guard = env_lock();
|
||||||
let config_home = temp_config_home();
|
let config_home = temp_config_home();
|
||||||
std::env::set_var("CLAUDE_CONFIG_HOME", &config_home);
|
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||||
@@ -931,8 +939,8 @@ mod tests {
|
|||||||
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
|
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
|
||||||
|
|
||||||
clear_oauth_credentials().expect("clear credentials");
|
clear_oauth_credentials().expect("clear credentials");
|
||||||
std::env::remove_var("CLAUDE_CONFIG_HOME");
|
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||||
std::fs::remove_dir_all(config_home).expect("cleanup temp dir");
|
cleanup_temp_config_home(&config_home);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use api::{
|
use api::{
|
||||||
AnthropicClient, ApiError, AuthSource, ContentBlockDelta, ContentBlockDeltaEvent,
|
ApiClient, ApiError, AuthSource, ContentBlockDelta, ContentBlockDeltaEvent,
|
||||||
ContentBlockStartEvent, InputContentBlock, InputMessage, MessageDeltaEvent, MessageRequest,
|
ContentBlockStartEvent, InputContentBlock, InputMessage, MessageDeltaEvent, MessageRequest,
|
||||||
OutputContentBlock, ProviderClient, StreamEvent, ToolChoice, ToolDefinition,
|
OutputContentBlock, ProviderClient, StreamEvent, ToolChoice, ToolDefinition,
|
||||||
};
|
};
|
||||||
@@ -34,7 +34,7 @@ async fn send_message_posts_json_and_parses_response() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = AnthropicClient::new("test-key")
|
let client = ApiClient::new("test-key")
|
||||||
.with_auth_token(Some("proxy-token".to_string()))
|
.with_auth_token(Some("proxy-token".to_string()))
|
||||||
.with_base_url(server.base_url());
|
.with_base_url(server.base_url());
|
||||||
let response = client
|
let response = client
|
||||||
@@ -104,7 +104,7 @@ async fn stream_message_parses_sse_events_with_tool_use() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = AnthropicClient::new("test-key")
|
let client = ApiClient::new("test-key")
|
||||||
.with_auth_token(Some("proxy-token".to_string()))
|
.with_auth_token(Some("proxy-token".to_string()))
|
||||||
.with_base_url(server.base_url());
|
.with_base_url(server.base_url());
|
||||||
let mut stream = client
|
let mut stream = client
|
||||||
@@ -182,7 +182,7 @@ async fn retries_retryable_failures_before_succeeding() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = AnthropicClient::new("test-key")
|
let client = ApiClient::new("test-key")
|
||||||
.with_base_url(server.base_url())
|
.with_base_url(server.base_url())
|
||||||
.with_retry_policy(2, Duration::from_millis(1), Duration::from_millis(2));
|
.with_retry_policy(2, Duration::from_millis(1), Duration::from_millis(2));
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ async fn surfaces_retry_exhaustion_for_persistent_retryable_errors() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = AnthropicClient::new("test-key")
|
let client = ApiClient::new("test-key")
|
||||||
.with_base_url(server.base_url())
|
.with_base_url(server.base_url())
|
||||||
.with_retry_policy(1, Duration::from_millis(1), Duration::from_millis(2));
|
.with_retry_policy(1, Duration::from_millis(1), Duration::from_millis(2));
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ async fn surfaces_retry_exhaustion_for_persistent_retryable_errors() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[ignore = "requires ANTHROPIC_API_KEY and network access"]
|
#[ignore = "requires ANTHROPIC_API_KEY and network access"]
|
||||||
async fn live_stream_smoke_test() {
|
async fn live_stream_smoke_test() {
|
||||||
let client = AnthropicClient::from_env().expect("ANTHROPIC_API_KEY must be set");
|
let client = ApiClient::from_env().expect("ANTHROPIC_API_KEY must be set");
|
||||||
let mut stream = client
|
let mut stream = client
|
||||||
.stream_message(&MessageRequest {
|
.stream_message(&MessageRequest {
|
||||||
model: std::env::var("ANTHROPIC_MODEL")
|
model: std::env::var("ANTHROPIC_MODEL")
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
plugins = { path = "../plugins" }
|
plugins = { path = "../plugins" }
|
||||||
runtime = { path = "../runtime" }
|
runtime = { path = "../runtime" }
|
||||||
serde_json = "1"
|
serde_json.workspace = true
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ publish.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -1208,6 +1208,8 @@ impl PluginManager {
|
|||||||
let install_path = install_root.join(sanitize_plugin_id(&plugin_id));
|
let install_path = install_root.join(sanitize_plugin_id(&plugin_id));
|
||||||
let now = unix_time_ms();
|
let now = unix_time_ms();
|
||||||
let existing_record = registry.plugins.get(&plugin_id);
|
let existing_record = registry.plugins.get(&plugin_id);
|
||||||
|
let installed_copy_is_valid =
|
||||||
|
install_path.exists() && load_plugin_from_directory(&install_path).is_ok();
|
||||||
let needs_sync = existing_record.is_none_or(|record| {
|
let needs_sync = existing_record.is_none_or(|record| {
|
||||||
record.kind != PluginKind::Bundled
|
record.kind != PluginKind::Bundled
|
||||||
|| record.version != manifest.version
|
|| record.version != manifest.version
|
||||||
@@ -1215,6 +1217,7 @@ impl PluginManager {
|
|||||||
|| record.description != manifest.description
|
|| record.description != manifest.description
|
||||||
|| record.install_path != install_path
|
|| record.install_path != install_path
|
||||||
|| !record.install_path.exists()
|
|| !record.install_path.exists()
|
||||||
|
|| !installed_copy_is_valid
|
||||||
});
|
});
|
||||||
|
|
||||||
if !needs_sync {
|
if !needs_sync {
|
||||||
@@ -1294,6 +1297,7 @@ impl PluginManager {
|
|||||||
fn load_registry(&self) -> Result<InstalledPluginRegistry, PluginError> {
|
fn load_registry(&self) -> Result<InstalledPluginRegistry, PluginError> {
|
||||||
let path = self.registry_path();
|
let path = self.registry_path();
|
||||||
match fs::read_to_string(&path) {
|
match fs::read_to_string(&path) {
|
||||||
|
Ok(contents) if contents.trim().is_empty() => Ok(InstalledPluginRegistry::default()),
|
||||||
Ok(contents) => Ok(serde_json::from_str(&contents)?),
|
Ok(contents) => Ok(serde_json::from_str(&contents)?),
|
||||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||||
Ok(InstalledPluginRegistry::default())
|
Ok(InstalledPluginRegistry::default())
|
||||||
@@ -2003,7 +2007,11 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn temp_dir(label: &str) -> PathBuf {
|
fn temp_dir(label: &str) -> PathBuf {
|
||||||
std::env::temp_dir().join(format!("plugins-{label}-{}", unix_time_ms()))
|
let nanos = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.expect("time should be after epoch")
|
||||||
|
.as_nanos();
|
||||||
|
std::env::temp_dir().join(format!("plugins-{label}-{nanos}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_file(path: &Path, contents: &str) {
|
fn write_file(path: &Path, contents: &str) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ glob = "0.3"
|
|||||||
plugins = { path = "../plugins" }
|
plugins = { path = "../plugins" }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json.workspace = true
|
||||||
tokio = { version = "1", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] }
|
tokio = { version = "1", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] }
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ pub use config::{
|
|||||||
RuntimePluginConfig, ScopedMcpServerConfig, CLAW_SETTINGS_SCHEMA_NAME,
|
RuntimePluginConfig, ScopedMcpServerConfig, CLAW_SETTINGS_SCHEMA_NAME,
|
||||||
};
|
};
|
||||||
pub use conversation::{
|
pub use conversation::{
|
||||||
auto_compaction_threshold_from_env, ApiClient, ApiRequest, AssistantEvent, AutoCompactionEvent,
|
ApiClient, ApiRequest, AssistantEvent, ConversationRuntime, RuntimeError, StaticToolExecutor,
|
||||||
ConversationRuntime, RuntimeError, StaticToolExecutor, ToolError, ToolExecutor, TurnSummary,
|
ToolError, ToolExecutor, TurnSummary,
|
||||||
};
|
};
|
||||||
pub use file_ops::{
|
pub use file_ops::{
|
||||||
edit_file, glob_search, grep_search, read_file, write_file, EditFileOutput, GlobSearchOutput,
|
edit_file, glob_search, grep_search, read_file, write_file, EditFileOutput, GlobSearchOutput,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pulldown-cmark = "0.13"
|
|||||||
rustyline = "15"
|
rustyline = "15"
|
||||||
runtime = { path = "../runtime" }
|
runtime = { path = "../runtime" }
|
||||||
plugins = { path = "../plugins" }
|
plugins = { path = "../plugins" }
|
||||||
serde_json = "1"
|
serde_json.workspace = true
|
||||||
syntect = "5"
|
syntect = "5"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "time"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "time"] }
|
||||||
tools = { path = "../tools" }
|
tools = { path = "../tools" }
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||||||
|
|
||||||
use api::{
|
use api::{
|
||||||
detect_provider_kind, max_tokens_for_model, resolve_model_alias, resolve_startup_auth_source,
|
detect_provider_kind, max_tokens_for_model, resolve_model_alias, resolve_startup_auth_source,
|
||||||
AnthropicClient, AuthSource, ContentBlockDelta, InputContentBlock, InputMessage,
|
ApiClient as ApiHttpClient, AuthSource, ContentBlockDelta, InputContentBlock, InputMessage,
|
||||||
MessageRequest, MessageResponse, OutputContentBlock, ProviderClient, ProviderKind,
|
MessageRequest, MessageResponse, OutputContentBlock, ProviderClient, ProviderKind,
|
||||||
StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock,
|
StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock,
|
||||||
};
|
};
|
||||||
@@ -191,7 +191,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
"-p" => {
|
"-p" => {
|
||||||
// Claude Code compat: -p "prompt" = one-shot prompt
|
// Claw Code compat: -p "prompt" = one-shot prompt
|
||||||
let prompt = args[index + 1..].join(" ");
|
let prompt = args[index + 1..].join(" ");
|
||||||
if prompt.trim().is_empty() {
|
if prompt.trim().is_empty() {
|
||||||
return Err("-p requires a prompt string".to_string());
|
return Err("-p requires a prompt string".to_string());
|
||||||
@@ -205,7 +205,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
"--print" => {
|
"--print" => {
|
||||||
// Claude Code compat: --print makes output non-interactive
|
// Claw Code compat: --print makes output non-interactive
|
||||||
output_format = CliOutputFormat::Text;
|
output_format = CliOutputFormat::Text;
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
@@ -484,7 +484,7 @@ fn run_login() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "oauth state mismatch").into());
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "oauth state mismatch").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = AnthropicClient::from_auth(AuthSource::None).with_base_url(api::read_base_url());
|
let client = ApiHttpClient::from_auth(AuthSource::None).with_base_url(api::read_base_url());
|
||||||
let exchange_request =
|
let exchange_request =
|
||||||
OAuthTokenExchangeRequest::from_config(oauth, code, state, pkce.verifier, redirect_uri);
|
OAuthTokenExchangeRequest::from_config(oauth, code, state, pkce.verifier, redirect_uri);
|
||||||
let runtime = tokio::runtime::Runtime::new()?;
|
let runtime = tokio::runtime::Runtime::new()?;
|
||||||
@@ -899,6 +899,14 @@ fn run_resume_command(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
SlashCommand::Resume { .. }
|
SlashCommand::Resume { .. }
|
||||||
|
| SlashCommand::Bughunter { .. }
|
||||||
|
| SlashCommand::Commit
|
||||||
|
| SlashCommand::Pr { .. }
|
||||||
|
| SlashCommand::Issue { .. }
|
||||||
|
| SlashCommand::Ultraplan { .. }
|
||||||
|
| SlashCommand::Teleport { .. }
|
||||||
|
| SlashCommand::DebugToolCall
|
||||||
|
| SlashCommand::Plugins { .. }
|
||||||
| SlashCommand::Model { .. }
|
| SlashCommand::Model { .. }
|
||||||
| SlashCommand::Permissions { .. }
|
| SlashCommand::Permissions { .. }
|
||||||
| SlashCommand::Session { .. }
|
| SlashCommand::Session { .. }
|
||||||
@@ -1153,6 +1161,17 @@ impl LiveCli {
|
|||||||
SlashCommand::Session { action, target } => {
|
SlashCommand::Session { action, target } => {
|
||||||
self.handle_session_command(action.as_deref(), target.as_deref())?
|
self.handle_session_command(action.as_deref(), target.as_deref())?
|
||||||
}
|
}
|
||||||
|
SlashCommand::Bughunter { .. }
|
||||||
|
| SlashCommand::Commit
|
||||||
|
| SlashCommand::Pr { .. }
|
||||||
|
| SlashCommand::Issue { .. }
|
||||||
|
| SlashCommand::Ultraplan { .. }
|
||||||
|
| SlashCommand::Teleport { .. }
|
||||||
|
| SlashCommand::DebugToolCall
|
||||||
|
| SlashCommand::Plugins { .. } => {
|
||||||
|
eprintln!("slash command not supported in this REPL yet");
|
||||||
|
false
|
||||||
|
}
|
||||||
SlashCommand::Unknown(name) => {
|
SlashCommand::Unknown(name) => {
|
||||||
eprintln!("unknown slash command: /{name}");
|
eprintln!("unknown slash command: /{name}");
|
||||||
false
|
false
|
||||||
@@ -1437,7 +1456,7 @@ impl LiveCli {
|
|||||||
|
|
||||||
fn sessions_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
fn sessions_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
let cwd = env::current_dir()?;
|
let cwd = env::current_dir()?;
|
||||||
let path = cwd.join(".claude").join("sessions");
|
let path = cwd.join(".claw").join("sessions");
|
||||||
fs::create_dir_all(&path)?;
|
fs::create_dir_all(&path)?;
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
@@ -2094,6 +2113,8 @@ impl ApiClient for ProviderRuntimeClient {
|
|||||||
input.push_str(&partial_json);
|
input.push_str(&partial_json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ContentBlockDelta::ThinkingDelta { .. }
|
||||||
|
| ContentBlockDelta::SignatureDelta { .. } => {}
|
||||||
},
|
},
|
||||||
ApiStreamEvent::ContentBlockStop(stop) => {
|
ApiStreamEvent::ContentBlockStop(stop) => {
|
||||||
if let Some(rendered) = markdown_stream.flush(&renderer) {
|
if let Some(rendered) = markdown_stream.flush(&renderer) {
|
||||||
@@ -2595,6 +2616,7 @@ fn push_output_block(
|
|||||||
};
|
};
|
||||||
pending_tools.insert(block_index, (id, name, initial_input));
|
pending_tools.insert(block_index, (id, name, initial_input));
|
||||||
}
|
}
|
||||||
|
OutputContentBlock::Thinking { .. } | OutputContentBlock::RedactedThinking { .. } => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3080,7 +3102,7 @@ mod tests {
|
|||||||
assert!(help.contains("/clear [--confirm]"));
|
assert!(help.contains("/clear [--confirm]"));
|
||||||
assert!(help.contains("/cost"));
|
assert!(help.contains("/cost"));
|
||||||
assert!(help.contains("/resume <session-path>"));
|
assert!(help.contains("/resume <session-path>"));
|
||||||
assert!(help.contains("/config [env|hooks|model]"));
|
assert!(help.contains("/config [env|hooks|model|plugins]"));
|
||||||
assert!(help.contains("/memory"));
|
assert!(help.contains("/memory"));
|
||||||
assert!(help.contains("/init"));
|
assert!(help.contains("/init"));
|
||||||
assert!(help.contains("/diff"));
|
assert!(help.contains("/diff"));
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ plugins = { path = "../plugins" }
|
|||||||
runtime = { path = "../runtime" }
|
runtime = { path = "../runtime" }
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json.workspace = true
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -1308,6 +1308,12 @@ fn resolve_skill_path(skill: &str) -> Result<std::path::PathBuf, String> {
|
|||||||
if let Ok(codex_home) = std::env::var("CODEX_HOME") {
|
if let Ok(codex_home) = std::env::var("CODEX_HOME") {
|
||||||
candidates.push(std::path::PathBuf::from(codex_home).join("skills"));
|
candidates.push(std::path::PathBuf::from(codex_home).join("skills"));
|
||||||
}
|
}
|
||||||
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
|
let home = std::path::PathBuf::from(home);
|
||||||
|
candidates.push(home.join(".agents").join("skills"));
|
||||||
|
candidates.push(home.join(".config").join("opencode").join("skills"));
|
||||||
|
candidates.push(home.join(".codex").join("skills"));
|
||||||
|
}
|
||||||
candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills"));
|
candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills"));
|
||||||
|
|
||||||
for root in candidates {
|
for root in candidates {
|
||||||
@@ -1537,7 +1543,7 @@ fn allowed_tools_for_subagent(subagent_type: &str) -> BTreeSet<String> {
|
|||||||
"SendUserMessage",
|
"SendUserMessage",
|
||||||
"PowerShell",
|
"PowerShell",
|
||||||
],
|
],
|
||||||
"claude-code-guide" => vec![
|
"claw-guide" => vec![
|
||||||
"read_file",
|
"read_file",
|
||||||
"glob_search",
|
"glob_search",
|
||||||
"grep_search",
|
"grep_search",
|
||||||
@@ -1716,6 +1722,8 @@ impl ApiClient for ProviderRuntimeClient {
|
|||||||
input.push_str(&partial_json);
|
input.push_str(&partial_json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ContentBlockDelta::ThinkingDelta { .. }
|
||||||
|
| ContentBlockDelta::SignatureDelta { .. } => {}
|
||||||
},
|
},
|
||||||
ApiStreamEvent::ContentBlockStop(stop) => {
|
ApiStreamEvent::ContentBlockStop(stop) => {
|
||||||
if let Some((id, name, input)) = pending_tools.remove(&stop.index) {
|
if let Some((id, name, input)) = pending_tools.remove(&stop.index) {
|
||||||
@@ -1861,6 +1869,7 @@ fn push_output_block(
|
|||||||
};
|
};
|
||||||
pending_tools.insert(block_index, (id, name, initial_input));
|
pending_tools.insert(block_index, (id, name, initial_input));
|
||||||
}
|
}
|
||||||
|
OutputContentBlock::Thinking { .. } | OutputContentBlock::RedactedThinking { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2089,7 +2098,7 @@ fn normalize_subagent_type(subagent_type: Option<&str>) -> String {
|
|||||||
"verification" | "verificationagent" | "verify" | "verifier" => {
|
"verification" | "verificationagent" | "verify" | "verifier" => {
|
||||||
String::from("Verification")
|
String::from("Verification")
|
||||||
}
|
}
|
||||||
"claudecodeguide" | "claudecodeguideagent" | "guide" => String::from("claude-code-guide"),
|
"clawguide" | "clawguideagent" | "guide" => String::from("claw-guide"),
|
||||||
"statusline" | "statuslinesetup" => String::from("statusline-setup"),
|
"statusline" | "statuslinesetup" => String::from("statusline-setup"),
|
||||||
_ => trimmed.to_string(),
|
_ => trimmed.to_string(),
|
||||||
}
|
}
|
||||||
@@ -2589,16 +2598,16 @@ fn config_file_for_scope(scope: ConfigScope) -> Result<PathBuf, String> {
|
|||||||
let cwd = std::env::current_dir().map_err(|error| error.to_string())?;
|
let cwd = std::env::current_dir().map_err(|error| error.to_string())?;
|
||||||
Ok(match scope {
|
Ok(match scope {
|
||||||
ConfigScope::Global => config_home_dir()?.join("settings.json"),
|
ConfigScope::Global => config_home_dir()?.join("settings.json"),
|
||||||
ConfigScope::Settings => cwd.join(".claude").join("settings.local.json"),
|
ConfigScope::Settings => cwd.join(".claw").join("settings.local.json"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_home_dir() -> Result<PathBuf, String> {
|
fn config_home_dir() -> Result<PathBuf, String> {
|
||||||
if let Ok(path) = std::env::var("CLAUDE_CONFIG_HOME") {
|
if let Ok(path) = std::env::var("CLAW_CONFIG_HOME") {
|
||||||
return Ok(PathBuf::from(path));
|
return Ok(PathBuf::from(path));
|
||||||
}
|
}
|
||||||
let home = std::env::var("HOME").map_err(|_| String::from("HOME is not set"))?;
|
let home = std::env::var("HOME").map_err(|_| String::from("HOME is not set"))?;
|
||||||
Ok(PathBuf::from(home).join(".claude"))
|
Ok(PathBuf::from(home).join(".claw"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_json_object(path: &Path) -> Result<serde_json::Map<String, Value>, String> {
|
fn read_json_object(path: &Path) -> Result<serde_json::Map<String, Value>, String> {
|
||||||
@@ -4043,19 +4052,19 @@ mod tests {
|
|||||||
));
|
));
|
||||||
let home = root.join("home");
|
let home = root.join("home");
|
||||||
let cwd = root.join("cwd");
|
let cwd = root.join("cwd");
|
||||||
std::fs::create_dir_all(home.join(".claude")).expect("home dir");
|
std::fs::create_dir_all(home.join(".claw")).expect("home dir");
|
||||||
std::fs::create_dir_all(cwd.join(".claude")).expect("cwd dir");
|
std::fs::create_dir_all(cwd.join(".claw")).expect("cwd dir");
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
home.join(".claude").join("settings.json"),
|
home.join(".claw").join("settings.json"),
|
||||||
r#"{"verbose":false}"#,
|
r#"{"verbose":false}"#,
|
||||||
)
|
)
|
||||||
.expect("write global settings");
|
.expect("write global settings");
|
||||||
|
|
||||||
let original_home = std::env::var("HOME").ok();
|
let original_home = std::env::var("HOME").ok();
|
||||||
let original_claude_home = std::env::var("CLAUDE_CONFIG_HOME").ok();
|
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||||
let original_dir = std::env::current_dir().expect("cwd");
|
let original_dir = std::env::current_dir().expect("cwd");
|
||||||
std::env::set_var("HOME", &home);
|
std::env::set_var("HOME", &home);
|
||||||
std::env::remove_var("CLAUDE_CONFIG_HOME");
|
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||||
std::env::set_current_dir(&cwd).expect("set cwd");
|
std::env::set_current_dir(&cwd).expect("set cwd");
|
||||||
|
|
||||||
let get = execute_tool("Config", &json!({"setting": "verbose"})).expect("get config");
|
let get = execute_tool("Config", &json!({"setting": "verbose"})).expect("get config");
|
||||||
@@ -4088,9 +4097,9 @@ mod tests {
|
|||||||
Some(value) => std::env::set_var("HOME", value),
|
Some(value) => std::env::set_var("HOME", value),
|
||||||
None => std::env::remove_var("HOME"),
|
None => std::env::remove_var("HOME"),
|
||||||
}
|
}
|
||||||
match original_claude_home {
|
match original_config_home {
|
||||||
Some(value) => std::env::set_var("CLAUDE_CONFIG_HOME", value),
|
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||||
None => std::env::remove_var("CLAUDE_CONFIG_HOME"),
|
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||||
}
|
}
|
||||||
let _ = std::fs::remove_dir_all(root);
|
let _ = std::fs::remove_dir_all(root);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user