auto refresh expired bedrock tokens
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user