mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
feat(bsk): fix different type of upload
This commit is contained in:
@@ -49,6 +49,9 @@ pub mod reason {
|
||||
pub const TAB_NOT_ACTIVE: &str = "tab_not_active";
|
||||
pub const BORROW_CONFLICT: &str = "borrow_conflict";
|
||||
pub const SCREENSHOT_CAPTURE_FAILED: &str = "screenshot_capture_failed";
|
||||
pub const FILE_CHOOSER_CONTROL_FAILED: &str = "file_chooser_control_failed";
|
||||
pub const FILE_CHOOSER_NOT_OPENED: &str = "file_chooser_not_opened";
|
||||
pub const UNSUPPORTED_FILE_CHOOSER: &str = "unsupported_file_chooser";
|
||||
pub const SESSION_BUSY: &str = crate::rpc_reason::SESSION_BUSY;
|
||||
pub const RECORD_START_PAGE_UNREACHABLE: &str = "record_start_page_unreachable";
|
||||
}
|
||||
@@ -280,6 +283,29 @@ pub fn info_for_error(code: ErrorCode, data: Option<&serde_json::Value>) -> Rend
|
||||
),
|
||||
exit_code: base.exit_code,
|
||||
},
|
||||
(ErrorCode::Unsupported, reason::UNSUPPORTED_FILE_CHOOSER) => RenderInfo {
|
||||
summary: "the page opened a non-input file picker",
|
||||
hint: Some(
|
||||
"do not retry upload; use `bsk request-help` and tell the user the exact original local path to select, or stop if human help is disabled",
|
||||
),
|
||||
exit_code: base.exit_code,
|
||||
},
|
||||
(ErrorCode::Timeout | ErrorCode::CdpFailed, reason::FILE_CHOOSER_CONTROL_FAILED) => {
|
||||
RenderInfo {
|
||||
summary: "the browser file-chooser transaction could not complete",
|
||||
hint: Some(
|
||||
"do not retry the same upload blindly; use `bsk request-help` when human help is available",
|
||||
),
|
||||
exit_code: base.exit_code,
|
||||
}
|
||||
}
|
||||
(ErrorCode::Timeout, reason::FILE_CHOOSER_NOT_OPENED) => RenderInfo {
|
||||
summary: "the upload action did not open a file chooser",
|
||||
hint: Some(
|
||||
"observe once after expanding any upload menu, then retry with the actual upload action or use `bsk request-help`",
|
||||
),
|
||||
exit_code: base.exit_code,
|
||||
},
|
||||
_ => base,
|
||||
}
|
||||
}
|
||||
@@ -419,6 +445,27 @@ mod tests {
|
||||
assert_eq!(info.exit_code, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_chooser_reasons_render_actionable_fallbacks() {
|
||||
let unsupported = serde_json::json!({ "reason": reason::UNSUPPORTED_FILE_CHOOSER });
|
||||
let info = info_for_error(ErrorCode::Unsupported, Some(&unsupported));
|
||||
assert_eq!(info.summary, "the page opened a non-input file picker");
|
||||
assert!(info.hint.unwrap().contains("exact original local path"));
|
||||
|
||||
let control = serde_json::json!({ "reason": reason::FILE_CHOOSER_CONTROL_FAILED });
|
||||
let info = info_for_error(ErrorCode::Timeout, Some(&control));
|
||||
assert!(info.summary.contains("transaction could not complete"));
|
||||
assert!(info.hint.unwrap().contains("do not retry"));
|
||||
|
||||
let not_opened = serde_json::json!({ "reason": reason::FILE_CHOOSER_NOT_OPENED });
|
||||
let info = info_for_error(ErrorCode::Timeout, Some(¬_opened));
|
||||
assert_eq!(
|
||||
info.summary,
|
||||
"the upload action did not open a file chooser"
|
||||
);
|
||||
assert!(info.hint.unwrap().contains("expanding any upload menu"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_not_found_overrides_not_found_copy() {
|
||||
let data = serde_json::json!({ "reason": reason::REF_NOT_FOUND });
|
||||
|
||||
@@ -62,6 +62,10 @@ pub type RpcHandler = Arc<
|
||||
>;
|
||||
|
||||
const DEFAULT_RPC_TIMEOUT: Duration = Duration::from_secs(15);
|
||||
// The upload transaction owns its operation deadline and may need a bounded
|
||||
// cleanup before it can return a useful structured error. Keep only that
|
||||
// transport alive slightly longer so it does not replace the result.
|
||||
const EXTENSION_RESPONSE_GRACE: Duration = Duration::from_secs(2);
|
||||
/// Upper bound on `wait_for_browser_ms` accepted over IPC.
|
||||
const MAX_BROWSER_WAIT: Duration = Duration::from_secs(60);
|
||||
// `session.stop` fast-fails while another tool is active; this budget
|
||||
@@ -332,7 +336,7 @@ async fn handle_tool_dispatch(
|
||||
data: None,
|
||||
});
|
||||
}
|
||||
let timeout = match tool_dispatch_timeout(¶ms) {
|
||||
let timeout = match tool_dispatch_transport_timeout(&method, ¶ms) {
|
||||
Ok(timeout) => timeout,
|
||||
Err(err) => return ResponseBody::Err(err),
|
||||
};
|
||||
@@ -696,6 +700,16 @@ fn tool_dispatch_timeout(params: &Value) -> Result<Duration, RpcError> {
|
||||
Ok(Duration::from_millis(u64::from(ms)))
|
||||
}
|
||||
|
||||
fn tool_dispatch_transport_timeout(method: &Method, params: &Value) -> Result<Duration, RpcError> {
|
||||
tool_dispatch_timeout(params).map(|timeout| {
|
||||
if method == &Method::ToolUpload {
|
||||
timeout.saturating_add(EXTENSION_RESPONSE_GRACE)
|
||||
} else {
|
||||
timeout
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Local CLI-facing shapes. Intentionally distinct from
|
||||
// `bsk_protocol::tools::SessionStart*` (which describes the WS-facing
|
||||
// `tool.session_*` round-trip with the extension) so the two sides can
|
||||
@@ -1494,6 +1508,30 @@ mod tests {
|
||||
assert_eq!(got, std::time::Duration::from_millis(300_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_transport_outlives_the_operation_deadline() {
|
||||
let params = serde_json::json!({
|
||||
"session_id": "abcd",
|
||||
"timeout_ms": 60_000,
|
||||
});
|
||||
let dispatch_timeout =
|
||||
tool_dispatch_transport_timeout(&Method::ToolUpload, ¶ms).unwrap();
|
||||
|
||||
assert_eq!(dispatch_timeout, Duration::from_secs(62));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_response_grace_does_not_change_other_tools() {
|
||||
let params = serde_json::json!({
|
||||
"session_id": "abcd",
|
||||
"timeout_ms": 60_000,
|
||||
});
|
||||
let dispatch_timeout =
|
||||
tool_dispatch_transport_timeout(&Method::ToolClick, ¶ms).unwrap();
|
||||
|
||||
assert_eq!(dispatch_timeout, Duration::from_secs(60));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_dispatch_timeout_rejects_zero_timeout_ms() {
|
||||
let params = serde_json::json!({
|
||||
|
||||
Reference in New Issue
Block a user