Offer Claude sign-in from failed sessions
This commit is contained in:
1 parent
0be15adbee
commit
f0661919bb
9 files changed
+140
-12
No files matched your search
@@ -367,10 +367,20 @@ impl Translator {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let said = message.get("result").and_then(Value::as_str);
|
||||
events.push(match said.and_then(usage_limit) {
|
||||
Some(resets_at) => Event::LimitReached { resets_at },
|
||||
events.push(match said {
|
||||
Some(message) if authentication_required(message) => {
|
||||
Event::AuthenticationRequired {
|
||||
message: message.to_string(),
|
||||
}
|
||||
}
|
||||
Some(message) => match usage_limit(message) {
|
||||
Some(resets_at) => Event::LimitReached { resets_at },
|
||||
None => Event::Error {
|
||||
message: message.to_string(),
|
||||
},
|
||||
},
|
||||
None => Event::Error {
|
||||
message: said.unwrap_or("the turn ended with an error").to_string(),
|
||||
message: "the turn ended with an error".to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1171,6 +1181,13 @@ fn usage_limit(result: &str) -> Option<Option<f64>> {
|
||||
Some(stamp)
|
||||
}
|
||||
|
||||
/// Claude Code's actionable login failure, kept here with its other dialect strings.
|
||||
fn authentication_required(message: &str) -> bool {
|
||||
message
|
||||
.to_ascii_lowercase()
|
||||
.contains("oauth session expired and could not be refreshed")
|
||||
}
|
||||
|
||||
/// A string field that is there and not empty, or `None`. The CLI omits these
|
||||
/// rather than sending them empty, but a caller that sends `""` means the same
|
||||
/// thing and should not produce a description that draws as a blank line.
|
||||
@@ -2482,6 +2499,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_expired_login_is_actionable_above_the_driver() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf(), test_subagents(&dir));
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"result","subtype":"error_during_execution","is_error":true,"result":"Failed to authenticate: OAuth session expired and could not be refreshed","usage":{}}"#,
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
events[0],
|
||||
Event::AuthenticationRequired {
|
||||
message: "Failed to authenticate: OAuth session expired and could not be refreshed"
|
||||
.to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// Running out of quota is a state, not a failure of the work.
|
||||
///
|
||||
/// The naive reading -- an error result like any other -- is what shipped
|
||||
|
||||
@@ -440,6 +440,14 @@ pub enum Event {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
resets_at: Option<f64>,
|
||||
},
|
||||
/// The provider refused the turn because its login can no longer be used.
|
||||
///
|
||||
/// Recognised by the driver for the same reason [`Event::LimitReached`] is:
|
||||
/// only that layer knows the provider's dialect, and the phone needs a
|
||||
/// state it can act on without matching error prose.
|
||||
AuthenticationRequired {
|
||||
message: String,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
|
||||
@@ -873,6 +873,9 @@ mod tests {
|
||||
tokens: 42,
|
||||
context: Some(42),
|
||||
},
|
||||
Event::AuthenticationRequired {
|
||||
message: "sign in again".into(),
|
||||
},
|
||||
Event::Error {
|
||||
message: "boom".into(),
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user