load and show claude settings including 1hr cache settings
This commit is contained in:
@@ -8,7 +8,7 @@ use aws_sdk_bedrockruntime::Client as BedrockRuntimeClient;
|
||||
use crate::settings::ai::BedrockAuthMethod;
|
||||
|
||||
use super::external_config::ExternalBedrockConfig;
|
||||
use super::convert::{build_converse_request, ConversationMessage, ToolDefinition};
|
||||
use super::convert::{build_converse_request, CachingConfig, ConversationMessage, ToolDefinition};
|
||||
use super::diagnostic::BedrockDiagnosticLogger;
|
||||
use super::models::apply_cross_region_prefix;
|
||||
use super::response_translator::bedrock_stream_to_response_events;
|
||||
@@ -148,6 +148,13 @@ impl BedrockClient {
|
||||
model_id.to_string()
|
||||
};
|
||||
|
||||
let external_config = ExternalBedrockConfig::load();
|
||||
let caching_config = CachingConfig::from_external_config(&external_config);
|
||||
|
||||
if !caching_config.enabled {
|
||||
log::info!("[bedrock] Prompt caching disabled (DISABLE_PROMPT_CACHING=1)");
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"[bedrock] converse_stream: model={effective_model_id}, region={}, messages={}, tools={}",
|
||||
self.region,
|
||||
@@ -163,6 +170,7 @@ impl BedrockClient {
|
||||
temperature,
|
||||
None,
|
||||
None,
|
||||
caching_config,
|
||||
);
|
||||
|
||||
if let Some(ref logger) = diagnostic_logger {
|
||||
|
||||
@@ -8,6 +8,32 @@ use aws_sdk_bedrockruntime::types::{
|
||||
use aws_smithy_types::Document;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use super::external_config::ExternalBedrockConfig;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CachingConfig {
|
||||
pub enabled: bool,
|
||||
pub extended_ttl_requested: bool,
|
||||
}
|
||||
|
||||
impl Default for CachingConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
extended_ttl_requested: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CachingConfig {
|
||||
pub fn from_external_config(external: &ExternalBedrockConfig) -> Self {
|
||||
Self {
|
||||
enabled: !external.disable_prompt_caching,
|
||||
extended_ttl_requested: external.enable_prompt_caching_1h,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConvertedRequest {
|
||||
pub messages: Vec<BedrockMessage>,
|
||||
pub system: Vec<SystemContentBlock>,
|
||||
@@ -73,11 +99,12 @@ pub fn build_converse_request(
|
||||
temperature: Option<f32>,
|
||||
top_p: Option<f32>,
|
||||
stop_sequences: Option<Vec<String>>,
|
||||
caching_config: CachingConfig,
|
||||
) -> ConvertedRequest {
|
||||
let bedrock_messages = convert_messages(messages);
|
||||
let system = convert_system_prompt(system_prompt);
|
||||
let bedrock_messages = convert_messages(messages, &caching_config);
|
||||
let system = convert_system_prompt(system_prompt, &caching_config);
|
||||
let inference_config = build_inference_config(max_tokens, temperature, top_p, stop_sequences);
|
||||
let tool_config = build_tool_config(tools);
|
||||
let tool_config = build_tool_config(tools, &caching_config);
|
||||
|
||||
ConvertedRequest {
|
||||
messages: bedrock_messages,
|
||||
@@ -112,7 +139,7 @@ fn json_to_document(value: JsonValue) -> Document {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_messages(messages: Vec<ConversationMessage>) -> Vec<BedrockMessage> {
|
||||
fn convert_messages(messages: Vec<ConversationMessage>, caching_config: &CachingConfig) -> Vec<BedrockMessage> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for msg in messages {
|
||||
@@ -213,7 +240,7 @@ fn convert_messages(messages: Vec<ConversationMessage>) -> Vec<BedrockMessage> {
|
||||
// Add a cache point to the second-to-last message (the conversation prefix
|
||||
// that is stable between requests). This allows Bedrock to cache all prior
|
||||
// context and only process the latest message as new input tokens.
|
||||
if messages.len() >= 2 {
|
||||
if caching_config.enabled && messages.len() >= 2 {
|
||||
let cache_idx = messages.len() - 2;
|
||||
let msg = messages.remove(cache_idx);
|
||||
let mut content = msg.content().to_vec();
|
||||
@@ -229,6 +256,11 @@ fn convert_messages(messages: Vec<ConversationMessage>) -> Vec<BedrockMessage> {
|
||||
.build()
|
||||
.expect("valid message with cache point");
|
||||
messages.insert(cache_idx, cached_msg);
|
||||
|
||||
if caching_config.extended_ttl_requested {
|
||||
log::info!("[bedrock] Extended 1-hour caching requested (ENABLE_PROMPT_CACHING_1H=1)");
|
||||
// TODO: Use explicit TTL when AWS SDK supports it
|
||||
}
|
||||
}
|
||||
|
||||
messages
|
||||
@@ -265,18 +297,19 @@ fn coalesce_consecutive_roles(messages: Vec<BedrockMessage>) -> Vec<BedrockMessa
|
||||
result
|
||||
}
|
||||
|
||||
fn convert_system_prompt(system_prompt: Option<String>) -> Vec<SystemContentBlock> {
|
||||
fn convert_system_prompt(system_prompt: Option<String>, caching_config: &CachingConfig) -> Vec<SystemContentBlock> {
|
||||
match system_prompt {
|
||||
Some(prompt) if !prompt.is_empty() => {
|
||||
vec![
|
||||
SystemContentBlock::Text(prompt),
|
||||
SystemContentBlock::CachePoint(
|
||||
let mut blocks = vec![SystemContentBlock::Text(prompt)];
|
||||
if caching_config.enabled {
|
||||
blocks.push(SystemContentBlock::CachePoint(
|
||||
CachePointBlock::builder()
|
||||
.r#type(CachePointType::Default)
|
||||
.build()
|
||||
.expect("valid cache point"),
|
||||
),
|
||||
]
|
||||
));
|
||||
}
|
||||
blocks
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
@@ -303,7 +336,7 @@ fn build_inference_config(
|
||||
builder.build()
|
||||
}
|
||||
|
||||
fn build_tool_config(tools: Vec<ToolDefinition>) -> Option<ToolConfiguration> {
|
||||
fn build_tool_config(tools: Vec<ToolDefinition>, caching_config: &CachingConfig) -> Option<ToolConfiguration> {
|
||||
if tools.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -323,12 +356,14 @@ fn build_tool_config(tools: Vec<ToolDefinition>) -> Option<ToolConfiguration> {
|
||||
})
|
||||
.collect();
|
||||
|
||||
tool_specs.push(Tool::CachePoint(
|
||||
CachePointBlock::builder()
|
||||
.r#type(CachePointType::Default)
|
||||
.build()
|
||||
.expect("valid cache point"),
|
||||
));
|
||||
if caching_config.enabled {
|
||||
tool_specs.push(Tool::CachePoint(
|
||||
CachePointBlock::builder()
|
||||
.r#type(CachePointType::Default)
|
||||
.build()
|
||||
.expect("valid cache point"),
|
||||
));
|
||||
}
|
||||
|
||||
Some(
|
||||
ToolConfiguration::builder()
|
||||
|
||||
@@ -10,7 +10,7 @@ fn test_text_message_converts_to_single_block() {
|
||||
content: MessageContent::Text("Hello".to_string()),
|
||||
}];
|
||||
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
|
||||
assert_eq!(result.messages.len(), 1);
|
||||
assert_eq!(result.messages[0].role(), &ConversationRole::User);
|
||||
@@ -29,7 +29,7 @@ fn test_tool_use_produces_valid_json_input() {
|
||||
},
|
||||
}];
|
||||
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
|
||||
assert_eq!(result.messages.len(), 1);
|
||||
assert_eq!(result.messages[0].role(), &ConversationRole::Assistant);
|
||||
@@ -53,7 +53,7 @@ fn test_tool_result_with_matching_id() {
|
||||
},
|
||||
}];
|
||||
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
|
||||
assert_eq!(result.messages.len(), 1);
|
||||
match &result.messages[0].content()[0] {
|
||||
@@ -75,7 +75,7 @@ fn test_tool_result_error_status() {
|
||||
},
|
||||
}];
|
||||
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
|
||||
match &result.messages[0].content()[0] {
|
||||
ContentBlock::ToolResult(block) => {
|
||||
@@ -101,7 +101,7 @@ fn test_consecutive_same_role_messages_coalesced() {
|
||||
},
|
||||
];
|
||||
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
|
||||
assert_eq!(result.messages.len(), 1);
|
||||
assert_eq!(result.messages[0].content().len(), 2);
|
||||
@@ -126,7 +126,7 @@ fn test_alternating_roles_not_coalesced() {
|
||||
},
|
||||
];
|
||||
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
|
||||
assert_eq!(result.messages.len(), 3);
|
||||
assert_eq!(result.messages[0].role(), &ConversationRole::User);
|
||||
@@ -149,6 +149,7 @@ fn test_system_prompt_separated_from_messages() {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
CachingConfig::default(),
|
||||
);
|
||||
|
||||
assert_eq!(result.system.len(), 1);
|
||||
@@ -159,16 +160,16 @@ fn test_system_prompt_separated_from_messages() {
|
||||
#[test]
|
||||
fn test_empty_system_prompt_produces_empty_vec() {
|
||||
let result =
|
||||
build_converse_request(vec![], Some("".to_string()), vec![], 4096, None, None, None);
|
||||
build_converse_request(vec![], Some("".to_string()), vec![], 4096, None, None, None, CachingConfig::default());
|
||||
assert!(result.system.is_empty());
|
||||
|
||||
let result2 = build_converse_request(vec![], None, vec![], 4096, None, None, None);
|
||||
let result2 = build_converse_request(vec![], None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
assert!(result2.system.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_tools_produce_none_config() {
|
||||
let result = build_converse_request(vec![], None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(vec![], None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
assert!(result.tool_config.is_none());
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ fn test_tool_definitions_produce_tool_config() {
|
||||
|
||||
#[test]
|
||||
fn test_inference_config_max_tokens_only() {
|
||||
let result = build_converse_request(vec![], None, vec![], 8192, None, None, None);
|
||||
let result = build_converse_request(vec![], None, vec![], 8192, None, None, None, CachingConfig::default());
|
||||
assert_eq!(result.inference_config.max_tokens(), Some(8192));
|
||||
assert_eq!(result.inference_config.temperature(), None);
|
||||
assert_eq!(result.inference_config.top_p(), None);
|
||||
@@ -212,6 +213,7 @@ fn test_inference_config_all_params() {
|
||||
Some(0.7),
|
||||
Some(0.9),
|
||||
Some(vec!["STOP".to_string()]),
|
||||
CachingConfig::default(),
|
||||
);
|
||||
assert_eq!(result.inference_config.max_tokens(), Some(4096));
|
||||
assert_eq!(result.inference_config.temperature(), Some(0.7));
|
||||
@@ -233,7 +235,7 @@ fn test_multipart_content_produces_multiple_blocks() {
|
||||
]),
|
||||
}];
|
||||
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
|
||||
assert_eq!(result.messages[0].content().len(), 2);
|
||||
assert!(matches!(
|
||||
@@ -271,7 +273,7 @@ fn test_tool_result_after_tool_use_coalesced_into_user_message() {
|
||||
},
|
||||
];
|
||||
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None);
|
||||
let result = build_converse_request(messages, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||
|
||||
assert_eq!(result.messages.len(), 3);
|
||||
assert_eq!(result.messages[0].role(), &ConversationRole::User);
|
||||
@@ -282,3 +284,127 @@ fn test_tool_result_after_tool_use_coalesced_into_user_message() {
|
||||
ContentBlock::ToolResult(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caching_disabled_no_cache_points() {
|
||||
let config = CachingConfig {
|
||||
enabled: false,
|
||||
extended_ttl_requested: false,
|
||||
};
|
||||
|
||||
let messages = vec![
|
||||
ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text("first message".to_string()),
|
||||
},
|
||||
ConversationMessage {
|
||||
role: MessageRole::Assistant,
|
||||
content: MessageContent::Text("response".to_string()),
|
||||
},
|
||||
ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text("second message".to_string()),
|
||||
},
|
||||
];
|
||||
|
||||
let tools = vec![ToolDefinition {
|
||||
name: "test_tool".to_string(),
|
||||
description: "Test tool".to_string(),
|
||||
input_schema: json!({"type": "object", "properties": {}}),
|
||||
}];
|
||||
|
||||
let result = build_converse_request(
|
||||
messages,
|
||||
Some("System prompt".to_string()),
|
||||
tools,
|
||||
4096,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
config,
|
||||
);
|
||||
|
||||
// Check that no cache points exist in messages
|
||||
for msg in &result.messages {
|
||||
for content in msg.content() {
|
||||
assert!(!matches!(content, ContentBlock::CachePoint(_)));
|
||||
}
|
||||
}
|
||||
|
||||
// Check that no cache points exist in system
|
||||
for block in &result.system {
|
||||
use aws_sdk_bedrockruntime::types::SystemContentBlock;
|
||||
assert!(!matches!(block, SystemContentBlock::CachePoint(_)));
|
||||
}
|
||||
|
||||
// Check that no cache points exist in tools
|
||||
if let Some(tool_config) = result.tool_config {
|
||||
for tool in tool_config.tools() {
|
||||
use aws_sdk_bedrockruntime::types::Tool;
|
||||
assert!(!matches!(tool, Tool::CachePoint(_)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caching_enabled_has_cache_points() {
|
||||
let config = CachingConfig::default(); // enabled by default
|
||||
|
||||
let messages = vec![
|
||||
ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text("first message".to_string()),
|
||||
},
|
||||
ConversationMessage {
|
||||
role: MessageRole::Assistant,
|
||||
content: MessageContent::Text("response".to_string()),
|
||||
},
|
||||
ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text("second message".to_string()),
|
||||
},
|
||||
];
|
||||
|
||||
let tools = vec![ToolDefinition {
|
||||
name: "test_tool".to_string(),
|
||||
description: "Test tool".to_string(),
|
||||
input_schema: json!({"type": "object", "properties": {}}),
|
||||
}];
|
||||
|
||||
let result = build_converse_request(
|
||||
messages,
|
||||
Some("System prompt".to_string()),
|
||||
tools,
|
||||
4096,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
config,
|
||||
);
|
||||
|
||||
// Check that cache point exists in second-to-last message (index 1 of 3)
|
||||
let second_to_last_msg = &result.messages[1];
|
||||
let has_cache_point = second_to_last_msg
|
||||
.content()
|
||||
.iter()
|
||||
.any(|c| matches!(c, ContentBlock::CachePoint(_)));
|
||||
assert!(has_cache_point, "Second-to-last message should have cache point");
|
||||
|
||||
// Check that cache point exists in system
|
||||
use aws_sdk_bedrockruntime::types::SystemContentBlock;
|
||||
let has_system_cache = result
|
||||
.system
|
||||
.iter()
|
||||
.any(|b| matches!(b, SystemContentBlock::CachePoint(_)));
|
||||
assert!(has_system_cache, "System should have cache point");
|
||||
|
||||
// Check that cache point exists in tools
|
||||
if let Some(tool_config) = result.tool_config {
|
||||
use aws_sdk_bedrockruntime::types::Tool;
|
||||
let has_tool_cache = tool_config
|
||||
.tools()
|
||||
.iter()
|
||||
.any(|t| matches!(t, Tool::CachePoint(_)));
|
||||
assert!(has_tool_cache, "Tools should have cache point");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ pub struct ExternalBedrockConfig {
|
||||
pub region: Option<String>,
|
||||
pub models: Vec<BedrockModelConfig>,
|
||||
pub auth_refresh_command: Option<String>,
|
||||
pub disable_prompt_caching: bool,
|
||||
pub enable_prompt_caching_1h: bool,
|
||||
pub anthropic_model: Option<String>,
|
||||
pub anthropic_small_fast_model: Option<String>,
|
||||
}
|
||||
|
||||
impl ExternalBedrockConfig {
|
||||
@@ -30,6 +34,10 @@ impl ExternalBedrockConfig {
|
||||
claude_config.models
|
||||
},
|
||||
auth_refresh_command: claude_config.auth_refresh_command,
|
||||
disable_prompt_caching: claude_config.disable_prompt_caching,
|
||||
enable_prompt_caching_1h: claude_config.enable_prompt_caching_1h,
|
||||
anthropic_model: claude_config.anthropic_model,
|
||||
anthropic_small_fast_model: claude_config.anthropic_small_fast_model,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +91,37 @@ fn parse_claude_code_config(path: PathBuf) -> ExternalBedrockConfig {
|
||||
|
||||
let models = parse_claude_code_model_map(env);
|
||||
|
||||
let disable_prompt_caching = env
|
||||
.get("DISABLE_PROMPT_CACHING")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s == "1")
|
||||
.unwrap_or(false);
|
||||
|
||||
let enable_prompt_caching_1h = env
|
||||
.get("ENABLE_PROMPT_CACHING_1H")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s == "1")
|
||||
.unwrap_or(false);
|
||||
|
||||
let anthropic_model = env
|
||||
.get("ANTHROPIC_MODEL")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let anthropic_small_fast_model = env
|
||||
.get("ANTHROPIC_SMALL_FAST_MODEL")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
ExternalBedrockConfig {
|
||||
profile,
|
||||
region,
|
||||
models,
|
||||
auth_refresh_command,
|
||||
disable_prompt_caching,
|
||||
enable_prompt_caching_1h,
|
||||
anthropic_model,
|
||||
anthropic_small_fast_model,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +268,10 @@ fn parse_opencode_config(path: PathBuf) -> ExternalBedrockConfig {
|
||||
region,
|
||||
models: Vec::new(),
|
||||
auth_refresh_command: None,
|
||||
disable_prompt_caching: false,
|
||||
enable_prompt_caching_1h: false,
|
||||
anthropic_model: None,
|
||||
anthropic_small_fast_model: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,73 @@ fn test_parse_claude_code_config_extracts_model_map() {
|
||||
assert!(config.models[0].vision_supported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_claude_code_config_extracts_caching_flags() {
|
||||
let mut file = NamedTempFile::new().unwrap();
|
||||
write!(
|
||||
file,
|
||||
r#"{{
|
||||
"env": {{
|
||||
"AWS_PROFILE": "test",
|
||||
"AWS_REGION": "us-east-1",
|
||||
"DISABLE_PROMPT_CACHING": "1",
|
||||
"ENABLE_PROMPT_CACHING_1H": "1"
|
||||
}}
|
||||
}}"#
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = parse_claude_code_config(file.path().to_path_buf());
|
||||
assert!(config.disable_prompt_caching);
|
||||
assert!(config.enable_prompt_caching_1h);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_claude_code_config_caching_flags_default_false() {
|
||||
let mut file = NamedTempFile::new().unwrap();
|
||||
write!(
|
||||
file,
|
||||
r#"{{
|
||||
"env": {{
|
||||
"AWS_PROFILE": "test",
|
||||
"AWS_REGION": "us-east-1"
|
||||
}}
|
||||
}}"#
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = parse_claude_code_config(file.path().to_path_buf());
|
||||
assert!(!config.disable_prompt_caching);
|
||||
assert!(!config.enable_prompt_caching_1h);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_claude_code_config_extracts_model_env_vars() {
|
||||
let mut file = NamedTempFile::new().unwrap();
|
||||
write!(
|
||||
file,
|
||||
r#"{{
|
||||
"env": {{
|
||||
"AWS_PROFILE": "test",
|
||||
"AWS_REGION": "us-east-1",
|
||||
"ANTHROPIC_MODEL": "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
"ANTHROPIC_SMALL_FAST_MODEL": "us.anthropic.claude-haiku-4-5-20251001-v1:0"
|
||||
}}
|
||||
}}"#
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = parse_claude_code_config(file.path().to_path_buf());
|
||||
assert_eq!(
|
||||
config.anthropic_model,
|
||||
Some("us.anthropic.claude-sonnet-4-5-20250929-v1:0".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
config.anthropic_small_fast_model,
|
||||
Some("us.anthropic.claude-haiku-4-5-20251001-v1:0".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_claude_code_config_missing_file() {
|
||||
let config = parse_claude_code_config(PathBuf::from("/nonexistent/path/settings.json"));
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod external_config;
|
||||
pub mod models;
|
||||
pub mod request_translator;
|
||||
pub mod response_translator;
|
||||
pub mod settings_view;
|
||||
pub mod translator;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
use crate::ai::bedrock::convert::CachingConfig;
|
||||
use crate::ai::bedrock::external_config::ExternalBedrockConfig;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::ui_components::blended_colors;
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
Container, CornerRadius, CrossAxisAlignment, Flex, ParentElement, Radius, Text,
|
||||
},
|
||||
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
};
|
||||
|
||||
pub struct SettingsView {
|
||||
external_config: ExternalBedrockConfig,
|
||||
caching_config: CachingConfig,
|
||||
}
|
||||
|
||||
impl SettingsView {
|
||||
pub fn new() -> Self {
|
||||
let external_config = ExternalBedrockConfig::load();
|
||||
let caching_config = CachingConfig::from_external_config(&external_config);
|
||||
Self {
|
||||
external_config,
|
||||
caching_config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View for SettingsView {
|
||||
fn ui_name() -> &'static str {
|
||||
"SettingsView"
|
||||
}
|
||||
|
||||
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let theme = appearance.theme();
|
||||
let font_size = appearance.ui_font_size();
|
||||
let text_color = blended_colors::text_main(theme, theme.surface_2());
|
||||
let label_color = blended_colors::text_sub(theme, theme.surface_2());
|
||||
|
||||
let mut column = Flex::column()
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
|
||||
.with_spacing(8.0);
|
||||
|
||||
// Header
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
"Galaxy Bedrock Settings",
|
||||
appearance.ui_font_family(),
|
||||
font_size + 2.0,
|
||||
)
|
||||
.with_color(text_color)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
// Separator
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
.with_color(label_color)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
// AWS Configuration
|
||||
if let Some(profile) = &self.external_config.profile {
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
format!("AWS Profile: {}", profile),
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
.with_color(text_color)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(region) = &self.external_config.region {
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
format!("AWS Region: {}", region),
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
.with_color(text_color)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
// Model Configuration
|
||||
if let Some(model) = &self.external_config.anthropic_model {
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
format!("Primary Model: {}", model),
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
.with_color(text_color)
|
||||
.soft_wrap(true)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(model) = &self.external_config.anthropic_small_fast_model {
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
format!("Small/Fast Model: {}", model),
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
.with_color(text_color)
|
||||
.soft_wrap(true)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
// Caching Configuration
|
||||
let caching_status = if self.caching_config.enabled {
|
||||
"✓ Enabled"
|
||||
} else {
|
||||
"✗ Disabled"
|
||||
};
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
format!("Prompt Caching: {}", caching_status),
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
.with_color(text_color)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
if self.caching_config.extended_ttl_requested {
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
"Extended 1h TTL: ✓ Requested",
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
.with_color(text_color)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
// Additional Models
|
||||
if !self.external_config.models.is_empty() {
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
format!("\nAdditional models: {}", self.external_config.models.len()),
|
||||
appearance.ui_font_family(),
|
||||
font_size,
|
||||
)
|
||||
.with_color(label_color)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
// Auth Refresh Command
|
||||
if let Some(cmd) = &self.external_config.auth_refresh_command {
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
format!("\nAuth Refresh: {}", cmd),
|
||||
appearance.ui_font_family(),
|
||||
font_size - 1.0,
|
||||
)
|
||||
.with_color(label_color)
|
||||
.soft_wrap(true)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
// Help text
|
||||
column = column.with_child(
|
||||
Text::new(
|
||||
"\nSettings loaded from ~/.claude/settings.json",
|
||||
appearance.ui_font_family(),
|
||||
font_size - 1.0,
|
||||
)
|
||||
.with_color(label_color)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
Container::new(column.finish())
|
||||
.with_uniform_padding(12.0)
|
||||
.with_background(theme.surface_2())
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.0)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for SettingsView {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl TypedActionView for SettingsView {
|
||||
type Action = ();
|
||||
|
||||
fn handle_action(&mut self, _action: &Self::Action, _ctx: &mut ViewContext<Self>) {}
|
||||
}
|
||||
Reference in New Issue
Block a user