mirror of
https://github.com/openai/codex.git
synced 2026-09-08 15:50:34 +00:00
refactor: add wasm-safe shims for host-only runtime crates
This commit is contained in:
@@ -15,16 +15,18 @@ workspace = true
|
||||
[dependencies]
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-plugin = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
sha1 = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
codex-login = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
] }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
mod analytics_client;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
include!("native.rs");
|
||||
|
||||
pub use analytics_client::AnalyticsEventsClient;
|
||||
pub use analytics_client::AnalyticsFact;
|
||||
pub use analytics_client::AnalyticsReducer;
|
||||
pub use analytics_client::AppInvocation;
|
||||
pub use analytics_client::AppMentionedInput;
|
||||
pub use analytics_client::AppUsedInput;
|
||||
pub use analytics_client::CustomAnalyticsFact;
|
||||
pub use analytics_client::InvocationType;
|
||||
pub use analytics_client::PluginState;
|
||||
pub use analytics_client::PluginStateChangedInput;
|
||||
pub use analytics_client::PluginUsedInput;
|
||||
pub use analytics_client::SkillInvocation;
|
||||
pub use analytics_client::SkillInvokedInput;
|
||||
pub use analytics_client::TrackEventsContext;
|
||||
pub use analytics_client::build_track_events_context;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
|
||||
17
codex-rs/analytics/src/native.rs
Normal file
17
codex-rs/analytics/src/native.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
mod analytics_client;
|
||||
|
||||
pub use analytics_client::AnalyticsEventsClient;
|
||||
pub use analytics_client::AnalyticsFact;
|
||||
pub use analytics_client::AnalyticsReducer;
|
||||
pub use analytics_client::AppInvocation;
|
||||
pub use analytics_client::AppMentionedInput;
|
||||
pub use analytics_client::AppUsedInput;
|
||||
pub use analytics_client::CustomAnalyticsFact;
|
||||
pub use analytics_client::InvocationType;
|
||||
pub use analytics_client::PluginState;
|
||||
pub use analytics_client::PluginStateChangedInput;
|
||||
pub use analytics_client::PluginUsedInput;
|
||||
pub use analytics_client::SkillInvocation;
|
||||
pub use analytics_client::SkillInvokedInput;
|
||||
pub use analytics_client::TrackEventsContext;
|
||||
pub use analytics_client::build_track_events_context;
|
||||
170
codex-rs/analytics/src/wasm.rs
Normal file
170
codex-rs/analytics/src/wasm.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use codex_plugin::PluginTelemetryMetadata;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackEventsContext {
|
||||
pub model_slug: String,
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
}
|
||||
|
||||
pub fn build_track_events_context(
|
||||
model_slug: String,
|
||||
thread_id: String,
|
||||
turn_id: String,
|
||||
) -> TrackEventsContext {
|
||||
TrackEventsContext {
|
||||
model_slug,
|
||||
thread_id,
|
||||
turn_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SkillInvocation {
|
||||
pub skill_name: String,
|
||||
pub skill_scope: SkillScope,
|
||||
pub skill_path: PathBuf,
|
||||
pub invocation_type: InvocationType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum InvocationType {
|
||||
Explicit,
|
||||
Implicit,
|
||||
}
|
||||
|
||||
pub struct AppInvocation {
|
||||
pub connector_id: Option<String>,
|
||||
pub app_name: Option<String>,
|
||||
pub invocation_type: Option<InvocationType>,
|
||||
}
|
||||
|
||||
pub enum AnalyticsFact {
|
||||
Initialize {
|
||||
connection_id: u64,
|
||||
params: codex_app_server_protocol::InitializeParams,
|
||||
},
|
||||
Request {
|
||||
connection_id: u64,
|
||||
request_id: codex_app_server_protocol::RequestId,
|
||||
request: Box<codex_app_server_protocol::ClientRequest>,
|
||||
},
|
||||
Response {
|
||||
connection_id: u64,
|
||||
response: Box<codex_app_server_protocol::ClientResponse>,
|
||||
},
|
||||
Notification(Box<codex_app_server_protocol::ServerNotification>),
|
||||
Custom(CustomAnalyticsFact),
|
||||
}
|
||||
|
||||
pub enum CustomAnalyticsFact {
|
||||
SkillInvoked(SkillInvokedInput),
|
||||
AppMentioned(AppMentionedInput),
|
||||
AppUsed(AppUsedInput),
|
||||
PluginUsed(PluginUsedInput),
|
||||
PluginStateChanged(PluginStateChangedInput),
|
||||
}
|
||||
|
||||
pub struct SkillInvokedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub invocations: Vec<SkillInvocation>,
|
||||
}
|
||||
|
||||
pub struct AppMentionedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub mentions: Vec<AppInvocation>,
|
||||
}
|
||||
|
||||
pub struct AppUsedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub app: AppInvocation,
|
||||
}
|
||||
|
||||
pub struct PluginUsedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub plugin: PluginTelemetryMetadata,
|
||||
}
|
||||
|
||||
pub struct PluginStateChangedInput {
|
||||
pub plugin: PluginTelemetryMetadata,
|
||||
pub state: PluginState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PluginState {
|
||||
Installed,
|
||||
Uninstalled,
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AnalyticsReducer;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct AnalyticsEventsClient {
|
||||
analytics_enabled: Option<bool>,
|
||||
_marker: Arc<()>,
|
||||
}
|
||||
|
||||
impl AnalyticsEventsClient {
|
||||
pub fn new<T>(
|
||||
_auth_manager: Arc<T>,
|
||||
_base_url: String,
|
||||
analytics_enabled: Option<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
analytics_enabled,
|
||||
_marker: Arc::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_skill_invocations(
|
||||
&self,
|
||||
_tracking: TrackEventsContext,
|
||||
_invocations: Vec<SkillInvocation>,
|
||||
) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_app_mentioned(
|
||||
&self,
|
||||
_tracking: TrackEventsContext,
|
||||
_mentions: Vec<AppInvocation>,
|
||||
) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_app_used(&self, _tracking: TrackEventsContext, _app: AppInvocation) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_used(
|
||||
&self,
|
||||
_tracking: TrackEventsContext,
|
||||
_plugin: PluginTelemetryMetadata,
|
||||
) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_installed(&self, _plugin: PluginTelemetryMetadata) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_uninstalled(&self, _plugin: PluginTelemetryMetadata) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_enabled(&self, _plugin: PluginTelemetryMetadata) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_disabled(&self, _plugin: PluginTelemetryMetadata) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,20 @@ path = "src/bin/codex-exec-server.rs"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
arc-swap = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-pty = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
arc-swap = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-utils-pty = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"fs",
|
||||
"io-std",
|
||||
@@ -38,7 +41,9 @@ tokio = { workspace = true, features = [
|
||||
"time",
|
||||
] }
|
||||
tokio-tungstenite = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tokio = { workspace = true, features = ["io-util", "macros", "rt", "sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use clap::Parser;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Debug, Parser)]
|
||||
struct ExecServerArgs {
|
||||
/// Transport endpoint URL. Supported values: `ws://IP:PORT` (default).
|
||||
@@ -11,8 +13,14 @@ struct ExecServerArgs {
|
||||
listen: String,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let args = ExecServerArgs::parse();
|
||||
codex_exec_server::run_main_with_listen_url(&args.listen).await
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
panic!("codex-exec-server binary is unavailable on wasm32");
|
||||
}
|
||||
|
||||
@@ -1,67 +1,7 @@
|
||||
mod client;
|
||||
mod client_api;
|
||||
mod connection;
|
||||
mod environment;
|
||||
mod file_system;
|
||||
mod local_file_system;
|
||||
mod local_process;
|
||||
mod process;
|
||||
mod process_id;
|
||||
mod protocol;
|
||||
mod remote_file_system;
|
||||
mod remote_process;
|
||||
mod rpc;
|
||||
mod server;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
include!("native.rs");
|
||||
|
||||
pub use client::ExecServerClient;
|
||||
pub use client::ExecServerError;
|
||||
pub use client_api::ExecServerClientConnectOptions;
|
||||
pub use client_api::RemoteExecServerConnectArgs;
|
||||
pub use codex_app_server_protocol::FsCopyParams;
|
||||
pub use codex_app_server_protocol::FsCopyResponse;
|
||||
pub use codex_app_server_protocol::FsCreateDirectoryParams;
|
||||
pub use codex_app_server_protocol::FsCreateDirectoryResponse;
|
||||
pub use codex_app_server_protocol::FsGetMetadataParams;
|
||||
pub use codex_app_server_protocol::FsGetMetadataResponse;
|
||||
pub use codex_app_server_protocol::FsReadDirectoryParams;
|
||||
pub use codex_app_server_protocol::FsReadDirectoryResponse;
|
||||
pub use codex_app_server_protocol::FsReadFileParams;
|
||||
pub use codex_app_server_protocol::FsReadFileResponse;
|
||||
pub use codex_app_server_protocol::FsRemoveParams;
|
||||
pub use codex_app_server_protocol::FsRemoveResponse;
|
||||
pub use codex_app_server_protocol::FsWriteFileParams;
|
||||
pub use codex_app_server_protocol::FsWriteFileResponse;
|
||||
pub use environment::CODEX_EXEC_SERVER_URL_ENV_VAR;
|
||||
pub use environment::Environment;
|
||||
pub use environment::EnvironmentManager;
|
||||
pub use environment::ExecutorEnvironment;
|
||||
pub use file_system::CopyOptions;
|
||||
pub use file_system::CreateDirectoryOptions;
|
||||
pub use file_system::ExecutorFileSystem;
|
||||
pub use file_system::FileMetadata;
|
||||
pub use file_system::FileSystemResult;
|
||||
pub use file_system::ReadDirectoryEntry;
|
||||
pub use file_system::RemoveOptions;
|
||||
pub use process::ExecBackend;
|
||||
pub use process::ExecProcess;
|
||||
pub use process::StartedExecProcess;
|
||||
pub use process_id::ProcessId;
|
||||
pub use protocol::ExecClosedNotification;
|
||||
pub use protocol::ExecExitedNotification;
|
||||
pub use protocol::ExecOutputDeltaNotification;
|
||||
pub use protocol::ExecOutputStream;
|
||||
pub use protocol::ExecParams;
|
||||
pub use protocol::ExecResponse;
|
||||
pub use protocol::InitializeParams;
|
||||
pub use protocol::InitializeResponse;
|
||||
pub use protocol::ReadParams;
|
||||
pub use protocol::ReadResponse;
|
||||
pub use protocol::TerminateParams;
|
||||
pub use protocol::TerminateResponse;
|
||||
pub use protocol::WriteParams;
|
||||
pub use protocol::WriteResponse;
|
||||
pub use protocol::WriteStatus;
|
||||
pub use server::DEFAULT_LISTEN_URL;
|
||||
pub use server::ExecServerListenUrlParseError;
|
||||
pub use server::run_main;
|
||||
pub use server::run_main_with_listen_url;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
|
||||
67
codex-rs/exec-server/src/native.rs
Normal file
67
codex-rs/exec-server/src/native.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
mod client;
|
||||
mod client_api;
|
||||
mod connection;
|
||||
mod environment;
|
||||
mod file_system;
|
||||
mod local_file_system;
|
||||
mod local_process;
|
||||
mod process;
|
||||
mod process_id;
|
||||
mod protocol;
|
||||
mod remote_file_system;
|
||||
mod remote_process;
|
||||
mod rpc;
|
||||
mod server;
|
||||
|
||||
pub use client::ExecServerClient;
|
||||
pub use client::ExecServerError;
|
||||
pub use client_api::ExecServerClientConnectOptions;
|
||||
pub use client_api::RemoteExecServerConnectArgs;
|
||||
pub use codex_app_server_protocol::FsCopyParams;
|
||||
pub use codex_app_server_protocol::FsCopyResponse;
|
||||
pub use codex_app_server_protocol::FsCreateDirectoryParams;
|
||||
pub use codex_app_server_protocol::FsCreateDirectoryResponse;
|
||||
pub use codex_app_server_protocol::FsGetMetadataParams;
|
||||
pub use codex_app_server_protocol::FsGetMetadataResponse;
|
||||
pub use codex_app_server_protocol::FsReadDirectoryParams;
|
||||
pub use codex_app_server_protocol::FsReadDirectoryResponse;
|
||||
pub use codex_app_server_protocol::FsReadFileParams;
|
||||
pub use codex_app_server_protocol::FsReadFileResponse;
|
||||
pub use codex_app_server_protocol::FsRemoveParams;
|
||||
pub use codex_app_server_protocol::FsRemoveResponse;
|
||||
pub use codex_app_server_protocol::FsWriteFileParams;
|
||||
pub use codex_app_server_protocol::FsWriteFileResponse;
|
||||
pub use environment::CODEX_EXEC_SERVER_URL_ENV_VAR;
|
||||
pub use environment::Environment;
|
||||
pub use environment::EnvironmentManager;
|
||||
pub use environment::ExecutorEnvironment;
|
||||
pub use file_system::CopyOptions;
|
||||
pub use file_system::CreateDirectoryOptions;
|
||||
pub use file_system::ExecutorFileSystem;
|
||||
pub use file_system::FileMetadata;
|
||||
pub use file_system::FileSystemResult;
|
||||
pub use file_system::ReadDirectoryEntry;
|
||||
pub use file_system::RemoveOptions;
|
||||
pub use process::ExecBackend;
|
||||
pub use process::ExecProcess;
|
||||
pub use process::StartedExecProcess;
|
||||
pub use process_id::ProcessId;
|
||||
pub use protocol::ExecClosedNotification;
|
||||
pub use protocol::ExecExitedNotification;
|
||||
pub use protocol::ExecOutputDeltaNotification;
|
||||
pub use protocol::ExecOutputStream;
|
||||
pub use protocol::ExecParams;
|
||||
pub use protocol::ExecResponse;
|
||||
pub use protocol::InitializeParams;
|
||||
pub use protocol::InitializeResponse;
|
||||
pub use protocol::ReadParams;
|
||||
pub use protocol::ReadResponse;
|
||||
pub use protocol::TerminateParams;
|
||||
pub use protocol::TerminateResponse;
|
||||
pub use protocol::WriteParams;
|
||||
pub use protocol::WriteResponse;
|
||||
pub use protocol::WriteStatus;
|
||||
pub use server::DEFAULT_LISTEN_URL;
|
||||
pub use server::ExecServerListenUrlParseError;
|
||||
pub use server::run_main;
|
||||
pub use server::run_main_with_listen_url;
|
||||
415
codex-rs/exec-server/src/wasm.rs
Normal file
415
codex-rs/exec-server/src/wasm.rs
Normal file
@@ -0,0 +1,415 @@
|
||||
#[path = "file_system.rs"]
|
||||
mod file_system;
|
||||
#[path = "process_id.rs"]
|
||||
mod process_id;
|
||||
#[path = "protocol.rs"]
|
||||
mod protocol;
|
||||
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::sync::watch;
|
||||
|
||||
pub use codex_app_server_protocol::FsCopyParams;
|
||||
pub use codex_app_server_protocol::FsCopyResponse;
|
||||
pub use codex_app_server_protocol::FsCreateDirectoryParams;
|
||||
pub use codex_app_server_protocol::FsCreateDirectoryResponse;
|
||||
pub use codex_app_server_protocol::FsGetMetadataParams;
|
||||
pub use codex_app_server_protocol::FsGetMetadataResponse;
|
||||
pub use codex_app_server_protocol::FsReadDirectoryParams;
|
||||
pub use codex_app_server_protocol::FsReadDirectoryResponse;
|
||||
pub use codex_app_server_protocol::FsReadFileParams;
|
||||
pub use codex_app_server_protocol::FsReadFileResponse;
|
||||
pub use codex_app_server_protocol::FsRemoveParams;
|
||||
pub use codex_app_server_protocol::FsRemoveResponse;
|
||||
pub use codex_app_server_protocol::FsWriteFileParams;
|
||||
pub use codex_app_server_protocol::FsWriteFileResponse;
|
||||
pub use file_system::CopyOptions;
|
||||
pub use file_system::CreateDirectoryOptions;
|
||||
pub use file_system::ExecutorFileSystem;
|
||||
pub use file_system::FileMetadata;
|
||||
pub use file_system::FileSystemResult;
|
||||
pub use file_system::ReadDirectoryEntry;
|
||||
pub use file_system::RemoveOptions;
|
||||
pub use process_id::ProcessId;
|
||||
pub use protocol::ExecClosedNotification;
|
||||
pub use protocol::ExecExitedNotification;
|
||||
pub use protocol::ExecOutputDeltaNotification;
|
||||
pub use protocol::ExecOutputStream;
|
||||
pub use protocol::ExecParams;
|
||||
pub use protocol::ExecResponse;
|
||||
pub use protocol::InitializeParams;
|
||||
pub use protocol::InitializeResponse;
|
||||
pub use protocol::ReadParams;
|
||||
pub use protocol::ReadResponse;
|
||||
pub use protocol::TerminateParams;
|
||||
pub use protocol::TerminateResponse;
|
||||
pub use protocol::WriteParams;
|
||||
pub use protocol::WriteResponse;
|
||||
pub use protocol::WriteStatus;
|
||||
|
||||
pub struct StartedExecProcess {
|
||||
pub process: Arc<dyn ExecProcess>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ExecProcess: Send + Sync {
|
||||
fn process_id(&self) -> &ProcessId;
|
||||
|
||||
fn subscribe_wake(&self) -> watch::Receiver<u64>;
|
||||
|
||||
async fn read(
|
||||
&self,
|
||||
after_seq: Option<u64>,
|
||||
max_bytes: Option<usize>,
|
||||
wait_ms: Option<u64>,
|
||||
) -> Result<ReadResponse, ExecServerError>;
|
||||
|
||||
async fn write(&self, chunk: Vec<u8>) -> Result<WriteResponse, ExecServerError>;
|
||||
|
||||
async fn terminate(&self) -> Result<(), ExecServerError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ExecBackend: Send + Sync {
|
||||
async fn start(&self, params: ExecParams) -> Result<StartedExecProcess, ExecServerError>;
|
||||
}
|
||||
|
||||
pub const CODEX_EXEC_SERVER_URL_ENV_VAR: &str = "CODEX_EXEC_SERVER_URL";
|
||||
pub const DEFAULT_LISTEN_URL: &str = "ws://127.0.0.1:0";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExecServerClientConnectOptions {
|
||||
pub client_name: String,
|
||||
pub initialize_timeout: std::time::Duration,
|
||||
}
|
||||
|
||||
impl Default for ExecServerClientConnectOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
client_name: "codex-core".to_string(),
|
||||
initialize_timeout: std::time::Duration::from_secs(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RemoteExecServerConnectArgs {
|
||||
pub websocket_url: String,
|
||||
pub client_name: String,
|
||||
pub connect_timeout: std::time::Duration,
|
||||
pub initialize_timeout: std::time::Duration,
|
||||
}
|
||||
|
||||
impl RemoteExecServerConnectArgs {
|
||||
pub fn new(websocket_url: String, client_name: String) -> Self {
|
||||
Self {
|
||||
websocket_url,
|
||||
client_name,
|
||||
connect_timeout: std::time::Duration::from_secs(10),
|
||||
initialize_timeout: std::time::Duration::from_secs(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RemoteExecServerConnectArgs> for ExecServerClientConnectOptions {
|
||||
fn from(value: RemoteExecServerConnectArgs) -> Self {
|
||||
Self {
|
||||
client_name: value.client_name,
|
||||
initialize_timeout: value.initialize_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ExecServerError {
|
||||
#[error("exec-server is unavailable on wasm32")]
|
||||
Unsupported,
|
||||
#[error("exec-server transport closed")]
|
||||
Closed,
|
||||
#[error("failed to serialize or deserialize exec-server JSON: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error("exec-server protocol error: {0}")]
|
||||
Protocol(String),
|
||||
#[error("exec-server rejected request ({code}): {message}")]
|
||||
Server { code: i64, message: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ExecServerListenUrlParseError {
|
||||
#[error("exec-server listen URLs are unavailable on wasm32: {0}")]
|
||||
UnsupportedListenUrl(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ExecServerClient;
|
||||
|
||||
impl ExecServerClient {
|
||||
pub async fn connect_websocket(
|
||||
_args: RemoteExecServerConnectArgs,
|
||||
) -> Result<Self, ExecServerError> {
|
||||
Err(ExecServerError::Unsupported)
|
||||
}
|
||||
|
||||
pub async fn initialize(
|
||||
&self,
|
||||
_options: ExecServerClientConnectOptions,
|
||||
) -> Result<InitializeResponse, ExecServerError> {
|
||||
Err(ExecServerError::Unsupported)
|
||||
}
|
||||
|
||||
pub async fn exec(&self, _params: ExecParams) -> Result<ExecResponse, ExecServerError> {
|
||||
Err(ExecServerError::Unsupported)
|
||||
}
|
||||
|
||||
pub async fn read(&self, _params: ReadParams) -> Result<ReadResponse, ExecServerError> {
|
||||
Err(ExecServerError::Unsupported)
|
||||
}
|
||||
|
||||
pub async fn write(
|
||||
&self,
|
||||
_process_id: &ProcessId,
|
||||
_chunk: Vec<u8>,
|
||||
) -> Result<WriteResponse, ExecServerError> {
|
||||
Err(ExecServerError::Unsupported)
|
||||
}
|
||||
|
||||
pub async fn terminate(
|
||||
&self,
|
||||
_process_id: &ProcessId,
|
||||
) -> Result<TerminateResponse, ExecServerError> {
|
||||
Err(ExecServerError::Unsupported)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExecutorEnvironment: Send + Sync {
|
||||
fn get_exec_backend(&self) -> Arc<dyn ExecBackend>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EnvironmentManager {
|
||||
exec_server_url: Option<String>,
|
||||
current_environment: OnceCell<Arc<Environment>>,
|
||||
}
|
||||
|
||||
impl EnvironmentManager {
|
||||
pub fn new(exec_server_url: Option<String>) -> Self {
|
||||
Self {
|
||||
exec_server_url: normalize_exec_server_url(exec_server_url),
|
||||
current_environment: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_env() -> Self {
|
||||
Self::new(std::env::var(CODEX_EXEC_SERVER_URL_ENV_VAR).ok())
|
||||
}
|
||||
|
||||
pub fn exec_server_url(&self) -> Option<&str> {
|
||||
self.exec_server_url.as_deref()
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> Result<Arc<Environment>, ExecServerError> {
|
||||
self.current_environment
|
||||
.get_or_init(|| async { Arc::new(Environment::default()) })
|
||||
.await;
|
||||
self.current_environment
|
||||
.get()
|
||||
.cloned()
|
||||
.ok_or(ExecServerError::Unsupported)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Environment {
|
||||
exec_server_url: Option<String>,
|
||||
exec_backend: Arc<dyn ExecBackend>,
|
||||
filesystem: Arc<dyn ExecutorFileSystem>,
|
||||
}
|
||||
|
||||
impl Default for Environment {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
exec_server_url: None,
|
||||
exec_backend: Arc::new(NoopExecBackend),
|
||||
filesystem: Arc::new(UnsupportedFileSystem),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub async fn create(exec_server_url: Option<String>) -> Result<Self, ExecServerError> {
|
||||
Ok(Self {
|
||||
exec_server_url: normalize_exec_server_url(exec_server_url),
|
||||
..Self::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn exec_server_url(&self) -> Option<&str> {
|
||||
self.exec_server_url.as_deref()
|
||||
}
|
||||
|
||||
pub fn get_exec_backend(&self) -> Arc<dyn ExecBackend> {
|
||||
Arc::clone(&self.exec_backend)
|
||||
}
|
||||
|
||||
pub fn get_filesystem(&self) -> Arc<dyn ExecutorFileSystem> {
|
||||
Arc::clone(&self.filesystem)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutorEnvironment for Environment {
|
||||
fn get_exec_backend(&self) -> Arc<dyn ExecBackend> {
|
||||
Arc::clone(&self.exec_backend)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct NoopExecBackend;
|
||||
|
||||
#[async_trait]
|
||||
impl ExecBackend for NoopExecBackend {
|
||||
async fn start(&self, params: ExecParams) -> Result<StartedExecProcess, ExecServerError> {
|
||||
Ok(StartedExecProcess {
|
||||
process: Arc::new(NoopExecProcess::new(params.process_id)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NoopExecProcess {
|
||||
process_id: ProcessId,
|
||||
wake_tx: watch::Sender<u64>,
|
||||
}
|
||||
|
||||
impl NoopExecProcess {
|
||||
fn new(process_id: ProcessId) -> Self {
|
||||
let (wake_tx, _wake_rx) = watch::channel(0);
|
||||
Self {
|
||||
process_id,
|
||||
wake_tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecProcess for NoopExecProcess {
|
||||
fn process_id(&self) -> &ProcessId {
|
||||
&self.process_id
|
||||
}
|
||||
|
||||
fn subscribe_wake(&self) -> watch::Receiver<u64> {
|
||||
self.wake_tx.subscribe()
|
||||
}
|
||||
|
||||
async fn read(
|
||||
&self,
|
||||
_after_seq: Option<u64>,
|
||||
_max_bytes: Option<usize>,
|
||||
_wait_ms: Option<u64>,
|
||||
) -> Result<ReadResponse, ExecServerError> {
|
||||
Ok(ReadResponse {
|
||||
chunks: Vec::new(),
|
||||
next_seq: 0,
|
||||
exited: true,
|
||||
exit_code: Some(1),
|
||||
closed: true,
|
||||
failure: Some("exec-server is unavailable on wasm32".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
async fn write(&self, _chunk: Vec<u8>) -> Result<WriteResponse, ExecServerError> {
|
||||
Ok(WriteResponse {
|
||||
status: WriteStatus::UnknownProcess,
|
||||
})
|
||||
}
|
||||
|
||||
async fn terminate(&self) -> Result<(), ExecServerError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct UnsupportedFileSystem;
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for UnsupportedFileSystem {
|
||||
async fn read_file(
|
||||
&self,
|
||||
_path: &codex_utils_absolute_path::AbsolutePathBuf,
|
||||
) -> FileSystemResult<Vec<u8>> {
|
||||
Err(unsupported_io_error())
|
||||
}
|
||||
|
||||
async fn write_file(
|
||||
&self,
|
||||
_path: &codex_utils_absolute_path::AbsolutePathBuf,
|
||||
_contents: Vec<u8>,
|
||||
) -> FileSystemResult<()> {
|
||||
Err(unsupported_io_error())
|
||||
}
|
||||
|
||||
async fn create_directory(
|
||||
&self,
|
||||
_path: &codex_utils_absolute_path::AbsolutePathBuf,
|
||||
_options: CreateDirectoryOptions,
|
||||
) -> FileSystemResult<()> {
|
||||
Err(unsupported_io_error())
|
||||
}
|
||||
|
||||
async fn get_metadata(
|
||||
&self,
|
||||
_path: &codex_utils_absolute_path::AbsolutePathBuf,
|
||||
) -> FileSystemResult<FileMetadata> {
|
||||
Err(unsupported_io_error())
|
||||
}
|
||||
|
||||
async fn read_directory(
|
||||
&self,
|
||||
_path: &codex_utils_absolute_path::AbsolutePathBuf,
|
||||
) -> FileSystemResult<Vec<ReadDirectoryEntry>> {
|
||||
Err(unsupported_io_error())
|
||||
}
|
||||
|
||||
async fn remove(
|
||||
&self,
|
||||
_path: &codex_utils_absolute_path::AbsolutePathBuf,
|
||||
_options: RemoveOptions,
|
||||
) -> FileSystemResult<()> {
|
||||
Err(unsupported_io_error())
|
||||
}
|
||||
|
||||
async fn copy(
|
||||
&self,
|
||||
_source_path: &codex_utils_absolute_path::AbsolutePathBuf,
|
||||
_destination_path: &codex_utils_absolute_path::AbsolutePathBuf,
|
||||
_options: CopyOptions,
|
||||
) -> FileSystemResult<()> {
|
||||
Err(unsupported_io_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Err(Box::new(ExecServerError::Unsupported))
|
||||
}
|
||||
|
||||
pub async fn run_main_with_listen_url(
|
||||
_listen_url: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Err(Box::new(ExecServerError::Unsupported))
|
||||
}
|
||||
|
||||
fn normalize_exec_server_url(exec_server_url: Option<String>) -> Option<String> {
|
||||
exec_server_url.and_then(|url| {
|
||||
let url = url.trim();
|
||||
(!url.is_empty()).then(|| url.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
fn unsupported_io_error() -> io::Error {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"exec-server filesystem is unavailable on wasm32",
|
||||
)
|
||||
}
|
||||
@@ -18,15 +18,17 @@ clap = { workspace = true, features = ["derive"] }
|
||||
chrono = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-home-dir = { workspace = true }
|
||||
codex-utils-rustls-provider = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
codex-utils-rustls-provider = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
rama-core = { version = "=0.3.0-alpha.4" }
|
||||
rama-http = { version = "=0.3.0-alpha.4" }
|
||||
rama-http-backend = { version = "=0.3.0-alpha.4", features = ["tls"] }
|
||||
@@ -35,9 +37,12 @@ rama-socks5 = { version = "=0.3.0-alpha.4" }
|
||||
rama-tcp = { version = "=0.3.0-alpha.4", features = ["http"] }
|
||||
rama-tls-rustls = { version = "=0.3.0-alpha.4", features = ["http"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tokio = { workspace = true, features = ["macros", "rt", "sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
[target.'cfg(all(target_family = "unix", not(target_arch = "wasm32")))'.dependencies]
|
||||
rama-unix = { version = "=0.3.0-alpha.4" }
|
||||
|
||||
@@ -1,56 +1,9 @@
|
||||
#![deny(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
mod certs;
|
||||
mod config;
|
||||
mod http_proxy;
|
||||
mod mitm;
|
||||
mod network_policy;
|
||||
mod policy;
|
||||
mod proxy;
|
||||
mod reasons;
|
||||
mod responses;
|
||||
mod runtime;
|
||||
mod socks5;
|
||||
mod state;
|
||||
mod upstream;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
include!("native.rs");
|
||||
|
||||
pub use config::NetworkDomainPermission;
|
||||
pub use config::NetworkDomainPermissionEntry;
|
||||
pub use config::NetworkDomainPermissions;
|
||||
pub use config::NetworkMode;
|
||||
pub use config::NetworkProxyConfig;
|
||||
pub use config::NetworkUnixSocketPermission;
|
||||
pub use config::NetworkUnixSocketPermissions;
|
||||
pub use config::host_and_port_from_network_addr;
|
||||
pub use network_policy::NetworkDecision;
|
||||
pub use network_policy::NetworkDecisionSource;
|
||||
pub use network_policy::NetworkPolicyDecider;
|
||||
pub use network_policy::NetworkPolicyDecision;
|
||||
pub use network_policy::NetworkPolicyRequest;
|
||||
pub use network_policy::NetworkPolicyRequestArgs;
|
||||
pub use network_policy::NetworkProtocol;
|
||||
pub use policy::normalize_host;
|
||||
pub use proxy::ALL_PROXY_ENV_KEYS;
|
||||
pub use proxy::ALLOW_LOCAL_BINDING_ENV_KEY;
|
||||
pub use proxy::Args;
|
||||
pub use proxy::DEFAULT_NO_PROXY_VALUE;
|
||||
pub use proxy::NO_PROXY_ENV_KEYS;
|
||||
pub use proxy::NetworkProxy;
|
||||
pub use proxy::NetworkProxyBuilder;
|
||||
pub use proxy::NetworkProxyHandle;
|
||||
pub use proxy::PROXY_URL_ENV_KEYS;
|
||||
pub use proxy::has_proxy_url_env_vars;
|
||||
pub use proxy::proxy_url_env_value;
|
||||
pub use runtime::BlockedRequest;
|
||||
pub use runtime::BlockedRequestArgs;
|
||||
pub use runtime::BlockedRequestObserver;
|
||||
pub use runtime::ConfigReloader;
|
||||
pub use runtime::ConfigState;
|
||||
pub use runtime::NetworkProxyState;
|
||||
pub use state::NetworkProxyAuditMetadata;
|
||||
pub use state::NetworkProxyConstraintError;
|
||||
pub use state::NetworkProxyConstraints;
|
||||
pub use state::PartialNetworkConfig;
|
||||
pub use state::PartialNetworkProxyConfig;
|
||||
pub use state::build_config_state;
|
||||
pub use state::validate_policy_against_constraints;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
|
||||
54
codex-rs/network-proxy/src/native.rs
Normal file
54
codex-rs/network-proxy/src/native.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
mod certs;
|
||||
mod config;
|
||||
mod http_proxy;
|
||||
mod mitm;
|
||||
mod network_policy;
|
||||
mod policy;
|
||||
mod proxy;
|
||||
mod reasons;
|
||||
mod responses;
|
||||
mod runtime;
|
||||
mod socks5;
|
||||
mod state;
|
||||
mod upstream;
|
||||
|
||||
pub use config::NetworkDomainPermission;
|
||||
pub use config::NetworkDomainPermissionEntry;
|
||||
pub use config::NetworkDomainPermissions;
|
||||
pub use config::NetworkMode;
|
||||
pub use config::NetworkProxyConfig;
|
||||
pub use config::NetworkUnixSocketPermission;
|
||||
pub use config::NetworkUnixSocketPermissions;
|
||||
pub use config::host_and_port_from_network_addr;
|
||||
pub use network_policy::NetworkDecision;
|
||||
pub use network_policy::NetworkDecisionSource;
|
||||
pub use network_policy::NetworkPolicyDecider;
|
||||
pub use network_policy::NetworkPolicyDecision;
|
||||
pub use network_policy::NetworkPolicyRequest;
|
||||
pub use network_policy::NetworkPolicyRequestArgs;
|
||||
pub use network_policy::NetworkProtocol;
|
||||
pub use policy::normalize_host;
|
||||
pub use proxy::ALL_PROXY_ENV_KEYS;
|
||||
pub use proxy::ALLOW_LOCAL_BINDING_ENV_KEY;
|
||||
pub use proxy::Args;
|
||||
pub use proxy::DEFAULT_NO_PROXY_VALUE;
|
||||
pub use proxy::NO_PROXY_ENV_KEYS;
|
||||
pub use proxy::NetworkProxy;
|
||||
pub use proxy::NetworkProxyBuilder;
|
||||
pub use proxy::NetworkProxyHandle;
|
||||
pub use proxy::PROXY_URL_ENV_KEYS;
|
||||
pub use proxy::has_proxy_url_env_vars;
|
||||
pub use proxy::proxy_url_env_value;
|
||||
pub use runtime::BlockedRequest;
|
||||
pub use runtime::BlockedRequestArgs;
|
||||
pub use runtime::BlockedRequestObserver;
|
||||
pub use runtime::ConfigReloader;
|
||||
pub use runtime::ConfigState;
|
||||
pub use runtime::NetworkProxyState;
|
||||
pub use state::NetworkProxyAuditMetadata;
|
||||
pub use state::NetworkProxyConstraintError;
|
||||
pub use state::NetworkProxyConstraints;
|
||||
pub use state::PartialNetworkConfig;
|
||||
pub use state::PartialNetworkProxyConfig;
|
||||
pub use state::build_config_state;
|
||||
pub use state::validate_policy_against_constraints;
|
||||
863
codex-rs/network-proxy/src/wasm.rs
Normal file
863
codex-rs/network-proxy/src/wasm.rs
Normal file
@@ -0,0 +1,863 @@
|
||||
#[path = "config.rs"]
|
||||
mod config;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub use config::NetworkDomainPermission;
|
||||
pub use config::NetworkDomainPermissionEntry;
|
||||
pub use config::NetworkDomainPermissions;
|
||||
pub use config::NetworkMode;
|
||||
pub use config::NetworkProxyConfig;
|
||||
pub use config::NetworkUnixSocketPermission;
|
||||
pub use config::NetworkUnixSocketPermissions;
|
||||
pub use config::host_and_port_from_network_addr;
|
||||
|
||||
pub const PROXY_URL_ENV_KEYS: &[&str] = &[
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"WS_PROXY",
|
||||
"WSS_PROXY",
|
||||
"ALL_PROXY",
|
||||
"FTP_PROXY",
|
||||
"YARN_HTTP_PROXY",
|
||||
"YARN_HTTPS_PROXY",
|
||||
"NPM_CONFIG_HTTP_PROXY",
|
||||
"NPM_CONFIG_HTTPS_PROXY",
|
||||
"NPM_CONFIG_PROXY",
|
||||
"BUNDLE_HTTP_PROXY",
|
||||
"BUNDLE_HTTPS_PROXY",
|
||||
"PIP_PROXY",
|
||||
"DOCKER_HTTP_PROXY",
|
||||
"DOCKER_HTTPS_PROXY",
|
||||
];
|
||||
pub const ALL_PROXY_ENV_KEYS: &[&str] = &["ALL_PROXY", "all_proxy"];
|
||||
pub const ALLOW_LOCAL_BINDING_ENV_KEY: &str = "CODEX_NETWORK_ALLOW_LOCAL_BINDING";
|
||||
pub const NO_PROXY_ENV_KEYS: &[&str] = &[
|
||||
"NO_PROXY",
|
||||
"no_proxy",
|
||||
"npm_config_noproxy",
|
||||
"NPM_CONFIG_NOPROXY",
|
||||
"YARN_NO_PROXY",
|
||||
"BUNDLE_NO_PROXY",
|
||||
];
|
||||
pub const DEFAULT_NO_PROXY_VALUE: &str = concat!(
|
||||
"localhost,127.0.0.1,::1,",
|
||||
"*.local,.local,",
|
||||
"169.254.0.0/16,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
|
||||
);
|
||||
|
||||
const FTP_PROXY_ENV_KEYS: &[&str] = &["FTP_PROXY", "ftp_proxy"];
|
||||
const WEBSOCKET_PROXY_ENV_KEYS: &[&str] = &["WS_PROXY", "WSS_PROXY", "ws_proxy", "wss_proxy"];
|
||||
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
#[command(name = "codex-network-proxy", about = "Codex network sandbox proxy")]
|
||||
pub struct Args {}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkProtocol {
|
||||
Http,
|
||||
HttpsConnect,
|
||||
Socks5Tcp,
|
||||
Socks5Udp,
|
||||
}
|
||||
|
||||
impl NetworkProtocol {
|
||||
pub const fn as_policy_protocol(self) -> &'static str {
|
||||
match self {
|
||||
Self::Http => "http",
|
||||
Self::HttpsConnect => "https_connect",
|
||||
Self::Socks5Tcp => "socks5_tcp",
|
||||
Self::Socks5Udp => "socks5_udp",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum NetworkPolicyDecision {
|
||||
Deny,
|
||||
Ask,
|
||||
}
|
||||
|
||||
impl NetworkPolicyDecision {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Deny => "deny",
|
||||
Self::Ask => "ask",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NetworkDecisionSource {
|
||||
BaselinePolicy,
|
||||
ModeGuard,
|
||||
ProxyState,
|
||||
Decider,
|
||||
}
|
||||
|
||||
impl NetworkDecisionSource {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::BaselinePolicy => "baseline_policy",
|
||||
Self::ModeGuard => "mode_guard",
|
||||
Self::ProxyState => "proxy_state",
|
||||
Self::Decider => "decider",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NetworkPolicyRequest {
|
||||
pub protocol: NetworkProtocol,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub client_addr: Option<String>,
|
||||
pub method: Option<String>,
|
||||
pub command: Option<String>,
|
||||
pub exec_policy_hint: Option<String>,
|
||||
}
|
||||
|
||||
pub struct NetworkPolicyRequestArgs {
|
||||
pub protocol: NetworkProtocol,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub client_addr: Option<String>,
|
||||
pub method: Option<String>,
|
||||
pub command: Option<String>,
|
||||
pub exec_policy_hint: Option<String>,
|
||||
}
|
||||
|
||||
impl NetworkPolicyRequest {
|
||||
pub fn new(args: NetworkPolicyRequestArgs) -> Self {
|
||||
let NetworkPolicyRequestArgs {
|
||||
protocol,
|
||||
host,
|
||||
port,
|
||||
client_addr,
|
||||
method,
|
||||
command,
|
||||
exec_policy_hint,
|
||||
} = args;
|
||||
Self {
|
||||
protocol,
|
||||
host,
|
||||
port,
|
||||
client_addr,
|
||||
method,
|
||||
command,
|
||||
exec_policy_hint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkDecision {
|
||||
Allow,
|
||||
Deny {
|
||||
reason: String,
|
||||
source: NetworkDecisionSource,
|
||||
decision: NetworkPolicyDecision,
|
||||
},
|
||||
}
|
||||
|
||||
impl NetworkDecision {
|
||||
pub fn deny(reason: impl Into<String>) -> Self {
|
||||
Self::deny_with_source(reason, NetworkDecisionSource::Decider)
|
||||
}
|
||||
|
||||
pub fn ask(reason: impl Into<String>) -> Self {
|
||||
Self::ask_with_source(reason, NetworkDecisionSource::Decider)
|
||||
}
|
||||
|
||||
pub fn deny_with_source(reason: impl Into<String>, source: NetworkDecisionSource) -> Self {
|
||||
Self::Deny {
|
||||
reason: reason.into(),
|
||||
source,
|
||||
decision: NetworkPolicyDecision::Deny,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ask_with_source(reason: impl Into<String>, source: NetworkDecisionSource) -> Self {
|
||||
Self::Deny {
|
||||
reason: reason.into(),
|
||||
source,
|
||||
decision: NetworkPolicyDecision::Ask,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait NetworkPolicyDecider: Send + Sync + 'static {
|
||||
async fn decide(&self, request: NetworkPolicyRequest) -> Result<NetworkDecision>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<D: NetworkPolicyDecider + ?Sized> NetworkPolicyDecider for Arc<D> {
|
||||
async fn decide(&self, request: NetworkPolicyRequest) -> Result<NetworkDecision> {
|
||||
(**self).decide(request).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<F, Fut> NetworkPolicyDecider for F
|
||||
where
|
||||
F: Fn(NetworkPolicyRequest) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = Result<NetworkDecision>> + Send,
|
||||
{
|
||||
async fn decide(&self, request: NetworkPolicyRequest) -> Result<NetworkDecision> {
|
||||
(self)(request).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_host(host: &str) -> String {
|
||||
let host = host.trim();
|
||||
if host.starts_with('[')
|
||||
&& let Some(end) = host.find(']')
|
||||
{
|
||||
return host[1..end]
|
||||
.to_ascii_lowercase()
|
||||
.trim_end_matches('.')
|
||||
.to_string();
|
||||
}
|
||||
|
||||
if host.bytes().filter(|b| *b == b':').count() == 1 {
|
||||
let trimmed = host.split(':').next().unwrap_or_default();
|
||||
return trimmed
|
||||
.to_ascii_lowercase()
|
||||
.trim_end_matches('.')
|
||||
.to_string();
|
||||
}
|
||||
|
||||
host.to_ascii_lowercase().trim_end_matches('.').to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct NetworkProxyConstraints {
|
||||
pub enabled: Option<bool>,
|
||||
pub mode: Option<NetworkMode>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
pub allowlist_expansion_enabled: Option<bool>,
|
||||
pub denied_domains: Option<Vec<String>>,
|
||||
pub denylist_expansion_enabled: Option<bool>,
|
||||
pub allow_unix_sockets: Option<Vec<String>>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PartialNetworkProxyConfig {
|
||||
#[serde(default)]
|
||||
pub network: PartialNetworkConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
pub struct PartialNetworkConfig {
|
||||
pub enabled: Option<bool>,
|
||||
pub mode: Option<NetworkMode>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub domains: Option<NetworkDomainPermissions>,
|
||||
#[serde(default)]
|
||||
pub unix_sockets: Option<NetworkUnixSocketPermissions>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum NetworkProxyConstraintError {
|
||||
#[error("invalid value for {field_name}: {candidate} (allowed {allowed})")]
|
||||
InvalidValue {
|
||||
field_name: &'static str,
|
||||
candidate: String,
|
||||
allowed: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl NetworkProxyConstraintError {
|
||||
pub fn into_anyhow(self) -> anyhow::Error {
|
||||
anyhow::anyhow!(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct NetworkProxyAuditMetadata {
|
||||
pub conversation_id: Option<String>,
|
||||
pub app_version: Option<String>,
|
||||
pub user_account_id: Option<String>,
|
||||
pub auth_mode: Option<String>,
|
||||
pub originator: Option<String>,
|
||||
pub user_email: Option<String>,
|
||||
pub terminal_type: Option<String>,
|
||||
pub model: Option<String>,
|
||||
pub slug: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
pub struct BlockedRequest {
|
||||
pub host: String,
|
||||
pub reason: String,
|
||||
pub client: Option<String>,
|
||||
pub method: Option<String>,
|
||||
pub mode: Option<NetworkMode>,
|
||||
pub protocol: String,
|
||||
pub decision: Option<String>,
|
||||
pub source: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
pub struct BlockedRequestArgs {
|
||||
pub host: String,
|
||||
pub reason: String,
|
||||
pub client: Option<String>,
|
||||
pub method: Option<String>,
|
||||
pub mode: Option<NetworkMode>,
|
||||
pub protocol: String,
|
||||
pub decision: Option<String>,
|
||||
pub source: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
impl BlockedRequest {
|
||||
pub fn new(args: BlockedRequestArgs) -> Self {
|
||||
let BlockedRequestArgs {
|
||||
host,
|
||||
reason,
|
||||
client,
|
||||
method,
|
||||
mode,
|
||||
protocol,
|
||||
decision,
|
||||
source,
|
||||
port,
|
||||
} = args;
|
||||
Self {
|
||||
host,
|
||||
reason,
|
||||
client,
|
||||
method,
|
||||
mode,
|
||||
protocol,
|
||||
decision,
|
||||
source,
|
||||
port,
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigState {
|
||||
pub config: NetworkProxyConfig,
|
||||
pub constraints: NetworkProxyConstraints,
|
||||
pub blocked: VecDeque<BlockedRequest>,
|
||||
pub blocked_total: u64,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ConfigReloader: Send + Sync {
|
||||
fn source_label(&self) -> String;
|
||||
async fn maybe_reload(&self) -> Result<Option<ConfigState>>;
|
||||
async fn reload_now(&self) -> Result<ConfigState>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait BlockedRequestObserver: Send + Sync + 'static {
|
||||
async fn on_blocked_request(&self, request: BlockedRequest);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<O: BlockedRequestObserver + ?Sized> BlockedRequestObserver for Arc<O> {
|
||||
async fn on_blocked_request(&self, request: BlockedRequest) {
|
||||
(**self).on_blocked_request(request).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<F, Fut> BlockedRequestObserver for F
|
||||
where
|
||||
F: Fn(BlockedRequest) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = ()> + Send,
|
||||
{
|
||||
async fn on_blocked_request(&self, request: BlockedRequest) {
|
||||
(self)(request).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_config_state(
|
||||
config: NetworkProxyConfig,
|
||||
constraints: NetworkProxyConstraints,
|
||||
) -> anyhow::Result<ConfigState> {
|
||||
validate_policy_against_constraints(&config, &constraints)?;
|
||||
Ok(ConfigState {
|
||||
config,
|
||||
constraints,
|
||||
blocked: VecDeque::new(),
|
||||
blocked_total: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_policy_against_constraints(
|
||||
config: &NetworkProxyConfig,
|
||||
constraints: &NetworkProxyConstraints,
|
||||
) -> Result<(), NetworkProxyConstraintError> {
|
||||
if let Some(false) = constraints.enabled
|
||||
&& config.network.enabled
|
||||
{
|
||||
return Err(NetworkProxyConstraintError::InvalidValue {
|
||||
field_name: "network.enabled",
|
||||
candidate: "true".to_string(),
|
||||
allowed: "false (disabled by managed config)".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(false) = constraints.allow_local_binding
|
||||
&& config.network.allow_local_binding
|
||||
{
|
||||
return Err(NetworkProxyConstraintError::InvalidValue {
|
||||
field_name: "network.allow_local_binding",
|
||||
candidate: "true".to_string(),
|
||||
allowed: "false (disabled by managed config)".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct NetworkProxyState {
|
||||
state: Arc<RwLock<ConfigState>>,
|
||||
reloader: Arc<dyn ConfigReloader>,
|
||||
blocked_request_observer: Arc<RwLock<Option<Arc<dyn BlockedRequestObserver>>>>,
|
||||
audit_metadata: NetworkProxyAuditMetadata,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NetworkProxyState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("NetworkProxyState").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NetworkProxyState {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
state: self.state.clone(),
|
||||
reloader: self.reloader.clone(),
|
||||
blocked_request_observer: self.blocked_request_observer.clone(),
|
||||
audit_metadata: self.audit_metadata.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkProxyState {
|
||||
pub fn with_reloader(state: ConfigState, reloader: Arc<dyn ConfigReloader>) -> Self {
|
||||
Self::with_reloader_and_audit_metadata(
|
||||
state,
|
||||
reloader,
|
||||
NetworkProxyAuditMetadata::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_reloader_and_blocked_observer(
|
||||
state: ConfigState,
|
||||
reloader: Arc<dyn ConfigReloader>,
|
||||
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
|
||||
) -> Self {
|
||||
Self::with_reloader_and_audit_metadata_and_blocked_observer(
|
||||
state,
|
||||
reloader,
|
||||
NetworkProxyAuditMetadata::default(),
|
||||
blocked_request_observer,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_reloader_and_audit_metadata(
|
||||
state: ConfigState,
|
||||
reloader: Arc<dyn ConfigReloader>,
|
||||
audit_metadata: NetworkProxyAuditMetadata,
|
||||
) -> Self {
|
||||
Self::with_reloader_and_audit_metadata_and_blocked_observer(
|
||||
state,
|
||||
reloader,
|
||||
audit_metadata,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_reloader_and_audit_metadata_and_blocked_observer(
|
||||
state: ConfigState,
|
||||
reloader: Arc<dyn ConfigReloader>,
|
||||
audit_metadata: NetworkProxyAuditMetadata,
|
||||
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
state: Arc::new(RwLock::new(state)),
|
||||
reloader,
|
||||
blocked_request_observer: Arc::new(RwLock::new(blocked_request_observer)),
|
||||
audit_metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_blocked_request_observer(
|
||||
&self,
|
||||
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
|
||||
) {
|
||||
let mut observer = self.blocked_request_observer.write().await;
|
||||
*observer = blocked_request_observer;
|
||||
}
|
||||
|
||||
pub fn audit_metadata(&self) -> &NetworkProxyAuditMetadata {
|
||||
&self.audit_metadata
|
||||
}
|
||||
|
||||
pub async fn current_cfg(&self) -> Result<NetworkProxyConfig> {
|
||||
self.reload_if_needed().await?;
|
||||
let guard = self.state.read().await;
|
||||
Ok(guard.config.clone())
|
||||
}
|
||||
|
||||
pub async fn add_allowed_domain(&self, host: &str) -> Result<()> {
|
||||
let mut guard = self.state.write().await;
|
||||
guard.config.network.upsert_domain_permission(
|
||||
host.to_string(),
|
||||
NetworkDomainPermission::Allow,
|
||||
normalize_host,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_denied_domain(&self, host: &str) -> Result<()> {
|
||||
let mut guard = self.state.write().await;
|
||||
guard.config.network.upsert_domain_permission(
|
||||
host.to_string(),
|
||||
NetworkDomainPermission::Deny,
|
||||
normalize_host,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn record_blocked(&self, entry: BlockedRequest) -> Result<()> {
|
||||
let blocked_for_observer = entry.clone();
|
||||
let blocked_request_observer = self.blocked_request_observer.read().await.clone();
|
||||
let mut guard = self.state.write().await;
|
||||
guard.blocked.push_back(entry);
|
||||
guard.blocked_total = guard.blocked_total.saturating_add(1);
|
||||
while guard.blocked.len() > 200 {
|
||||
guard.blocked.pop_front();
|
||||
}
|
||||
drop(guard);
|
||||
|
||||
if let Some(observer) = blocked_request_observer {
|
||||
observer.on_blocked_request(blocked_for_observer).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reload_if_needed(&self) -> Result<()> {
|
||||
if let Some(mut new_state) = self.reloader.maybe_reload().await? {
|
||||
let blocked = {
|
||||
let guard = self.state.read().await;
|
||||
(guard.blocked.clone(), guard.blocked_total)
|
||||
};
|
||||
new_state.blocked = blocked.0;
|
||||
new_state.blocked_total = blocked.1;
|
||||
let mut guard = self.state.write().await;
|
||||
*guard = new_state;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NetworkProxyBuilder {
|
||||
state: Option<Arc<NetworkProxyState>>,
|
||||
http_addr: Option<SocketAddr>,
|
||||
socks_addr: Option<SocketAddr>,
|
||||
_managed_by_codex: bool,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
|
||||
}
|
||||
|
||||
impl Default for NetworkProxyBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
state: None,
|
||||
http_addr: None,
|
||||
socks_addr: None,
|
||||
_managed_by_codex: true,
|
||||
policy_decider: None,
|
||||
blocked_request_observer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkProxyBuilder {
|
||||
pub fn state(mut self, state: Arc<NetworkProxyState>) -> Self {
|
||||
self.state = Some(state);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn http_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.http_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn socks_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.socks_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn managed_by_codex(mut self, managed_by_codex: bool) -> Self {
|
||||
self._managed_by_codex = managed_by_codex;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn policy_decider<D>(mut self, decider: D) -> Self
|
||||
where
|
||||
D: NetworkPolicyDecider,
|
||||
{
|
||||
self.policy_decider = Some(Arc::new(decider));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn policy_decider_arc(mut self, decider: Arc<dyn NetworkPolicyDecider>) -> Self {
|
||||
self.policy_decider = Some(decider);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn blocked_request_observer<O>(mut self, observer: O) -> Self
|
||||
where
|
||||
O: BlockedRequestObserver,
|
||||
{
|
||||
self.blocked_request_observer = Some(Arc::new(observer));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn blocked_request_observer_arc(
|
||||
mut self,
|
||||
observer: Arc<dyn BlockedRequestObserver>,
|
||||
) -> Self {
|
||||
self.blocked_request_observer = Some(observer);
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn build(self) -> Result<NetworkProxy> {
|
||||
let state = self.state.context("NetworkProxyBuilder requires a state")?;
|
||||
state
|
||||
.set_blocked_request_observer(self.blocked_request_observer.clone())
|
||||
.await;
|
||||
let current_cfg = state.current_cfg().await?;
|
||||
let http_addr = self
|
||||
.http_addr
|
||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 0)));
|
||||
let socks_addr = self
|
||||
.socks_addr
|
||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 0)));
|
||||
Ok(NetworkProxy {
|
||||
state,
|
||||
http_addr,
|
||||
socks_addr,
|
||||
socks_enabled: current_cfg.network.enable_socks5,
|
||||
allow_local_binding: current_cfg.network.allow_local_binding,
|
||||
allow_unix_sockets: current_cfg.network.allow_unix_sockets(),
|
||||
dangerously_allow_all_unix_sockets: current_cfg
|
||||
.network
|
||||
.dangerously_allow_all_unix_sockets,
|
||||
_policy_decider: self.policy_decider,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NetworkProxy {
|
||||
state: Arc<NetworkProxyState>,
|
||||
http_addr: SocketAddr,
|
||||
socks_addr: SocketAddr,
|
||||
socks_enabled: bool,
|
||||
allow_local_binding: bool,
|
||||
allow_unix_sockets: Vec<String>,
|
||||
dangerously_allow_all_unix_sockets: bool,
|
||||
_policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NetworkProxy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("NetworkProxy")
|
||||
.field("http_addr", &self.http_addr)
|
||||
.field("socks_addr", &self.socks_addr)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NetworkProxy {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.http_addr == other.http_addr
|
||||
&& self.socks_addr == other.socks_addr
|
||||
&& self.allow_local_binding == other.allow_local_binding
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for NetworkProxy {}
|
||||
|
||||
pub fn proxy_url_env_value<'a>(
|
||||
env: &'a HashMap<String, String>,
|
||||
canonical_key: &str,
|
||||
) -> Option<&'a str> {
|
||||
if let Some(value) = env.get(canonical_key) {
|
||||
return Some(value.as_str());
|
||||
}
|
||||
let lower_key = canonical_key.to_ascii_lowercase();
|
||||
env.get(lower_key.as_str()).map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn has_proxy_url_env_vars(env: &HashMap<String, String>) -> bool {
|
||||
PROXY_URL_ENV_KEYS
|
||||
.iter()
|
||||
.any(|key| proxy_url_env_value(env, key).is_some_and(|value| !value.trim().is_empty()))
|
||||
}
|
||||
|
||||
fn set_env_keys(env: &mut HashMap<String, String>, keys: &[&str], value: &str) {
|
||||
for key in keys {
|
||||
env.insert((*key).to_string(), value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_proxy_env_overrides(
|
||||
env: &mut HashMap<String, String>,
|
||||
http_addr: SocketAddr,
|
||||
socks_addr: SocketAddr,
|
||||
socks_enabled: bool,
|
||||
allow_local_binding: bool,
|
||||
) {
|
||||
let http_proxy_url = format!("http://{http_addr}");
|
||||
let socks_proxy_url = format!("socks5h://{socks_addr}");
|
||||
env.insert(
|
||||
ALLOW_LOCAL_BINDING_ENV_KEY.to_string(),
|
||||
if allow_local_binding { "1" } else { "0" }.to_string(),
|
||||
);
|
||||
set_env_keys(
|
||||
env,
|
||||
&[
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"YARN_HTTP_PROXY",
|
||||
"YARN_HTTPS_PROXY",
|
||||
"npm_config_http_proxy",
|
||||
"npm_config_https_proxy",
|
||||
"npm_config_proxy",
|
||||
"NPM_CONFIG_HTTP_PROXY",
|
||||
"NPM_CONFIG_HTTPS_PROXY",
|
||||
"NPM_CONFIG_PROXY",
|
||||
"BUNDLE_HTTP_PROXY",
|
||||
"BUNDLE_HTTPS_PROXY",
|
||||
"PIP_PROXY",
|
||||
"DOCKER_HTTP_PROXY",
|
||||
"DOCKER_HTTPS_PROXY",
|
||||
],
|
||||
&http_proxy_url,
|
||||
);
|
||||
set_env_keys(env, WEBSOCKET_PROXY_ENV_KEYS, &http_proxy_url);
|
||||
set_env_keys(env, NO_PROXY_ENV_KEYS, DEFAULT_NO_PROXY_VALUE);
|
||||
env.insert("ELECTRON_GET_USE_PROXY".to_string(), "true".to_string());
|
||||
if socks_enabled {
|
||||
set_env_keys(env, ALL_PROXY_ENV_KEYS, &socks_proxy_url);
|
||||
set_env_keys(env, FTP_PROXY_ENV_KEYS, &socks_proxy_url);
|
||||
} else {
|
||||
set_env_keys(env, ALL_PROXY_ENV_KEYS, &http_proxy_url);
|
||||
set_env_keys(env, FTP_PROXY_ENV_KEYS, &http_proxy_url);
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkProxy {
|
||||
pub fn builder() -> NetworkProxyBuilder {
|
||||
NetworkProxyBuilder::default()
|
||||
}
|
||||
|
||||
pub fn http_addr(&self) -> SocketAddr {
|
||||
self.http_addr
|
||||
}
|
||||
|
||||
pub fn socks_addr(&self) -> SocketAddr {
|
||||
self.socks_addr
|
||||
}
|
||||
|
||||
pub async fn current_cfg(&self) -> Result<NetworkProxyConfig> {
|
||||
self.state.current_cfg().await
|
||||
}
|
||||
|
||||
pub async fn add_allowed_domain(&self, host: &str) -> Result<()> {
|
||||
self.state.add_allowed_domain(host).await
|
||||
}
|
||||
|
||||
pub async fn add_denied_domain(&self, host: &str) -> Result<()> {
|
||||
self.state.add_denied_domain(host).await
|
||||
}
|
||||
|
||||
pub fn allow_local_binding(&self) -> bool {
|
||||
self.allow_local_binding
|
||||
}
|
||||
|
||||
pub fn allow_unix_sockets(&self) -> &[String] {
|
||||
&self.allow_unix_sockets
|
||||
}
|
||||
|
||||
pub fn dangerously_allow_all_unix_sockets(&self) -> bool {
|
||||
self.dangerously_allow_all_unix_sockets
|
||||
}
|
||||
|
||||
pub fn apply_to_env(&self, env: &mut HashMap<String, String>) {
|
||||
apply_proxy_env_overrides(
|
||||
env,
|
||||
self.http_addr,
|
||||
self.socks_addr,
|
||||
self.socks_enabled,
|
||||
self.allow_local_binding,
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> Result<NetworkProxyHandle> {
|
||||
Ok(NetworkProxyHandle::noop())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetworkProxyHandle {
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
impl NetworkProxyHandle {
|
||||
fn noop() -> Self {
|
||||
Self { completed: true }
|
||||
}
|
||||
|
||||
pub async fn wait(mut self) -> Result<()> {
|
||||
self.completed = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn shutdown(mut self) -> Result<()> {
|
||||
self.completed = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NetworkProxyHandle {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.completed;
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,54 @@ name = "codex-rmcp-client"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
autobins = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
native-bin = []
|
||||
|
||||
[[bin]]
|
||||
name = "rmcp_test_server"
|
||||
path = "src/bin/rmcp_test_server.rs"
|
||||
required-features = ["native-bin"]
|
||||
|
||||
[[bin]]
|
||||
name = "test_stdio_server"
|
||||
path = "src/bin/test_stdio_server.rs"
|
||||
required-features = ["native-bin"]
|
||||
|
||||
[[bin]]
|
||||
name = "test_streamable_http_server"
|
||||
path = "src/bin/test_streamable_http_server.rs"
|
||||
required-features = ["native-bin"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
codex-protocol = { workspace = true }
|
||||
futures = { workspace = true, default-features = false, features = ["std"] }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
|
||||
[dev-dependencies]
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
serial_test = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
axum = { workspace = true, default-features = false, features = [
|
||||
"http1",
|
||||
"tokio",
|
||||
] }
|
||||
codex-client = { workspace = true }
|
||||
codex-keyring-store = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-pty = { workspace = true }
|
||||
codex-utils-home-dir = { workspace = true }
|
||||
futures = { workspace = true, default-features = false, features = ["std"] }
|
||||
codex-utils-pty = { workspace = true }
|
||||
keyring = { workspace = true, features = ["crypto-rust"] }
|
||||
oauth2 = "5"
|
||||
reqwest = { version = "0.12", default-features = false, features = [
|
||||
@@ -37,12 +69,8 @@ rmcp = { workspace = true, default-features = false, features = [
|
||||
"transport-streamable-http-client-reqwest",
|
||||
"transport-streamable-http-server",
|
||||
] }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
sse-stream = "0.2.1"
|
||||
thiserror = { workspace = true }
|
||||
tiny_http = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"io-util",
|
||||
@@ -53,24 +81,18 @@ tokio = { workspace = true, features = [
|
||||
"io-std",
|
||||
"time",
|
||||
] }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
urlencoding = { workspace = true }
|
||||
webbrowser = { workspace = true }
|
||||
which = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
serial_test = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
[target.'cfg(all(not(target_arch = "wasm32"), target_os = "linux"))'.dependencies]
|
||||
keyring = { workspace = true, features = ["linux-native-async-persistent"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
[target.'cfg(all(not(target_arch = "wasm32"), target_os = "macos"))'.dependencies]
|
||||
keyring = { workspace = true, features = ["apple-native"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
[target.'cfg(all(not(target_arch = "wasm32"), target_os = "windows"))'.dependencies]
|
||||
keyring = { workspace = true, features = ["windows-native"] }
|
||||
|
||||
[target.'cfg(any(target_os = "freebsd", target_os = "openbsd"))'.dependencies]
|
||||
[target.'cfg(all(not(target_arch = "wasm32"), any(target_os = "freebsd", target_os = "openbsd")))'.dependencies]
|
||||
keyring = { workspace = true, features = ["sync-secret-service"] }
|
||||
|
||||
@@ -1,31 +1,7 @@
|
||||
mod auth_status;
|
||||
mod logging_client_handler;
|
||||
mod oauth;
|
||||
mod perform_oauth_login;
|
||||
mod program_resolver;
|
||||
mod rmcp_client;
|
||||
mod utils;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
include!("native.rs");
|
||||
|
||||
pub use auth_status::StreamableHttpOAuthDiscovery;
|
||||
pub use auth_status::determine_streamable_http_auth_status;
|
||||
pub use auth_status::discover_streamable_http_oauth;
|
||||
pub use auth_status::supports_oauth_login;
|
||||
pub use codex_protocol::protocol::McpAuthStatus;
|
||||
pub use oauth::OAuthCredentialsStoreMode;
|
||||
pub use oauth::StoredOAuthTokens;
|
||||
pub use oauth::WrappedOAuthTokenResponse;
|
||||
pub use oauth::delete_oauth_tokens;
|
||||
pub(crate) use oauth::load_oauth_tokens;
|
||||
pub use oauth::save_oauth_tokens;
|
||||
pub use perform_oauth_login::OAuthProviderError;
|
||||
pub use perform_oauth_login::OauthLoginHandle;
|
||||
pub use perform_oauth_login::perform_oauth_login;
|
||||
pub use perform_oauth_login::perform_oauth_login_return_url;
|
||||
pub use perform_oauth_login::perform_oauth_login_silent;
|
||||
pub use rmcp::model::ElicitationAction;
|
||||
pub use rmcp_client::Elicitation;
|
||||
pub use rmcp_client::ElicitationResponse;
|
||||
pub use rmcp_client::ListToolsWithConnectorIdResult;
|
||||
pub use rmcp_client::RmcpClient;
|
||||
pub use rmcp_client::SendElicitation;
|
||||
pub use rmcp_client::ToolWithConnectorId;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
|
||||
31
codex-rs/rmcp-client/src/native.rs
Normal file
31
codex-rs/rmcp-client/src/native.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
mod auth_status;
|
||||
mod logging_client_handler;
|
||||
mod oauth;
|
||||
mod perform_oauth_login;
|
||||
mod program_resolver;
|
||||
mod rmcp_client;
|
||||
mod utils;
|
||||
|
||||
pub use auth_status::StreamableHttpOAuthDiscovery;
|
||||
pub use auth_status::determine_streamable_http_auth_status;
|
||||
pub use auth_status::discover_streamable_http_oauth;
|
||||
pub use auth_status::supports_oauth_login;
|
||||
pub use codex_protocol::protocol::McpAuthStatus;
|
||||
pub use oauth::OAuthCredentialsStoreMode;
|
||||
pub use oauth::StoredOAuthTokens;
|
||||
pub use oauth::WrappedOAuthTokenResponse;
|
||||
pub use oauth::delete_oauth_tokens;
|
||||
pub(crate) use oauth::load_oauth_tokens;
|
||||
pub use oauth::save_oauth_tokens;
|
||||
pub use perform_oauth_login::OAuthProviderError;
|
||||
pub use perform_oauth_login::OauthLoginHandle;
|
||||
pub use perform_oauth_login::perform_oauth_login;
|
||||
pub use perform_oauth_login::perform_oauth_login_return_url;
|
||||
pub use perform_oauth_login::perform_oauth_login_silent;
|
||||
pub use rmcp::model::ElicitationAction;
|
||||
pub use rmcp_client::Elicitation;
|
||||
pub use rmcp_client::ElicitationResponse;
|
||||
pub use rmcp_client::ListToolsWithConnectorIdResult;
|
||||
pub use rmcp_client::RmcpClient;
|
||||
pub use rmcp_client::SendElicitation;
|
||||
pub use rmcp_client::ToolWithConnectorId;
|
||||
340
codex-rs/rmcp-client/src/wasm.rs
Normal file
340
codex-rs/rmcp-client/src/wasm.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use futures::future::BoxFuture;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
pub use codex_protocol::protocol::McpAuthStatus;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ElicitationAction {
|
||||
Accept,
|
||||
Decline,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StreamableHttpOAuthDiscovery {
|
||||
pub scopes_supported: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub async fn determine_streamable_http_auth_status(
|
||||
_server_name: &str,
|
||||
_url: &str,
|
||||
bearer_token_env_var: Option<&str>,
|
||||
http_headers: Option<HashMap<String, String>>,
|
||||
env_http_headers: Option<HashMap<String, String>>,
|
||||
_store_mode: OAuthCredentialsStoreMode,
|
||||
) -> Result<McpAuthStatus> {
|
||||
if bearer_token_env_var.is_some() {
|
||||
return Ok(McpAuthStatus::BearerToken);
|
||||
}
|
||||
|
||||
let has_auth_header = http_headers
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(env_http_headers.into_iter().flatten())
|
||||
.any(|(key, _)| key.eq_ignore_ascii_case("authorization"));
|
||||
if has_auth_header {
|
||||
return Ok(McpAuthStatus::BearerToken);
|
||||
}
|
||||
|
||||
Ok(McpAuthStatus::Unsupported)
|
||||
}
|
||||
|
||||
pub async fn supports_oauth_login(_url: &str) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub async fn discover_streamable_http_oauth(
|
||||
_url: &str,
|
||||
_http_headers: Option<HashMap<String, String>>,
|
||||
_env_http_headers: Option<HashMap<String, String>>,
|
||||
) -> Result<Option<StreamableHttpOAuthDiscovery>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum OAuthCredentialsStoreMode {
|
||||
#[default]
|
||||
Auto,
|
||||
File,
|
||||
Keyring,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct StoredOAuthTokens {
|
||||
pub server_name: String,
|
||||
pub url: String,
|
||||
pub client_id: String,
|
||||
pub token_response: WrappedOAuthTokenResponse,
|
||||
#[serde(default)]
|
||||
pub expires_at: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
pub struct WrappedOAuthTokenResponse(pub serde_json::Value);
|
||||
|
||||
pub fn delete_oauth_tokens(
|
||||
_server_name: &str,
|
||||
_url: &str,
|
||||
_store_mode: OAuthCredentialsStoreMode,
|
||||
) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn save_oauth_tokens(
|
||||
_server_name: &str,
|
||||
_tokens: &StoredOAuthTokens,
|
||||
_store_mode: OAuthCredentialsStoreMode,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OAuthProviderError {
|
||||
error: Option<String>,
|
||||
error_description: Option<String>,
|
||||
}
|
||||
|
||||
impl OAuthProviderError {
|
||||
pub fn new(error: Option<String>, error_description: Option<String>) -> Self {
|
||||
Self {
|
||||
error,
|
||||
error_description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for OAuthProviderError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match (self.error.as_deref(), self.error_description.as_deref()) {
|
||||
(Some(error), Some(error_description)) => {
|
||||
write!(f, "OAuth provider returned `{error}`: {error_description}")
|
||||
}
|
||||
(Some(error), None) => write!(f, "OAuth provider returned `{error}`"),
|
||||
(None, Some(error_description)) => write!(f, "OAuth error: {error_description}"),
|
||||
(None, None) => write!(f, "OAuth provider returned an error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for OAuthProviderError {}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn perform_oauth_login(
|
||||
_server_name: &str,
|
||||
_server_url: &str,
|
||||
_store_mode: OAuthCredentialsStoreMode,
|
||||
_http_headers: Option<HashMap<String, String>>,
|
||||
_env_http_headers: Option<HashMap<String, String>>,
|
||||
_scopes: &[String],
|
||||
_oauth_resource: Option<&str>,
|
||||
_callback_port: Option<u16>,
|
||||
_callback_url: Option<&str>,
|
||||
) -> Result<()> {
|
||||
Err(anyhow!("MCP OAuth login is unavailable on wasm32"))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn perform_oauth_login_silent(
|
||||
_server_name: &str,
|
||||
_server_url: &str,
|
||||
_store_mode: OAuthCredentialsStoreMode,
|
||||
_http_headers: Option<HashMap<String, String>>,
|
||||
_env_http_headers: Option<HashMap<String, String>>,
|
||||
_scopes: &[String],
|
||||
_oauth_resource: Option<&str>,
|
||||
_callback_port: Option<u16>,
|
||||
_callback_url: Option<&str>,
|
||||
) -> Result<()> {
|
||||
Err(anyhow!("MCP OAuth login is unavailable on wasm32"))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn perform_oauth_login_return_url(
|
||||
_server_name: &str,
|
||||
_server_url: &str,
|
||||
_store_mode: OAuthCredentialsStoreMode,
|
||||
_http_headers: Option<HashMap<String, String>>,
|
||||
_env_http_headers: Option<HashMap<String, String>>,
|
||||
_scopes: &[String],
|
||||
_oauth_resource: Option<&str>,
|
||||
_callback_port: Option<u16>,
|
||||
_callback_url: Option<&str>,
|
||||
) -> Result<OauthLoginHandle> {
|
||||
Err(anyhow!("MCP OAuth login is unavailable on wasm32"))
|
||||
}
|
||||
|
||||
pub struct OauthLoginHandle {
|
||||
authorization_url: String,
|
||||
}
|
||||
|
||||
impl OauthLoginHandle {
|
||||
pub fn authorization_url(&self) -> &str {
|
||||
&self.authorization_url
|
||||
}
|
||||
|
||||
pub fn into_parts(self) -> (String, futures::channel::oneshot::Receiver<Result<()>>) {
|
||||
let (_tx, rx) = futures::channel::oneshot::channel();
|
||||
(self.authorization_url, rx)
|
||||
}
|
||||
|
||||
pub async fn wait(self) -> Result<()> {
|
||||
Err(anyhow!("MCP OAuth login is unavailable on wasm32"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct RequestId(pub String);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct Elicitation {
|
||||
#[serde(default)]
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ElicitationResponse {
|
||||
pub action: ElicitationAction,
|
||||
pub content: Option<serde_json::Value>,
|
||||
#[serde(rename = "_meta")]
|
||||
pub meta: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub type SendElicitation = Box<
|
||||
dyn Fn(RequestId, Elicitation) -> BoxFuture<'static, Result<ElicitationResponse>> + Send + Sync,
|
||||
>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct ToolDefinition {
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct ToolWithConnectorId {
|
||||
pub tool: ToolDefinition,
|
||||
pub connector_id: Option<String>,
|
||||
pub connector_name: Option<String>,
|
||||
pub connector_description: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ListToolsWithConnectorIdResult {
|
||||
pub next_cursor: Option<String>,
|
||||
pub tools: Vec<ToolWithConnectorId>,
|
||||
}
|
||||
|
||||
pub struct RmcpClient;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct InitializeRequestParams;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct InitializeResult;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct PaginatedRequestParams {
|
||||
#[serde(default)]
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct ListResourcesResult;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct ListResourceTemplatesResult;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct ReadResourceRequestParams;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct ReadResourceResult;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct CallToolResult;
|
||||
|
||||
impl RmcpClient {
|
||||
pub async fn new_stdio_client(
|
||||
_program: OsString,
|
||||
_args: Vec<OsString>,
|
||||
_env: Option<HashMap<OsString, OsString>>,
|
||||
_env_vars: &[String],
|
||||
_cwd: Option<PathBuf>,
|
||||
) -> std::io::Result<Self> {
|
||||
Err(std::io::Error::other(
|
||||
"MCP stdio transport is unavailable on wasm32",
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn new_streamable_http_client(
|
||||
_server_name: &str,
|
||||
_url: &str,
|
||||
_bearer_token: Option<String>,
|
||||
_http_headers: Option<HashMap<String, String>>,
|
||||
_env_http_headers: Option<HashMap<String, String>>,
|
||||
_store_mode: OAuthCredentialsStoreMode,
|
||||
) -> Result<Self> {
|
||||
Err(anyhow!("MCP HTTP transport is unavailable on wasm32"))
|
||||
}
|
||||
|
||||
pub async fn initialize(
|
||||
&self,
|
||||
_params: InitializeRequestParams,
|
||||
_timeout: Option<Duration>,
|
||||
_send_elicitation: SendElicitation,
|
||||
) -> Result<InitializeResult> {
|
||||
Err(anyhow!("MCP initialize is unavailable on wasm32"))
|
||||
}
|
||||
|
||||
pub async fn list_tools_with_connector_ids(
|
||||
&self,
|
||||
_params: Option<PaginatedRequestParams>,
|
||||
_timeout: Option<Duration>,
|
||||
) -> Result<ListToolsWithConnectorIdResult> {
|
||||
Err(anyhow!("MCP tools are unavailable on wasm32"))
|
||||
}
|
||||
|
||||
pub async fn list_resources(
|
||||
&self,
|
||||
_params: Option<PaginatedRequestParams>,
|
||||
_timeout: Option<Duration>,
|
||||
) -> Result<ListResourcesResult> {
|
||||
Err(anyhow!("MCP resources are unavailable on wasm32"))
|
||||
}
|
||||
|
||||
pub async fn list_resource_templates(
|
||||
&self,
|
||||
_params: Option<PaginatedRequestParams>,
|
||||
_timeout: Option<Duration>,
|
||||
) -> Result<ListResourceTemplatesResult> {
|
||||
Err(anyhow!("MCP resource templates are unavailable on wasm32"))
|
||||
}
|
||||
|
||||
pub async fn read_resource(
|
||||
&self,
|
||||
_params: ReadResourceRequestParams,
|
||||
_timeout: Option<Duration>,
|
||||
) -> Result<ReadResourceResult> {
|
||||
Err(anyhow!("MCP resources are unavailable on wasm32"))
|
||||
}
|
||||
|
||||
pub async fn call_tool(
|
||||
&self,
|
||||
_name: String,
|
||||
_arguments: Option<serde_json::Value>,
|
||||
_meta: Option<serde_json::Value>,
|
||||
_timeout: Option<Duration>,
|
||||
) -> Result<CallToolResult> {
|
||||
Err(anyhow!("MCP tools are unavailable on wasm32"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user