mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
Add app-server remote browser bridge
Add an experimental browserSession command/notification pair, post commands to a configurable remote browser HTTP endpoint, and emit browser state updates back to clients. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
co-authored by
Codex
parent
527244910f
commit
4b69a17b9f
@@ -384,6 +384,62 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"BrowserSessionState": {
|
||||
"properties": {
|
||||
"selectedTabId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tabs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/BrowserTabState"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"selectedTabId",
|
||||
"tabs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"BrowserSessionUpdatedNotification": {
|
||||
"properties": {
|
||||
"browserSessionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"browserState": {
|
||||
"$ref": "#/definitions/BrowserSessionState"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"browserSessionId",
|
||||
"browserState"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"BrowserTabState": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"selected": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"selected",
|
||||
"title",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -4394,6 +4450,26 @@
|
||||
"title": "App/list/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"browserSession/updated"
|
||||
],
|
||||
"title": "BrowserSession/updatedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/BrowserSessionUpdatedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "BrowserSession/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
|
||||
@@ -4053,6 +4053,26 @@
|
||||
"title": "App/list/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"browserSession/updated"
|
||||
],
|
||||
"title": "BrowserSession/updatedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/BrowserSessionUpdatedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "BrowserSession/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -5495,6 +5515,64 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"BrowserSessionState": {
|
||||
"properties": {
|
||||
"selectedTabId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tabs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/BrowserTabState"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"selectedTabId",
|
||||
"tabs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"BrowserSessionUpdatedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"browserSessionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"browserState": {
|
||||
"$ref": "#/definitions/v2/BrowserSessionState"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"browserSessionId",
|
||||
"browserState"
|
||||
],
|
||||
"title": "BrowserSessionUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"BrowserTabState": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"selected": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"selected",
|
||||
"title",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
|
||||
@@ -715,6 +715,64 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"BrowserSessionState": {
|
||||
"properties": {
|
||||
"selectedTabId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tabs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/BrowserTabState"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"selectedTabId",
|
||||
"tabs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"BrowserSessionUpdatedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"browserSessionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"browserState": {
|
||||
"$ref": "#/definitions/BrowserSessionState"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"browserSessionId",
|
||||
"browserState"
|
||||
],
|
||||
"title": "BrowserSessionUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"BrowserTabState": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"selected": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"selected",
|
||||
"title",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -8518,6 +8576,26 @@
|
||||
"title": "App/list/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"browserSession/updated"
|
||||
],
|
||||
"title": "BrowserSession/updatedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/BrowserSessionUpdatedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "BrowserSession/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"BrowserSessionState": {
|
||||
"properties": {
|
||||
"selectedTabId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tabs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/BrowserTabState"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"selectedTabId",
|
||||
"tabs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"BrowserTabState": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"selected": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"selected",
|
||||
"title",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"browserSessionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"browserState": {
|
||||
"$ref": "#/definitions/BrowserSessionState"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"browserSessionId",
|
||||
"browserState"
|
||||
],
|
||||
"title": "BrowserSessionUpdatedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import type { AccountRateLimitsUpdatedNotification } from "./v2/AccountRateLimit
|
||||
import type { AccountUpdatedNotification } from "./v2/AccountUpdatedNotification";
|
||||
import type { AgentMessageDeltaNotification } from "./v2/AgentMessageDeltaNotification";
|
||||
import type { AppListUpdatedNotification } from "./v2/AppListUpdatedNotification";
|
||||
import type { BrowserSessionUpdatedNotification } from "./v2/BrowserSessionUpdatedNotification";
|
||||
import type { CommandExecOutputDeltaNotification } from "./v2/CommandExecOutputDeltaNotification";
|
||||
import type { CommandExecutionOutputDeltaNotification } from "./v2/CommandExecutionOutputDeltaNotification";
|
||||
import type { ConfigWarningNotification } from "./v2/ConfigWarningNotification";
|
||||
@@ -56,4 +57,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW
|
||||
/**
|
||||
* Notification sent from the server to the client.
|
||||
*/
|
||||
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcriptUpdated", "params": ThreadRealtimeTranscriptUpdatedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };
|
||||
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "browserSession/updated", "params": BrowserSessionUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcriptUpdated", "params": ThreadRealtimeTranscriptUpdatedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { BrowserTabState } from "./BrowserTabState";
|
||||
|
||||
export type BrowserSessionState = { selectedTabId: string, tabs: Array<BrowserTabState>, };
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { BrowserSessionState } from "./BrowserSessionState";
|
||||
|
||||
export type BrowserSessionUpdatedNotification = { browserSessionId: string, browserState: BrowserSessionState, };
|
||||
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type BrowserTabState = { id: string, title: string, url: string, selected: boolean, };
|
||||
@@ -25,6 +25,9 @@ export type { AppsDefaultConfig } from "./AppsDefaultConfig";
|
||||
export type { AppsListParams } from "./AppsListParams";
|
||||
export type { AppsListResponse } from "./AppsListResponse";
|
||||
export type { AskForApproval } from "./AskForApproval";
|
||||
export type { BrowserSessionState } from "./BrowserSessionState";
|
||||
export type { BrowserSessionUpdatedNotification } from "./BrowserSessionUpdatedNotification";
|
||||
export type { BrowserTabState } from "./BrowserTabState";
|
||||
export type { ByteRange } from "./ByteRange";
|
||||
export type { CancelLoginAccountParams } from "./CancelLoginAccountParams";
|
||||
export type { CancelLoginAccountResponse } from "./CancelLoginAccountResponse";
|
||||
|
||||
@@ -308,6 +308,11 @@ client_request_definitions! {
|
||||
params: v2::AppsListParams,
|
||||
response: v2::AppsListResponse,
|
||||
},
|
||||
#[experimental("browserSession/command")]
|
||||
BrowserSessionCommand => "browserSession/command" {
|
||||
params: v2::BrowserSessionCommandParams,
|
||||
response: v2::BrowserSessionCommandResponse,
|
||||
},
|
||||
FsReadFile => "fs/readFile" {
|
||||
params: v2::FsReadFileParams,
|
||||
response: v2::FsReadFileResponse,
|
||||
@@ -909,6 +914,8 @@ server_notification_definitions! {
|
||||
AccountUpdated => "account/updated" (v2::AccountUpdatedNotification),
|
||||
AccountRateLimitsUpdated => "account/rateLimits/updated" (v2::AccountRateLimitsUpdatedNotification),
|
||||
AppListUpdated => "app/list/updated" (v2::AppListUpdatedNotification),
|
||||
#[experimental("browserSession/updated")]
|
||||
BrowserSessionUpdated => "browserSession/updated" (v2::BrowserSessionUpdatedNotification),
|
||||
ReasoningSummaryTextDelta => "item/reasoning/summaryTextDelta" (v2::ReasoningSummaryTextDeltaNotification),
|
||||
ReasoningSummaryPartAdded => "item/reasoning/summaryPartAdded" (v2::ReasoningSummaryPartAddedNotification),
|
||||
ReasoningTextDelta => "item/reasoning/textDelta" (v2::ReasoningTextDeltaNotification),
|
||||
@@ -1609,6 +1616,75 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_browser_session_command_request() -> Result<()> {
|
||||
let request = ClientRequest::BrowserSessionCommand {
|
||||
request_id: RequestId::Integer(7),
|
||||
params: v2::BrowserSessionCommandParams {
|
||||
browser_session_id: Some("sess_123".to_string()),
|
||||
command: "navigateTabUrl".to_string(),
|
||||
arguments: Some(json!({
|
||||
"tabId": "tab_123",
|
||||
"url": "https://example.com",
|
||||
})),
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "browserSession/command",
|
||||
"id": 7,
|
||||
"params": {
|
||||
"browserSessionId": "sess_123",
|
||||
"command": "navigateTabUrl",
|
||||
"arguments": {
|
||||
"tabId": "tab_123",
|
||||
"url": "https://example.com"
|
||||
}
|
||||
}
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_browser_session_updated_notification() -> Result<()> {
|
||||
let notification =
|
||||
ServerNotification::BrowserSessionUpdated(v2::BrowserSessionUpdatedNotification {
|
||||
browser_session_id: "sess_123".to_string(),
|
||||
browser_state: v2::BrowserSessionState {
|
||||
selected_tab_id: "tab_123".to_string(),
|
||||
tabs: vec![v2::BrowserTabState {
|
||||
id: "tab_123".to_string(),
|
||||
title: "Example".to_string(),
|
||||
url: "https://example.com".to_string(),
|
||||
selected: true,
|
||||
}],
|
||||
},
|
||||
});
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "browserSession/updated",
|
||||
"params": {
|
||||
"browserSessionId": "sess_123",
|
||||
"browserState": {
|
||||
"selectedTabId": "tab_123",
|
||||
"tabs": [
|
||||
{
|
||||
"id": "tab_123",
|
||||
"title": "Example",
|
||||
"url": "https://example.com",
|
||||
"selected": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
serde_json::to_value(¬ification)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mock_experimental_method_is_marked_experimental() {
|
||||
let request = ClientRequest::MockExperimentalMethod {
|
||||
@@ -1618,6 +1694,35 @@ mod tests {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request);
|
||||
assert_eq!(reason, Some("mock/experimentalMethod"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn browser_session_command_is_marked_experimental() {
|
||||
let request = ClientRequest::BrowserSessionCommand {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: v2::BrowserSessionCommandParams {
|
||||
browser_session_id: None,
|
||||
command: "ping".to_string(),
|
||||
arguments: None,
|
||||
},
|
||||
};
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request);
|
||||
assert_eq!(reason, Some("browserSession/command"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn browser_session_updated_is_marked_experimental() {
|
||||
let notification =
|
||||
ServerNotification::BrowserSessionUpdated(v2::BrowserSessionUpdatedNotification {
|
||||
browser_session_id: "sess_123".to_string(),
|
||||
browser_state: v2::BrowserSessionState {
|
||||
selected_tab_id: String::new(),
|
||||
tabs: Vec::new(),
|
||||
},
|
||||
});
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(¬ification);
|
||||
assert_eq!(reason, Some("browserSession/updated"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_realtime_start_is_marked_experimental() {
|
||||
let request = ClientRequest::ThreadRealtimeStart {
|
||||
|
||||
@@ -2099,6 +2099,52 @@ pub struct AppListUpdatedNotification {
|
||||
pub data: Vec<AppInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct BrowserTabState {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub url: String,
|
||||
pub selected: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct BrowserSessionState {
|
||||
pub selected_tab_id: String,
|
||||
pub tabs: Vec<BrowserTabState>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct BrowserSessionCommandParams {
|
||||
#[ts(optional = nullable)]
|
||||
pub browser_session_id: Option<String>,
|
||||
pub command: String,
|
||||
#[ts(optional = nullable)]
|
||||
pub arguments: Option<JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct BrowserSessionCommandResponse {
|
||||
pub browser_session_id: String,
|
||||
pub result: JsonValue,
|
||||
pub browser_state: BrowserSessionState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct BrowserSessionUpdatedNotification {
|
||||
pub browser_session_id: String,
|
||||
pub browser_state: BrowserSessionState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
@@ -53,6 +53,7 @@ chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
futures = { workspace = true }
|
||||
owo-colors = { workspace = true, features = ["supports-colors"] }
|
||||
reqwest = { workspace = true, features = ["json", "rustls-tls"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -722,6 +722,9 @@ impl CodexMessageProcessor {
|
||||
self.apps_list(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::BrowserSessionCommand { .. } => {
|
||||
unreachable!("browserSession/command is handled by MessageProcessor");
|
||||
}
|
||||
ClientRequest::SkillsConfigWrite { request_id, params } => {
|
||||
self.skills_config_write(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
|
||||
@@ -417,6 +417,7 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
|
||||
config_warnings: args.config_warnings,
|
||||
session_source: args.session_source,
|
||||
enable_codex_api_key_env: args.enable_codex_api_key_env,
|
||||
remote_browser_endpoint: None,
|
||||
});
|
||||
let mut thread_created_rx = processor.thread_created_receiver();
|
||||
let mut session = ConnectionSessionState::default();
|
||||
|
||||
@@ -71,6 +71,7 @@ pub mod in_process;
|
||||
mod message_processor;
|
||||
mod models;
|
||||
mod outgoing_message;
|
||||
mod remote_browser_api;
|
||||
mod server_request_error;
|
||||
mod thread_state;
|
||||
mod thread_status;
|
||||
@@ -329,6 +330,7 @@ pub async fn run_main(
|
||||
cli_config_overrides: CliConfigOverrides,
|
||||
loader_overrides: LoaderOverrides,
|
||||
default_analytics_enabled: bool,
|
||||
remote_browser_endpoint: Option<String>,
|
||||
) -> IoResult<()> {
|
||||
run_main_with_transport(
|
||||
arg0_paths,
|
||||
@@ -337,6 +339,7 @@ pub async fn run_main(
|
||||
default_analytics_enabled,
|
||||
AppServerTransport::Stdio,
|
||||
SessionSource::VSCode,
|
||||
remote_browser_endpoint,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -348,6 +351,7 @@ pub async fn run_main_with_transport(
|
||||
default_analytics_enabled: bool,
|
||||
transport: AppServerTransport,
|
||||
session_source: SessionSource,
|
||||
remote_browser_endpoint: Option<String>,
|
||||
) -> IoResult<()> {
|
||||
let (transport_event_tx, mut transport_event_rx) =
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
@@ -625,6 +629,7 @@ pub async fn run_main_with_transport(
|
||||
config_warnings,
|
||||
session_source,
|
||||
enable_codex_api_key_env: false,
|
||||
remote_browser_endpoint,
|
||||
});
|
||||
let mut thread_created_rx = processor.thread_created_receiver();
|
||||
let mut running_turn_count_rx = processor.subscribe_running_assistant_turn_count();
|
||||
|
||||
@@ -31,6 +31,10 @@ struct AppServerArgs {
|
||||
value_parser = SessionSource::from_startup_arg
|
||||
)]
|
||||
session_source: SessionSource,
|
||||
|
||||
/// Optional HTTP endpoint that receives remote browser commands.
|
||||
#[arg(long = "remote-browser-endpoint", value_name = "URL")]
|
||||
remote_browser_endpoint: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
@@ -43,6 +47,7 @@ fn main() -> anyhow::Result<()> {
|
||||
};
|
||||
let transport = args.listen;
|
||||
let session_source = args.session_source;
|
||||
let remote_browser_endpoint = args.remote_browser_endpoint;
|
||||
|
||||
run_main_with_transport(
|
||||
arg0_paths,
|
||||
@@ -51,6 +56,7 @@ fn main() -> anyhow::Result<()> {
|
||||
/*default_analytics_enabled*/ false,
|
||||
transport,
|
||||
session_source,
|
||||
remote_browser_endpoint,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
@@ -15,8 +15,11 @@ use crate::outgoing_message::ConnectionId;
|
||||
use crate::outgoing_message::ConnectionRequestId;
|
||||
use crate::outgoing_message::OutgoingMessageSender;
|
||||
use crate::outgoing_message::RequestContext;
|
||||
use crate::remote_browser_api::RemoteBrowserApi;
|
||||
use crate::transport::AppServerTransport;
|
||||
use async_trait::async_trait;
|
||||
use codex_app_server_protocol::BrowserSessionCommandParams;
|
||||
use codex_app_server_protocol::BrowserSessionUpdatedNotification;
|
||||
use codex_app_server_protocol::ChatgptAuthTokensRefreshParams;
|
||||
use codex_app_server_protocol::ChatgptAuthTokensRefreshReason;
|
||||
use codex_app_server_protocol::ChatgptAuthTokensRefreshResponse;
|
||||
@@ -151,6 +154,7 @@ pub(crate) struct MessageProcessor {
|
||||
config_api: ConfigApi,
|
||||
external_agent_config_api: ExternalAgentConfigApi,
|
||||
fs_api: FsApi,
|
||||
remote_browser_api: RemoteBrowserApi,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
config: Arc<Config>,
|
||||
config_warnings: Arc<Vec<ConfigWarningNotification>>,
|
||||
@@ -179,6 +183,7 @@ pub(crate) struct MessageProcessorArgs {
|
||||
pub(crate) config_warnings: Vec<ConfigWarningNotification>,
|
||||
pub(crate) session_source: SessionSource,
|
||||
pub(crate) enable_codex_api_key_env: bool,
|
||||
pub(crate) remote_browser_endpoint: Option<String>,
|
||||
}
|
||||
|
||||
impl MessageProcessor {
|
||||
@@ -199,6 +204,7 @@ impl MessageProcessor {
|
||||
config_warnings,
|
||||
session_source,
|
||||
enable_codex_api_key_env,
|
||||
remote_browser_endpoint,
|
||||
} = args;
|
||||
let (auth_manager, thread_manager) = match (auth_manager, thread_manager) {
|
||||
(Some(auth_manager), Some(thread_manager)) => (auth_manager, thread_manager),
|
||||
@@ -259,6 +265,7 @@ impl MessageProcessor {
|
||||
);
|
||||
let external_agent_config_api = ExternalAgentConfigApi::new(config.codex_home.clone());
|
||||
let fs_api = FsApi::default();
|
||||
let remote_browser_api = RemoteBrowserApi::new(remote_browser_endpoint);
|
||||
|
||||
Self {
|
||||
outgoing,
|
||||
@@ -266,6 +273,7 @@ impl MessageProcessor {
|
||||
config_api,
|
||||
external_agent_config_api,
|
||||
fs_api,
|
||||
remote_browser_api,
|
||||
auth_manager,
|
||||
config,
|
||||
config_warnings: Arc::new(config_warnings),
|
||||
@@ -766,6 +774,16 @@ impl MessageProcessor {
|
||||
)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::BrowserSessionCommand { request_id, params } => {
|
||||
self.handle_browser_session_command(
|
||||
ConnectionRequestId {
|
||||
connection_id,
|
||||
request_id,
|
||||
},
|
||||
params,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
other => {
|
||||
// Box the delegated future so this wrapper's async state machine does not
|
||||
// inline the full `CodexMessageProcessor::process_request` future, which
|
||||
@@ -917,6 +935,29 @@ impl MessageProcessor {
|
||||
Err(error) => self.outgoing.send_error(request_id, error).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_browser_session_command(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
params: BrowserSessionCommandParams,
|
||||
) {
|
||||
match self.remote_browser_api.command(params).await {
|
||||
Ok(response) => {
|
||||
self.outgoing
|
||||
.send_response(request_id, response.clone())
|
||||
.await;
|
||||
self.outgoing
|
||||
.send_server_notification(ServerNotification::BrowserSessionUpdated(
|
||||
BrowserSessionUpdatedNotification {
|
||||
browser_session_id: response.browser_session_id.clone(),
|
||||
browser_state: response.browser_state.clone(),
|
||||
},
|
||||
))
|
||||
.await;
|
||||
}
|
||||
Err(error) => self.outgoing.send_error(request_id, error).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -7,6 +7,11 @@ use crate::transport::AppServerTransport;
|
||||
use anyhow::Result;
|
||||
use app_test_support::create_mock_responses_server_repeating_assistant;
|
||||
use app_test_support::write_mock_responses_config_toml;
|
||||
use codex_app_server_protocol::BrowserSessionCommandParams;
|
||||
use codex_app_server_protocol::BrowserSessionCommandResponse;
|
||||
use codex_app_server_protocol::BrowserSessionState;
|
||||
use codex_app_server_protocol::BrowserSessionUpdatedNotification;
|
||||
use codex_app_server_protocol::BrowserTabState;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::InitializeCapabilities;
|
||||
@@ -113,11 +118,11 @@ struct TracingHarness {
|
||||
}
|
||||
|
||||
impl TracingHarness {
|
||||
async fn new() -> Result<Self> {
|
||||
async fn new(remote_browser_endpoint: Option<String>) -> Result<Self> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
let config = Arc::new(build_test_config(codex_home.path(), &server.uri()).await?);
|
||||
let (processor, outgoing_rx) = build_test_processor(config);
|
||||
let (processor, outgoing_rx) = build_test_processor(config, remote_browser_endpoint);
|
||||
let tracing = init_test_tracing();
|
||||
tracing.exporter.reset();
|
||||
tracing::callsite::rebuild_interest_cache();
|
||||
@@ -226,6 +231,7 @@ async fn build_test_config(codex_home: &Path, server_uri: &str) -> Result<Config
|
||||
|
||||
fn build_test_processor(
|
||||
config: Arc<Config>,
|
||||
remote_browser_endpoint: Option<String>,
|
||||
) -> (
|
||||
MessageProcessor,
|
||||
mpsc::Receiver<crate::outgoing_message::OutgoingEnvelope>,
|
||||
@@ -246,6 +252,7 @@ fn build_test_processor(
|
||||
config_warnings: Vec::new(),
|
||||
session_source: SessionSource::VSCode,
|
||||
enable_codex_api_key_env: false,
|
||||
remote_browser_endpoint,
|
||||
});
|
||||
(processor, outgoing_rx)
|
||||
}
|
||||
@@ -455,6 +462,30 @@ async fn read_thread_started_notification(
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_browser_session_updated_notification(
|
||||
outgoing_rx: &mut mpsc::Receiver<crate::outgoing_message::OutgoingEnvelope>,
|
||||
) -> BrowserSessionUpdatedNotification {
|
||||
loop {
|
||||
let envelope = tokio::time::timeout(std::time::Duration::from_secs(5), outgoing_rx.recv())
|
||||
.await
|
||||
.expect("timed out waiting for browserSession/updated notification")
|
||||
.expect("outgoing channel closed");
|
||||
let message = match envelope {
|
||||
crate::outgoing_message::OutgoingEnvelope::ToConnection { message, .. }
|
||||
| crate::outgoing_message::OutgoingEnvelope::Broadcast { message } => message,
|
||||
};
|
||||
let crate::outgoing_message::OutgoingMessage::AppServerNotification(notification) = message
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if let codex_app_server_protocol::ServerNotification::BrowserSessionUpdated(notification) =
|
||||
notification
|
||||
{
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_exported_spans<F>(tracing: &TestTracing, predicate: F) -> Vec<SpanData>
|
||||
where
|
||||
F: Fn(&[SpanData]) -> bool,
|
||||
@@ -498,7 +529,7 @@ where
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() -> Result<()> {
|
||||
let _guard = tracing_test_guard().lock().await;
|
||||
let mut harness = TracingHarness::new().await?;
|
||||
let mut harness = TracingHarness::new(None).await?;
|
||||
|
||||
let RemoteTrace {
|
||||
trace_id: remote_trace_id,
|
||||
@@ -569,7 +600,7 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() ->
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
||||
let _guard = tracing_test_guard().lock().await;
|
||||
let mut harness = TracingHarness::new().await?;
|
||||
let mut harness = TracingHarness::new(None).await?;
|
||||
let thread_start_response = harness.start_thread(2, None).await;
|
||||
let thread_id = thread_start_response.thread.id.clone();
|
||||
|
||||
@@ -637,3 +668,77 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn browser_session_command_posts_and_emits_state_notification() -> Result<()> {
|
||||
let _guard = tracing_test_guard().lock().await;
|
||||
let remote_browser = wiremock::MockServer::start().await;
|
||||
let browser_state = BrowserSessionState {
|
||||
selected_tab_id: "tab_123".to_string(),
|
||||
tabs: vec![BrowserTabState {
|
||||
id: "tab_123".to_string(),
|
||||
title: "Example".to_string(),
|
||||
url: "https://example.com".to_string(),
|
||||
selected: true,
|
||||
}],
|
||||
};
|
||||
let response_body = serde_json::json!({
|
||||
"browserSessionId": "sess_123",
|
||||
"result": {
|
||||
"ok": true,
|
||||
},
|
||||
"browserState": {
|
||||
"selectedTabId": "tab_123",
|
||||
"tabs": [{
|
||||
"id": "tab_123",
|
||||
"title": "Example",
|
||||
"url": "https://example.com",
|
||||
"selected": true,
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
wiremock::Mock::given(wiremock::matchers::method("POST"))
|
||||
.and(wiremock::matchers::path("/"))
|
||||
.and(wiremock::matchers::body_json(serde_json::json!({
|
||||
"browserSessionId": "sess_123",
|
||||
"command": "navigateTabUrl",
|
||||
"arguments": {
|
||||
"tabId": "tab_123",
|
||||
"url": "https://example.com"
|
||||
}
|
||||
})))
|
||||
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(response_body))
|
||||
.mount(&remote_browser)
|
||||
.await;
|
||||
|
||||
let mut harness = TracingHarness::new(Some(remote_browser.uri())).await?;
|
||||
let response: BrowserSessionCommandResponse = harness
|
||||
.request(
|
||||
ClientRequest::BrowserSessionCommand {
|
||||
request_id: RequestId::Integer(4),
|
||||
params: BrowserSessionCommandParams {
|
||||
browser_session_id: Some("sess_123".to_string()),
|
||||
command: "navigateTabUrl".to_string(),
|
||||
arguments: Some(serde_json::json!({
|
||||
"tabId": "tab_123",
|
||||
"url": "https://example.com",
|
||||
})),
|
||||
},
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response.browser_session_id, "sess_123");
|
||||
assert_eq!(response.result, serde_json::json!({"ok": true}));
|
||||
assert_eq!(response.browser_state, browser_state);
|
||||
|
||||
let notification = read_browser_session_updated_notification(&mut harness.outgoing_rx).await;
|
||||
assert_eq!(notification.browser_session_id, "sess_123");
|
||||
assert_eq!(notification.browser_state, browser_state);
|
||||
harness.shutdown().await;
|
||||
|
||||
remote_browser.verify().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::error_code::INTERNAL_ERROR_CODE;
|
||||
use crate::error_code::INVALID_REQUEST_ERROR_CODE;
|
||||
use codex_app_server_protocol::BrowserSessionCommandParams;
|
||||
use codex_app_server_protocol::BrowserSessionCommandResponse;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct RemoteBrowserApi {
|
||||
endpoint: Option<String>,
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl RemoteBrowserApi {
|
||||
pub(crate) fn new(endpoint: Option<String>) -> Self {
|
||||
Self {
|
||||
endpoint,
|
||||
client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn command(
|
||||
&self,
|
||||
params: BrowserSessionCommandParams,
|
||||
) -> Result<BrowserSessionCommandResponse, JSONRPCErrorError> {
|
||||
let Some(endpoint) = &self.endpoint else {
|
||||
return Err(JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
message: "Remote browser endpoint is not configured".to_string(),
|
||||
data: None,
|
||||
});
|
||||
};
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.post(endpoint)
|
||||
.json(¶ms)
|
||||
.send()
|
||||
.await
|
||||
.map_err(map_reqwest_error)?;
|
||||
|
||||
let status = response.status();
|
||||
let body = response.text().await.map_err(map_reqwest_error)?;
|
||||
if !status.is_success() {
|
||||
return Err(JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message: format!("remote browser endpoint returned HTTP {status}: {body}"),
|
||||
data: None,
|
||||
});
|
||||
}
|
||||
|
||||
serde_json::from_str(&body).map_err(|err| JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message: format!("failed to decode remote browser response: {err}; body={body}"),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn map_reqwest_error(err: reqwest::Error) -> JSONRPCErrorError {
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message: format!("remote browser request failed: {err}"),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user