auto refresh expired bedrock tokens

This commit is contained in:
Josh Woodcock
2026-05-27 13:29:11 -05:00
parent d55befec1c
commit 0e72791483
6 changed files with 139 additions and 11 deletions
+14 -1
View File
@@ -8,6 +8,7 @@ pub struct ExternalBedrockConfig {
pub profile: Option<String>,
pub region: Option<String>,
pub models: Vec<BedrockModelConfig>,
pub auth_refresh_command: Option<String>,
}
impl ExternalBedrockConfig {
@@ -28,6 +29,7 @@ impl ExternalBedrockConfig {
} else {
claude_config.models
},
auth_refresh_command: claude_config.auth_refresh_command,
}
}
@@ -55,9 +57,18 @@ fn parse_claude_code_config(path: PathBuf) -> ExternalBedrockConfig {
Err(_) => return ExternalBedrockConfig::default(),
};
// Read the top-level awsAuthRefresh command (used by Claude Code for SSO login)
let auth_refresh_command = json
.get("awsAuthRefresh")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let env = match json.get("env").and_then(|v| v.as_object()) {
Some(e) => e,
None => return ExternalBedrockConfig::default(),
None => return ExternalBedrockConfig {
auth_refresh_command,
..Default::default()
},
};
let profile = env
@@ -76,6 +87,7 @@ fn parse_claude_code_config(path: PathBuf) -> ExternalBedrockConfig {
profile,
region,
models,
auth_refresh_command,
}
}
@@ -221,6 +233,7 @@ fn parse_opencode_config(path: PathBuf) -> ExternalBedrockConfig {
profile,
region,
models: Vec::new(),
auth_refresh_command: None,
}
}
+34 -1
View File
@@ -2508,7 +2508,22 @@ impl BlocklistAIController {
});
}
let mut renderable_error: RenderableAIError = e.as_ref().into();
let mut renderable_error: RenderableAIError =
if let AIApiError::Stream { stream_type, source } = e.as_ref() {
if *stream_type == "bedrock_converse"
&& is_bedrock_credentials_error(&source.to_string())
{
let model_name =
response_stream.as_ref(ctx).model_id().to_string();
RenderableAIError::AwsBedrockCredentialsExpiredOrInvalid {
model_name,
}
} else {
e.as_ref().into()
}
} else {
e.as_ref().into()
};
if let RenderableAIError::Other {
will_attempt_resume,
waiting_for_network,
@@ -2994,6 +3009,24 @@ pub struct ClientIdentifiers {
pub response_stream_id: Option<ResponseStreamId>,
}
/// Returns `true` if the given error message from a Bedrock stream indicates an
/// AWS credentials issue (expired, invalid, or missing session token).
fn is_bedrock_credentials_error(msg: &str) -> bool {
let lower = msg.to_lowercase();
// "Session token not found or invalid" is the most common SSO expiry message.
// AccessDenied / ExpiredToken / UnrecognizedClient cover other credential failures.
// SSO cache file not found means the token file was deleted or never created.
lower.contains("session token not found")
|| lower.contains("expiredtoken")
|| lower.contains("expired token")
|| lower.contains("unrecognizedclientexception")
|| lower.contains("unauthorizedexception")
|| (lower.contains("sso/cache") && lower.contains("notfound"))
|| (lower.contains("sso/cache") && lower.contains("no such file"))
|| (lower.contains("accessdenied")
&& (lower.contains("token") || lower.contains("credential") || lower.contains("security")))
}
#[allow(clippy::too_many_arguments)]
fn input_for_query(
query: String,
@@ -149,6 +149,11 @@ impl ResponseStream {
&self.params.bedrock_messages_sent
}
/// Returns the model ID associated with this response stream's request.
pub fn model_id(&self) -> &str {
self.params.model.as_str()
}
/// Returns true if we should attempt to resume the conversation after the stream finishes.
pub fn should_resume_conversation_after_stream_finished(&self) -> bool {
self.should_resume_conversation_after_stream_finished