Merge branch 'load-and-show-claude-settings' into 'master'
load and show claude settings including 1hr cache settings See merge request samnasbo/shared/galaxy!5
This commit is contained in:
Generated
+11
-11
@@ -1602,7 +1602,7 @@ dependencies = [
|
|||||||
"aws-smithy-async",
|
"aws-smithy-async",
|
||||||
"aws-smithy-eventstream",
|
"aws-smithy-eventstream",
|
||||||
"aws-smithy-http 0.63.6",
|
"aws-smithy-http 0.63.6",
|
||||||
"aws-smithy-json 0.62.6",
|
"aws-smithy-json 0.62.7",
|
||||||
"aws-smithy-observability",
|
"aws-smithy-observability",
|
||||||
"aws-smithy-runtime",
|
"aws-smithy-runtime",
|
||||||
"aws-smithy-runtime-api",
|
"aws-smithy-runtime-api",
|
||||||
@@ -1796,9 +1796,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-http-client"
|
name = "aws-smithy-http-client"
|
||||||
version = "1.1.12"
|
version = "1.1.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769"
|
checksum = "5c3ef8931ad1c98aa6a55b4256f847f3116090819844e0dd41ea682cac5dd2d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-async",
|
"aws-smithy-async",
|
||||||
"aws-smithy-runtime-api",
|
"aws-smithy-runtime-api",
|
||||||
@@ -1835,9 +1835,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-json"
|
name = "aws-smithy-json"
|
||||||
version = "0.62.6"
|
version = "0.62.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "517089205f18ab4adc5a3e02888cb139bbbbb2e168eac9f396216925d1fbeaf5"
|
checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-runtime-api",
|
"aws-smithy-runtime-api",
|
||||||
"aws-smithy-schema",
|
"aws-smithy-schema",
|
||||||
@@ -1891,9 +1891,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-runtime-api"
|
name = "aws-smithy-runtime-api"
|
||||||
version = "1.12.1"
|
version = "1.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc117c179ecf39a62a0a3f49f600e9ac26a7ad7dd172177999f83933af776c32"
|
checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-async",
|
"aws-smithy-async",
|
||||||
"aws-smithy-runtime-api-macros",
|
"aws-smithy-runtime-api-macros",
|
||||||
@@ -1931,9 +1931,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-types"
|
name = "aws-smithy-types"
|
||||||
version = "1.4.8"
|
version = "1.4.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "056b66dbce2f81cc0c1e2b05bb402eb58f8a3530479d650efadd5bbae9a4050b"
|
checksum = "53f93074121a1be41317b9aa607143ae17900631f7f59a99f2b905d519d6783b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64-simd",
|
"base64-simd",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -9552,7 +9552,7 @@ version = "5.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.7",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
"http 1.4.1",
|
"http 1.4.1",
|
||||||
@@ -10209,7 +10209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+1
-1
@@ -288,7 +288,7 @@ tokio-util.workspace = true
|
|||||||
aws-config = { version = "1.8.12", features = ["credentials-login"] }
|
aws-config = { version = "1.8.12", features = ["credentials-login"] }
|
||||||
aws-credential-types = "1"
|
aws-credential-types = "1"
|
||||||
aws-sdk-bedrock = "1"
|
aws-sdk-bedrock = "1"
|
||||||
aws-sdk-bedrockruntime = "1"
|
aws-sdk-bedrockruntime = "1.132"
|
||||||
aws-sdk-sts = "1"
|
aws-sdk-sts = "1"
|
||||||
aws-smithy-types = "1"
|
aws-smithy-types = "1"
|
||||||
aws-types = "1"
|
aws-types = "1"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use aws_sdk_bedrockruntime::Client as BedrockRuntimeClient;
|
|||||||
use crate::settings::ai::BedrockAuthMethod;
|
use crate::settings::ai::BedrockAuthMethod;
|
||||||
|
|
||||||
use super::external_config::ExternalBedrockConfig;
|
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::diagnostic::BedrockDiagnosticLogger;
|
||||||
use super::models::apply_cross_region_prefix;
|
use super::models::apply_cross_region_prefix;
|
||||||
use super::response_translator::bedrock_stream_to_response_events;
|
use super::response_translator::bedrock_stream_to_response_events;
|
||||||
@@ -149,6 +149,13 @@ impl BedrockClient {
|
|||||||
model_id.to_string()
|
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!(
|
log::info!(
|
||||||
"[bedrock] converse_stream: model={effective_model_id}, region={}, messages={}, tools={}",
|
"[bedrock] converse_stream: model={effective_model_id}, region={}, messages={}, tools={}",
|
||||||
self.region,
|
self.region,
|
||||||
@@ -165,6 +172,7 @@ impl BedrockClient {
|
|||||||
temperature,
|
temperature,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
caching_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(ref logger) = diagnostic_logger {
|
if let Some(ref logger) = diagnostic_logger {
|
||||||
|
|||||||
@@ -3,12 +3,38 @@ use std::collections::HashMap;
|
|||||||
use aws_sdk_bedrockruntime::types::{
|
use aws_sdk_bedrockruntime::types::{
|
||||||
CachePointBlock, CachePointType, CacheTtl, ContentBlock, ConversationRole,
|
CachePointBlock, CachePointType, CacheTtl, ContentBlock, ConversationRole,
|
||||||
InferenceConfiguration, Message as BedrockMessage, SystemContentBlock, Tool,
|
InferenceConfiguration, Message as BedrockMessage, SystemContentBlock, Tool,
|
||||||
ToolConfiguration, ToolInputSchema, ToolResultBlock, ToolResultContentBlock, ToolResultStatus,
|
ToolConfiguration, ToolInputSchema, ToolResultBlock, ToolResultContentBlock,
|
||||||
ToolSpecification, ToolUseBlock,
|
ToolResultStatus, ToolSpecification, ToolUseBlock,
|
||||||
};
|
};
|
||||||
use aws_smithy_types::Document;
|
use aws_smithy_types::Document;
|
||||||
use serde_json::Value as JsonValue;
|
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 struct ConvertedRequest {
|
||||||
pub messages: Vec<BedrockMessage>,
|
pub messages: Vec<BedrockMessage>,
|
||||||
pub system: Vec<SystemContentBlock>,
|
pub system: Vec<SystemContentBlock>,
|
||||||
@@ -59,7 +85,7 @@ pub enum ContentPart {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ToolDefinition {
|
pub struct ToolDefinition {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
@@ -75,11 +101,12 @@ pub fn build_converse_request(
|
|||||||
temperature: Option<f32>,
|
temperature: Option<f32>,
|
||||||
top_p: Option<f32>,
|
top_p: Option<f32>,
|
||||||
stop_sequences: Option<Vec<String>>,
|
stop_sequences: Option<Vec<String>>,
|
||||||
|
caching_config: CachingConfig,
|
||||||
) -> ConvertedRequest {
|
) -> ConvertedRequest {
|
||||||
let bedrock_messages = convert_messages(messages);
|
let bedrock_messages = convert_messages(messages, &caching_config);
|
||||||
let system = convert_system_prompt(system_prompt, compact_summary);
|
let system = convert_system_prompt(system_prompt, compact_summary, &caching_config);
|
||||||
let inference_config = build_inference_config(max_tokens, temperature, top_p, stop_sequences);
|
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 {
|
ConvertedRequest {
|
||||||
messages: bedrock_messages,
|
messages: bedrock_messages,
|
||||||
@@ -114,7 +141,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();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
for msg in messages {
|
for msg in messages {
|
||||||
@@ -215,16 +242,19 @@ fn convert_messages(messages: Vec<ConversationMessage>) -> Vec<BedrockMessage> {
|
|||||||
// Add a cache point to the second-to-last message (the conversation prefix
|
// 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
|
// that is stable between requests). This allows Bedrock to cache all prior
|
||||||
// context and only process the latest message as new input tokens.
|
// 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 cache_idx = messages.len() - 2;
|
||||||
let msg = messages.remove(cache_idx);
|
let msg = messages.remove(cache_idx);
|
||||||
let mut content = msg.content().to_vec();
|
let mut content = msg.content().to_vec();
|
||||||
|
|
||||||
|
let mut builder = CachePointBlock::builder().r#type(CachePointType::Default);
|
||||||
|
if caching_config.extended_ttl_requested {
|
||||||
|
builder = builder.ttl(CacheTtl::OneHour);
|
||||||
|
log::info!("[bedrock] Using 1-hour cache TTL (ENABLE_PROMPT_CACHING_1H=1)");
|
||||||
|
}
|
||||||
|
|
||||||
content.push(ContentBlock::CachePoint(
|
content.push(ContentBlock::CachePoint(
|
||||||
CachePointBlock::builder()
|
builder.build().expect("valid cache point"),
|
||||||
.r#type(CachePointType::Default)
|
|
||||||
.ttl(CacheTtl::OneHour)
|
|
||||||
.build()
|
|
||||||
.expect("valid cache point"),
|
|
||||||
));
|
));
|
||||||
let cached_msg = BedrockMessage::builder()
|
let cached_msg = BedrockMessage::builder()
|
||||||
.role(msg.role().clone())
|
.role(msg.role().clone())
|
||||||
@@ -271,6 +301,7 @@ fn coalesce_consecutive_roles(messages: Vec<BedrockMessage>) -> Vec<BedrockMessa
|
|||||||
fn convert_system_prompt(
|
fn convert_system_prompt(
|
||||||
system_prompt: Option<String>,
|
system_prompt: Option<String>,
|
||||||
compact_summary: Option<String>,
|
compact_summary: Option<String>,
|
||||||
|
caching_config: &CachingConfig,
|
||||||
) -> Vec<SystemContentBlock> {
|
) -> Vec<SystemContentBlock> {
|
||||||
let mut blocks = Vec::new();
|
let mut blocks = Vec::new();
|
||||||
|
|
||||||
@@ -286,13 +317,13 @@ fn convert_system_prompt(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !blocks.is_empty() {
|
if !blocks.is_empty() && caching_config.enabled {
|
||||||
|
let mut builder = CachePointBlock::builder().r#type(CachePointType::Default);
|
||||||
|
if caching_config.extended_ttl_requested {
|
||||||
|
builder = builder.ttl(CacheTtl::OneHour);
|
||||||
|
}
|
||||||
blocks.push(SystemContentBlock::CachePoint(
|
blocks.push(SystemContentBlock::CachePoint(
|
||||||
CachePointBlock::builder()
|
builder.build().expect("valid cache point"),
|
||||||
.r#type(CachePointType::Default)
|
|
||||||
.ttl(CacheTtl::OneHour)
|
|
||||||
.build()
|
|
||||||
.expect("valid cache point"),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +351,7 @@ fn build_inference_config(
|
|||||||
builder.build()
|
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() {
|
if tools.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -340,13 +371,15 @@ fn build_tool_config(tools: Vec<ToolDefinition>) -> Option<ToolConfiguration> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
tool_specs.push(Tool::CachePoint(
|
if caching_config.enabled {
|
||||||
CachePointBlock::builder()
|
let mut builder = CachePointBlock::builder().r#type(CachePointType::Default);
|
||||||
.r#type(CachePointType::Default)
|
if caching_config.extended_ttl_requested {
|
||||||
.ttl(CacheTtl::OneHour)
|
builder = builder.ttl(CacheTtl::OneHour);
|
||||||
.build()
|
}
|
||||||
.expect("valid cache point"),
|
tool_specs.push(Tool::CachePoint(
|
||||||
));
|
builder.build().expect("valid cache point"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ToolConfiguration::builder()
|
ToolConfiguration::builder()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ fn test_text_message_converts_to_single_block() {
|
|||||||
content: MessageContent::Text("Hello".to_string()),
|
content: MessageContent::Text("Hello".to_string()),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
assert_eq!(result.messages.len(), 1);
|
assert_eq!(result.messages.len(), 1);
|
||||||
assert_eq!(result.messages[0].role(), &ConversationRole::User);
|
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, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
assert_eq!(result.messages.len(), 1);
|
assert_eq!(result.messages.len(), 1);
|
||||||
assert_eq!(result.messages[0].role(), &ConversationRole::Assistant);
|
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, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
assert_eq!(result.messages.len(), 1);
|
assert_eq!(result.messages.len(), 1);
|
||||||
match &result.messages[0].content()[0] {
|
match &result.messages[0].content()[0] {
|
||||||
@@ -75,7 +75,7 @@ fn test_tool_result_error_status() {
|
|||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
match &result.messages[0].content()[0] {
|
match &result.messages[0].content()[0] {
|
||||||
ContentBlock::ToolResult(block) => {
|
ContentBlock::ToolResult(block) => {
|
||||||
@@ -101,7 +101,7 @@ fn test_consecutive_same_role_messages_coalesced() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
assert_eq!(result.messages.len(), 1);
|
assert_eq!(result.messages.len(), 1);
|
||||||
assert_eq!(result.messages[0].content().len(), 2);
|
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, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
assert_eq!(result.messages.len(), 3);
|
assert_eq!(result.messages.len(), 3);
|
||||||
assert_eq!(result.messages[0].role(), &ConversationRole::User);
|
assert_eq!(result.messages[0].role(), &ConversationRole::User);
|
||||||
@@ -150,6 +150,7 @@ fn test_system_prompt_separated_from_messages() {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
CachingConfig::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(result.system.len(), 1);
|
assert_eq!(result.system.len(), 1);
|
||||||
@@ -160,16 +161,16 @@ fn test_system_prompt_separated_from_messages() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_empty_system_prompt_produces_empty_vec() {
|
fn test_empty_system_prompt_produces_empty_vec() {
|
||||||
let result =
|
let result =
|
||||||
build_converse_request(vec![], Some("".to_string()), None, vec![], 4096, None, None, None);
|
build_converse_request(vec![], Some("".to_string()), None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
assert!(result.system.is_empty());
|
assert!(result.system.is_empty());
|
||||||
|
|
||||||
let result2 = build_converse_request(vec![], None, None, vec![], 4096, None, None, None);
|
let result2 = build_converse_request(vec![], None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
assert!(result2.system.is_empty());
|
assert!(result2.system.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty_tools_produce_none_config() {
|
fn test_empty_tools_produce_none_config() {
|
||||||
let result = build_converse_request(vec![], None, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(vec![], None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
assert!(result.tool_config.is_none());
|
assert!(result.tool_config.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +188,7 @@ fn test_tool_definitions_produce_tool_config() {
|
|||||||
}),
|
}),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let result = build_converse_request(vec![], None, None, tools, 4096, None, None, None);
|
let result = build_converse_request(vec![], None, None, tools, 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
assert!(result.tool_config.is_some());
|
assert!(result.tool_config.is_some());
|
||||||
let config = result.tool_config.unwrap();
|
let config = result.tool_config.unwrap();
|
||||||
@@ -196,7 +197,7 @@ fn test_tool_definitions_produce_tool_config() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_inference_config_max_tokens_only() {
|
fn test_inference_config_max_tokens_only() {
|
||||||
let result = build_converse_request(vec![], None, None, vec![], 8192, None, None, None);
|
let result = build_converse_request(vec![], None, None, vec![], 8192, None, None, None, CachingConfig::default());
|
||||||
assert_eq!(result.inference_config.max_tokens(), Some(8192));
|
assert_eq!(result.inference_config.max_tokens(), Some(8192));
|
||||||
assert_eq!(result.inference_config.temperature(), None);
|
assert_eq!(result.inference_config.temperature(), None);
|
||||||
assert_eq!(result.inference_config.top_p(), None);
|
assert_eq!(result.inference_config.top_p(), None);
|
||||||
@@ -214,6 +215,7 @@ fn test_inference_config_all_params() {
|
|||||||
Some(0.7),
|
Some(0.7),
|
||||||
Some(0.9),
|
Some(0.9),
|
||||||
Some(vec!["STOP".to_string()]),
|
Some(vec!["STOP".to_string()]),
|
||||||
|
CachingConfig::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(result.inference_config.max_tokens(), Some(4096));
|
assert_eq!(result.inference_config.max_tokens(), Some(4096));
|
||||||
assert_eq!(result.inference_config.temperature(), Some(0.7));
|
assert_eq!(result.inference_config.temperature(), Some(0.7));
|
||||||
@@ -235,7 +237,7 @@ fn test_multipart_content_produces_multiple_blocks() {
|
|||||||
]),
|
]),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
assert_eq!(result.messages[0].content().len(), 2);
|
assert_eq!(result.messages[0].content().len(), 2);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
@@ -273,7 +275,7 @@ fn test_tool_result_after_tool_use_coalesced_into_user_message() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None);
|
let result = build_converse_request(messages, None, None, vec![], 4096, None, None, None, CachingConfig::default());
|
||||||
|
|
||||||
assert_eq!(result.messages.len(), 3);
|
assert_eq!(result.messages.len(), 3);
|
||||||
assert_eq!(result.messages[0].role(), &ConversationRole::User);
|
assert_eq!(result.messages[0].role(), &ConversationRole::User);
|
||||||
@@ -284,3 +286,129 @@ fn test_tool_result_after_tool_use_coalesced_into_user_message() {
|
|||||||
ContentBlock::ToolResult(_)
|
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()),
|
||||||
|
None,
|
||||||
|
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()),
|
||||||
|
None,
|
||||||
|
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 region: Option<String>,
|
||||||
pub models: Vec<BedrockModelConfig>,
|
pub models: Vec<BedrockModelConfig>,
|
||||||
pub auth_refresh_command: Option<String>,
|
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 {
|
impl ExternalBedrockConfig {
|
||||||
@@ -30,6 +34,10 @@ impl ExternalBedrockConfig {
|
|||||||
claude_config.models
|
claude_config.models
|
||||||
},
|
},
|
||||||
auth_refresh_command: claude_config.auth_refresh_command,
|
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 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 {
|
ExternalBedrockConfig {
|
||||||
profile,
|
profile,
|
||||||
region,
|
region,
|
||||||
models,
|
models,
|
||||||
auth_refresh_command,
|
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,
|
region,
|
||||||
models: Vec::new(),
|
models: Vec::new(),
|
||||||
auth_refresh_command: None,
|
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);
|
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]
|
#[test]
|
||||||
fn test_parse_claude_code_config_missing_file() {
|
fn test_parse_claude_code_config_missing_file() {
|
||||||
let config = parse_claude_code_config(PathBuf::from("/nonexistent/path/settings.json"));
|
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 models;
|
||||||
pub mod request_translator;
|
pub mod request_translator;
|
||||||
pub mod response_translator;
|
pub mod response_translator;
|
||||||
|
pub mod settings_view;
|
||||||
pub mod translator;
|
pub mod translator;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
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
|
||||||
|
column = column.with_child(
|
||||||
|
Text::new(
|
||||||
|
"\n━━━ Model Selection ━━━",
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size - 1.0,
|
||||||
|
)
|
||||||
|
.with_color(label_color)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(model) = &self.external_config.anthropic_model {
|
||||||
|
column = column.with_child(
|
||||||
|
Text::new(
|
||||||
|
format!("Default Model (ANTHROPIC_MODEL): {}", model),
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size,
|
||||||
|
)
|
||||||
|
.with_color(text_color)
|
||||||
|
.soft_wrap(true)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
column = column.with_child(
|
||||||
|
Text::new(
|
||||||
|
"Sets the initial model in dropdown. You can change it.",
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size - 1.0,
|
||||||
|
)
|
||||||
|
.with_color(label_color)
|
||||||
|
.soft_wrap(true)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
column = column.with_child(
|
||||||
|
Text::new(
|
||||||
|
"Default Model: Not set",
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size,
|
||||||
|
)
|
||||||
|
.with_color(label_color)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
column = column.with_child(
|
||||||
|
Text::new(
|
||||||
|
"Set ANTHROPIC_MODEL in settings.json to set initial model",
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size - 1.0,
|
||||||
|
)
|
||||||
|
.with_color(label_color)
|
||||||
|
.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 Cache TTL: ✓ Active",
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size,
|
||||||
|
)
|
||||||
|
.with_color(text_color)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
column = column.with_child(
|
||||||
|
Text::new(
|
||||||
|
"All cache points use 1-hour TTL. Model list filtered to compatible models only.",
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size - 1.0,
|
||||||
|
)
|
||||||
|
.with_color(label_color)
|
||||||
|
.soft_wrap(true)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
column = column.with_child(
|
||||||
|
Text::new(
|
||||||
|
"Compatible: Claude Opus 4.5, Sonnet 4.5, Haiku 4.5",
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size - 1.0,
|
||||||
|
)
|
||||||
|
.with_color(label_color)
|
||||||
|
.soft_wrap(true)
|
||||||
|
.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(
|
||||||
|
"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
||||||
|
appearance.ui_font_family(),
|
||||||
|
font_size,
|
||||||
|
)
|
||||||
|
.with_color(label_color)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
column = column.with_child(
|
||||||
|
Text::new(
|
||||||
|
"Settings 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>) {}
|
||||||
|
}
|
||||||
@@ -41,8 +41,10 @@ pub async fn execute(
|
|||||||
.map(|tc| tc.tasks.is_empty())
|
.map(|tc| tc.tasks.is_empty())
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
// Use the model from params (selected in UI or defaulted from ANTHROPIC_MODEL)
|
||||||
let mut model_id = params.model_id;
|
let mut model_id = params.model_id;
|
||||||
if model_id.is_empty() || model_id == "auto" {
|
if model_id.is_empty() || model_id == "auto" {
|
||||||
|
// Fall back to default if nothing is set
|
||||||
model_id = "us.anthropic.claude-opus-4-6".to_string();
|
model_id = "us.anthropic.claude-opus-4-6".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+73
-10
@@ -646,7 +646,42 @@ impl LLMPreferences {
|
|||||||
let region = settings.bedrock_region.value().clone();
|
let region = settings.bedrock_region.value().clone();
|
||||||
let cross_region = *settings.bedrock_cross_region_inference.value();
|
let cross_region = *settings.bedrock_cross_region_inference.value();
|
||||||
|
|
||||||
let effective = get_effective_models(&user_models);
|
// Check if user wants only 1-hour cache models
|
||||||
|
use crate::ai::bedrock::external_config::ExternalBedrockConfig;
|
||||||
|
let external_config = ExternalBedrockConfig::load();
|
||||||
|
let require_1h_cache = external_config.enable_prompt_caching_1h;
|
||||||
|
|
||||||
|
let mut effective = get_effective_models(&user_models);
|
||||||
|
|
||||||
|
// Filter out models that don't support 1-hour caching if required
|
||||||
|
if require_1h_cache {
|
||||||
|
effective.retain(|model| {
|
||||||
|
// 1-hour caching is supported by Claude 4.5+ models
|
||||||
|
// Opus 4.5+, Sonnet 4.5+, Haiku 4.5+
|
||||||
|
let supports_1h = model.model_id.contains("4-5")
|
||||||
|
|| model.model_id.contains("4.5")
|
||||||
|
|| model.model_id.contains("-4-6") // Opus/Sonnet 4.6+ also support 1h
|
||||||
|
|| model.model_id.contains("4.6")
|
||||||
|
|| model.model_id.contains("-4-7")
|
||||||
|
|| model.model_id.contains("4.7")
|
||||||
|
|| model.model_id.contains("-4-8")
|
||||||
|
|| model.model_id.contains("4.8");
|
||||||
|
|
||||||
|
if !supports_1h {
|
||||||
|
log::info!(
|
||||||
|
"[bedrock] Filtering out model {} - does not support 1-hour cache (ENABLE_PROMPT_CACHING_1H=1)",
|
||||||
|
model.model_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
supports_1h
|
||||||
|
});
|
||||||
|
|
||||||
|
if effective.is_empty() {
|
||||||
|
log::warn!("[bedrock] No models left after filtering for 1-hour cache support!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let effective = effective;
|
||||||
for model in effective {
|
for model in effective {
|
||||||
let model_id = if cross_region && !region.is_empty() {
|
let model_id = if cross_region && !region.is_empty() {
|
||||||
super::bedrock::models::apply_cross_region_prefix(&model.model_id, ®ion)
|
super::bedrock::models::apply_cross_region_prefix(&model.model_id, ®ion)
|
||||||
@@ -700,15 +735,43 @@ impl LLMPreferences {
|
|||||||
cli.choices.retain(|m| m.provider != LLMProvider::Unknown);
|
cli.choices.retain(|m| m.provider != LLMProvider::Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default agent mode to Claude Opus 4.6, falling back to the first available model.
|
// Default agent mode to ANTHROPIC_MODEL from external config if set,
|
||||||
if let Some(id) = self
|
// otherwise Claude Opus 4.6, falling back to the first available model.
|
||||||
.models_by_feature
|
// Note: external_config already loaded above for filtering
|
||||||
.agent_mode
|
let external_config_for_default = ExternalBedrockConfig::load();
|
||||||
.choices
|
|
||||||
.iter()
|
if let Some(id) = external_config_for_default
|
||||||
.find(|m| m.display_name.contains("Opus 4.6"))
|
.anthropic_model
|
||||||
.or_else(|| self.models_by_feature.agent_mode.choices.first())
|
.as_ref()
|
||||||
.map(|m| m.id.clone())
|
.and_then(|model_id| {
|
||||||
|
// Match by model_id (with or without cross-region prefix)
|
||||||
|
self.models_by_feature
|
||||||
|
.agent_mode
|
||||||
|
.choices
|
||||||
|
.iter()
|
||||||
|
.find(|m| {
|
||||||
|
m.id.as_str() == model_id
|
||||||
|
|| m.id.as_str().ends_with(model_id)
|
||||||
|
|| model_id.ends_with(m.id.as_str())
|
||||||
|
})
|
||||||
|
.map(|m| {
|
||||||
|
log::info!(
|
||||||
|
"[bedrock] Setting default agent mode model from ANTHROPIC_MODEL: {} -> {}",
|
||||||
|
model_id,
|
||||||
|
m.display_name
|
||||||
|
);
|
||||||
|
m.id.clone()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
self.models_by_feature
|
||||||
|
.agent_mode
|
||||||
|
.choices
|
||||||
|
.iter()
|
||||||
|
.find(|m| m.display_name.contains("Opus 4.6"))
|
||||||
|
.or_else(|| self.models_by_feature.agent_mode.choices.first())
|
||||||
|
.map(|m| m.id.clone())
|
||||||
|
})
|
||||||
{
|
{
|
||||||
self.models_by_feature.agent_mode.default_id = id;
|
self.models_by_feature.agent_mode.default_id = id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,6 +396,15 @@ pub const CONTEXT: StaticCommand = StaticCommand {
|
|||||||
argument: None,
|
argument: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const SETTINGS: StaticCommand = StaticCommand {
|
||||||
|
name: "/settings",
|
||||||
|
description: "Show Galaxy configuration (Bedrock, models, caching)",
|
||||||
|
icon_path: "bundled/svg/settings-06.svg",
|
||||||
|
availability: Availability::ALWAYS,
|
||||||
|
auto_enter_ai_mode: false,
|
||||||
|
argument: None,
|
||||||
|
};
|
||||||
|
|
||||||
pub const CONVERSATIONS: StaticCommand = StaticCommand {
|
pub const CONVERSATIONS: StaticCommand = StaticCommand {
|
||||||
name: "/conversations",
|
name: "/conversations",
|
||||||
description: "Open conversation history",
|
description: "Open conversation history",
|
||||||
@@ -531,6 +540,7 @@ fn all_commands() -> Vec<StaticCommand> {
|
|||||||
CONVERSATIONS,
|
CONVERSATIONS,
|
||||||
EXPORT_TO_CLIPBOARD,
|
EXPORT_TO_CLIPBOARD,
|
||||||
MODEL.clone(),
|
MODEL.clone(),
|
||||||
|
SETTINGS,
|
||||||
];
|
];
|
||||||
|
|
||||||
if FeatureFlag::LocalDockerSandbox.is_enabled() {
|
if FeatureFlag::LocalDockerSandbox.is_enabled() {
|
||||||
|
|||||||
@@ -712,6 +712,9 @@ impl Input {
|
|||||||
ctx.dispatch_typed_action(&TerminalAction::ToggleContextView);
|
ctx.dispatch_typed_action(&TerminalAction::ToggleContextView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_settings if command.name == commands::SETTINGS.name => {
|
||||||
|
ctx.dispatch_typed_action(&TerminalAction::ToggleSettingsView);
|
||||||
|
}
|
||||||
_fork if command.name == commands::FORK.name => {
|
_fork if command.name == commands::FORK.name => {
|
||||||
let Some(conversation_id) = self
|
let Some(conversation_id) = self
|
||||||
.ai_context_model
|
.ai_context_model
|
||||||
|
|||||||
@@ -2589,6 +2589,9 @@ pub struct TerminalView {
|
|||||||
/// View ID of the context window debug view, if visible.
|
/// View ID of the context window debug view, if visible.
|
||||||
context_view_id: Option<EntityId>,
|
context_view_id: Option<EntityId>,
|
||||||
|
|
||||||
|
/// View ID of the settings view, if visible.
|
||||||
|
settings_view_id: Option<EntityId>,
|
||||||
|
|
||||||
// Whether the block onboarding view is active or not.
|
// Whether the block onboarding view is active or not.
|
||||||
block_onboarding_active: bool,
|
block_onboarding_active: bool,
|
||||||
|
|
||||||
@@ -4093,6 +4096,7 @@ impl TerminalView {
|
|||||||
rich_content_views: Vec::new(),
|
rich_content_views: Vec::new(),
|
||||||
usage_footer_view_ids: Default::default(),
|
usage_footer_view_ids: Default::default(),
|
||||||
context_view_id: None,
|
context_view_id: None,
|
||||||
|
settings_view_id: None,
|
||||||
block_onboarding_active: false,
|
block_onboarding_active: false,
|
||||||
onboarding_agentic_suggestions_block: None,
|
onboarding_agentic_suggestions_block: None,
|
||||||
onboarding_prompt_block: None,
|
onboarding_prompt_block: None,
|
||||||
@@ -5792,6 +5796,36 @@ impl TerminalView {
|
|||||||
ctx.notify();
|
ctx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_settings_view(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
|
use crate::ai::bedrock::settings_view::SettingsView;
|
||||||
|
|
||||||
|
// If already showing, remove it
|
||||||
|
if let Some(view_id) = self.settings_view_id.take() {
|
||||||
|
let mut model = self.model.lock();
|
||||||
|
model.block_list_mut().remove_rich_content(view_id);
|
||||||
|
drop(model);
|
||||||
|
self.rich_content_views.retain(|rc| rc.view_id() != view_id);
|
||||||
|
ctx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and insert the settings view
|
||||||
|
let settings_view = ctx.add_view(|_| SettingsView::new());
|
||||||
|
let view_id = settings_view.id();
|
||||||
|
self.settings_view_id = Some(view_id);
|
||||||
|
|
||||||
|
self.insert_rich_content(
|
||||||
|
None,
|
||||||
|
settings_view,
|
||||||
|
None,
|
||||||
|
RichContentInsertionPosition::Append {
|
||||||
|
insert_below_long_running_block: true,
|
||||||
|
},
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
ctx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the window is wide enough to auto-open side panels.
|
/// Returns true if the window is wide enough to auto-open side panels.
|
||||||
pub fn can_auto_open_panel(&self) -> bool {
|
pub fn can_auto_open_panel(&self) -> bool {
|
||||||
self.size_info.pane_width_px().as_f32() > MINIMUM_WIDTH_TO_AUTO_OPEN_PANE
|
self.size_info.pane_width_px().as_f32() > MINIMUM_WIDTH_TO_AUTO_OPEN_PANE
|
||||||
@@ -24532,6 +24566,7 @@ impl TypedActionView for TerminalView {
|
|||||||
| ExecuteRewindFromInlineMenu { .. }
|
| ExecuteRewindFromInlineMenu { .. }
|
||||||
| ToggleUsageFooter
|
| ToggleUsageFooter
|
||||||
| ToggleContextView
|
| ToggleContextView
|
||||||
|
| ToggleSettingsView
|
||||||
| RevealChildAgent { .. }
|
| RevealChildAgent { .. }
|
||||||
| OpenCLIAgentRichInput
|
| OpenCLIAgentRichInput
|
||||||
| ToggleSessionRecording => Empty,
|
| ToggleSessionRecording => Empty,
|
||||||
@@ -25556,6 +25591,9 @@ impl TypedActionView for TerminalView {
|
|||||||
ToggleContextView => {
|
ToggleContextView => {
|
||||||
self.toggle_context_view(ctx);
|
self.toggle_context_view(ctx);
|
||||||
}
|
}
|
||||||
|
ToggleSettingsView => {
|
||||||
|
self.toggle_settings_view(ctx);
|
||||||
|
}
|
||||||
RevealChildAgent { conversation_id } => {
|
RevealChildAgent { conversation_id } => {
|
||||||
ctx.emit(Event::RevealChildAgent {
|
ctx.emit(Event::RevealChildAgent {
|
||||||
conversation_id: *conversation_id,
|
conversation_id: *conversation_id,
|
||||||
|
|||||||
@@ -424,6 +424,8 @@ pub enum TerminalAction {
|
|||||||
ToggleUsageFooter,
|
ToggleUsageFooter,
|
||||||
/// Toggle the context window debug view showing bedrock_message_history.
|
/// Toggle the context window debug view showing bedrock_message_history.
|
||||||
ToggleContextView,
|
ToggleContextView,
|
||||||
|
/// Toggle the settings view showing Galaxy Bedrock configuration.
|
||||||
|
ToggleSettingsView,
|
||||||
/// Reveal a hidden child agent pane from the orchestrator status card.
|
/// Reveal a hidden child agent pane from the orchestrator status card.
|
||||||
RevealChildAgent {
|
RevealChildAgent {
|
||||||
conversation_id: AIConversationId,
|
conversation_id: AIConversationId,
|
||||||
@@ -705,6 +707,7 @@ impl fmt::Debug for TerminalAction {
|
|||||||
AwsCliNotInstalledBanner(action) => write!(f, "AwsCliNotInstalledBanner({action:?})"),
|
AwsCliNotInstalledBanner(action) => write!(f, "AwsCliNotInstalledBanner({action:?})"),
|
||||||
ToggleUsageFooter => write!(f, "ToggleUsageFooter"),
|
ToggleUsageFooter => write!(f, "ToggleUsageFooter"),
|
||||||
ToggleContextView => write!(f, "ToggleContextView"),
|
ToggleContextView => write!(f, "ToggleContextView"),
|
||||||
|
ToggleSettingsView => write!(f, "ToggleSettingsView"),
|
||||||
RevealChildAgent { .. } => write!(f, "RevealChildAgent"),
|
RevealChildAgent { .. } => write!(f, "RevealChildAgent"),
|
||||||
ToggleSessionRecording => write!(f, "ToggleSessionRecording"),
|
ToggleSessionRecording => write!(f, "ToggleSessionRecording"),
|
||||||
OpenCLIAgentRichInput => write!(f, "OpenCLIAgentRichInput"),
|
OpenCLIAgentRichInput => write!(f, "OpenCLIAgentRichInput"),
|
||||||
|
|||||||
Reference in New Issue
Block a user