mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
[codex] Remove async_trait from first-party code (#27475)
## Why First-party async traits should expose their `Send` contracts explicitly without requiring `async_trait`. This completes the migration pattern established in #27303 and #27304. ## What changed - Replaced the remaining first-party `async_trait` traits with native return-position `impl Future + Send` where statically dispatched and explicit boxed `Send` futures where object safety is required. - Kept implementations behavior-preserving, outlining existing async bodies into inherent methods where that keeps the diff reviewable. - Removed all direct first-party `async-trait` dependencies and the workspace dependency declaration. - Added a cargo-deny policy that permits `async-trait` only through the remaining transitive wrapper crates. - Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and keep the full cargo-deny check passing. ## Validation - `just test -p codex-exec-server`: 216 passed, 2 skipped. - `just test -p codex-model-provider`: 39 passed. - `just test -p codex-core` and `just test`: changed tests passed; remaining failures are environment-sensitive suites unrelated to this migration. - `cargo deny check` - `just fix` - `just fmt` - `cargo shear` - `just bazel-lock-check`
This commit is contained in:
Generated
+1
@@ -1420,6 +1420,7 @@
|
||||
"rama-utils_0.3.0-alpha.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8\"},{\"name\":\"const_format\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"name\":\"rama-macros\",\"req\":\"^0.3.0-alpha.4\"},{\"name\":\"regex\",\"req\":\"^1.12\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1\"},{\"features\":[\"write\",\"serde\",\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"req\":\"^1.15\"},{\"name\":\"smol_str\",\"req\":\"^0.3\"},{\"features\":[\"time\",\"macros\"],\"name\":\"tokio\",\"req\":\"^1.48\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"name\":\"wildcard\",\"req\":\"^0.3\"}],\"features\":{}}",
|
||||
"rand_0.10.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"rng\"],\"name\":\"chacha20\",\"optional\":true,\"req\":\"^0.10.0\"},{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.1.3\"},{\"default_features\":false,\"name\":\"rand_core\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.7\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.103\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.140\"}],\"features\":{\"alloc\":[],\"chacha\":[\"dep:chacha20\"],\"default\":[\"std\",\"std_rng\",\"sys_rng\",\"thread_rng\"],\"log\":[],\"serde\":[\"dep:serde\"],\"simd_support\":[],\"std\":[\"alloc\",\"getrandom?/std\"],\"std_rng\":[\"dep:chacha20\"],\"sys_rng\":[\"dep:getrandom\",\"getrandom/sys_rng\"],\"thread_rng\":[\"std\",\"std_rng\",\"sys_rng\"],\"unbiased\":[]}}",
|
||||
"rand_0.8.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.2.1\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.22\",\"target\":\"cfg(unix)\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.4\"},{\"features\":[\"into_bits\"],\"name\":\"packed_simd\",\"optional\":true,\"package\":\"packed_simd_2\",\"req\":\"^0.3.7\"},{\"default_features\":false,\"name\":\"rand_chacha\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"rand_core\",\"req\":\"^0.6.0\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.3.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.103\"}],\"features\":{\"alloc\":[\"rand_core/alloc\"],\"default\":[\"std\",\"std_rng\"],\"getrandom\":[\"rand_core/getrandom\"],\"min_const_gen\":[],\"nightly\":[],\"serde1\":[\"serde\",\"rand_core/serde1\"],\"simd_support\":[\"packed_simd\"],\"small_rng\":[],\"std\":[\"rand_core/std\",\"rand_chacha/std\",\"alloc\",\"getrandom\",\"libc\"],\"std_rng\":[\"rand_chacha\"]}}",
|
||||
"rand_0.8.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.2.1\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.22\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"name\":\"rand_chacha\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"rand_core\",\"req\":\"^0.6.0\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.3.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.103\"}],\"features\":{\"alloc\":[\"rand_core/alloc\"],\"default\":[\"std\",\"std_rng\"],\"getrandom\":[\"rand_core/getrandom\"],\"log\":[],\"min_const_gen\":[],\"nightly\":[],\"serde1\":[\"serde\",\"rand_core/serde1\"],\"small_rng\":[],\"std\":[\"rand_core/std\",\"rand_chacha/std\",\"alloc\",\"getrandom\",\"libc\"],\"std_rng\":[\"rand_chacha\"]}}",
|
||||
"rand_0.9.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.2.1\"},{\"default_features\":false,\"name\":\"rand_chacha\",\"optional\":true,\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"rand_core\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.7\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.103\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.140\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"std_rng\",\"os_rng\",\"small_rng\",\"thread_rng\"],\"log\":[],\"nightly\":[],\"os_rng\":[\"rand_core/os_rng\"],\"serde\":[\"dep:serde\",\"rand_core/serde\"],\"simd_support\":[],\"small_rng\":[],\"std\":[\"rand_core/std\",\"rand_chacha?/std\",\"alloc\"],\"std_rng\":[\"dep:rand_chacha\"],\"thread_rng\":[\"std\",\"std_rng\",\"os_rng\"],\"unbiased\":[]}}",
|
||||
"rand_chacha_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"simd\"],\"name\":\"ppv-lite86\",\"req\":\"^0.2.8\"},{\"name\":\"rand_core\",\"req\":\"^0.6.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"serde1\":[\"serde\"],\"simd\":[],\"std\":[\"ppv-lite86/std\"]}}",
|
||||
"rand_chacha_0.9.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"simd\"],\"name\":\"ppv-lite86\",\"req\":\"^0.2.14\"},{\"name\":\"rand_core\",\"req\":\"^0.9.0\"},{\"features\":[\"os_rng\"],\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.9.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"os_rng\":[\"rand_core/os_rng\"],\"serde\":[\"dep:serde\"],\"std\":[\"ppv-lite86/std\",\"rand_core/std\"]}}",
|
||||
|
||||
Generated
+9
-31
@@ -213,7 +213,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"nom 7.1.3",
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"rust-embed",
|
||||
"scrypt",
|
||||
"sha2 0.10.9",
|
||||
@@ -234,7 +234,7 @@ dependencies = [
|
||||
"hkdf 0.12.4",
|
||||
"io_tee",
|
||||
"nom 7.1.3",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"secrecy",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
@@ -786,7 +786,7 @@ dependencies = [
|
||||
"hex",
|
||||
"http 1.4.0",
|
||||
"p256",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"ring",
|
||||
"sha2 0.10.9",
|
||||
"time",
|
||||
@@ -1852,7 +1852,6 @@ dependencies = [
|
||||
name = "codex-agent-graph-store"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"codex-protocol",
|
||||
"codex-state",
|
||||
"pretty_assertions",
|
||||
@@ -1919,7 +1918,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
"async-channel",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chrono",
|
||||
@@ -1953,7 +1951,6 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app_test_support",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -2218,7 +2215,6 @@ dependencies = [
|
||||
name = "codex-async-utils"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"pretty_assertions",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -2376,7 +2372,6 @@ dependencies = [
|
||||
name = "codex-client"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"codex-utils-cargo-bin",
|
||||
"codex-utils-rustls-provider",
|
||||
@@ -2431,7 +2426,6 @@ name = "codex-cloud-tasks"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-client",
|
||||
@@ -2463,7 +2457,6 @@ name = "codex-cloud-tasks-client"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"codex-api",
|
||||
"codex-backend-client",
|
||||
@@ -2477,7 +2470,6 @@ dependencies = [
|
||||
name = "codex-cloud-tasks-mock-client"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"codex-cloud-tasks-client",
|
||||
"diffy",
|
||||
@@ -2507,7 +2499,6 @@ name = "codex-config"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"codex-app-server-protocol",
|
||||
"codex-execpolicy",
|
||||
@@ -2584,7 +2575,6 @@ dependencies = [
|
||||
"assert_cmd",
|
||||
"assert_matches",
|
||||
"async-channel",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bm25",
|
||||
"chrono",
|
||||
@@ -2844,7 +2834,6 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@@ -3012,7 +3001,6 @@ dependencies = [
|
||||
name = "codex-file-system"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-path-uri",
|
||||
@@ -3204,7 +3192,6 @@ name = "codex-login"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"codex-agent-identity",
|
||||
@@ -3409,7 +3396,6 @@ dependencies = [
|
||||
name = "codex-model-provider"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"codex-agent-identity",
|
||||
"codex-api",
|
||||
"codex-aws-auth",
|
||||
@@ -3450,7 +3436,6 @@ dependencies = [
|
||||
name = "codex-models-manager"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"codex-app-server-protocol",
|
||||
"codex-collaboration-mode-templates",
|
||||
@@ -3472,7 +3457,6 @@ name = "codex-network-proxy"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"clap",
|
||||
@@ -3707,7 +3691,6 @@ name = "codex-rollout"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"codex-file-search",
|
||||
"codex-git-utils",
|
||||
@@ -3749,7 +3732,6 @@ name = "codex-sandboxing"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"codex-network-proxy",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
@@ -3812,7 +3794,6 @@ name = "codex-shell-escalation"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"clap",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
@@ -3841,7 +3822,6 @@ dependencies = [
|
||||
name = "codex-skills-extension"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"codex-core-skills",
|
||||
"codex-exec-server",
|
||||
"codex-extension-api",
|
||||
@@ -3926,7 +3906,6 @@ dependencies = [
|
||||
name = "codex-thread-store"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"codex-git-utils",
|
||||
"codex-install-context",
|
||||
@@ -4260,7 +4239,6 @@ name = "codex-utils-readiness"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -4361,7 +4339,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"glob",
|
||||
"pretty_assertions",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
@@ -9470,7 +9448,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"getrandom 0.2.17",
|
||||
"http 1.4.0",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -11109,9 +11087,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
@@ -11977,7 +11955,7 @@ dependencies = [
|
||||
"hkdf 0.12.4",
|
||||
"num",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"zbus",
|
||||
@@ -15642,7 +15620,7 @@ dependencies = [
|
||||
"hex",
|
||||
"nix 0.29.0",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"sha1 0.10.6",
|
||||
|
||||
@@ -265,7 +265,6 @@ assert_matches = "1.5.0"
|
||||
async-channel = "2.3.1"
|
||||
async-io = "2.6.0"
|
||||
async-stream = "0.3.6"
|
||||
async-trait = "0.1.89"
|
||||
aws-config = "1"
|
||||
aws-credential-types = "1"
|
||||
aws-sigv4 = "1"
|
||||
|
||||
@@ -13,7 +13,6 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-state = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_state::StateRuntime;
|
||||
use std::sync::Arc;
|
||||
@@ -29,7 +28,6 @@ impl LocalAgentGraphStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AgentGraphStore for LocalAgentGraphStore {
|
||||
async fn upsert_thread_spawn_edge(
|
||||
&self,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::ThreadId;
|
||||
|
||||
use crate::AgentGraphStoreResult;
|
||||
@@ -8,38 +7,37 @@ use crate::ThreadSpawnEdgeStatus;
|
||||
///
|
||||
/// Implementations are expected to return stable ordering for list methods so callers can merge
|
||||
/// persisted graph state with live in-memory state without introducing nondeterministic output.
|
||||
#[async_trait]
|
||||
pub trait AgentGraphStore: Send + Sync {
|
||||
/// Insert or replace the directional parent/child edge for a spawned thread.
|
||||
///
|
||||
/// `child_thread_id` has at most one persisted parent. Re-inserting the same child should
|
||||
/// update both the parent and status to match the supplied values.
|
||||
async fn upsert_thread_spawn_edge(
|
||||
fn upsert_thread_spawn_edge(
|
||||
&self,
|
||||
parent_thread_id: ThreadId,
|
||||
child_thread_id: ThreadId,
|
||||
status: ThreadSpawnEdgeStatus,
|
||||
) -> AgentGraphStoreResult<()>;
|
||||
) -> impl std::future::Future<Output = AgentGraphStoreResult<()>> + Send;
|
||||
|
||||
/// Update the persisted lifecycle status of a spawned thread's incoming edge.
|
||||
///
|
||||
/// Implementations should treat missing children as a successful no-op.
|
||||
async fn set_thread_spawn_edge_status(
|
||||
fn set_thread_spawn_edge_status(
|
||||
&self,
|
||||
child_thread_id: ThreadId,
|
||||
status: ThreadSpawnEdgeStatus,
|
||||
) -> AgentGraphStoreResult<()>;
|
||||
) -> impl std::future::Future<Output = AgentGraphStoreResult<()>> + Send;
|
||||
|
||||
/// List direct spawned children of a parent thread.
|
||||
///
|
||||
/// When `status_filter` is `Some`, only child edges with that exact status are returned. When
|
||||
/// it is `None`, all direct child edges are returned regardless of status, including statuses
|
||||
/// that may be added by a future store implementation.
|
||||
async fn list_thread_spawn_children(
|
||||
fn list_thread_spawn_children(
|
||||
&self,
|
||||
parent_thread_id: ThreadId,
|
||||
status_filter: Option<ThreadSpawnEdgeStatus>,
|
||||
) -> AgentGraphStoreResult<Vec<ThreadId>>;
|
||||
) -> impl std::future::Future<Output = AgentGraphStoreResult<Vec<ThreadId>>> + Send;
|
||||
|
||||
/// List spawned descendants breadth-first by depth, then by thread id.
|
||||
///
|
||||
@@ -47,9 +45,9 @@ pub trait AgentGraphStore: Send + Sync {
|
||||
/// For example, `Some(Open)` walks only open edges, so descendants under a closed edge are not
|
||||
/// included even if their own incoming edge is open. `None` walks and returns every persisted
|
||||
/// edge regardless of status.
|
||||
async fn list_thread_spawn_descendants(
|
||||
fn list_thread_spawn_descendants(
|
||||
&self,
|
||||
root_thread_id: ThreadId,
|
||||
status_filter: Option<ThreadSpawnEdgeStatus>,
|
||||
) -> AgentGraphStoreResult<Vec<ThreadId>>;
|
||||
) -> impl std::future::Future<Output = AgentGraphStoreResult<Vec<ThreadId>>> + Send;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
axum = { workspace = true, default-features = false, features = [
|
||||
"http1",
|
||||
|
||||
@@ -99,7 +99,6 @@ mod tests {
|
||||
use crate::extensions::ThreadExtensionDependencies;
|
||||
use crate::extensions::guardian_agent_spawner;
|
||||
use crate::extensions::thread_extensions;
|
||||
use async_trait::async_trait;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_config::CloudConfigBundleLoader;
|
||||
use codex_config::LoaderOverrides;
|
||||
@@ -245,8 +244,7 @@ mod tests {
|
||||
bad_loads: AtomicUsize,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ThreadConfigLoader for CountingThreadConfigLoader {
|
||||
impl CountingThreadConfigLoader {
|
||||
async fn load(
|
||||
&self,
|
||||
context: ThreadConfigContext,
|
||||
@@ -265,4 +263,13 @@ mod tests {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadConfigLoader for CountingThreadConfigLoader {
|
||||
fn load(
|
||||
&self,
|
||||
context: ThreadConfigContext,
|
||||
) -> codex_config::ThreadConfigLoaderFuture<'_, Vec<ThreadConfigSource>> {
|
||||
Box::pin(CountingThreadConfigLoader::load(self, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ use crate::thread_state::ConnectionCapabilities;
|
||||
use crate::thread_state::ThreadStateManager;
|
||||
use crate::transport::AppServerTransport;
|
||||
use crate::transport::RemoteControlHandle;
|
||||
use async_trait::async_trait;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_analytics::AppServerRpcTransport;
|
||||
use codex_app_server_protocol::AuthMode as LoginAuthMode;
|
||||
@@ -106,13 +105,6 @@ impl ExternalAuthRefreshBridge {
|
||||
ExternalAuthRefreshReason::Unauthorized => ChatgptAuthTokensRefreshReason::Unauthorized,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExternalAuth for ExternalAuthRefreshBridge {
|
||||
fn auth_mode(&self) -> LoginAuthMode {
|
||||
LoginAuthMode::Chatgpt
|
||||
}
|
||||
|
||||
async fn refresh(
|
||||
&self,
|
||||
@@ -163,6 +155,19 @@ impl ExternalAuth for ExternalAuthRefreshBridge {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalAuth for ExternalAuthRefreshBridge {
|
||||
fn auth_mode(&self) -> LoginAuthMode {
|
||||
LoginAuthMode::Chatgpt
|
||||
}
|
||||
|
||||
fn refresh(
|
||||
&self,
|
||||
context: ExternalAuthRefreshContext,
|
||||
) -> codex_login::ExternalAuthFuture<'_, ExternalAuthTokens> {
|
||||
Box::pin(ExternalAuthRefreshBridge::refresh(self, context))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MessageProcessor {
|
||||
outgoing: Arc<OutgoingMessageSender>,
|
||||
skills_watcher: Arc<SkillsWatcher>,
|
||||
|
||||
@@ -8,7 +8,6 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "time"] }
|
||||
tokio-util.workspace = true
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_trait::async_trait;
|
||||
use std::future::Future;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
@@ -7,14 +6,15 @@ pub enum CancelErr {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait OrCancelExt: Sized {
|
||||
type Output;
|
||||
|
||||
async fn or_cancel(self, token: &CancellationToken) -> Result<Self::Output, CancelErr>;
|
||||
fn or_cancel(
|
||||
self,
|
||||
token: &CancellationToken,
|
||||
) -> impl Future<Output = Result<Self::Output, CancelErr>> + Send;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<F> OrCancelExt for F
|
||||
where
|
||||
F: Future + Send,
|
||||
|
||||
@@ -15,7 +15,6 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-api = { workspace = true }
|
||||
codex-backend-client = { workspace = true }
|
||||
|
||||
@@ -2,8 +2,11 @@ use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, CloudTaskError>;
|
||||
pub type CloudBackendFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CloudTaskError {
|
||||
@@ -130,41 +133,44 @@ impl Default for TaskText {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait CloudBackend: Send + Sync {
|
||||
async fn list_tasks(
|
||||
&self,
|
||||
env: Option<&str>,
|
||||
fn list_tasks<'a>(
|
||||
&'a self,
|
||||
env: Option<&'a str>,
|
||||
limit: Option<i64>,
|
||||
cursor: Option<&str>,
|
||||
) -> Result<TaskListPage>;
|
||||
async fn get_task_summary(&self, id: TaskId) -> Result<TaskSummary>;
|
||||
async fn get_task_diff(&self, id: TaskId) -> Result<Option<String>>;
|
||||
cursor: Option<&'a str>,
|
||||
) -> CloudBackendFuture<'a, TaskListPage>;
|
||||
fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary>;
|
||||
fn get_task_diff(&self, id: TaskId) -> CloudBackendFuture<'_, Option<String>>;
|
||||
/// Return assistant output messages (no diff) when available.
|
||||
async fn get_task_messages(&self, id: TaskId) -> Result<Vec<String>>;
|
||||
fn get_task_messages(&self, id: TaskId) -> CloudBackendFuture<'_, Vec<String>>;
|
||||
/// Return the creating prompt and assistant messages (when available).
|
||||
async fn get_task_text(&self, id: TaskId) -> Result<TaskText>;
|
||||
fn get_task_text(&self, id: TaskId) -> CloudBackendFuture<'_, TaskText>;
|
||||
/// Return any sibling attempts (best-of-N) for the given assistant turn.
|
||||
async fn list_sibling_attempts(
|
||||
fn list_sibling_attempts(
|
||||
&self,
|
||||
task: TaskId,
|
||||
turn_id: String,
|
||||
) -> Result<Vec<TurnAttempt>>;
|
||||
) -> CloudBackendFuture<'_, Vec<TurnAttempt>>;
|
||||
/// Dry-run apply (preflight) that validates whether the patch would apply cleanly.
|
||||
/// Never modifies the working tree. When `diff_override` is supplied, the provided diff is
|
||||
/// used instead of re-fetching the task details so callers can apply alternate attempts.
|
||||
async fn apply_task_preflight(
|
||||
fn apply_task_preflight(
|
||||
&self,
|
||||
id: TaskId,
|
||||
diff_override: Option<String>,
|
||||
) -> Result<ApplyOutcome>;
|
||||
async fn apply_task(&self, id: TaskId, diff_override: Option<String>) -> Result<ApplyOutcome>;
|
||||
async fn create_task(
|
||||
) -> CloudBackendFuture<'_, ApplyOutcome>;
|
||||
fn apply_task(
|
||||
&self,
|
||||
env_id: &str,
|
||||
prompt: &str,
|
||||
git_ref: &str,
|
||||
id: TaskId,
|
||||
diff_override: Option<String>,
|
||||
) -> CloudBackendFuture<'_, ApplyOutcome>;
|
||||
fn create_task<'a>(
|
||||
&'a self,
|
||||
env_id: &'a str,
|
||||
prompt: &'a str,
|
||||
git_ref: &'a str,
|
||||
qa_mode: bool,
|
||||
best_of_n: usize,
|
||||
) -> Result<CreatedTask>;
|
||||
) -> CloudBackendFuture<'a, CreatedTask>;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::ApplyOutcome;
|
||||
use crate::ApplyStatus;
|
||||
use crate::AttemptStatus;
|
||||
use crate::CloudBackend;
|
||||
use crate::CloudBackendFuture;
|
||||
use crate::CloudTaskError;
|
||||
use crate::DiffSummary;
|
||||
use crate::Result;
|
||||
@@ -61,68 +62,77 @@ impl HttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CloudBackend for HttpClient {
|
||||
async fn list_tasks(
|
||||
&self,
|
||||
env: Option<&str>,
|
||||
fn list_tasks<'a>(
|
||||
&'a self,
|
||||
env: Option<&'a str>,
|
||||
limit: Option<i64>,
|
||||
cursor: Option<&str>,
|
||||
) -> Result<TaskListPage> {
|
||||
self.tasks_api().list(env, limit, cursor).await
|
||||
cursor: Option<&'a str>,
|
||||
) -> CloudBackendFuture<'a, TaskListPage> {
|
||||
Box::pin(async move { self.tasks_api().list(env, limit, cursor).await })
|
||||
}
|
||||
|
||||
async fn get_task_summary(&self, id: TaskId) -> Result<TaskSummary> {
|
||||
self.tasks_api().summary(id).await
|
||||
fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary> {
|
||||
Box::pin(async move { self.tasks_api().summary(id).await })
|
||||
}
|
||||
|
||||
async fn get_task_diff(&self, id: TaskId) -> Result<Option<String>> {
|
||||
self.tasks_api().diff(id).await
|
||||
fn get_task_diff(&self, id: TaskId) -> CloudBackendFuture<'_, Option<String>> {
|
||||
Box::pin(async move { self.tasks_api().diff(id).await })
|
||||
}
|
||||
|
||||
async fn get_task_messages(&self, id: TaskId) -> Result<Vec<String>> {
|
||||
self.tasks_api().messages(id).await
|
||||
fn get_task_messages(&self, id: TaskId) -> CloudBackendFuture<'_, Vec<String>> {
|
||||
Box::pin(async move { self.tasks_api().messages(id).await })
|
||||
}
|
||||
|
||||
async fn get_task_text(&self, id: TaskId) -> Result<TaskText> {
|
||||
self.tasks_api().task_text(id).await
|
||||
fn get_task_text(&self, id: TaskId) -> CloudBackendFuture<'_, TaskText> {
|
||||
Box::pin(async move { self.tasks_api().task_text(id).await })
|
||||
}
|
||||
|
||||
async fn list_sibling_attempts(
|
||||
fn list_sibling_attempts(
|
||||
&self,
|
||||
task: TaskId,
|
||||
turn_id: String,
|
||||
) -> Result<Vec<TurnAttempt>> {
|
||||
self.attempts_api().list(task, turn_id).await
|
||||
) -> CloudBackendFuture<'_, Vec<TurnAttempt>> {
|
||||
Box::pin(async move { self.attempts_api().list(task, turn_id).await })
|
||||
}
|
||||
|
||||
async fn apply_task(&self, id: TaskId, diff_override: Option<String>) -> Result<ApplyOutcome> {
|
||||
self.apply_api()
|
||||
.run(id, diff_override, /*preflight*/ false)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn apply_task_preflight(
|
||||
fn apply_task(
|
||||
&self,
|
||||
id: TaskId,
|
||||
diff_override: Option<String>,
|
||||
) -> Result<ApplyOutcome> {
|
||||
self.apply_api()
|
||||
.run(id, diff_override, /*preflight*/ true)
|
||||
.await
|
||||
) -> CloudBackendFuture<'_, ApplyOutcome> {
|
||||
Box::pin(async move {
|
||||
self.apply_api()
|
||||
.run(id, diff_override, /*preflight*/ false)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_task(
|
||||
fn apply_task_preflight(
|
||||
&self,
|
||||
env_id: &str,
|
||||
prompt: &str,
|
||||
git_ref: &str,
|
||||
id: TaskId,
|
||||
diff_override: Option<String>,
|
||||
) -> CloudBackendFuture<'_, ApplyOutcome> {
|
||||
Box::pin(async move {
|
||||
self.apply_api()
|
||||
.run(id, diff_override, /*preflight*/ true)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
fn create_task<'a>(
|
||||
&'a self,
|
||||
env_id: &'a str,
|
||||
prompt: &'a str,
|
||||
git_ref: &'a str,
|
||||
qa_mode: bool,
|
||||
best_of_n: usize,
|
||||
) -> Result<crate::CreatedTask> {
|
||||
self.tasks_api()
|
||||
.create(env_id, prompt, git_ref, qa_mode, best_of_n)
|
||||
.await
|
||||
) -> CloudBackendFuture<'a, crate::CreatedTask> {
|
||||
Box::pin(async move {
|
||||
self.tasks_api()
|
||||
.create(env_id, prompt, git_ref, qa_mode, best_of_n)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ pub use api::ApplyOutcome;
|
||||
pub use api::ApplyStatus;
|
||||
pub use api::AttemptStatus;
|
||||
pub use api::CloudBackend;
|
||||
pub use api::CloudBackendFuture;
|
||||
pub use api::CloudTaskError;
|
||||
pub use api::CreatedTask;
|
||||
pub use api::DiffSummary;
|
||||
|
||||
@@ -15,7 +15,6 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
codex-cloud-tasks-client = { workspace = true }
|
||||
diffy = { workspace = true }
|
||||
|
||||
@@ -3,6 +3,7 @@ use codex_cloud_tasks_client::ApplyOutcome;
|
||||
use codex_cloud_tasks_client::ApplyStatus;
|
||||
use codex_cloud_tasks_client::AttemptStatus;
|
||||
use codex_cloud_tasks_client::CloudBackend;
|
||||
use codex_cloud_tasks_client::CloudBackendFuture;
|
||||
use codex_cloud_tasks_client::CloudTaskError;
|
||||
use codex_cloud_tasks_client::CreatedTask;
|
||||
use codex_cloud_tasks_client::DiffSummary;
|
||||
@@ -17,8 +18,7 @@ use codex_cloud_tasks_client::TurnAttempt;
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MockClient;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CloudBackend for MockClient {
|
||||
impl MockClient {
|
||||
async fn list_tasks(
|
||||
&self,
|
||||
_env: Option<&str>,
|
||||
@@ -160,6 +160,70 @@ impl CloudBackend for MockClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl CloudBackend for MockClient {
|
||||
fn list_tasks<'a>(
|
||||
&'a self,
|
||||
env: Option<&'a str>,
|
||||
limit: Option<i64>,
|
||||
cursor: Option<&'a str>,
|
||||
) -> CloudBackendFuture<'a, TaskListPage> {
|
||||
Box::pin(MockClient::list_tasks(self, env, limit, cursor))
|
||||
}
|
||||
|
||||
fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary> {
|
||||
Box::pin(MockClient::get_task_summary(self, id))
|
||||
}
|
||||
|
||||
fn get_task_diff(&self, id: TaskId) -> CloudBackendFuture<'_, Option<String>> {
|
||||
Box::pin(MockClient::get_task_diff(self, id))
|
||||
}
|
||||
|
||||
fn get_task_messages(&self, id: TaskId) -> CloudBackendFuture<'_, Vec<String>> {
|
||||
Box::pin(MockClient::get_task_messages(self, id))
|
||||
}
|
||||
|
||||
fn get_task_text(&self, id: TaskId) -> CloudBackendFuture<'_, TaskText> {
|
||||
Box::pin(MockClient::get_task_text(self, id))
|
||||
}
|
||||
|
||||
fn apply_task(
|
||||
&self,
|
||||
id: TaskId,
|
||||
diff_override: Option<String>,
|
||||
) -> CloudBackendFuture<'_, ApplyOutcome> {
|
||||
Box::pin(MockClient::apply_task(self, id, diff_override))
|
||||
}
|
||||
|
||||
fn apply_task_preflight(
|
||||
&self,
|
||||
id: TaskId,
|
||||
diff_override: Option<String>,
|
||||
) -> CloudBackendFuture<'_, ApplyOutcome> {
|
||||
Box::pin(MockClient::apply_task_preflight(self, id, diff_override))
|
||||
}
|
||||
|
||||
fn list_sibling_attempts(
|
||||
&self,
|
||||
task: TaskId,
|
||||
turn_id: String,
|
||||
) -> CloudBackendFuture<'_, Vec<TurnAttempt>> {
|
||||
Box::pin(MockClient::list_sibling_attempts(self, task, turn_id))
|
||||
}
|
||||
|
||||
fn create_task<'a>(
|
||||
&'a self,
|
||||
env_id: &'a str,
|
||||
prompt: &'a str,
|
||||
git_ref: &'a str,
|
||||
qa_mode: bool,
|
||||
best_of_n: usize,
|
||||
) -> CloudBackendFuture<'a, CreatedTask> {
|
||||
Box::pin(MockClient::create_task(
|
||||
self, env_id, prompt, git_ref, qa_mode, best_of_n,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_diff_for(id: &TaskId) -> String {
|
||||
match id.0.as_str() {
|
||||
"T-1000" => {
|
||||
|
||||
@@ -39,8 +39,5 @@ tracing = { workspace = true, features = ["log"] }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
unicode-width = { workspace = true }
|
||||
|
||||
[dependencies.async-trait]
|
||||
workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
@@ -354,6 +354,7 @@ pub enum AppEvent {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::Utc;
|
||||
use codex_cloud_tasks_client::CloudBackendFuture;
|
||||
use codex_cloud_tasks_client::CloudTaskError;
|
||||
|
||||
struct FakeBackend {
|
||||
@@ -361,14 +362,13 @@ mod tests {
|
||||
by_env: std::collections::HashMap<Option<String>, Vec<&'static str>>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl codex_cloud_tasks_client::CloudBackend for FakeBackend {
|
||||
impl FakeBackend {
|
||||
async fn list_tasks(
|
||||
&self,
|
||||
env: Option<&str>,
|
||||
limit: Option<i64>,
|
||||
cursor: Option<&str>,
|
||||
) -> codex_cloud_tasks_client::Result<codex_cloud_tasks_client::TaskListPage> {
|
||||
) -> Result<codex_cloud_tasks_client::TaskListPage, CloudTaskError> {
|
||||
let key = env.map(str::to_string);
|
||||
let titles = self
|
||||
.by_env
|
||||
@@ -404,10 +404,7 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_task_summary(
|
||||
&self,
|
||||
id: TaskId,
|
||||
) -> codex_cloud_tasks_client::Result<TaskSummary> {
|
||||
async fn get_task_summary(&self, id: TaskId) -> Result<TaskSummary, CloudTaskError> {
|
||||
self.list_tasks(/*env*/ None, /*limit*/ None, /*cursor*/ None)
|
||||
.await?
|
||||
.tasks
|
||||
@@ -416,25 +413,10 @@ mod tests {
|
||||
.ok_or_else(|| CloudTaskError::Msg(format!("Task {} not found", id.0)))
|
||||
}
|
||||
|
||||
async fn get_task_diff(
|
||||
&self,
|
||||
_id: TaskId,
|
||||
) -> codex_cloud_tasks_client::Result<Option<String>> {
|
||||
Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented(
|
||||
"not used in test",
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_task_messages(
|
||||
&self,
|
||||
_id: TaskId,
|
||||
) -> codex_cloud_tasks_client::Result<Vec<String>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn get_task_text(
|
||||
&self,
|
||||
_id: TaskId,
|
||||
) -> codex_cloud_tasks_client::Result<codex_cloud_tasks_client::TaskText> {
|
||||
) -> Result<codex_cloud_tasks_client::TaskText, CloudTaskError> {
|
||||
Ok(codex_cloud_tasks_client::TaskText {
|
||||
prompt: Some("Example prompt".to_string()),
|
||||
messages: Vec::new(),
|
||||
@@ -444,46 +426,86 @@ mod tests {
|
||||
attempt_status: codex_cloud_tasks_client::AttemptStatus::Completed,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_sibling_attempts(
|
||||
impl codex_cloud_tasks_client::CloudBackend for FakeBackend {
|
||||
fn list_tasks<'a>(
|
||||
&'a self,
|
||||
env: Option<&'a str>,
|
||||
limit: Option<i64>,
|
||||
cursor: Option<&'a str>,
|
||||
) -> CloudBackendFuture<'a, codex_cloud_tasks_client::TaskListPage> {
|
||||
Box::pin(FakeBackend::list_tasks(self, env, limit, cursor))
|
||||
}
|
||||
|
||||
fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary> {
|
||||
Box::pin(FakeBackend::get_task_summary(self, id))
|
||||
}
|
||||
|
||||
fn get_task_diff(&self, _id: TaskId) -> CloudBackendFuture<'_, Option<String>> {
|
||||
Box::pin(async {
|
||||
Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented(
|
||||
"not used in test",
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn get_task_messages(&self, _id: TaskId) -> CloudBackendFuture<'_, Vec<String>> {
|
||||
Box::pin(async { Ok(vec![]) })
|
||||
}
|
||||
|
||||
fn get_task_text(
|
||||
&self,
|
||||
id: TaskId,
|
||||
) -> CloudBackendFuture<'_, codex_cloud_tasks_client::TaskText> {
|
||||
Box::pin(FakeBackend::get_task_text(self, id))
|
||||
}
|
||||
|
||||
fn list_sibling_attempts(
|
||||
&self,
|
||||
_task: TaskId,
|
||||
_turn_id: String,
|
||||
) -> codex_cloud_tasks_client::Result<Vec<codex_cloud_tasks_client::TurnAttempt>> {
|
||||
Ok(Vec::new())
|
||||
) -> CloudBackendFuture<'_, Vec<codex_cloud_tasks_client::TurnAttempt>> {
|
||||
Box::pin(async { Ok(Vec::new()) })
|
||||
}
|
||||
|
||||
async fn apply_task(
|
||||
fn apply_task(
|
||||
&self,
|
||||
_id: TaskId,
|
||||
_diff_override: Option<String>,
|
||||
) -> codex_cloud_tasks_client::Result<codex_cloud_tasks_client::ApplyOutcome> {
|
||||
Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented(
|
||||
"not used in test",
|
||||
))
|
||||
) -> CloudBackendFuture<'_, codex_cloud_tasks_client::ApplyOutcome> {
|
||||
Box::pin(async {
|
||||
Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented(
|
||||
"not used in test",
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
async fn apply_task_preflight(
|
||||
fn apply_task_preflight(
|
||||
&self,
|
||||
_id: TaskId,
|
||||
_diff_override: Option<String>,
|
||||
) -> codex_cloud_tasks_client::Result<codex_cloud_tasks_client::ApplyOutcome> {
|
||||
Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented(
|
||||
"not used in test",
|
||||
))
|
||||
) -> CloudBackendFuture<'_, codex_cloud_tasks_client::ApplyOutcome> {
|
||||
Box::pin(async {
|
||||
Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented(
|
||||
"not used in test",
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_task(
|
||||
&self,
|
||||
_env_id: &str,
|
||||
_prompt: &str,
|
||||
_git_ref: &str,
|
||||
fn create_task<'a>(
|
||||
&'a self,
|
||||
_env_id: &'a str,
|
||||
_prompt: &'a str,
|
||||
_git_ref: &'a str,
|
||||
_qa_mode: bool,
|
||||
_best_of_n: usize,
|
||||
) -> codex_cloud_tasks_client::Result<codex_cloud_tasks_client::CreatedTask> {
|
||||
Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented(
|
||||
"not used in test",
|
||||
))
|
||||
) -> CloudBackendFuture<'a, codex_cloud_tasks_client::CreatedTask> {
|
||||
Box::pin(async {
|
||||
Err(codex_cloud_tasks_client::CloudTaskError::Unimplemented(
|
||||
"not used in test",
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,16 +106,20 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
|
||||
})
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
trait GitInfoProvider {
|
||||
async fn default_branch_name(&self, path: &std::path::Path) -> Option<String>;
|
||||
fn default_branch_name(
|
||||
&self,
|
||||
path: &std::path::Path,
|
||||
) -> impl std::future::Future<Output = Option<String>> + Send;
|
||||
|
||||
async fn current_branch_name(&self, path: &std::path::Path) -> Option<String>;
|
||||
fn current_branch_name(
|
||||
&self,
|
||||
path: &std::path::Path,
|
||||
) -> impl std::future::Future<Output = Option<String>> + Send;
|
||||
}
|
||||
|
||||
struct RealGitInfo;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl GitInfoProvider for RealGitInfo {
|
||||
async fn default_branch_name(&self, path: &std::path::Path) -> Option<String> {
|
||||
default_branch_name(path).await
|
||||
@@ -2156,7 +2160,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::GitInfoProvider for StubGitInfo {
|
||||
async fn default_branch_name(&self, _path: &std::path::Path) -> Option<String> {
|
||||
self.default_branch.clone()
|
||||
|
||||
@@ -6,7 +6,6 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use async_trait::async_trait;
|
||||
use codex_client::Request;
|
||||
use codex_client::TransportError;
|
||||
use http::HeaderMap;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Error returned while applying authentication to an outbound request.
|
||||
@@ -26,7 +27,6 @@ impl From<AuthError> for TransportError {
|
||||
///
|
||||
/// Header-only providers can implement `add_auth_headers`; providers that sign
|
||||
/// complete requests can override `apply_auth`.
|
||||
#[async_trait]
|
||||
pub trait AuthProvider: Send + Sync {
|
||||
/// Adds any auth headers that are available without request body access.
|
||||
///
|
||||
@@ -52,13 +52,18 @@ pub trait AuthProvider: Send + Sync {
|
||||
///
|
||||
/// Callers must always use the returned request as authoritative.
|
||||
/// If this returns [`AuthError`], the request should not be sent.
|
||||
async fn apply_auth(&self, request: Request) -> Result<Request, AuthError> {
|
||||
let mut request = request;
|
||||
self.add_auth_headers(&mut request.headers);
|
||||
Ok(request)
|
||||
fn apply_auth(&self, request: Request) -> AuthProviderFuture<'_> {
|
||||
Box::pin(async move {
|
||||
let mut request = request;
|
||||
self.add_auth_headers(&mut request.headers);
|
||||
Ok(request)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type AuthProviderFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = Result<Request, AuthError>> + Send + 'a>>;
|
||||
|
||||
/// Shared auth handle passed through API clients.
|
||||
pub type SharedAuthProvider = Arc<dyn AuthProvider>;
|
||||
|
||||
|
||||
@@ -77,7 +77,6 @@ struct CompactHistoryResponse {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use async_trait::async_trait;
|
||||
use codex_client::Request;
|
||||
use codex_client::Response;
|
||||
use codex_client::StreamResponse;
|
||||
@@ -86,7 +85,6 @@ mod tests {
|
||||
#[derive(Clone, Default)]
|
||||
struct DummyTransport;
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for DummyTransport {
|
||||
async fn execute(&self, _req: Request) -> Result<Response, TransportError> {
|
||||
Err(TransportError::Build("execute should not run".to_string()))
|
||||
|
||||
@@ -80,7 +80,6 @@ mod tests {
|
||||
use crate::images::ImageQuality;
|
||||
use crate::images::ImageUrl;
|
||||
use crate::provider::RetryConfig;
|
||||
use async_trait::async_trait;
|
||||
use codex_client::Request;
|
||||
use codex_client::RequestBody;
|
||||
use codex_client::Response;
|
||||
@@ -114,7 +113,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for CapturingTransport {
|
||||
async fn execute(&self, req: Request) -> Result<Response, TransportError> {
|
||||
*self.last_request.lock().expect("lock request store") = Some(req);
|
||||
|
||||
@@ -71,7 +71,6 @@ mod tests {
|
||||
use crate::common::RawMemory;
|
||||
use crate::common::RawMemoryMetadata;
|
||||
use crate::provider::RetryConfig;
|
||||
use async_trait::async_trait;
|
||||
use codex_client::Request;
|
||||
use codex_client::RequestBody;
|
||||
use codex_client::Response;
|
||||
@@ -89,7 +88,6 @@ mod tests {
|
||||
#[derive(Clone, Default)]
|
||||
struct DummyTransport;
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for DummyTransport {
|
||||
async fn execute(&self, _req: Request) -> Result<Response, TransportError> {
|
||||
Err(TransportError::Build("execute should not run".to_string()))
|
||||
@@ -122,7 +120,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for CapturingTransport {
|
||||
async fn execute(&self, req: Request) -> Result<Response, TransportError> {
|
||||
*self.last_request.lock().expect("lock request store") = Some(req);
|
||||
|
||||
@@ -78,7 +78,6 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::auth::AuthProvider;
|
||||
use crate::provider::RetryConfig;
|
||||
use async_trait::async_trait;
|
||||
use codex_client::Request;
|
||||
use codex_client::Response;
|
||||
use codex_client::StreamResponse;
|
||||
@@ -108,7 +107,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for CapturingTransport {
|
||||
async fn execute(&self, req: Request) -> Result<Response, TransportError> {
|
||||
*self.last_request.lock().unwrap() = Some(req);
|
||||
|
||||
@@ -226,7 +226,6 @@ mod tests {
|
||||
use crate::endpoint::realtime_websocket::RealtimeOutputModality;
|
||||
use crate::endpoint::realtime_websocket::RealtimeSessionMode;
|
||||
use crate::provider::RetryConfig;
|
||||
use async_trait::async_trait;
|
||||
use codex_client::Request;
|
||||
use codex_client::Response;
|
||||
use codex_client::StreamResponse;
|
||||
@@ -265,7 +264,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for CapturingTransport {
|
||||
async fn execute(&self, req: Request) -> Result<Response, TransportError> {
|
||||
*self.last_request.lock().unwrap() = Some(req);
|
||||
|
||||
@@ -64,7 +64,6 @@ mod tests {
|
||||
use crate::search::SearchInput;
|
||||
use crate::search::SearchQuery;
|
||||
use crate::search::SearchSettings;
|
||||
use async_trait::async_trait;
|
||||
use codex_client::Request;
|
||||
use codex_client::RequestBody;
|
||||
use codex_client::Response;
|
||||
@@ -100,7 +99,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for CapturingTransport {
|
||||
async fn execute(&self, req: Request) -> Result<Response, TransportError> {
|
||||
*self.last_request.lock().expect("lock request store") = Some(req);
|
||||
|
||||
@@ -21,6 +21,7 @@ pub use crate::api_bridge::map_api_error;
|
||||
pub use crate::auth::AuthError;
|
||||
pub use crate::auth::AuthHeaderTelemetry;
|
||||
pub use crate::auth::AuthProvider;
|
||||
pub use crate::auth::AuthProviderFuture;
|
||||
pub use crate::auth::SharedAuthProvider;
|
||||
pub use crate::auth::auth_header_telemetry;
|
||||
pub use crate::common::CompactionInput;
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use codex_api::ApiError;
|
||||
use codex_api::AuthError;
|
||||
@@ -71,7 +70,6 @@ impl RecordingTransport {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for RecordingTransport {
|
||||
async fn execute(&self, _req: Request) -> Result<Response, TransportError> {
|
||||
Err(TransportError::Build("execute should not run".to_string()))
|
||||
@@ -195,11 +193,6 @@ impl FailsOnceAuth {
|
||||
.lock()
|
||||
.unwrap_or_else(|err| panic!("mutex poisoned: {err}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthProvider for FailsOnceAuth {
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
|
||||
async fn apply_auth(&self, request: Request) -> Result<Request, AuthError> {
|
||||
let mut attempts = self
|
||||
@@ -219,7 +212,14 @@ impl AuthProvider for FailsOnceAuth {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthProvider for FailsOnceAuth {
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
|
||||
fn apply_auth(&self, request: Request) -> codex_api::AuthProviderFuture<'_> {
|
||||
Box::pin(FailsOnceAuth::apply_auth(self, request))
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpTransport for FlakyTransport {
|
||||
async fn execute(&self, _req: Request) -> Result<Response, TransportError> {
|
||||
Err(TransportError::Build("execute should not run".to_string()))
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use codex_api::AuthProvider;
|
||||
use codex_api::Compression;
|
||||
@@ -32,7 +31,6 @@ impl FixtureSseTransport {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for FixtureSseTransport {
|
||||
async fn execute(&self, _req: Request) -> Result<Response, TransportError> {
|
||||
Err(TransportError::Build("execute should not run".to_string()))
|
||||
|
||||
@@ -5,7 +5,6 @@ name = "codex-client"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
eventsource-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::error::TransportError;
|
||||
use crate::request::Request;
|
||||
use crate::request::RequestBody;
|
||||
use crate::request::Response;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::BoxStream;
|
||||
@@ -23,10 +22,15 @@ pub struct StreamResponse {
|
||||
pub bytes: ByteStream,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait HttpTransport: Send + Sync {
|
||||
async fn execute(&self, req: Request) -> Result<Response, TransportError>;
|
||||
async fn stream(&self, req: Request) -> Result<StreamResponse, TransportError>;
|
||||
fn execute(
|
||||
&self,
|
||||
req: Request,
|
||||
) -> impl std::future::Future<Output = Result<Response, TransportError>> + Send;
|
||||
fn stream(
|
||||
&self,
|
||||
req: Request,
|
||||
) -> impl std::future::Future<Output = Result<StreamResponse, TransportError>> + Send;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -86,7 +90,6 @@ fn request_body_for_trace(req: &Request) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpTransport for ReqwestTransport {
|
||||
async fn execute(&self, req: Request) -> Result<Response, TransportError> {
|
||||
if enabled!(Level::TRACE) {
|
||||
|
||||
@@ -13,7 +13,6 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
|
||||
@@ -150,6 +150,7 @@ pub use thread_config::ThreadConfigContext;
|
||||
pub use thread_config::ThreadConfigLoadError;
|
||||
pub use thread_config::ThreadConfigLoadErrorCode;
|
||||
pub use thread_config::ThreadConfigLoader;
|
||||
pub use thread_config::ThreadConfigLoaderFuture;
|
||||
pub use thread_config::ThreadConfigSource;
|
||||
pub use thread_config::UserThreadConfig;
|
||||
pub use toml::Value as TomlValue;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use super::*;
|
||||
use async_trait::async_trait;
|
||||
use codex_file_system::CopyOptions;
|
||||
use codex_file_system::CreateDirectoryOptions;
|
||||
use codex_file_system::ExecutorFileSystemFuture;
|
||||
use codex_file_system::FileMetadata;
|
||||
use codex_file_system::FileSystemResult;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_file_system::ReadDirectoryEntry;
|
||||
use codex_file_system::RemoveOptions;
|
||||
@@ -13,78 +12,81 @@ use tempfile::tempdir;
|
||||
|
||||
struct TestFileSystem;
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for TestFileSystem {
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<PathUri> {
|
||||
let path = path.to_abs_path()?;
|
||||
let canonicalized = path.canonicalize()?;
|
||||
PathUri::from_abs_path(&canonicalized)
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri> {
|
||||
Box::pin(async move {
|
||||
let path = path.to_abs_path()?;
|
||||
let canonicalized = path.canonicalize()?;
|
||||
PathUri::from_abs_path(&canonicalized)
|
||||
})
|
||||
}
|
||||
|
||||
async fn read_file(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<Vec<u8>> {
|
||||
let path = path.to_abs_path()?;
|
||||
tokio::fs::read(path.as_path()).await
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>> {
|
||||
Box::pin(async move {
|
||||
let path = path.to_abs_path()?;
|
||||
tokio::fs::read(path.as_path()).await
|
||||
})
|
||||
}
|
||||
|
||||
async fn write_file(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
_path: &'a PathUri,
|
||||
_contents: Vec<u8>,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()> {
|
||||
unimplemented!("test filesystem only supports reads")
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(async move { unimplemented!("test filesystem only supports reads") })
|
||||
}
|
||||
|
||||
async fn create_directory(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
_path: &'a PathUri,
|
||||
_create_directory_options: CreateDirectoryOptions,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()> {
|
||||
unimplemented!("test filesystem only supports reads")
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(async move { unimplemented!("test filesystem only supports reads") })
|
||||
}
|
||||
|
||||
async fn get_metadata(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<FileMetadata> {
|
||||
unimplemented!("test filesystem only supports reads")
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
_path: &'a PathUri,
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata> {
|
||||
Box::pin(async move { unimplemented!("test filesystem only supports reads") })
|
||||
}
|
||||
|
||||
async fn read_directory(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<Vec<ReadDirectoryEntry>> {
|
||||
unimplemented!("test filesystem only supports reads")
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
_path: &'a PathUri,
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {
|
||||
Box::pin(async move { unimplemented!("test filesystem only supports reads") })
|
||||
}
|
||||
|
||||
async fn remove(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
_path: &'a PathUri,
|
||||
_remove_options: RemoveOptions,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()> {
|
||||
unimplemented!("test filesystem only supports reads")
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(async move { unimplemented!("test filesystem only supports reads") })
|
||||
}
|
||||
|
||||
async fn copy(
|
||||
&self,
|
||||
_source_path: &PathUri,
|
||||
_destination_path: &PathUri,
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
_source_path: &'a PathUri,
|
||||
_destination_path: &'a PathUri,
|
||||
_copy_options: CopyOptions,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()> {
|
||||
unimplemented!("test filesystem only supports reads")
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(async move { unimplemented!("test filesystem only supports reads") })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -86,7 +87,6 @@ impl ThreadConfigLoadError {
|
||||
/// return typed payloads without applying precedence or merge rules. Callers
|
||||
/// are responsible for resolving the returned sources into the effective
|
||||
/// runtime config.
|
||||
#[async_trait]
|
||||
pub trait ThreadConfigLoader: Send + Sync {
|
||||
/// Load source-specific typed config.
|
||||
///
|
||||
@@ -94,24 +94,29 @@ pub trait ThreadConfigLoader: Send + Sync {
|
||||
/// their owned sources. Most callers should use [`Self::load_config_layers`]
|
||||
/// so precedence and merging continue through the ordinary config layer
|
||||
/// stack.
|
||||
async fn load(
|
||||
fn load(
|
||||
&self,
|
||||
context: ThreadConfigContext,
|
||||
) -> Result<Vec<ThreadConfigSource>, ThreadConfigLoadError>;
|
||||
) -> ThreadConfigLoaderFuture<'_, Vec<ThreadConfigSource>>;
|
||||
|
||||
async fn load_config_layers(
|
||||
fn load_config_layers(
|
||||
&self,
|
||||
context: ThreadConfigContext,
|
||||
) -> Result<Vec<ConfigLayerEntry>, ThreadConfigLoadError> {
|
||||
let sources = self.load(context).await?;
|
||||
sources
|
||||
.into_iter()
|
||||
.map(thread_config_source_to_layer)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map(|layers| layers.into_iter().flatten().collect())
|
||||
) -> ThreadConfigLoaderFuture<'_, Vec<ConfigLayerEntry>> {
|
||||
Box::pin(async move {
|
||||
let sources = self.load(context).await?;
|
||||
sources
|
||||
.into_iter()
|
||||
.map(thread_config_source_to_layer)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map(|layers| layers.into_iter().flatten().collect())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type ThreadConfigLoaderFuture<'a, T> =
|
||||
Pin<Box<dyn Future<Output = Result<T, ThreadConfigLoadError>> + Send + 'a>>;
|
||||
|
||||
/// Loader backed by a static set of typed thread config sources.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct StaticThreadConfigLoader {
|
||||
@@ -124,13 +129,12 @@ impl StaticThreadConfigLoader {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ThreadConfigLoader for StaticThreadConfigLoader {
|
||||
async fn load(
|
||||
fn load(
|
||||
&self,
|
||||
_context: ThreadConfigContext,
|
||||
) -> Result<Vec<ThreadConfigSource>, ThreadConfigLoadError> {
|
||||
Ok(self.sources.clone())
|
||||
) -> ThreadConfigLoaderFuture<'_, Vec<ThreadConfigSource>> {
|
||||
Box::pin(async { Ok(self.sources.clone()) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,13 +142,12 @@ impl ThreadConfigLoader for StaticThreadConfigLoader {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NoopThreadConfigLoader;
|
||||
|
||||
#[async_trait]
|
||||
impl ThreadConfigLoader for NoopThreadConfigLoader {
|
||||
async fn load(
|
||||
fn load(
|
||||
&self,
|
||||
_context: ThreadConfigContext,
|
||||
) -> Result<Vec<ThreadConfigSource>, ThreadConfigLoadError> {
|
||||
Ok(Vec::new())
|
||||
) -> ThreadConfigLoaderFuture<'_, Vec<ThreadConfigSource>> {
|
||||
Box::pin(async { Ok(Vec::new()) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||
use std::num::NonZeroU64;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
@@ -14,6 +13,7 @@ use super::ThreadConfigContext;
|
||||
use super::ThreadConfigLoadError;
|
||||
use super::ThreadConfigLoadErrorCode;
|
||||
use super::ThreadConfigLoader;
|
||||
use super::ThreadConfigLoaderFuture;
|
||||
use super::ThreadConfigSource;
|
||||
use super::UserThreadConfig;
|
||||
use proto::thread_config_loader_client::ThreadConfigLoaderClient;
|
||||
@@ -49,10 +49,7 @@ impl RemoteThreadConfigLoader {
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ThreadConfigLoader for RemoteThreadConfigLoader {
|
||||
async fn load(
|
||||
&self,
|
||||
context: ThreadConfigContext,
|
||||
@@ -73,6 +70,15 @@ impl ThreadConfigLoader for RemoteThreadConfigLoader {
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadConfigLoader for RemoteThreadConfigLoader {
|
||||
fn load(
|
||||
&self,
|
||||
context: ThreadConfigContext,
|
||||
) -> ThreadConfigLoaderFuture<'_, Vec<ThreadConfigSource>> {
|
||||
Box::pin(RemoteThreadConfigLoader::load(self, context))
|
||||
}
|
||||
}
|
||||
|
||||
fn load_thread_config_request(
|
||||
context: ThreadConfigContext,
|
||||
) -> tonic::Request<proto::LoadThreadConfigRequest> {
|
||||
@@ -321,8 +327,7 @@ mod tests {
|
||||
expected_cwd: String,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl thread_config_loader_server::ThreadConfigLoader for TestServer {
|
||||
impl TestServer {
|
||||
async fn load(
|
||||
&self,
|
||||
request: Request<proto::LoadThreadConfigRequest>,
|
||||
@@ -341,6 +346,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl thread_config_loader_server::ThreadConfigLoader for TestServer {
|
||||
fn load<'a, 'async_trait>(
|
||||
&'a self,
|
||||
request: Request<proto::LoadThreadConfigRequest>,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = Result<Response<proto::LoadThreadConfigResponse>, Status>,
|
||||
> + Send
|
||||
+ 'async_trait,
|
||||
>,
|
||||
>
|
||||
where
|
||||
'a: 'async_trait,
|
||||
Self: 'async_trait,
|
||||
{
|
||||
Box::pin(TestServer::load(self, request))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_thread_config_calls_remote_service() {
|
||||
let cwd = workspace_dir().join("project");
|
||||
|
||||
@@ -19,7 +19,6 @@ workspace = true
|
||||
anyhow = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bm25 = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::*;
|
||||
use crate::config::ConfigBuilder;
|
||||
use async_trait::async_trait;
|
||||
use codex_config::ConfigLayerEntry;
|
||||
use codex_config::ConfigLayerStack;
|
||||
use codex_config::ConfigRequirements;
|
||||
use codex_config::ConfigRequirementsToml;
|
||||
use codex_exec_server::CopyOptions;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_exec_server::ExecutorFileSystemFuture;
|
||||
use codex_exec_server::FileMetadata;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
@@ -39,8 +39,7 @@ struct FailingFileSystem {
|
||||
failure: InjectedFailure,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for FailingFileSystem {
|
||||
impl FailingFileSystem {
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
@@ -121,6 +120,85 @@ impl ExecutorFileSystem for FailingFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutorFileSystem for FailingFileSystem {
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri> {
|
||||
Box::pin(FailingFileSystem::canonicalize(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>> {
|
||||
Box::pin(FailingFileSystem::read_file(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
contents: Vec<u8>,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(FailingFileSystem::write_file(self, path, contents, sandbox))
|
||||
}
|
||||
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: CreateDirectoryOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(FailingFileSystem::create_directory(
|
||||
self, path, options, sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata> {
|
||||
Box::pin(FailingFileSystem::get_metadata(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {
|
||||
Box::pin(FailingFileSystem::read_directory(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: RemoveOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(FailingFileSystem::remove(self, path, options, sandbox))
|
||||
}
|
||||
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
source_path: &'a PathUri,
|
||||
destination_path: &'a PathUri,
|
||||
options: CopyOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(FailingFileSystem::copy(
|
||||
self,
|
||||
source_path,
|
||||
destination_path,
|
||||
options,
|
||||
sandbox,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct TestConfig {
|
||||
config: Config,
|
||||
user_instructions: Option<UserInstructions>,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use async_trait::async_trait;
|
||||
use codex_config::NetworkConstraints;
|
||||
use codex_execpolicy::Policy;
|
||||
use codex_network_proxy::BlockedRequestObserver;
|
||||
use codex_network_proxy::ConfigReloader;
|
||||
use codex_network_proxy::ConfigReloaderFuture;
|
||||
use codex_network_proxy::ConfigState;
|
||||
use codex_network_proxy::NetworkDecision;
|
||||
use codex_network_proxy::NetworkPolicyDecider;
|
||||
@@ -58,14 +58,13 @@ impl StaticNetworkProxyReloader {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ConfigReloader for StaticNetworkProxyReloader {
|
||||
async fn maybe_reload(&self) -> anyhow::Result<Option<ConfigState>> {
|
||||
Ok(None)
|
||||
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {
|
||||
Box::pin(async { Ok(None) })
|
||||
}
|
||||
|
||||
async fn reload_now(&self) -> anyhow::Result<ConfigState> {
|
||||
Ok(self.state.clone())
|
||||
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {
|
||||
Box::pin(async { Ok(self.state.clone()) })
|
||||
}
|
||||
|
||||
fn source_label(&self) -> String {
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::exec_policy::format_exec_policy_error_with_source;
|
||||
use crate::exec_policy::load_exec_policy;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_config::CONFIG_TOML_FILE;
|
||||
use codex_config::ConfigLayerStack;
|
||||
@@ -23,6 +22,7 @@ use codex_config::permissions_toml::PermissionsToml;
|
||||
use codex_config::permissions_toml::overlay_network_domain_permissions;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
use codex_network_proxy::ConfigReloader;
|
||||
use codex_network_proxy::ConfigReloaderFuture;
|
||||
use codex_network_proxy::ConfigState;
|
||||
use codex_network_proxy::NetworkMode;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
@@ -361,13 +361,6 @@ impl MtimeConfigReloader {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ConfigReloader for MtimeConfigReloader {
|
||||
fn source_label(&self) -> String {
|
||||
"config layers".to_string()
|
||||
}
|
||||
|
||||
async fn maybe_reload(&self) -> Result<Option<ConfigState>> {
|
||||
if !self.needs_reload().await {
|
||||
@@ -388,6 +381,20 @@ impl ConfigReloader for MtimeConfigReloader {
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigReloader for MtimeConfigReloader {
|
||||
fn source_label(&self) -> String {
|
||||
"config layers".to_string()
|
||||
}
|
||||
|
||||
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {
|
||||
Box::pin(MtimeConfigReloader::maybe_reload(self))
|
||||
}
|
||||
|
||||
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {
|
||||
Box::pin(MtimeConfigReloader::reload_now(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "network_proxy_loader_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::tools::sandboxing::managed_network_for_sandbox_permissions;
|
||||
use codex_network_proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER;
|
||||
use codex_network_proxy::CUSTOM_CA_ENV_KEYS;
|
||||
use codex_network_proxy::ConfigReloader;
|
||||
use codex_network_proxy::ConfigReloaderFuture;
|
||||
use codex_network_proxy::ConfigState;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
@@ -35,18 +36,17 @@ use tokio::sync::watch;
|
||||
|
||||
struct StaticReloader;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ConfigReloader for StaticReloader {
|
||||
fn source_label(&self) -> String {
|
||||
"test config state".to_string()
|
||||
}
|
||||
|
||||
async fn maybe_reload(&self) -> anyhow::Result<Option<ConfigState>> {
|
||||
Ok(None)
|
||||
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {
|
||||
Box::pin(async { Ok(None) })
|
||||
}
|
||||
|
||||
async fn reload_now(&self) -> anyhow::Result<ConfigState> {
|
||||
Err(anyhow::anyhow!("force reload is not supported in tests"))
|
||||
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {
|
||||
Box::pin(async { Err(anyhow::anyhow!("force reload is not supported in tests")) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,12 +56,14 @@ use codex_shell_escalation::EscalationDecision;
|
||||
use codex_shell_escalation::EscalationExecution;
|
||||
use codex_shell_escalation::EscalationPermissions;
|
||||
use codex_shell_escalation::EscalationPolicy;
|
||||
use codex_shell_escalation::EscalationPolicyFuture;
|
||||
use codex_shell_escalation::EscalationSession;
|
||||
use codex_shell_escalation::ExecParams;
|
||||
use codex_shell_escalation::ExecResult;
|
||||
use codex_shell_escalation::PreparedExec;
|
||||
use codex_shell_escalation::ResolvedPermissionProfile;
|
||||
use codex_shell_escalation::ShellCommandExecutor;
|
||||
use codex_shell_escalation::ShellCommandExecutorFuture;
|
||||
use codex_shell_escalation::Stopwatch;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashMap;
|
||||
@@ -597,8 +599,7 @@ impl CoreShellActionProvider {
|
||||
// execve interception.
|
||||
const ENABLE_INTERCEPTED_EXEC_POLICY_SHELL_WRAPPER_PARSING: bool = false;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EscalationPolicy for CoreShellActionProvider {
|
||||
impl CoreShellActionProvider {
|
||||
async fn determine_action(
|
||||
&self,
|
||||
program: &AbsolutePathBuf,
|
||||
@@ -665,6 +666,19 @@ impl EscalationPolicy for CoreShellActionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl EscalationPolicy for CoreShellActionProvider {
|
||||
fn determine_action<'a>(
|
||||
&'a self,
|
||||
program: &'a AbsolutePathBuf,
|
||||
argv: &'a [String],
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
) -> EscalationPolicyFuture<'a> {
|
||||
Box::pin(CoreShellActionProvider::determine_action(
|
||||
self, program, argv, workdir,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_intercepted_exec_policy(
|
||||
policy: &Policy,
|
||||
program: &AbsolutePathBuf,
|
||||
@@ -787,15 +801,43 @@ struct PrepareSandboxedExecParams<'a> {
|
||||
additional_permissions: Option<AdditionalPermissionProfile>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
async fn run(
|
||||
fn run(
|
||||
&self,
|
||||
_command: Vec<String>,
|
||||
_cwd: PathBuf,
|
||||
env_overlay: HashMap<String, String>,
|
||||
cancel_rx: CancellationToken,
|
||||
after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> ShellCommandExecutorFuture<'_, ExecResult> {
|
||||
Box::pin(CoreShellCommandExecutor::run(
|
||||
self,
|
||||
env_overlay,
|
||||
cancel_rx,
|
||||
after_spawn,
|
||||
))
|
||||
}
|
||||
|
||||
fn prepare_escalated_exec<'a>(
|
||||
&'a self,
|
||||
program: &'a AbsolutePathBuf,
|
||||
argv: &'a [String],
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
env: HashMap<String, String>,
|
||||
execution: EscalationExecution,
|
||||
) -> ShellCommandExecutorFuture<'a, PreparedExec> {
|
||||
Box::pin(CoreShellCommandExecutor::prepare_escalated_exec(
|
||||
self, program, argv, workdir, env, execution,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreShellCommandExecutor {
|
||||
async fn run(
|
||||
&self,
|
||||
env_overlay: HashMap<String, String>,
|
||||
cancel_rx: CancellationToken,
|
||||
after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> anyhow::Result<ExecResult> {
|
||||
let mut exec_env = self.env.clone();
|
||||
// `env_overlay` comes from `EscalationSession::env()`, so merge only the
|
||||
@@ -900,9 +942,7 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
|
||||
|
||||
Ok(prepared)
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreShellCommandExecutor {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn prepare_sandboxed_exec(
|
||||
&self,
|
||||
|
||||
@@ -38,7 +38,6 @@ use codex_shell_escalation::EscalationExecution;
|
||||
use codex_shell_escalation::EscalationPermissions;
|
||||
use codex_shell_escalation::ExecResult;
|
||||
use codex_shell_escalation::ResolvedPermissionProfile;
|
||||
use codex_shell_escalation::ShellCommandExecutor;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -10,10 +10,9 @@ use crate::session::turn_context::TurnContext;
|
||||
use crate::tools::context::ExecCommandToolOutput;
|
||||
use crate::unified_exec::WriteStdinRequest;
|
||||
use crate::unified_exec::process::OutputHandles;
|
||||
use async_trait::async_trait;
|
||||
use codex_exec_server::ExecProcess;
|
||||
use codex_exec_server::ExecProcessEventReceiver;
|
||||
use codex_exec_server::ExecServerError;
|
||||
use codex_exec_server::ExecProcessFuture;
|
||||
use codex_exec_server::ProcessId;
|
||||
use codex_exec_server::ProcessSignal;
|
||||
use codex_exec_server::ReadResponse;
|
||||
@@ -218,7 +217,31 @@ struct BlockingTerminateExecProcess {
|
||||
wake_tx: watch::Sender<u64>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl BlockingTerminateExecProcess {
|
||||
async fn read(&self) -> Result<ReadResponse, codex_exec_server::ExecServerError> {
|
||||
Ok(ReadResponse {
|
||||
chunks: Vec::new(),
|
||||
next_seq: 1,
|
||||
exited: false,
|
||||
exit_code: None,
|
||||
closed: false,
|
||||
failure: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn write(&self) -> Result<WriteResponse, codex_exec_server::ExecServerError> {
|
||||
Ok(WriteResponse {
|
||||
status: WriteStatus::Accepted,
|
||||
})
|
||||
}
|
||||
|
||||
async fn terminate(&self) -> Result<(), codex_exec_server::ExecServerError> {
|
||||
let _ = self.terminate_started.send(true);
|
||||
self.allow_terminate.notified().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecProcess for BlockingTerminateExecProcess {
|
||||
fn process_id(&self) -> &ProcessId {
|
||||
&self.process_id
|
||||
@@ -232,36 +255,25 @@ impl ExecProcess for BlockingTerminateExecProcess {
|
||||
ExecProcessEventReceiver::empty()
|
||||
}
|
||||
|
||||
async fn read(
|
||||
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: 1,
|
||||
exited: false,
|
||||
exit_code: None,
|
||||
closed: false,
|
||||
failure: None,
|
||||
})
|
||||
) -> ExecProcessFuture<'_, ReadResponse> {
|
||||
Box::pin(BlockingTerminateExecProcess::read(self))
|
||||
}
|
||||
|
||||
async fn write(&self, _chunk: Vec<u8>) -> Result<WriteResponse, ExecServerError> {
|
||||
Ok(WriteResponse {
|
||||
status: WriteStatus::Accepted,
|
||||
})
|
||||
fn write(&self, _chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse> {
|
||||
Box::pin(BlockingTerminateExecProcess::write(self))
|
||||
}
|
||||
|
||||
async fn signal(&self, _signal: ProcessSignal) -> Result<(), ExecServerError> {
|
||||
Ok(())
|
||||
fn signal(&self, _signal: ProcessSignal) -> ExecProcessFuture<'_, ()> {
|
||||
Box::pin(async { Ok(()) })
|
||||
}
|
||||
|
||||
async fn terminate(&self) -> Result<(), ExecServerError> {
|
||||
let _ = self.terminate_started.send(true);
|
||||
self.allow_terminate.notified().await;
|
||||
Ok(())
|
||||
fn terminate(&self) -> ExecProcessFuture<'_, ()> {
|
||||
Box::pin(BlockingTerminateExecProcess::terminate(self))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::process::UnifiedExecProcess;
|
||||
use crate::unified_exec::UnifiedExecError;
|
||||
use async_trait::async_trait;
|
||||
use codex_exec_server::ExecProcess;
|
||||
use codex_exec_server::ExecProcessEventReceiver;
|
||||
use codex_exec_server::ExecProcessFuture;
|
||||
use codex_exec_server::ExecServerError;
|
||||
use codex_exec_server::ProcessId;
|
||||
use codex_exec_server::ProcessSignal;
|
||||
@@ -26,26 +26,8 @@ struct MockExecProcess {
|
||||
wake_tx: watch::Sender<u64>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecProcess for MockExecProcess {
|
||||
fn process_id(&self) -> &ProcessId {
|
||||
&self.process_id
|
||||
}
|
||||
|
||||
fn subscribe_wake(&self) -> watch::Receiver<u64> {
|
||||
self.wake_tx.subscribe()
|
||||
}
|
||||
|
||||
fn subscribe_events(&self) -> ExecProcessEventReceiver {
|
||||
ExecProcessEventReceiver::empty()
|
||||
}
|
||||
|
||||
async fn read(
|
||||
&self,
|
||||
_after_seq: Option<u64>,
|
||||
_max_bytes: Option<usize>,
|
||||
_wait_ms: Option<u64>,
|
||||
) -> Result<ReadResponse, ExecServerError> {
|
||||
impl MockExecProcess {
|
||||
async fn read(&self) -> Result<ReadResponse, ExecServerError> {
|
||||
Ok(self
|
||||
.read_responses
|
||||
.lock()
|
||||
@@ -61,14 +43,6 @@ impl ExecProcess for MockExecProcess {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn write(&self, _chunk: Vec<u8>) -> Result<WriteResponse, ExecServerError> {
|
||||
Ok(self.write_response.clone())
|
||||
}
|
||||
|
||||
async fn signal(&self, _signal: ProcessSignal) -> Result<(), ExecServerError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn terminate(&self) -> Result<(), ExecServerError> {
|
||||
if let Some(message) = &self.terminate_error {
|
||||
return Err(ExecServerError::Protocol(message.clone()));
|
||||
@@ -77,6 +51,41 @@ impl ExecProcess for MockExecProcess {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecProcess for MockExecProcess {
|
||||
fn process_id(&self) -> &ProcessId {
|
||||
&self.process_id
|
||||
}
|
||||
|
||||
fn subscribe_wake(&self) -> watch::Receiver<u64> {
|
||||
self.wake_tx.subscribe()
|
||||
}
|
||||
|
||||
fn subscribe_events(&self) -> ExecProcessEventReceiver {
|
||||
ExecProcessEventReceiver::empty()
|
||||
}
|
||||
|
||||
fn read(
|
||||
&self,
|
||||
_after_seq: Option<u64>,
|
||||
_max_bytes: Option<usize>,
|
||||
_wait_ms: Option<u64>,
|
||||
) -> ExecProcessFuture<'_, ReadResponse> {
|
||||
Box::pin(MockExecProcess::read(self))
|
||||
}
|
||||
|
||||
fn write(&self, _chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse> {
|
||||
Box::pin(async { Ok(self.write_response.clone()) })
|
||||
}
|
||||
|
||||
fn signal(&self, _signal: ProcessSignal) -> ExecProcessFuture<'_, ()> {
|
||||
Box::pin(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn terminate(&self) -> ExecProcessFuture<'_, ()> {
|
||||
Box::pin(MockExecProcess::terminate(self))
|
||||
}
|
||||
}
|
||||
|
||||
async fn remote_process(
|
||||
write_status: WriteStatus,
|
||||
terminate_error: Option<String>,
|
||||
|
||||
@@ -221,6 +221,16 @@ deny = [
|
||||
# Wrapper crates can optionally be specified to allow the crate when it
|
||||
# is a direct dependency of the otherwise banned crate
|
||||
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
|
||||
# Removing async-trait materially improves debug build times:
|
||||
# https://github.com/openai/codex/pull/27304
|
||||
{ crate = "async-trait", wrappers = [
|
||||
"hickory-proto",
|
||||
"opentelemetry-http",
|
||||
"pagable",
|
||||
"rmcp",
|
||||
"tonic",
|
||||
"zbus",
|
||||
], reason = "first-party traits must use native async trait methods with explicit Send bounds" },
|
||||
]
|
||||
|
||||
# List of features to allow/deny
|
||||
|
||||
@@ -12,7 +12,6 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
arc-swap = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
axum = { workspace = true, features = ["http1", "tokio", "ws"] }
|
||||
base64 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use async_trait::async_trait;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::Environment;
|
||||
use crate::ExecServerError;
|
||||
@@ -13,12 +14,14 @@ use crate::environment::REMOTE_ENVIRONMENT_ID;
|
||||
/// selection. Providers should only return provider-owned remote environments;
|
||||
/// `include_local` controls whether `EnvironmentManager` should add the local
|
||||
/// environment to the snapshot.
|
||||
#[async_trait]
|
||||
pub trait EnvironmentProvider: Send + Sync {
|
||||
/// Returns the provider-owned environment startup snapshot.
|
||||
async fn snapshot(&self) -> Result<EnvironmentProviderSnapshot, ExecServerError>;
|
||||
fn snapshot(&self) -> EnvironmentProviderFuture<'_>;
|
||||
}
|
||||
|
||||
pub type EnvironmentProviderFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = Result<EnvironmentProviderSnapshot, ExecServerError>> + Send + 'a>>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EnvironmentProviderSnapshot {
|
||||
pub environments: Vec<(String, Environment)>,
|
||||
@@ -80,10 +83,9 @@ impl DefaultEnvironmentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EnvironmentProvider for DefaultEnvironmentProvider {
|
||||
async fn snapshot(&self) -> Result<EnvironmentProviderSnapshot, ExecServerError> {
|
||||
Ok(self.snapshot_inner())
|
||||
fn snapshot(&self) -> EnvironmentProviderFuture<'_> {
|
||||
Box::pin(async { Ok(self.snapshot_inner()) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::Deserialize;
|
||||
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
|
||||
|
||||
use crate::DefaultEnvironmentProvider;
|
||||
use crate::Environment;
|
||||
use crate::EnvironmentProvider;
|
||||
use crate::EnvironmentProviderFuture;
|
||||
use crate::ExecServerError;
|
||||
use crate::client_api::DEFAULT_REMOTE_EXEC_SERVER_CONNECT_TIMEOUT;
|
||||
use crate::client_api::DEFAULT_REMOTE_EXEC_SERVER_INITIALIZE_TIMEOUT;
|
||||
@@ -92,10 +92,7 @@ impl TomlEnvironmentProvider {
|
||||
environments: parsed_environments,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EnvironmentProvider for TomlEnvironmentProvider {
|
||||
async fn snapshot(&self) -> Result<EnvironmentProviderSnapshot, ExecServerError> {
|
||||
let mut environments = Vec::with_capacity(self.environments.len());
|
||||
for (id, transport_params) in &self.environments {
|
||||
@@ -116,6 +113,12 @@ impl EnvironmentProvider for TomlEnvironmentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl EnvironmentProvider for TomlEnvironmentProvider {
|
||||
fn snapshot(&self) -> EnvironmentProviderFuture<'_> {
|
||||
Box::pin(TomlEnvironmentProvider::snapshot(self))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_environment_toml(
|
||||
item: EnvironmentToml,
|
||||
config_dir: Option<&Path>,
|
||||
|
||||
@@ -33,6 +33,7 @@ pub use client_api::RemoteExecServerConnectArgs;
|
||||
pub use codex_file_system::CopyOptions;
|
||||
pub use codex_file_system::CreateDirectoryOptions;
|
||||
pub use codex_file_system::ExecutorFileSystem;
|
||||
pub use codex_file_system::ExecutorFileSystemFuture;
|
||||
pub use codex_file_system::FileMetadata;
|
||||
pub use codex_file_system::FileSystemResult;
|
||||
pub use codex_file_system::FileSystemSandboxContext;
|
||||
@@ -45,14 +46,17 @@ pub use environment::LOCAL_ENVIRONMENT_ID;
|
||||
pub use environment::REMOTE_ENVIRONMENT_ID;
|
||||
pub use environment_provider::DefaultEnvironmentProvider;
|
||||
pub use environment_provider::EnvironmentProvider;
|
||||
pub use environment_provider::EnvironmentProviderFuture;
|
||||
pub use fs_helper::CODEX_FS_HELPER_ARG1;
|
||||
pub use fs_helper_main::main as run_fs_helper_main;
|
||||
pub use local_file_system::LOCAL_FS;
|
||||
pub use local_file_system::LocalFileSystem;
|
||||
pub use process::ExecBackend;
|
||||
pub use process::ExecBackendFuture;
|
||||
pub use process::ExecProcess;
|
||||
pub use process::ExecProcessEvent;
|
||||
pub use process::ExecProcessEventReceiver;
|
||||
pub use process::ExecProcessFuture;
|
||||
pub use process::StartedExecProcess;
|
||||
pub use process_id::ProcessId;
|
||||
pub use protocol::EnvironmentInfo;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_trait::async_trait;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use std::path::Path;
|
||||
@@ -13,6 +12,7 @@ use crate::CopyOptions;
|
||||
use crate::CreateDirectoryOptions;
|
||||
use crate::ExecServerRuntimePaths;
|
||||
use crate::ExecutorFileSystem;
|
||||
use crate::ExecutorFileSystemFuture;
|
||||
use crate::FileMetadata;
|
||||
use crate::FileSystemResult;
|
||||
use crate::FileSystemSandboxContext;
|
||||
@@ -78,8 +78,7 @@ impl LocalFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for LocalFileSystem {
|
||||
impl LocalFileSystem {
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
@@ -160,8 +159,86 @@ impl ExecutorFileSystem for LocalFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for UnsandboxedFileSystem {
|
||||
impl ExecutorFileSystem for LocalFileSystem {
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri> {
|
||||
Box::pin(LocalFileSystem::canonicalize(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>> {
|
||||
Box::pin(LocalFileSystem::read_file(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
contents: Vec<u8>,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(LocalFileSystem::write_file(self, path, contents, sandbox))
|
||||
}
|
||||
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: CreateDirectoryOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(LocalFileSystem::create_directory(
|
||||
self, path, options, sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata> {
|
||||
Box::pin(LocalFileSystem::get_metadata(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {
|
||||
Box::pin(LocalFileSystem::read_directory(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: RemoveOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(LocalFileSystem::remove(self, path, options, sandbox))
|
||||
}
|
||||
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
source_path: &'a PathUri,
|
||||
destination_path: &'a PathUri,
|
||||
options: CopyOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(LocalFileSystem::copy(
|
||||
self,
|
||||
source_path,
|
||||
destination_path,
|
||||
options,
|
||||
sandbox,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl UnsandboxedFileSystem {
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
@@ -255,8 +332,88 @@ impl ExecutorFileSystem for UnsandboxedFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for DirectFileSystem {
|
||||
impl ExecutorFileSystem for UnsandboxedFileSystem {
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri> {
|
||||
Box::pin(UnsandboxedFileSystem::canonicalize(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>> {
|
||||
Box::pin(UnsandboxedFileSystem::read_file(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
contents: Vec<u8>,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(UnsandboxedFileSystem::write_file(
|
||||
self, path, contents, sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: CreateDirectoryOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(UnsandboxedFileSystem::create_directory(
|
||||
self, path, options, sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata> {
|
||||
Box::pin(UnsandboxedFileSystem::get_metadata(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {
|
||||
Box::pin(UnsandboxedFileSystem::read_directory(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: RemoveOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(UnsandboxedFileSystem::remove(self, path, options, sandbox))
|
||||
}
|
||||
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
source_path: &'a PathUri,
|
||||
destination_path: &'a PathUri,
|
||||
options: CopyOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(UnsandboxedFileSystem::copy(
|
||||
self,
|
||||
source_path,
|
||||
destination_path,
|
||||
options,
|
||||
sandbox,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectFileSystem {
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
@@ -434,6 +591,85 @@ impl ExecutorFileSystem for DirectFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutorFileSystem for DirectFileSystem {
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri> {
|
||||
Box::pin(DirectFileSystem::canonicalize(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>> {
|
||||
Box::pin(DirectFileSystem::read_file(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
contents: Vec<u8>,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(DirectFileSystem::write_file(self, path, contents, sandbox))
|
||||
}
|
||||
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: CreateDirectoryOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(DirectFileSystem::create_directory(
|
||||
self, path, options, sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata> {
|
||||
Box::pin(DirectFileSystem::get_metadata(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {
|
||||
Box::pin(DirectFileSystem::read_directory(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: RemoveOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(DirectFileSystem::remove(self, path, options, sandbox))
|
||||
}
|
||||
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
source_path: &'a PathUri,
|
||||
destination_path: &'a PathUri,
|
||||
options: CopyOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(DirectFileSystem::copy(
|
||||
self,
|
||||
source_path,
|
||||
destination_path,
|
||||
options,
|
||||
sandbox,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn reject_sandbox_context(sandbox: Option<&FileSystemSandboxContext>) -> io::Result<()> {
|
||||
if sandbox.is_some() {
|
||||
return Err(io::Error::new(
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::collections::hash_map::Entry;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_protocol::config_types::EnvironmentVariablePattern;
|
||||
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||
@@ -18,9 +17,11 @@ use tokio::sync::mpsc;
|
||||
use tokio::sync::watch;
|
||||
|
||||
use crate::ExecBackend;
|
||||
use crate::ExecBackendFuture;
|
||||
use crate::ExecProcess;
|
||||
use crate::ExecProcessEvent;
|
||||
use crate::ExecProcessEventReceiver;
|
||||
use crate::ExecProcessFuture;
|
||||
use crate::ExecServerError;
|
||||
use crate::ProcessId;
|
||||
use crate::StartedExecProcess;
|
||||
@@ -460,8 +461,7 @@ fn shell_environment_policy(env_policy: &ExecEnvPolicy) -> ShellEnvironmentPolic
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecBackend for LocalProcess {
|
||||
impl LocalProcess {
|
||||
async fn start(&self, params: ExecParams) -> Result<StartedExecProcess, ExecServerError> {
|
||||
let (response, wake_tx, events) = self
|
||||
.start_process(params)
|
||||
@@ -478,20 +478,13 @@ impl ExecBackend for LocalProcess {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecProcess for LocalExecProcess {
|
||||
fn process_id(&self) -> &ProcessId {
|
||||
&self.process_id
|
||||
}
|
||||
|
||||
fn subscribe_wake(&self) -> watch::Receiver<u64> {
|
||||
self.wake_tx.subscribe()
|
||||
}
|
||||
|
||||
fn subscribe_events(&self) -> ExecProcessEventReceiver {
|
||||
self.events.subscribe()
|
||||
impl ExecBackend for LocalProcess {
|
||||
fn start(&self, params: ExecParams) -> ExecBackendFuture<'_> {
|
||||
Box::pin(LocalProcess::start(self, params))
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalExecProcess {
|
||||
async fn read(
|
||||
&self,
|
||||
after_seq: Option<u64>,
|
||||
@@ -516,6 +509,41 @@ impl ExecProcess for LocalExecProcess {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecProcess for LocalExecProcess {
|
||||
fn process_id(&self) -> &ProcessId {
|
||||
&self.process_id
|
||||
}
|
||||
|
||||
fn subscribe_wake(&self) -> watch::Receiver<u64> {
|
||||
self.wake_tx.subscribe()
|
||||
}
|
||||
|
||||
fn subscribe_events(&self) -> ExecProcessEventReceiver {
|
||||
self.events.subscribe()
|
||||
}
|
||||
|
||||
fn read(
|
||||
&self,
|
||||
after_seq: Option<u64>,
|
||||
max_bytes: Option<usize>,
|
||||
wait_ms: Option<u64>,
|
||||
) -> ExecProcessFuture<'_, ReadResponse> {
|
||||
Box::pin(LocalExecProcess::read(self, after_seq, max_bytes, wait_ms))
|
||||
}
|
||||
|
||||
fn write(&self, chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse> {
|
||||
Box::pin(LocalExecProcess::write(self, chunk))
|
||||
}
|
||||
|
||||
fn signal(&self, signal: ProcessSignal) -> ExecProcessFuture<'_, ()> {
|
||||
Box::pin(LocalExecProcess::signal(self, signal))
|
||||
}
|
||||
|
||||
fn terminate(&self) -> ExecProcessFuture<'_, ()> {
|
||||
Box::pin(LocalExecProcess::terminate(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalProcess {
|
||||
async fn read(
|
||||
&self,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::watch;
|
||||
|
||||
@@ -162,7 +163,6 @@ impl ExecProcessEventReceiver {
|
||||
/// `read` is the request/response API for callers that want to page through
|
||||
/// buffered output, while `subscribe_events` is the streaming API for callers
|
||||
/// that want output and lifecycle changes delivered as they happen.
|
||||
#[async_trait]
|
||||
pub trait ExecProcess: Send + Sync {
|
||||
fn process_id(&self) -> &ProcessId;
|
||||
|
||||
@@ -170,25 +170,30 @@ pub trait ExecProcess: Send + Sync {
|
||||
|
||||
fn subscribe_events(&self) -> ExecProcessEventReceiver;
|
||||
|
||||
async fn read(
|
||||
fn read(
|
||||
&self,
|
||||
after_seq: Option<u64>,
|
||||
max_bytes: Option<usize>,
|
||||
wait_ms: Option<u64>,
|
||||
) -> Result<ReadResponse, ExecServerError>;
|
||||
) -> ExecProcessFuture<'_, ReadResponse>;
|
||||
|
||||
async fn write(&self, chunk: Vec<u8>) -> Result<WriteResponse, ExecServerError>;
|
||||
fn write(&self, chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse>;
|
||||
|
||||
async fn signal(&self, signal: ProcessSignal) -> Result<(), ExecServerError>;
|
||||
fn signal(&self, signal: ProcessSignal) -> ExecProcessFuture<'_, ()>;
|
||||
|
||||
async fn terminate(&self) -> Result<(), ExecServerError>;
|
||||
fn terminate(&self) -> ExecProcessFuture<'_, ()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub type ExecProcessFuture<'a, T> =
|
||||
Pin<Box<dyn Future<Output = Result<T, ExecServerError>> + Send + 'a>>;
|
||||
|
||||
pub trait ExecBackend: Send + Sync {
|
||||
async fn start(&self, params: ExecParams) -> Result<StartedExecProcess, ExecServerError>;
|
||||
fn start(&self, params: ExecParams) -> ExecBackendFuture<'_>;
|
||||
}
|
||||
|
||||
pub type ExecBackendFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = Result<StartedExecProcess, ExecServerError>> + Send + 'a>>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_trait::async_trait;
|
||||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
@@ -9,6 +8,7 @@ use crate::CopyOptions;
|
||||
use crate::CreateDirectoryOptions;
|
||||
use crate::ExecServerError;
|
||||
use crate::ExecutorFileSystem;
|
||||
use crate::ExecutorFileSystemFuture;
|
||||
use crate::FileMetadata;
|
||||
use crate::FileSystemResult;
|
||||
use crate::FileSystemSandboxContext;
|
||||
@@ -36,10 +36,7 @@ impl RemoteFileSystem {
|
||||
trace!("remote fs new");
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for RemoteFileSystem {
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
@@ -207,6 +204,85 @@ impl ExecutorFileSystem for RemoteFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutorFileSystem for RemoteFileSystem {
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri> {
|
||||
Box::pin(RemoteFileSystem::canonicalize(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>> {
|
||||
Box::pin(RemoteFileSystem::read_file(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
contents: Vec<u8>,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(RemoteFileSystem::write_file(self, path, contents, sandbox))
|
||||
}
|
||||
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: CreateDirectoryOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(RemoteFileSystem::create_directory(
|
||||
self, path, options, sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata> {
|
||||
Box::pin(RemoteFileSystem::get_metadata(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {
|
||||
Box::pin(RemoteFileSystem::read_directory(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: RemoveOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(RemoteFileSystem::remove(self, path, options, sandbox))
|
||||
}
|
||||
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
source_path: &'a PathUri,
|
||||
destination_path: &'a PathUri,
|
||||
options: CopyOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(RemoteFileSystem::copy(
|
||||
self,
|
||||
source_path,
|
||||
destination_path,
|
||||
options,
|
||||
sandbox,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_sandbox_context(
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> Option<FileSystemSandboxContext> {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::watch;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::ExecBackend;
|
||||
use crate::ExecBackendFuture;
|
||||
use crate::ExecProcess;
|
||||
use crate::ExecProcessEventReceiver;
|
||||
use crate::ExecServerError;
|
||||
use crate::ExecProcessFuture;
|
||||
use crate::StartedExecProcess;
|
||||
use crate::client::LazyRemoteExecServerClient;
|
||||
use crate::client::Session;
|
||||
@@ -30,11 +30,11 @@ impl RemoteProcess {
|
||||
trace!("remote process new");
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecBackend for RemoteProcess {
|
||||
async fn start(&self, params: ExecParams) -> Result<StartedExecProcess, ExecServerError> {
|
||||
async fn start(
|
||||
&self,
|
||||
params: ExecParams,
|
||||
) -> Result<StartedExecProcess, crate::ExecServerError> {
|
||||
let process_id = params.process_id.clone();
|
||||
let client = self.client.get().await?;
|
||||
let session = client.register_session(&process_id).await?;
|
||||
@@ -49,7 +49,38 @@ impl ExecBackend for RemoteProcess {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecBackend for RemoteProcess {
|
||||
fn start(&self, params: ExecParams) -> ExecBackendFuture<'_> {
|
||||
Box::pin(RemoteProcess::start(self, params))
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteExecProcess {
|
||||
async fn read(
|
||||
&self,
|
||||
after_seq: Option<u64>,
|
||||
max_bytes: Option<usize>,
|
||||
wait_ms: Option<u64>,
|
||||
) -> Result<ReadResponse, crate::ExecServerError> {
|
||||
self.session.read(after_seq, max_bytes, wait_ms).await
|
||||
}
|
||||
|
||||
async fn write(&self, chunk: Vec<u8>) -> Result<WriteResponse, crate::ExecServerError> {
|
||||
trace!("exec process write");
|
||||
self.session.write(chunk).await
|
||||
}
|
||||
|
||||
async fn signal(&self, signal: ProcessSignal) -> Result<(), crate::ExecServerError> {
|
||||
trace!("exec process signal");
|
||||
self.session.signal(signal).await
|
||||
}
|
||||
|
||||
async fn terminate(&self) -> Result<(), crate::ExecServerError> {
|
||||
trace!("exec process terminate");
|
||||
self.session.terminate().await
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecProcess for RemoteExecProcess {
|
||||
fn process_id(&self) -> &crate::ProcessId {
|
||||
self.session.process_id()
|
||||
@@ -63,28 +94,25 @@ impl ExecProcess for RemoteExecProcess {
|
||||
self.session.subscribe_events()
|
||||
}
|
||||
|
||||
async fn read(
|
||||
fn read(
|
||||
&self,
|
||||
after_seq: Option<u64>,
|
||||
max_bytes: Option<usize>,
|
||||
wait_ms: Option<u64>,
|
||||
) -> Result<ReadResponse, ExecServerError> {
|
||||
self.session.read(after_seq, max_bytes, wait_ms).await
|
||||
) -> ExecProcessFuture<'_, ReadResponse> {
|
||||
Box::pin(RemoteExecProcess::read(self, after_seq, max_bytes, wait_ms))
|
||||
}
|
||||
|
||||
async fn write(&self, chunk: Vec<u8>) -> Result<WriteResponse, ExecServerError> {
|
||||
trace!("exec process write");
|
||||
self.session.write(chunk).await
|
||||
fn write(&self, chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse> {
|
||||
Box::pin(RemoteExecProcess::write(self, chunk))
|
||||
}
|
||||
|
||||
async fn signal(&self, signal: ProcessSignal) -> Result<(), ExecServerError> {
|
||||
trace!("exec process signal");
|
||||
self.session.signal(signal).await
|
||||
fn signal(&self, signal: ProcessSignal) -> ExecProcessFuture<'_, ()> {
|
||||
Box::pin(RemoteExecProcess::signal(self, signal))
|
||||
}
|
||||
|
||||
async fn terminate(&self) -> Result<(), ExecServerError> {
|
||||
trace!("exec process terminate");
|
||||
self.session.terminate().await
|
||||
fn terminate(&self) -> ExecProcessFuture<'_, ()> {
|
||||
Box::pin(RemoteExecProcess::terminate(self))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_trait::async_trait;
|
||||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
@@ -9,6 +8,7 @@ use crate::CopyOptions;
|
||||
use crate::CreateDirectoryOptions;
|
||||
use crate::ExecServerRuntimePaths;
|
||||
use crate::ExecutorFileSystem;
|
||||
use crate::ExecutorFileSystemFuture;
|
||||
use crate::FileMetadata;
|
||||
use crate::FileSystemResult;
|
||||
use crate::FileSystemSandboxContext;
|
||||
@@ -50,8 +50,7 @@ impl SandboxedFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for SandboxedFileSystem {
|
||||
impl SandboxedFileSystem {
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
@@ -248,6 +247,92 @@ impl ExecutorFileSystem for SandboxedFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutorFileSystem for SandboxedFileSystem {
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri> {
|
||||
Box::pin(SandboxedFileSystem::canonicalize(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>> {
|
||||
Box::pin(SandboxedFileSystem::read_file(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
contents: Vec<u8>,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(SandboxedFileSystem::write_file(
|
||||
self, path, contents, sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
options: CreateDirectoryOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(SandboxedFileSystem::create_directory(
|
||||
self, path, options, sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata> {
|
||||
Box::pin(SandboxedFileSystem::get_metadata(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {
|
||||
Box::pin(SandboxedFileSystem::read_directory(self, path, sandbox))
|
||||
}
|
||||
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
remove_options: RemoveOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(SandboxedFileSystem::remove(
|
||||
self,
|
||||
path,
|
||||
remove_options,
|
||||
sandbox,
|
||||
))
|
||||
}
|
||||
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
source_path: &'a PathUri,
|
||||
destination_path: &'a PathUri,
|
||||
options: CopyOptions,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(SandboxedFileSystem::copy(
|
||||
self,
|
||||
source_path,
|
||||
destination_path,
|
||||
options,
|
||||
sandbox,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_native_path(path: &PathUri) -> FileSystemResult<()> {
|
||||
path.to_abs_path().map(drop)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,5 @@ tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
async-trait = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codex_core_skills::HostLoadedSkills;
|
||||
use codex_core_skills::loader::SkillRoot;
|
||||
use codex_core_skills::loader::load_skills_from_roots;
|
||||
@@ -11,8 +10,8 @@ use codex_exec_server::CopyOptions;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_exec_server::ExecutorFileSystemFuture;
|
||||
use codex_exec_server::FileMetadata;
|
||||
use codex_exec_server::FileSystemResult;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::ReadDirectoryEntry;
|
||||
use codex_exec_server::RemoveOptions;
|
||||
@@ -36,6 +35,42 @@ struct SyntheticFileSystem {
|
||||
}
|
||||
|
||||
impl SyntheticFileSystem {
|
||||
async fn canonicalize(&self, path: &PathUri) -> io::Result<PathUri> {
|
||||
let path = path.to_abs_path()?;
|
||||
if path == self.alias_root {
|
||||
return PathUri::from_abs_path(&self.canonical_root);
|
||||
}
|
||||
self.metadata(&path)?;
|
||||
PathUri::from_abs_path(&path)
|
||||
}
|
||||
|
||||
async fn read_file(&self, path: &PathUri) -> io::Result<Vec<u8>> {
|
||||
if path.to_abs_path()? == self.canonical_root.join("skill/SKILL.md") {
|
||||
Ok(SKILL_CONTENTS.as_bytes().to_vec())
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_directory(&self, path: &PathUri) -> io::Result<Vec<ReadDirectoryEntry>> {
|
||||
let path = path.to_abs_path()?;
|
||||
if path == self.canonical_root {
|
||||
Ok(vec![ReadDirectoryEntry {
|
||||
file_name: "skill".to_string(),
|
||||
is_directory: true,
|
||||
is_file: false,
|
||||
}])
|
||||
} else if path == self.canonical_root.join("skill") {
|
||||
Ok(vec![ReadDirectoryEntry {
|
||||
file_name: "SKILL.md".to_string(),
|
||||
is_directory: false,
|
||||
is_file: true,
|
||||
}])
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata(&self, path: &AbsolutePathBuf) -> io::Result<FileMetadata> {
|
||||
let skill_dir = self.canonical_root.join("skill");
|
||||
let skill_path = skill_dir.join("SKILL.md");
|
||||
@@ -56,99 +91,74 @@ impl SyntheticFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecutorFileSystem for SyntheticFileSystem {
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<PathUri> {
|
||||
let path = path.to_abs_path()?;
|
||||
if path == self.alias_root {
|
||||
return PathUri::from_abs_path(&self.canonical_root);
|
||||
}
|
||||
self.metadata(&path)?;
|
||||
PathUri::from_abs_path(&path)
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri> {
|
||||
Box::pin(SyntheticFileSystem::canonicalize(self, path))
|
||||
}
|
||||
|
||||
async fn read_file(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<Vec<u8>> {
|
||||
if path.to_abs_path()? == self.canonical_root.join("skill/SKILL.md") {
|
||||
Ok(SKILL_CONTENTS.as_bytes().to_vec())
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||
}
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>> {
|
||||
Box::pin(SyntheticFileSystem::read_file(self, path))
|
||||
}
|
||||
|
||||
async fn write_file(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
_path: &'a PathUri,
|
||||
_contents: Vec<u8>,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "read only"))
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(async move { Err(io::Error::new(io::ErrorKind::Unsupported, "read only")) })
|
||||
}
|
||||
|
||||
async fn create_directory(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
_path: &'a PathUri,
|
||||
_options: CreateDirectoryOptions,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "read only"))
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(async move { Err(io::Error::new(io::ErrorKind::Unsupported, "read only")) })
|
||||
}
|
||||
|
||||
async fn get_metadata(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<FileMetadata> {
|
||||
self.metadata(&path.to_abs_path()?)
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata> {
|
||||
Box::pin(async move { self.metadata(&path.to_abs_path()?) })
|
||||
}
|
||||
|
||||
async fn read_directory(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<Vec<ReadDirectoryEntry>> {
|
||||
let path = path.to_abs_path()?;
|
||||
if path == self.canonical_root {
|
||||
Ok(vec![ReadDirectoryEntry {
|
||||
file_name: "skill".to_string(),
|
||||
is_directory: true,
|
||||
is_file: false,
|
||||
}])
|
||||
} else if path == self.canonical_root.join("skill") {
|
||||
Ok(vec![ReadDirectoryEntry {
|
||||
file_name: "SKILL.md".to_string(),
|
||||
is_directory: false,
|
||||
is_file: true,
|
||||
}])
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||
}
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {
|
||||
Box::pin(SyntheticFileSystem::read_directory(self, path))
|
||||
}
|
||||
|
||||
async fn remove(
|
||||
&self,
|
||||
_path: &PathUri,
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
_path: &'a PathUri,
|
||||
_options: RemoveOptions,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "read only"))
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(async move { Err(io::Error::new(io::ErrorKind::Unsupported, "read only")) })
|
||||
}
|
||||
|
||||
async fn copy(
|
||||
&self,
|
||||
_source_path: &PathUri,
|
||||
_destination_path: &PathUri,
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
_source_path: &'a PathUri,
|
||||
_destination_path: &'a PathUri,
|
||||
_options: CopyOptions,
|
||||
_sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "read only"))
|
||||
_sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()> {
|
||||
Box::pin(async move { Err(io::Error::new(io::ErrorKind::Unsupported, "read only")) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-path-uri = { workspace = true }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
@@ -10,8 +9,10 @@ use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct CreateDirectoryOptions {
|
||||
@@ -130,71 +131,76 @@ fn file_system_policy_has_cwd_dependent_entries(
|
||||
|
||||
pub type FileSystemResult<T> = io::Result<T>;
|
||||
|
||||
/// Future returned by [`ExecutorFileSystem`] operations.
|
||||
pub type ExecutorFileSystemFuture<'a, T> =
|
||||
Pin<Box<dyn Future<Output = FileSystemResult<T>> + Send + 'a>>;
|
||||
|
||||
/// Abstract filesystem access used by components that may operate locally or via
|
||||
/// a remote environment.
|
||||
#[async_trait]
|
||||
pub trait ExecutorFileSystem: Send + Sync {
|
||||
/// Resolves a path within this filesystem.
|
||||
async fn canonicalize(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<PathUri>;
|
||||
fn canonicalize<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, PathUri>;
|
||||
|
||||
async fn read_file(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<Vec<u8>>;
|
||||
fn read_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<u8>>;
|
||||
|
||||
/// Reads a file and decodes it as UTF-8 text.
|
||||
async fn read_file_text(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<String> {
|
||||
let bytes = self.read_file(path, sandbox).await?;
|
||||
String::from_utf8(bytes).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
|
||||
fn read_file_text<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, String> {
|
||||
Box::pin(async move {
|
||||
let bytes = self.read_file(path, sandbox).await?;
|
||||
String::from_utf8(bytes).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
|
||||
})
|
||||
}
|
||||
|
||||
async fn write_file(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
fn write_file<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
contents: Vec<u8>,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()>;
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()>;
|
||||
|
||||
async fn create_directory(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
fn create_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
create_directory_options: CreateDirectoryOptions,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()>;
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()>;
|
||||
|
||||
async fn get_metadata(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<FileMetadata>;
|
||||
fn get_metadata<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, FileMetadata>;
|
||||
|
||||
async fn read_directory(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<Vec<ReadDirectoryEntry>>;
|
||||
fn read_directory<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>>;
|
||||
|
||||
async fn remove(
|
||||
&self,
|
||||
path: &PathUri,
|
||||
fn remove<'a>(
|
||||
&'a self,
|
||||
path: &'a PathUri,
|
||||
remove_options: RemoveOptions,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()>;
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()>;
|
||||
|
||||
async fn copy(
|
||||
&self,
|
||||
source_path: &PathUri,
|
||||
destination_path: &PathUri,
|
||||
fn copy<'a>(
|
||||
&'a self,
|
||||
source_path: &'a PathUri,
|
||||
destination_path: &'a PathUri,
|
||||
copy_options: CopyOptions,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> FileSystemResult<()>;
|
||||
sandbox: Option<&'a FileSystemSandboxContext>,
|
||||
) -> ExecutorFileSystemFuture<'a, ()>;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-agent-identity = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::manager::ExternalAuth;
|
||||
use super::manager::ExternalAuthFuture;
|
||||
use super::manager::ExternalAuthRefreshContext;
|
||||
use super::manager::ExternalAuthTokens;
|
||||
use async_trait::async_trait;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
use std::fmt;
|
||||
@@ -25,13 +25,6 @@ impl BearerTokenRefresher {
|
||||
state: Arc::new(ExternalBearerAuthState::new(config)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExternalAuth for BearerTokenRefresher {
|
||||
fn auth_mode(&self) -> AuthMode {
|
||||
AuthMode::ApiKey
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::await_holding_invalid_type,
|
||||
@@ -76,6 +69,23 @@ impl ExternalAuth for BearerTokenRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalAuth for BearerTokenRefresher {
|
||||
fn auth_mode(&self) -> AuthMode {
|
||||
AuthMode::ApiKey
|
||||
}
|
||||
|
||||
fn resolve(&self) -> ExternalAuthFuture<'_, Option<ExternalAuthTokens>> {
|
||||
Box::pin(BearerTokenRefresher::resolve(self))
|
||||
}
|
||||
|
||||
fn refresh(
|
||||
&self,
|
||||
context: ExternalAuthRefreshContext,
|
||||
) -> ExternalAuthFuture<'_, ExternalAuthTokens> {
|
||||
Box::pin(BearerTokenRefresher::refresh(self, context))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BearerTokenRefresher {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BearerTokenRefresher")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use reqwest::StatusCode;
|
||||
use serde::Deserialize;
|
||||
@@ -7,8 +6,10 @@ use serde::Serialize;
|
||||
use serial_test::serial;
|
||||
use std::env;
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
@@ -167,7 +168,6 @@ pub struct ExternalAuthRefreshContext {
|
||||
pub previous_account_id: Option<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
/// Pluggable auth provider used by `AuthManager` for externally managed auth flows.
|
||||
///
|
||||
/// Implementations may either resolve auth eagerly via `resolve()` or provide refreshed
|
||||
@@ -178,17 +178,19 @@ pub trait ExternalAuth: Send + Sync {
|
||||
|
||||
/// Returns cached or immediately available auth, if this provider can resolve it synchronously
|
||||
/// from the caller's perspective.
|
||||
async fn resolve(&self) -> std::io::Result<Option<ExternalAuthTokens>> {
|
||||
Ok(None)
|
||||
fn resolve(&self) -> ExternalAuthFuture<'_, Option<ExternalAuthTokens>> {
|
||||
Box::pin(async { Ok(None) })
|
||||
}
|
||||
|
||||
/// Refreshes auth in response to a manager-driven refresh attempt.
|
||||
async fn refresh(
|
||||
fn refresh(
|
||||
&self,
|
||||
context: ExternalAuthRefreshContext,
|
||||
) -> std::io::Result<ExternalAuthTokens>;
|
||||
) -> ExternalAuthFuture<'_, ExternalAuthTokens>;
|
||||
}
|
||||
|
||||
pub type ExternalAuthFuture<'a, T> = Pin<Box<dyn Future<Output = std::io::Result<T>> + Send + 'a>>;
|
||||
|
||||
impl RefreshTokenError {
|
||||
pub fn failed_reason(&self) -> Option<RefreshTokenFailedReason> {
|
||||
match self {
|
||||
|
||||
@@ -27,6 +27,7 @@ pub use auth::CODEX_API_KEY_ENV_VAR;
|
||||
pub use auth::CodexAuth;
|
||||
pub use auth::ExternalAuth;
|
||||
pub use auth::ExternalAuthChatgptMetadata;
|
||||
pub use auth::ExternalAuthFuture;
|
||||
pub use auth::ExternalAuthRefreshContext;
|
||||
pub use auth::ExternalAuthRefreshReason;
|
||||
pub use auth::ExternalAuthTokens;
|
||||
|
||||
@@ -11,6 +11,7 @@ use codex_git_utils::reset_git_repository;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_model_provider::ModelProvider;
|
||||
use codex_model_provider::ModelProviderFuture;
|
||||
use codex_model_provider::ProviderAccountResult;
|
||||
use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::create_model_provider;
|
||||
@@ -38,10 +39,8 @@ use core_test_support::test_codex::TestCodex;
|
||||
use core_test_support::test_codex::test_codex;
|
||||
use core_test_support::wait_for_event;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::Duration;
|
||||
@@ -616,13 +615,7 @@ impl ModelProvider for MockMemoryModelProvider {
|
||||
self.delegate.auth_manager()
|
||||
}
|
||||
|
||||
fn auth<'life0, 'async_trait>(
|
||||
&'life0 self,
|
||||
) -> Pin<Box<dyn Future<Output = Option<CodexAuth>> + Send + 'async_trait>>
|
||||
where
|
||||
'life0: 'async_trait,
|
||||
Self: 'async_trait,
|
||||
{
|
||||
fn auth(&self) -> ModelProviderFuture<'_, Option<CodexAuth>> {
|
||||
let delegate = Arc::clone(&self.delegate);
|
||||
Box::pin(async move { delegate.auth().await })
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ path = "src/lib.rs"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
codex-api = { workspace = true }
|
||||
codex-agent-identity = { workspace = true }
|
||||
codex-aws-auth = { workspace = true }
|
||||
|
||||
@@ -133,11 +133,6 @@ impl BedrockMantleSigV4AuthProvider {
|
||||
fn new(context: AwsAuthContext) -> Self {
|
||||
Self { context }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AuthProvider for BedrockMantleSigV4AuthProvider {
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
|
||||
async fn apply_auth(&self, request: Request) -> std::result::Result<Request, AuthError> {
|
||||
let mut request = request;
|
||||
@@ -162,6 +157,14 @@ impl AuthProvider for BedrockMantleSigV4AuthProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthProvider for BedrockMantleSigV4AuthProvider {
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
|
||||
fn apply_auth(&self, request: Request) -> codex_api::AuthProviderFuture<'_> {
|
||||
Box::pin(BedrockMantleSigV4AuthProvider::apply_auth(self, request))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use codex_api::AuthProvider;
|
||||
|
||||
@@ -20,6 +20,7 @@ use codex_protocol::error::Result;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
|
||||
use crate::provider::ModelProvider;
|
||||
use crate::provider::ModelProviderFuture;
|
||||
use crate::provider::ProviderAccountResult;
|
||||
use crate::provider::ProviderAccountState;
|
||||
use crate::provider::ProviderCapabilities;
|
||||
@@ -68,9 +69,32 @@ impl AmazonBedrockModelProvider {
|
||||
| CodexAuth::PersonalAccessToken(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn auth(&self) -> Option<CodexAuth> {
|
||||
self.managed_auth().map(CodexAuth::BedrockApiKey)
|
||||
}
|
||||
|
||||
async fn api_provider(&self) -> Result<Provider> {
|
||||
let managed_auth = self.managed_auth();
|
||||
let mut api_provider_info = self.info.clone();
|
||||
api_provider_info.base_url =
|
||||
Some(runtime_base_url(managed_auth.as_ref(), &self.aws).await?);
|
||||
api_provider_info.to_api_provider(/*auth_mode*/ None)
|
||||
}
|
||||
|
||||
async fn runtime_base_url(&self) -> Result<Option<String>> {
|
||||
let managed_auth = self.managed_auth();
|
||||
Ok(Some(
|
||||
runtime_base_url(managed_auth.as_ref(), &self.aws).await?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn api_auth(&self) -> Result<SharedAuthProvider> {
|
||||
let managed_auth = self.managed_auth();
|
||||
resolve_provider_auth(managed_auth.as_ref(), &self.aws).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ModelProvider for AmazonBedrockModelProvider {
|
||||
fn info(&self) -> &ModelProviderInfo {
|
||||
&self.info
|
||||
@@ -101,8 +125,8 @@ impl ModelProvider for AmazonBedrockModelProvider {
|
||||
.and_then(|_| self.auth_manager.as_ref().cloned())
|
||||
}
|
||||
|
||||
async fn auth(&self) -> Option<CodexAuth> {
|
||||
self.managed_auth().map(CodexAuth::BedrockApiKey)
|
||||
fn auth(&self) -> ModelProviderFuture<'_, Option<CodexAuth>> {
|
||||
Box::pin(AmazonBedrockModelProvider::auth(self))
|
||||
}
|
||||
|
||||
fn account_state(&self) -> ProviderAccountResult {
|
||||
@@ -112,24 +136,16 @@ impl ModelProvider for AmazonBedrockModelProvider {
|
||||
})
|
||||
}
|
||||
|
||||
async fn api_provider(&self) -> Result<Provider> {
|
||||
let managed_auth = self.managed_auth();
|
||||
let mut api_provider_info = self.info.clone();
|
||||
api_provider_info.base_url =
|
||||
Some(runtime_base_url(managed_auth.as_ref(), &self.aws).await?);
|
||||
api_provider_info.to_api_provider(/*auth_mode*/ None)
|
||||
fn api_provider(&self) -> ModelProviderFuture<'_, Result<Provider>> {
|
||||
Box::pin(AmazonBedrockModelProvider::api_provider(self))
|
||||
}
|
||||
|
||||
async fn runtime_base_url(&self) -> Result<Option<String>> {
|
||||
let managed_auth = self.managed_auth();
|
||||
Ok(Some(
|
||||
runtime_base_url(managed_auth.as_ref(), &self.aws).await?,
|
||||
))
|
||||
fn runtime_base_url(&self) -> ModelProviderFuture<'_, Result<Option<String>>> {
|
||||
Box::pin(AmazonBedrockModelProvider::runtime_base_url(self))
|
||||
}
|
||||
|
||||
async fn api_auth(&self) -> Result<SharedAuthProvider> {
|
||||
let managed_auth = self.managed_auth();
|
||||
resolve_provider_auth(managed_auth.as_ref(), &self.aws).await
|
||||
fn api_auth(&self) -> ModelProviderFuture<'_, Result<SharedAuthProvider>> {
|
||||
Box::pin(AmazonBedrockModelProvider::api_auth(self))
|
||||
}
|
||||
|
||||
fn models_manager(
|
||||
|
||||
@@ -10,6 +10,7 @@ pub use bearer_auth_provider::BearerAuthProvider;
|
||||
pub use bearer_auth_provider::BearerAuthProvider as CoreAuthProvider;
|
||||
pub use codex_protocol::account::ProviderAccount;
|
||||
pub use provider::ModelProvider;
|
||||
pub use provider::ModelProviderFuture;
|
||||
pub use provider::ProviderAccountError;
|
||||
pub use provider::ProviderAccountResult;
|
||||
pub use provider::ProviderAccountState;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codex_api::ModelsClient;
|
||||
use codex_api::RequestTelemetry;
|
||||
use codex_api::ReqwestTransport;
|
||||
@@ -17,6 +16,7 @@ use codex_login::collect_auth_env_telemetry;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::manager::ModelsEndpointClient;
|
||||
use codex_models_manager::manager::ModelsEndpointFuture;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CoreResult;
|
||||
@@ -56,21 +56,6 @@ impl OpenAiModelsEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
fn auth_env(&self) -> AuthEnvTelemetry {
|
||||
let codex_api_key_env_enabled = self
|
||||
.auth_manager
|
||||
.as_ref()
|
||||
.is_some_and(|auth_manager| auth_manager.codex_api_key_env_enabled());
|
||||
collect_auth_env_telemetry(&self.provider_info, codex_api_key_env_enabled)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ModelsEndpointClient for OpenAiModelsEndpoint {
|
||||
fn has_command_auth(&self) -> bool {
|
||||
self.provider_info.has_command_auth()
|
||||
}
|
||||
|
||||
async fn uses_codex_backend(&self) -> bool {
|
||||
self.auth()
|
||||
.await
|
||||
@@ -107,6 +92,31 @@ impl ModelsEndpointClient for OpenAiModelsEndpoint {
|
||||
.map_err(|_| CodexErr::Timeout)?
|
||||
.map_err(map_api_error)
|
||||
}
|
||||
|
||||
fn auth_env(&self) -> AuthEnvTelemetry {
|
||||
let codex_api_key_env_enabled = self
|
||||
.auth_manager
|
||||
.as_ref()
|
||||
.is_some_and(|auth_manager| auth_manager.codex_api_key_env_enabled());
|
||||
collect_auth_env_telemetry(&self.provider_info, codex_api_key_env_enabled)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelsEndpointClient for OpenAiModelsEndpoint {
|
||||
fn has_command_auth(&self) -> bool {
|
||||
self.provider_info.has_command_auth()
|
||||
}
|
||||
|
||||
fn uses_codex_backend(&self) -> ModelsEndpointFuture<'_, bool> {
|
||||
Box::pin(OpenAiModelsEndpoint::uses_codex_backend(self))
|
||||
}
|
||||
|
||||
fn list_models<'a>(
|
||||
&'a self,
|
||||
client_version: &'a str,
|
||||
) -> ModelsEndpointFuture<'a, CoreResult<(Vec<ModelInfo>, Option<String>)>> {
|
||||
Box::pin(OpenAiModelsEndpoint::list_models(self, client_version))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_api::Provider;
|
||||
@@ -94,7 +96,6 @@ pub const DEFAULT_MEMORY_CONSOLIDATION_PREFERRED_MODEL: &str = "gpt-5.4";
|
||||
/// Implementations own provider-specific behavior for a model backend. The
|
||||
/// `ModelProviderInfo` returned by `info` is the serialized/configured provider
|
||||
/// metadata used by the default OpenAI-compatible implementation.
|
||||
#[async_trait::async_trait]
|
||||
pub trait ModelProvider: fmt::Debug + Send + Sync {
|
||||
/// Returns the configured provider metadata.
|
||||
fn info(&self) -> &ModelProviderInfo;
|
||||
@@ -139,27 +140,35 @@ pub trait ModelProvider: fmt::Debug + Send + Sync {
|
||||
fn auth_manager(&self) -> Option<Arc<AuthManager>>;
|
||||
|
||||
/// Returns the current provider-scoped auth value, if one is configured.
|
||||
async fn auth(&self) -> Option<CodexAuth>;
|
||||
fn auth(&self) -> ModelProviderFuture<'_, Option<CodexAuth>>;
|
||||
|
||||
/// Returns the current app-visible account state for this provider.
|
||||
fn account_state(&self) -> ProviderAccountResult;
|
||||
|
||||
/// Returns provider configuration adapted for the API client.
|
||||
async fn api_provider(&self) -> codex_protocol::error::Result<Provider> {
|
||||
let auth = self.auth().await;
|
||||
self.info()
|
||||
.to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))
|
||||
fn api_provider(&self) -> ModelProviderFuture<'_, codex_protocol::error::Result<Provider>> {
|
||||
Box::pin(async move {
|
||||
let auth = self.auth().await;
|
||||
self.info()
|
||||
.to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the provider base URL that will be used at request time.
|
||||
async fn runtime_base_url(&self) -> codex_protocol::error::Result<Option<String>> {
|
||||
Ok(self.info().base_url.clone())
|
||||
fn runtime_base_url(
|
||||
&self,
|
||||
) -> ModelProviderFuture<'_, codex_protocol::error::Result<Option<String>>> {
|
||||
Box::pin(async { Ok(self.info().base_url.clone()) })
|
||||
}
|
||||
|
||||
/// Returns the auth provider used to attach request credentials.
|
||||
async fn api_auth(&self) -> codex_protocol::error::Result<SharedAuthProvider> {
|
||||
let auth = self.auth().await;
|
||||
resolve_provider_auth(auth.as_ref(), self.info())
|
||||
fn api_auth(
|
||||
&self,
|
||||
) -> ModelProviderFuture<'_, codex_protocol::error::Result<SharedAuthProvider>> {
|
||||
Box::pin(async move {
|
||||
let auth = self.auth().await;
|
||||
resolve_provider_auth(auth.as_ref(), self.info())
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates the model manager implementation appropriate for this provider.
|
||||
@@ -170,6 +179,8 @@ pub trait ModelProvider: fmt::Debug + Send + Sync {
|
||||
) -> SharedModelsManager;
|
||||
}
|
||||
|
||||
pub type ModelProviderFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
/// Shared runtime model provider handle.
|
||||
pub type SharedModelProvider = Arc<dyn ModelProvider>;
|
||||
|
||||
@@ -202,7 +213,6 @@ impl ConfiguredModelProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ModelProvider for ConfiguredModelProvider {
|
||||
fn info(&self) -> &ModelProviderInfo {
|
||||
&self.info
|
||||
@@ -219,11 +229,13 @@ impl ModelProvider for ConfiguredModelProvider {
|
||||
.is_some_and(|auth| auth.is_chatgpt_auth())
|
||||
}
|
||||
|
||||
async fn auth(&self) -> Option<CodexAuth> {
|
||||
match self.auth_manager.as_ref() {
|
||||
Some(auth_manager) => auth_manager.auth().await,
|
||||
None => None,
|
||||
}
|
||||
fn auth(&self) -> ModelProviderFuture<'_, Option<CodexAuth>> {
|
||||
Box::pin(async move {
|
||||
match self.auth_manager.as_ref() {
|
||||
Some(auth_manager) => auth_manager.auth().await,
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn account_state(&self) -> ProviderAccountResult {
|
||||
|
||||
@@ -13,7 +13,6 @@ path = "src/lib.rs"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-collaboration-mode-templates = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::cache::ModelsCacheManager;
|
||||
use crate::collaboration_mode_presets::builtin_collaboration_mode_presets;
|
||||
use crate::config::ModelsManagerConfig;
|
||||
use crate::model_info;
|
||||
use async_trait::async_trait;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_login::AuthManager;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
@@ -12,7 +11,9 @@ use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ModelVisibility;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -29,21 +30,22 @@ const DEFAULT_MODEL_CACHE_TTL: Duration = Duration::from_secs(300);
|
||||
/// Implementations own provider-specific auth and transport details. The model
|
||||
/// manager owns refresh policy, cache behavior, and catalog merging; it calls
|
||||
/// this endpoint only when it decides a remote refresh should happen.
|
||||
#[async_trait]
|
||||
pub trait ModelsEndpointClient: fmt::Debug + Send + Sync {
|
||||
/// Returns whether this provider can authenticate command-scoped requests.
|
||||
fn has_command_auth(&self) -> bool;
|
||||
|
||||
/// Returns whether the currently resolved auth can use Codex backend-only models.
|
||||
async fn uses_codex_backend(&self) -> bool;
|
||||
fn uses_codex_backend(&self) -> ModelsEndpointFuture<'_, bool>;
|
||||
|
||||
/// Fetches the latest remote model catalog and optional ETag.
|
||||
async fn list_models(
|
||||
&self,
|
||||
client_version: &str,
|
||||
) -> CoreResult<(Vec<ModelInfo>, Option<String>)>;
|
||||
fn list_models<'a>(
|
||||
&'a self,
|
||||
client_version: &'a str,
|
||||
) -> ModelsEndpointFuture<'a, CoreResult<(Vec<ModelInfo>, Option<String>)>>;
|
||||
}
|
||||
|
||||
pub type ModelsEndpointFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
/// Strategy for refreshing available models.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RefreshStrategy {
|
||||
@@ -74,28 +76,34 @@ impl fmt::Display for RefreshStrategy {
|
||||
type SharedModelsEndpointClient = Arc<dyn ModelsEndpointClient>;
|
||||
|
||||
/// Coordinates model discovery plus cached metadata on disk.
|
||||
#[async_trait]
|
||||
pub trait ModelsManager: fmt::Debug + Send + Sync {
|
||||
/// List all available models, refreshing according to the specified strategy.
|
||||
///
|
||||
/// Returns model presets sorted by priority and filtered by auth mode and visibility.
|
||||
async fn list_models(&self, refresh_strategy: RefreshStrategy) -> Vec<ModelPreset> {
|
||||
async move {
|
||||
let catalog = self.raw_model_catalog(refresh_strategy).await;
|
||||
self.build_available_models(catalog.models)
|
||||
}
|
||||
.instrument(tracing::info_span!(
|
||||
"list_models",
|
||||
refresh_strategy = %refresh_strategy
|
||||
))
|
||||
.await
|
||||
fn list_models(
|
||||
&self,
|
||||
refresh_strategy: RefreshStrategy,
|
||||
) -> ModelsManagerFuture<'_, Vec<ModelPreset>> {
|
||||
Box::pin(
|
||||
async move {
|
||||
let catalog = self.raw_model_catalog(refresh_strategy).await;
|
||||
self.build_available_models(catalog.models)
|
||||
}
|
||||
.instrument(tracing::info_span!(
|
||||
"list_models",
|
||||
refresh_strategy = %refresh_strategy
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the active raw model catalog, refreshing according to the specified strategy.
|
||||
async fn raw_model_catalog(&self, refresh_strategy: RefreshStrategy) -> ModelsResponse;
|
||||
fn raw_model_catalog(
|
||||
&self,
|
||||
refresh_strategy: RefreshStrategy,
|
||||
) -> ModelsManagerFuture<'_, ModelsResponse>;
|
||||
|
||||
/// Return the current in-memory remote model catalog without refreshing or loading cache state.
|
||||
async fn get_remote_models(&self) -> Vec<ModelInfo>;
|
||||
fn get_remote_models(&self) -> ModelsManagerFuture<'_, Vec<ModelInfo>>;
|
||||
|
||||
/// Attempt to return the current in-memory remote model catalog without blocking.
|
||||
///
|
||||
@@ -138,42 +146,50 @@ pub trait ModelsManager: fmt::Debug + Send + Sync {
|
||||
///
|
||||
/// If `model` is provided, returns it directly. Otherwise selects the default based on
|
||||
/// auth mode and available models.
|
||||
async fn get_default_model(
|
||||
&self,
|
||||
model: &Option<String>,
|
||||
fn get_default_model<'a>(
|
||||
&'a self,
|
||||
model: &'a Option<String>,
|
||||
refresh_strategy: RefreshStrategy,
|
||||
) -> String {
|
||||
async move {
|
||||
if let Some(model) = model.as_ref() {
|
||||
return model.to_string();
|
||||
) -> ModelsManagerFuture<'a, String> {
|
||||
Box::pin(
|
||||
async move {
|
||||
if let Some(model) = model.as_ref() {
|
||||
return model.to_string();
|
||||
}
|
||||
default_model_from_available(self.list_models(refresh_strategy).await)
|
||||
}
|
||||
default_model_from_available(self.list_models(refresh_strategy).await)
|
||||
}
|
||||
.instrument(tracing::info_span!(
|
||||
"get_default_model",
|
||||
model.provided = model.is_some(),
|
||||
refresh_strategy = %refresh_strategy
|
||||
))
|
||||
.await
|
||||
.instrument(tracing::info_span!(
|
||||
"get_default_model",
|
||||
model.provided = model.is_some(),
|
||||
refresh_strategy = %refresh_strategy
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
// todo(aibrahim): look if we can tighten it to pub(crate)
|
||||
/// Look up model metadata, applying remote overrides and config adjustments.
|
||||
async fn get_model_info(&self, model: &str, config: &ModelsManagerConfig) -> ModelInfo {
|
||||
async move {
|
||||
let remote_models = self.get_remote_models().await;
|
||||
construct_model_info_from_candidates(model, &remote_models, config)
|
||||
}
|
||||
.instrument(tracing::info_span!("get_model_info", model = model))
|
||||
.await
|
||||
fn get_model_info<'a>(
|
||||
&'a self,
|
||||
model: &'a str,
|
||||
config: &'a ModelsManagerConfig,
|
||||
) -> ModelsManagerFuture<'a, ModelInfo> {
|
||||
Box::pin(
|
||||
async move {
|
||||
let remote_models = self.get_remote_models().await;
|
||||
construct_model_info_from_candidates(model, &remote_models, config)
|
||||
}
|
||||
.instrument(tracing::info_span!("get_model_info", model = model)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Refresh models if the provided ETag differs from the cached ETag.
|
||||
///
|
||||
/// Uses `Online` strategy to fetch latest models when ETags differ.
|
||||
async fn refresh_if_new_etag(&self, etag: String);
|
||||
fn refresh_if_new_etag(&self, etag: String) -> ModelsManagerFuture<'_, ()>;
|
||||
}
|
||||
|
||||
pub type ModelsManagerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
/// Shared model manager handle used across runtime services.
|
||||
pub type SharedModelsManager = Arc<dyn ModelsManager>;
|
||||
|
||||
@@ -224,19 +240,19 @@ impl StaticModelsManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ModelsManager for OpenAiModelsManager {
|
||||
async fn raw_model_catalog(&self, refresh_strategy: RefreshStrategy) -> ModelsResponse {
|
||||
if let Err(err) = self.refresh_available_models(refresh_strategy).await {
|
||||
error!("failed to refresh available models: {err}");
|
||||
}
|
||||
ModelsResponse {
|
||||
models: self.get_remote_models().await,
|
||||
}
|
||||
fn raw_model_catalog(
|
||||
&self,
|
||||
refresh_strategy: RefreshStrategy,
|
||||
) -> ModelsManagerFuture<'_, ModelsResponse> {
|
||||
Box::pin(OpenAiModelsManager::raw_model_catalog(
|
||||
self,
|
||||
refresh_strategy,
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_remote_models(&self) -> Vec<ModelInfo> {
|
||||
self.remote_models.read().await.clone()
|
||||
fn get_remote_models(&self) -> ModelsManagerFuture<'_, Vec<ModelInfo>> {
|
||||
Box::pin(async move { self.remote_models.read().await.clone() })
|
||||
}
|
||||
|
||||
fn try_get_remote_models(&self) -> Result<Vec<ModelInfo>, TryLockError> {
|
||||
@@ -251,6 +267,21 @@ impl ModelsManager for OpenAiModelsManager {
|
||||
builtin_collaboration_mode_presets()
|
||||
}
|
||||
|
||||
fn refresh_if_new_etag(&self, etag: String) -> ModelsManagerFuture<'_, ()> {
|
||||
Box::pin(OpenAiModelsManager::refresh_if_new_etag(self, etag))
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenAiModelsManager {
|
||||
async fn raw_model_catalog(&self, refresh_strategy: RefreshStrategy) -> ModelsResponse {
|
||||
if let Err(err) = self.refresh_available_models(refresh_strategy).await {
|
||||
error!("failed to refresh available models: {err}");
|
||||
}
|
||||
ModelsResponse {
|
||||
models: self.get_remote_models().await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn refresh_if_new_etag(&self, etag: String) {
|
||||
let current_etag = self.get_etag().await;
|
||||
if current_etag.clone().is_some() && current_etag.as_deref() == Some(etag.as_str()) {
|
||||
@@ -263,9 +294,7 @@ impl ModelsManager for OpenAiModelsManager {
|
||||
error!("failed to refresh available models: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenAiModelsManager {
|
||||
/// Refresh available models according to the specified strategy.
|
||||
async fn refresh_available_models(&self, refresh_strategy: RefreshStrategy) -> CoreResult<()> {
|
||||
if !self.should_refresh_models().await {
|
||||
@@ -378,16 +407,20 @@ impl OpenAiModelsManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ModelsManager for StaticModelsManager {
|
||||
async fn raw_model_catalog(&self, _refresh_strategy: RefreshStrategy) -> ModelsResponse {
|
||||
ModelsResponse {
|
||||
models: self.get_remote_models().await,
|
||||
}
|
||||
fn raw_model_catalog(
|
||||
&self,
|
||||
_refresh_strategy: RefreshStrategy,
|
||||
) -> ModelsManagerFuture<'_, ModelsResponse> {
|
||||
Box::pin(async move {
|
||||
ModelsResponse {
|
||||
models: self.get_remote_models().await,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_remote_models(&self) -> Vec<ModelInfo> {
|
||||
self.remote_models.clone()
|
||||
fn get_remote_models(&self) -> ModelsManagerFuture<'_, Vec<ModelInfo>> {
|
||||
Box::pin(async { self.remote_models.clone() })
|
||||
}
|
||||
|
||||
fn try_get_remote_models(&self) -> Result<Vec<ModelInfo>, TryLockError> {
|
||||
@@ -402,7 +435,9 @@ impl ModelsManager for StaticModelsManager {
|
||||
builtin_collaboration_mode_presets()
|
||||
}
|
||||
|
||||
async fn refresh_if_new_etag(&self, _etag: String) {}
|
||||
fn refresh_if_new_etag(&self, _etag: String) -> ModelsManagerFuture<'_, ()> {
|
||||
Box::pin(async {})
|
||||
}
|
||||
}
|
||||
|
||||
fn load_remote_models_from_file() -> Result<Vec<ModelInfo>, std::io::Error> {
|
||||
|
||||
@@ -100,64 +100,8 @@ impl TestModelsEndpoint {
|
||||
fn fetch_count(&self) -> usize {
|
||||
self.fetch_count.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestExternalApiKeyAuth;
|
||||
|
||||
#[async_trait]
|
||||
impl ExternalAuth for TestExternalApiKeyAuth {
|
||||
fn auth_mode(&self) -> AuthMode {
|
||||
AuthMode::ApiKey
|
||||
}
|
||||
|
||||
async fn resolve(&self) -> std::io::Result<Option<ExternalAuthTokens>> {
|
||||
Ok(Some(ExternalAuthTokens::access_token_only(
|
||||
"test-external-api-key",
|
||||
)))
|
||||
}
|
||||
|
||||
async fn refresh(
|
||||
&self,
|
||||
_context: ExternalAuthRefreshContext,
|
||||
) -> std::io::Result<ExternalAuthTokens> {
|
||||
Ok(ExternalAuthTokens::access_token_only(
|
||||
"test-external-api-key",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestUnresolvedExternalApiKeyAuth;
|
||||
|
||||
#[async_trait]
|
||||
impl ExternalAuth for TestUnresolvedExternalApiKeyAuth {
|
||||
fn auth_mode(&self) -> AuthMode {
|
||||
AuthMode::ApiKey
|
||||
}
|
||||
|
||||
async fn refresh(
|
||||
&self,
|
||||
_context: ExternalAuthRefreshContext,
|
||||
) -> std::io::Result<ExternalAuthTokens> {
|
||||
Err(std::io::Error::other("unresolved test auth"))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ModelsEndpointClient for TestModelsEndpoint {
|
||||
fn has_command_auth(&self) -> bool {
|
||||
self.has_command_auth
|
||||
}
|
||||
|
||||
async fn uses_codex_backend(&self) -> bool {
|
||||
self.uses_codex_backend
|
||||
}
|
||||
|
||||
async fn list_models(
|
||||
&self,
|
||||
_client_version: &str,
|
||||
) -> CoreResult<(Vec<ModelInfo>, Option<String>)> {
|
||||
async fn list_models(&self) -> CoreResult<(Vec<ModelInfo>, Option<String>)> {
|
||||
self.fetch_count.fetch_add(1, Ordering::SeqCst);
|
||||
let models = self
|
||||
.responses
|
||||
@@ -169,6 +113,67 @@ impl ModelsEndpointClient for TestModelsEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestExternalApiKeyAuth;
|
||||
|
||||
impl ExternalAuth for TestExternalApiKeyAuth {
|
||||
fn auth_mode(&self) -> AuthMode {
|
||||
AuthMode::ApiKey
|
||||
}
|
||||
|
||||
fn resolve(&self) -> codex_login::ExternalAuthFuture<'_, Option<ExternalAuthTokens>> {
|
||||
Box::pin(async {
|
||||
Ok(Some(ExternalAuthTokens::access_token_only(
|
||||
"test-external-api-key",
|
||||
)))
|
||||
})
|
||||
}
|
||||
|
||||
fn refresh(
|
||||
&self,
|
||||
_context: ExternalAuthRefreshContext,
|
||||
) -> codex_login::ExternalAuthFuture<'_, ExternalAuthTokens> {
|
||||
Box::pin(async {
|
||||
Ok(ExternalAuthTokens::access_token_only(
|
||||
"test-external-api-key",
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestUnresolvedExternalApiKeyAuth;
|
||||
|
||||
impl ExternalAuth for TestUnresolvedExternalApiKeyAuth {
|
||||
fn auth_mode(&self) -> AuthMode {
|
||||
AuthMode::ApiKey
|
||||
}
|
||||
|
||||
fn refresh(
|
||||
&self,
|
||||
_context: ExternalAuthRefreshContext,
|
||||
) -> codex_login::ExternalAuthFuture<'_, ExternalAuthTokens> {
|
||||
Box::pin(async { Err(std::io::Error::other("unresolved test auth")) })
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelsEndpointClient for TestModelsEndpoint {
|
||||
fn has_command_auth(&self) -> bool {
|
||||
self.has_command_auth
|
||||
}
|
||||
|
||||
fn uses_codex_backend(&self) -> ModelsEndpointFuture<'_, bool> {
|
||||
Box::pin(async { self.uses_codex_backend })
|
||||
}
|
||||
|
||||
fn list_models<'a>(
|
||||
&'a self,
|
||||
_client_version: &'a str,
|
||||
) -> ModelsEndpointFuture<'a, CoreResult<(Vec<ModelInfo>, Option<String>)>> {
|
||||
Box::pin(TestModelsEndpoint::list_models(self))
|
||||
}
|
||||
}
|
||||
|
||||
fn openai_manager_for_tests(
|
||||
codex_home: std::path::PathBuf,
|
||||
endpoint_client: Arc<dyn ModelsEndpointClient>,
|
||||
@@ -713,13 +718,6 @@ impl TestAuthAwareModelsEndpoint {
|
||||
fn fetch_count(&self) -> usize {
|
||||
self.fetch_count.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ModelsEndpointClient for TestAuthAwareModelsEndpoint {
|
||||
fn has_command_auth(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn uses_codex_backend(&self) -> bool {
|
||||
match self.auth_manager.as_ref() {
|
||||
@@ -732,10 +730,7 @@ impl ModelsEndpointClient for TestAuthAwareModelsEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_models(
|
||||
&self,
|
||||
_client_version: &str,
|
||||
) -> CoreResult<(Vec<ModelInfo>, Option<String>)> {
|
||||
async fn list_models(&self) -> CoreResult<(Vec<ModelInfo>, Option<String>)> {
|
||||
self.fetch_count.fetch_add(1, Ordering::SeqCst);
|
||||
let models = self
|
||||
.responses
|
||||
@@ -747,6 +742,23 @@ impl ModelsEndpointClient for TestAuthAwareModelsEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelsEndpointClient for TestAuthAwareModelsEndpoint {
|
||||
fn has_command_auth(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn uses_codex_backend(&self) -> ModelsEndpointFuture<'_, bool> {
|
||||
Box::pin(TestAuthAwareModelsEndpoint::uses_codex_backend(self))
|
||||
}
|
||||
|
||||
fn list_models<'a>(
|
||||
&'a self,
|
||||
_client_version: &'a str,
|
||||
) -> ModelsEndpointFuture<'a, CoreResult<(Vec<ModelInfo>, Option<String>)>> {
|
||||
Box::pin(TestAuthAwareModelsEndpoint::list_models(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_available_models_skips_network_when_external_api_key_overrides_chatgpt_auth() {
|
||||
let dynamic_slug = "dynamic-model-only-for-test-external-api-key";
|
||||
|
||||
@@ -14,7 +14,6 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
chrono = { workspace = true }
|
||||
|
||||
@@ -34,6 +34,7 @@ pub use mitm_hook::MitmHookMatchConfig;
|
||||
pub use network_policy::NetworkDecision;
|
||||
pub use network_policy::NetworkDecisionSource;
|
||||
pub use network_policy::NetworkPolicyDecider;
|
||||
pub use network_policy::NetworkPolicyDeciderFuture;
|
||||
pub use network_policy::NetworkPolicyDecision;
|
||||
pub use network_policy::NetworkPolicyRequest;
|
||||
pub use network_policy::NetworkPolicyRequestArgs;
|
||||
@@ -59,7 +60,9 @@ pub use proxy::proxy_url_env_value;
|
||||
pub use runtime::BlockedRequest;
|
||||
pub use runtime::BlockedRequestArgs;
|
||||
pub use runtime::BlockedRequestObserver;
|
||||
pub use runtime::BlockedRequestObserverFuture;
|
||||
pub use runtime::ConfigReloader;
|
||||
pub use runtime::ConfigReloaderFuture;
|
||||
pub use runtime::ConfigState;
|
||||
pub use runtime::NetworkProxyState;
|
||||
pub use state::NetworkProxyAuditMetadata;
|
||||
|
||||
@@ -3,10 +3,10 @@ use crate::runtime::HostBlockDecision;
|
||||
use crate::runtime::HostBlockReason;
|
||||
use crate::state::NetworkProxyState;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use chrono::SecondsFormat;
|
||||
use chrono::Utc;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
const AUDIT_TARGET: &str = "codex_otel.network_proxy";
|
||||
@@ -263,26 +263,26 @@ fn audit_timestamp() -> String {
|
||||
/// If `command` or `exec_policy_hint` is provided, callers can map exec-policy
|
||||
/// approvals to network access (e.g., allow all requests for commands matching
|
||||
/// approved prefixes like `curl *`).
|
||||
#[async_trait]
|
||||
pub trait NetworkPolicyDecider: Send + Sync + 'static {
|
||||
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision;
|
||||
fn decide(&self, req: NetworkPolicyRequest) -> NetworkPolicyDeciderFuture<'_>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub type NetworkPolicyDeciderFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = NetworkDecision> + Send + 'a>>;
|
||||
|
||||
impl<D: NetworkPolicyDecider + ?Sized> NetworkPolicyDecider for Arc<D> {
|
||||
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision {
|
||||
(**self).decide(req).await
|
||||
fn decide(&self, req: NetworkPolicyRequest) -> NetworkPolicyDeciderFuture<'_> {
|
||||
Box::pin(async move { (**self).decide(req).await })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<F, Fut> NetworkPolicyDecider for F
|
||||
where
|
||||
F: Fn(NetworkPolicyRequest) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = NetworkDecision> + Send,
|
||||
Fut: Future<Output = NetworkDecision> + Send + 'static,
|
||||
{
|
||||
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision {
|
||||
(self)(req).await
|
||||
fn decide(&self, req: NetworkPolicyRequest) -> NetworkPolicyDeciderFuture<'_> {
|
||||
Box::pin((self)(req))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,6 +541,7 @@ mod tests {
|
||||
use crate::reasons::REASON_NOT_ALLOWED;
|
||||
use crate::reasons::REASON_NOT_ALLOWED_LOCAL;
|
||||
use crate::runtime::ConfigReloader;
|
||||
use crate::runtime::ConfigReloaderFuture;
|
||||
use crate::runtime::ConfigState;
|
||||
use crate::runtime::NetworkProxyAuditMetadata;
|
||||
use crate::state::NetworkProxyConstraints;
|
||||
@@ -560,14 +561,13 @@ mod tests {
|
||||
state: ConfigState,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ConfigReloader for StaticReloader {
|
||||
async fn maybe_reload(&self) -> anyhow::Result<Option<ConfigState>> {
|
||||
Ok(None)
|
||||
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {
|
||||
Box::pin(async { Ok(None) })
|
||||
}
|
||||
|
||||
async fn reload_now(&self) -> anyhow::Result<ConfigState> {
|
||||
Ok(self.state.clone())
|
||||
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {
|
||||
Box::pin(async { Ok(self.state.clone()) })
|
||||
}
|
||||
|
||||
fn source_label(&self) -> String {
|
||||
|
||||
@@ -20,7 +20,6 @@ use crate::state::build_config_state;
|
||||
use crate::state::validate_policy_against_constraints;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use globset::GlobSet;
|
||||
use serde::Serialize;
|
||||
@@ -30,6 +29,7 @@ use std::future::Future;
|
||||
use std::net::IpAddr;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
@@ -168,38 +168,38 @@ pub struct ConfigState {
|
||||
pub blocked_total: u64,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ConfigReloader: Send + Sync {
|
||||
/// Human-readable description of where config is loaded from, for logs.
|
||||
fn source_label(&self) -> String;
|
||||
|
||||
/// Return a freshly loaded state if a reload is needed; otherwise, return `None`.
|
||||
async fn maybe_reload(&self) -> Result<Option<ConfigState>>;
|
||||
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>>;
|
||||
|
||||
/// Force a reload, regardless of whether a change was detected.
|
||||
async fn reload_now(&self) -> Result<ConfigState>;
|
||||
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub type ConfigReloaderFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
|
||||
|
||||
pub trait BlockedRequestObserver: Send + Sync + 'static {
|
||||
async fn on_blocked_request(&self, request: BlockedRequest);
|
||||
fn on_blocked_request(&self, request: BlockedRequest) -> BlockedRequestObserverFuture<'_>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub type BlockedRequestObserverFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
|
||||
|
||||
impl<O: BlockedRequestObserver + ?Sized> BlockedRequestObserver for Arc<O> {
|
||||
async fn on_blocked_request(&self, request: BlockedRequest) {
|
||||
(**self).on_blocked_request(request).await
|
||||
fn on_blocked_request(&self, request: BlockedRequest) -> BlockedRequestObserverFuture<'_> {
|
||||
Box::pin(async move { (**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,
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
async fn on_blocked_request(&self, request: BlockedRequest) {
|
||||
(self)(request).await
|
||||
fn on_blocked_request(&self, request: BlockedRequest) -> BlockedRequestObserverFuture<'_> {
|
||||
Box::pin((self)(request))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -891,18 +891,17 @@ pub(crate) fn network_proxy_state_for_policy(
|
||||
struct NoopReloader;
|
||||
|
||||
#[cfg(test)]
|
||||
#[async_trait]
|
||||
impl ConfigReloader for NoopReloader {
|
||||
fn source_label(&self) -> String {
|
||||
"test config state".to_string()
|
||||
}
|
||||
|
||||
async fn maybe_reload(&self) -> Result<Option<ConfigState>> {
|
||||
Ok(None)
|
||||
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {
|
||||
Box::pin(async { Ok(None) })
|
||||
}
|
||||
|
||||
async fn reload_now(&self) -> Result<ConfigState> {
|
||||
Err(anyhow::anyhow!("force reload is not supported in tests"))
|
||||
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {
|
||||
Box::pin(async { Err(anyhow::anyhow!("force reload is not supported in tests")) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -714,10 +714,10 @@ mod tests {
|
||||
use crate::network_policy::test_support::capture_events;
|
||||
use crate::network_policy::test_support::find_event_by_name;
|
||||
use crate::runtime::ConfigReloader;
|
||||
use crate::runtime::ConfigReloaderFuture;
|
||||
use crate::runtime::ConfigState;
|
||||
use crate::state::NetworkProxyConstraints;
|
||||
use crate::state::build_config_state;
|
||||
use async_trait::async_trait;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rama_core::extensions::Extensions;
|
||||
use rama_core::extensions::ExtensionsMut;
|
||||
@@ -738,14 +738,13 @@ mod tests {
|
||||
state: ConfigState,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ConfigReloader for StaticReloader {
|
||||
async fn maybe_reload(&self) -> anyhow::Result<Option<ConfigState>> {
|
||||
Ok(None)
|
||||
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {
|
||||
Box::pin(async { Ok(None) })
|
||||
}
|
||||
|
||||
async fn reload_now(&self) -> anyhow::Result<ConfigState> {
|
||||
Ok(self.state.clone())
|
||||
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {
|
||||
Box::pin(async { Ok(self.state.clone()) })
|
||||
}
|
||||
|
||||
fn source_label(&self) -> String {
|
||||
|
||||
@@ -14,7 +14,6 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-file-search = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#![allow(warnings, clippy::all)]
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codex_utils_path as path_utils;
|
||||
use std::cmp::Reverse;
|
||||
use std::ffi::OsStr;
|
||||
@@ -195,15 +194,14 @@ impl AnchorState {
|
||||
///
|
||||
/// We need to apply different logic if we're ultimately going to be returning
|
||||
/// threads ordered by created_at or updated_at.
|
||||
#[async_trait]
|
||||
trait RolloutFileVisitor {
|
||||
async fn visit(
|
||||
fn visit(
|
||||
&mut self,
|
||||
ts: OffsetDateTime,
|
||||
id: Uuid,
|
||||
path: PathBuf,
|
||||
scanned: usize,
|
||||
) -> ControlFlow<()>;
|
||||
) -> impl std::future::Future<Output = ControlFlow<()>> + Send;
|
||||
}
|
||||
|
||||
/// Collects thread items during directory traversal in created_at order,
|
||||
@@ -218,7 +216,6 @@ struct FilesByCreatedAtVisitor<'a> {
|
||||
cwd_filters: Option<&'a [PathBuf]>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> RolloutFileVisitor for FilesByCreatedAtVisitor<'a> {
|
||||
async fn visit(
|
||||
&mut self,
|
||||
@@ -263,7 +260,6 @@ struct FilesByUpdatedAtVisitor<'a> {
|
||||
candidates: &'a mut Vec<ThreadCandidate>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> RolloutFileVisitor for FilesByUpdatedAtVisitor<'a> {
|
||||
async fn visit(
|
||||
&mut self,
|
||||
|
||||
@@ -26,7 +26,6 @@ which = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt"] }
|
||||
|
||||
@@ -12,6 +12,7 @@ use super::seatbelt_regex_for_unreadable_glob;
|
||||
use super::unix_socket_dir_params;
|
||||
use super::unix_socket_policy;
|
||||
use codex_network_proxy::ConfigReloader;
|
||||
use codex_network_proxy::ConfigReloaderFuture;
|
||||
use codex_network_proxy::ConfigState;
|
||||
use codex_network_proxy::NetworkMode;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
@@ -81,18 +82,17 @@ fn seatbelt_protected_metadata_name_requirements(root: &Path) -> String {
|
||||
|
||||
struct TestConfigReloader;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ConfigReloader for TestConfigReloader {
|
||||
fn source_label(&self) -> String {
|
||||
"seatbelt test config".to_string()
|
||||
}
|
||||
|
||||
async fn maybe_reload(&self) -> anyhow::Result<Option<ConfigState>> {
|
||||
Ok(None)
|
||||
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {
|
||||
Box::pin(async { Ok(None) })
|
||||
}
|
||||
|
||||
async fn reload_now(&self) -> anyhow::Result<ConfigState> {
|
||||
Err(anyhow::anyhow!("seatbelt test config cannot reload"))
|
||||
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {
|
||||
Box::pin(async { Err(anyhow::anyhow!("seatbelt test config cannot reload")) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
|
||||
@@ -16,6 +16,8 @@ pub use unix::EscalationPermissions;
|
||||
#[cfg(unix)]
|
||||
pub use unix::EscalationPolicy;
|
||||
#[cfg(unix)]
|
||||
pub use unix::EscalationPolicyFuture;
|
||||
#[cfg(unix)]
|
||||
pub use unix::EscalationSession;
|
||||
#[cfg(unix)]
|
||||
pub use unix::ExecParams;
|
||||
@@ -28,6 +30,8 @@ pub use unix::ResolvedPermissionProfile;
|
||||
#[cfg(unix)]
|
||||
pub use unix::ShellCommandExecutor;
|
||||
#[cfg(unix)]
|
||||
pub use unix::ShellCommandExecutorFuture;
|
||||
#[cfg(unix)]
|
||||
pub use unix::Stopwatch;
|
||||
#[cfg(unix)]
|
||||
pub use unix::main_execve_wrapper;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
@@ -31,7 +33,6 @@ use crate::unix::socket::AsyncSocket;
|
||||
/// This lets `shell-escalation` own the Unix escalation protocol while the caller
|
||||
/// keeps control over process spawning, output capture, and sandbox integration.
|
||||
/// Implementations can capture any sandbox state they need.
|
||||
#[async_trait::async_trait]
|
||||
pub trait ShellCommandExecutor: Send + Sync {
|
||||
/// Runs the requested shell command and returns the captured result.
|
||||
///
|
||||
@@ -41,26 +42,29 @@ pub trait ShellCommandExecutor: Send + Sync {
|
||||
/// for the shell process. `after_spawn` should be invoked immediately after
|
||||
/// the shell process has been spawned so the parent copy of the inherited
|
||||
/// escalation socket can be closed.
|
||||
async fn run(
|
||||
fn run(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
cwd: PathBuf,
|
||||
env_overlay: HashMap<String, String>,
|
||||
cancel_rx: CancellationToken,
|
||||
after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> anyhow::Result<ExecResult>;
|
||||
) -> ShellCommandExecutorFuture<'_, ExecResult>;
|
||||
|
||||
/// Prepares an escalated subcommand for execution on the server side.
|
||||
async fn prepare_escalated_exec(
|
||||
&self,
|
||||
program: &AbsolutePathBuf,
|
||||
argv: &[String],
|
||||
workdir: &AbsolutePathBuf,
|
||||
fn prepare_escalated_exec<'a>(
|
||||
&'a self,
|
||||
program: &'a AbsolutePathBuf,
|
||||
argv: &'a [String],
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
env: HashMap<String, String>,
|
||||
execution: EscalationExecution,
|
||||
) -> anyhow::Result<PreparedExec>;
|
||||
) -> ShellCommandExecutorFuture<'a, PreparedExec>;
|
||||
}
|
||||
|
||||
pub type ShellCommandExecutorFuture<'a, T> =
|
||||
Pin<Box<dyn Future<Output = anyhow::Result<T>> + Send + 'a>>;
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ExecParams {
|
||||
/// The command string to pass to the shell via `-c` or `-lc`.
|
||||
@@ -377,6 +381,7 @@ async fn handle_escalate_session_with_policy(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::unix::escalation_policy::EscalationPolicyFuture;
|
||||
use codex_protocol::approvals::EscalationPermissions;
|
||||
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
@@ -401,15 +406,14 @@ mod tests {
|
||||
decision: EscalationDecision,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EscalationPolicy for DeterministicEscalationPolicy {
|
||||
async fn determine_action(
|
||||
&self,
|
||||
_file: &AbsolutePathBuf,
|
||||
_argv: &[String],
|
||||
_workdir: &AbsolutePathBuf,
|
||||
) -> anyhow::Result<EscalationDecision> {
|
||||
Ok(self.decision.clone())
|
||||
fn determine_action<'a>(
|
||||
&'a self,
|
||||
_file: &'a AbsolutePathBuf,
|
||||
_argv: &'a [String],
|
||||
_workdir: &'a AbsolutePathBuf,
|
||||
) -> EscalationPolicyFuture<'a> {
|
||||
Box::pin(async move { Ok(self.decision.clone()) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,42 +422,30 @@ mod tests {
|
||||
expected_workdir: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EscalationPolicy for AssertingEscalationPolicy {
|
||||
async fn determine_action(
|
||||
&self,
|
||||
file: &AbsolutePathBuf,
|
||||
_argv: &[String],
|
||||
workdir: &AbsolutePathBuf,
|
||||
) -> anyhow::Result<EscalationDecision> {
|
||||
assert_eq!(file, &self.expected_file);
|
||||
assert_eq!(workdir, &self.expected_workdir);
|
||||
Ok(EscalationDecision::run())
|
||||
fn determine_action<'a>(
|
||||
&'a self,
|
||||
file: &'a AbsolutePathBuf,
|
||||
_argv: &'a [String],
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
) -> EscalationPolicyFuture<'a> {
|
||||
Box::pin(async move {
|
||||
assert_eq!(file, &self.expected_file);
|
||||
assert_eq!(workdir, &self.expected_workdir);
|
||||
Ok(EscalationDecision::run())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ForwardingShellCommandExecutor;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ShellCommandExecutor for ForwardingShellCommandExecutor {
|
||||
async fn run(
|
||||
&self,
|
||||
_command: Vec<String>,
|
||||
_cwd: PathBuf,
|
||||
_env_overlay: HashMap<String, String>,
|
||||
_cancel_rx: CancellationToken,
|
||||
_after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> anyhow::Result<ExecResult> {
|
||||
unreachable!("run() is not used by handle_escalate_session_with_policy() tests")
|
||||
}
|
||||
|
||||
impl ForwardingShellCommandExecutor {
|
||||
async fn prepare_escalated_exec(
|
||||
&self,
|
||||
program: &AbsolutePathBuf,
|
||||
argv: &[String],
|
||||
workdir: &AbsolutePathBuf,
|
||||
env: HashMap<String, String>,
|
||||
_execution: EscalationExecution,
|
||||
) -> anyhow::Result<PreparedExec> {
|
||||
Ok(PreparedExec {
|
||||
command: std::iter::once(program.to_string_lossy().to_string())
|
||||
@@ -466,23 +458,39 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct PermissionAssertingShellCommandExecutor {
|
||||
expected_permissions: EscalationPermissions,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ShellCommandExecutor for PermissionAssertingShellCommandExecutor {
|
||||
async fn run(
|
||||
impl ShellCommandExecutor for ForwardingShellCommandExecutor {
|
||||
fn run(
|
||||
&self,
|
||||
_command: Vec<String>,
|
||||
_cwd: PathBuf,
|
||||
_env_overlay: HashMap<String, String>,
|
||||
_cancel_rx: CancellationToken,
|
||||
_after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> anyhow::Result<ExecResult> {
|
||||
unreachable!("run() is not used by handle_escalate_session_with_policy() tests")
|
||||
) -> ShellCommandExecutorFuture<'_, ExecResult> {
|
||||
Box::pin(async {
|
||||
unreachable!("run() is not used by handle_escalate_session_with_policy() tests")
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_escalated_exec<'a>(
|
||||
&'a self,
|
||||
program: &'a AbsolutePathBuf,
|
||||
argv: &'a [String],
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
env: HashMap<String, String>,
|
||||
_execution: EscalationExecution,
|
||||
) -> ShellCommandExecutorFuture<'a, PreparedExec> {
|
||||
Box::pin(ForwardingShellCommandExecutor::prepare_escalated_exec(
|
||||
self, program, argv, workdir, env,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct PermissionAssertingShellCommandExecutor {
|
||||
expected_permissions: EscalationPermissions,
|
||||
}
|
||||
|
||||
impl PermissionAssertingShellCommandExecutor {
|
||||
async fn prepare_escalated_exec(
|
||||
&self,
|
||||
program: &AbsolutePathBuf,
|
||||
@@ -506,6 +514,36 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellCommandExecutor for PermissionAssertingShellCommandExecutor {
|
||||
fn run(
|
||||
&self,
|
||||
_command: Vec<String>,
|
||||
_cwd: PathBuf,
|
||||
_env_overlay: HashMap<String, String>,
|
||||
_cancel_rx: CancellationToken,
|
||||
_after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> ShellCommandExecutorFuture<'_, ExecResult> {
|
||||
Box::pin(async {
|
||||
unreachable!("run() is not used by handle_escalate_session_with_policy() tests")
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_escalated_exec<'a>(
|
||||
&'a self,
|
||||
program: &'a AbsolutePathBuf,
|
||||
argv: &'a [String],
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
env: HashMap<String, String>,
|
||||
execution: EscalationExecution,
|
||||
) -> ShellCommandExecutorFuture<'a, PreparedExec> {
|
||||
Box::pin(
|
||||
PermissionAssertingShellCommandExecutor::prepare_escalated_exec(
|
||||
self, program, argv, workdir, env, execution,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_pid_file(pid_file: &std::path::Path) -> anyhow::Result<i32> {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
loop {
|
||||
@@ -534,14 +572,10 @@ mod tests {
|
||||
after_spawn_invoked: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ShellCommandExecutor for AfterSpawnAssertingShellCommandExecutor {
|
||||
impl AfterSpawnAssertingShellCommandExecutor {
|
||||
async fn run(
|
||||
&self,
|
||||
_command: Vec<String>,
|
||||
_cwd: PathBuf,
|
||||
env_overlay: HashMap<String, String>,
|
||||
_cancel_rx: CancellationToken,
|
||||
after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> anyhow::Result<ExecResult> {
|
||||
let socket_fd = env_overlay
|
||||
@@ -560,16 +594,33 @@ mod tests {
|
||||
timed_out: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn prepare_escalated_exec(
|
||||
impl ShellCommandExecutor for AfterSpawnAssertingShellCommandExecutor {
|
||||
fn run(
|
||||
&self,
|
||||
_program: &AbsolutePathBuf,
|
||||
_argv: &[String],
|
||||
_workdir: &AbsolutePathBuf,
|
||||
_command: Vec<String>,
|
||||
_cwd: PathBuf,
|
||||
env_overlay: HashMap<String, String>,
|
||||
_cancel_rx: CancellationToken,
|
||||
after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> ShellCommandExecutorFuture<'_, ExecResult> {
|
||||
Box::pin(AfterSpawnAssertingShellCommandExecutor::run(
|
||||
self,
|
||||
env_overlay,
|
||||
after_spawn,
|
||||
))
|
||||
}
|
||||
|
||||
fn prepare_escalated_exec<'a>(
|
||||
&'a self,
|
||||
_program: &'a AbsolutePathBuf,
|
||||
_argv: &'a [String],
|
||||
_workdir: &'a AbsolutePathBuf,
|
||||
_env: HashMap<String, String>,
|
||||
_execution: EscalationExecution,
|
||||
) -> anyhow::Result<PreparedExec> {
|
||||
unreachable!("prepare_escalated_exec() is not used by exec() tests")
|
||||
) -> ShellCommandExecutorFuture<'a, PreparedExec> {
|
||||
Box::pin(async { unreachable!("prepare_escalated_exec() is not used by exec() tests") })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use crate::unix::escalate_protocol::EscalationDecision;
|
||||
|
||||
/// Decides what action to take in response to an execve request from a client.
|
||||
#[async_trait::async_trait]
|
||||
pub trait EscalationPolicy: Send + Sync {
|
||||
async fn determine_action(
|
||||
&self,
|
||||
file: &AbsolutePathBuf,
|
||||
argv: &[String],
|
||||
workdir: &AbsolutePathBuf,
|
||||
) -> anyhow::Result<EscalationDecision>;
|
||||
fn determine_action<'a>(
|
||||
&'a self,
|
||||
file: &'a AbsolutePathBuf,
|
||||
argv: &'a [String],
|
||||
workdir: &'a AbsolutePathBuf,
|
||||
) -> EscalationPolicyFuture<'a>;
|
||||
}
|
||||
|
||||
pub type EscalationPolicyFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = anyhow::Result<EscalationDecision>> + Send + 'a>>;
|
||||
|
||||
@@ -72,7 +72,9 @@ pub use self::escalate_server::ExecParams;
|
||||
pub use self::escalate_server::ExecResult;
|
||||
pub use self::escalate_server::PreparedExec;
|
||||
pub use self::escalate_server::ShellCommandExecutor;
|
||||
pub use self::escalate_server::ShellCommandExecutorFuture;
|
||||
pub use self::escalation_policy::EscalationPolicy;
|
||||
pub use self::escalation_policy::EscalationPolicyFuture;
|
||||
pub use self::execve_wrapper::main_execve_wrapper;
|
||||
pub use self::stopwatch::Stopwatch;
|
||||
pub use codex_protocol::approvals::EscalationPermissions;
|
||||
|
||||
@@ -13,7 +13,6 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-install-context = { workspace = true }
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::sync::Mutex;
|
||||
use std::sync::MutexGuard;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -31,6 +30,7 @@ use crate::ThreadMetadataPatch;
|
||||
use crate::ThreadPage;
|
||||
use crate::ThreadStore;
|
||||
use crate::ThreadStoreError;
|
||||
use crate::ThreadStoreFuture;
|
||||
use crate::ThreadStoreResult;
|
||||
use crate::UpdateThreadMetadataParams;
|
||||
|
||||
@@ -160,13 +160,6 @@ impl InMemoryThreadStore {
|
||||
pub async fn calls(&self) -> InMemoryThreadStoreCalls {
|
||||
self.state.lock().await.calls.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ThreadStore for InMemoryThreadStore {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
async fn create_thread(&self, params: CreateThreadParams) -> ThreadStoreResult<()> {
|
||||
let mut state = self.state.lock().await;
|
||||
@@ -230,26 +223,6 @@ impl ThreadStore for InMemoryThreadStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_thread(&self, _thread_id: ThreadId) -> ThreadStoreResult<()> {
|
||||
self.state.lock().await.calls.persist_thread += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn flush_thread(&self, _thread_id: ThreadId) -> ThreadStoreResult<()> {
|
||||
self.state.lock().await.calls.flush_thread += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn shutdown_thread(&self, _thread_id: ThreadId) -> ThreadStoreResult<()> {
|
||||
self.state.lock().await.calls.shutdown_thread += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn discard_thread(&self, _thread_id: ThreadId) -> ThreadStoreResult<()> {
|
||||
self.state.lock().await.calls.discard_thread += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_history(
|
||||
&self,
|
||||
params: LoadThreadHistoryParams,
|
||||
@@ -293,7 +266,7 @@ impl ThreadStore for InMemoryThreadStore {
|
||||
stored_thread_from_state(&state, thread_id, params.include_history)
|
||||
}
|
||||
|
||||
async fn list_threads(&self, _params: ListThreadsParams) -> ThreadStoreResult<ThreadPage> {
|
||||
async fn list_threads(&self) -> ThreadStoreResult<ThreadPage> {
|
||||
let mut state = self.state.lock().await;
|
||||
state.calls.list_threads += 1;
|
||||
let mut items = state
|
||||
@@ -327,20 +300,6 @@ impl ThreadStore for InMemoryThreadStore {
|
||||
stored_thread_from_state(&state, params.thread_id, /*include_history*/ false)
|
||||
}
|
||||
|
||||
async fn archive_thread(&self, _params: ArchiveThreadParams) -> ThreadStoreResult<()> {
|
||||
self.state.lock().await.calls.archive_thread += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn unarchive_thread(
|
||||
&self,
|
||||
params: ArchiveThreadParams,
|
||||
) -> ThreadStoreResult<StoredThread> {
|
||||
let mut state = self.state.lock().await;
|
||||
state.calls.unarchive_thread += 1;
|
||||
stored_thread_from_state(&state, params.thread_id, /*include_history*/ false)
|
||||
}
|
||||
|
||||
async fn delete_thread(&self, params: DeleteThreadParams) -> ThreadStoreResult<()> {
|
||||
let mut state = self.state.lock().await;
|
||||
state.calls.delete_thread += 1;
|
||||
@@ -361,6 +320,102 @@ impl ThreadStore for InMemoryThreadStore {
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadStore for InMemoryThreadStore {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn create_thread(&self, params: CreateThreadParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(InMemoryThreadStore::create_thread(self, params))
|
||||
}
|
||||
|
||||
fn resume_thread(&self, params: ResumeThreadParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(InMemoryThreadStore::resume_thread(self, params))
|
||||
}
|
||||
|
||||
fn append_items(&self, params: AppendThreadItemsParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(InMemoryThreadStore::append_items(self, params))
|
||||
}
|
||||
|
||||
fn persist_thread(&self, _thread_id: ThreadId) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.state.lock().await.calls.persist_thread += 1;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn flush_thread(&self, _thread_id: ThreadId) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.state.lock().await.calls.flush_thread += 1;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn shutdown_thread(&self, _thread_id: ThreadId) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.state.lock().await.calls.shutdown_thread += 1;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn discard_thread(&self, _thread_id: ThreadId) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.state.lock().await.calls.discard_thread += 1;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn load_history(
|
||||
&self,
|
||||
params: LoadThreadHistoryParams,
|
||||
) -> ThreadStoreFuture<'_, StoredThreadHistory> {
|
||||
Box::pin(InMemoryThreadStore::load_history(self, params))
|
||||
}
|
||||
|
||||
fn read_thread(&self, params: ReadThreadParams) -> ThreadStoreFuture<'_, StoredThread> {
|
||||
Box::pin(InMemoryThreadStore::read_thread(self, params))
|
||||
}
|
||||
|
||||
fn read_thread_by_rollout_path(
|
||||
&self,
|
||||
params: ReadThreadByRolloutPathParams,
|
||||
) -> ThreadStoreFuture<'_, StoredThread> {
|
||||
Box::pin(InMemoryThreadStore::read_thread_by_rollout_path(
|
||||
self, params,
|
||||
))
|
||||
}
|
||||
|
||||
fn list_threads(&self, _params: ListThreadsParams) -> ThreadStoreFuture<'_, ThreadPage> {
|
||||
Box::pin(InMemoryThreadStore::list_threads(self))
|
||||
}
|
||||
|
||||
fn update_thread_metadata(
|
||||
&self,
|
||||
params: UpdateThreadMetadataParams,
|
||||
) -> ThreadStoreFuture<'_, StoredThread> {
|
||||
Box::pin(InMemoryThreadStore::update_thread_metadata(self, params))
|
||||
}
|
||||
|
||||
fn archive_thread(&self, _params: ArchiveThreadParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.state.lock().await.calls.archive_thread += 1;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn unarchive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreFuture<'_, StoredThread> {
|
||||
Box::pin(async move {
|
||||
let mut state = self.state.lock().await;
|
||||
state.calls.unarchive_thread += 1;
|
||||
stored_thread_from_state(&state, params.thread_id, /*include_history*/ false)
|
||||
})
|
||||
}
|
||||
|
||||
fn delete_thread(&self, params: DeleteThreadParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(InMemoryThreadStore::delete_thread(self, params))
|
||||
}
|
||||
}
|
||||
|
||||
fn stored_thread_from_state(
|
||||
state: &InMemoryThreadStoreState,
|
||||
thread_id: ThreadId,
|
||||
|
||||
@@ -21,6 +21,7 @@ pub use live_thread::LiveThreadInitGuard;
|
||||
pub use local::LocalThreadStore;
|
||||
pub use local::LocalThreadStoreConfig;
|
||||
pub use store::ThreadStore;
|
||||
pub use store::ThreadStoreFuture;
|
||||
pub use types::AppendThreadItemsParams;
|
||||
pub use types::ArchiveThreadParams;
|
||||
pub use types::ClearableField;
|
||||
|
||||
@@ -12,7 +12,6 @@ mod update_thread_metadata;
|
||||
#[cfg(test)]
|
||||
mod test_support;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_rollout::RolloutRecorder;
|
||||
use codex_rollout::StateDbHandle;
|
||||
@@ -38,6 +37,7 @@ use crate::ThreadPage;
|
||||
use crate::ThreadSearchPage;
|
||||
use crate::ThreadStore;
|
||||
use crate::ThreadStoreError;
|
||||
use crate::ThreadStoreFuture;
|
||||
use crate::ThreadStoreResult;
|
||||
use crate::UpdateThreadMetadataParams;
|
||||
|
||||
@@ -166,41 +166,6 @@ impl LocalThreadStore {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ThreadStore for LocalThreadStore {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
async fn create_thread(&self, params: CreateThreadParams) -> ThreadStoreResult<()> {
|
||||
live_writer::create_thread(self, params).await
|
||||
}
|
||||
|
||||
async fn resume_thread(&self, params: ResumeThreadParams) -> ThreadStoreResult<()> {
|
||||
live_writer::resume_thread(self, params).await
|
||||
}
|
||||
|
||||
async fn append_items(&self, params: AppendThreadItemsParams) -> ThreadStoreResult<()> {
|
||||
live_writer::append_items(self, params).await
|
||||
}
|
||||
|
||||
async fn persist_thread(&self, thread_id: ThreadId) -> ThreadStoreResult<()> {
|
||||
live_writer::persist_thread(self, thread_id).await
|
||||
}
|
||||
|
||||
async fn flush_thread(&self, thread_id: ThreadId) -> ThreadStoreResult<()> {
|
||||
live_writer::flush_thread(self, thread_id).await
|
||||
}
|
||||
|
||||
async fn shutdown_thread(&self, thread_id: ThreadId) -> ThreadStoreResult<()> {
|
||||
live_writer::shutdown_thread(self, thread_id).await
|
||||
}
|
||||
|
||||
async fn discard_thread(&self, thread_id: ThreadId) -> ThreadStoreResult<()> {
|
||||
live_writer::discard_thread(self, thread_id).await
|
||||
}
|
||||
|
||||
async fn load_history(
|
||||
&self,
|
||||
@@ -245,11 +210,7 @@ impl ThreadStore for LocalThreadStore {
|
||||
})
|
||||
}
|
||||
|
||||
async fn read_thread(&self, params: ReadThreadParams) -> ThreadStoreResult<StoredThread> {
|
||||
read_thread::read_thread(self, params).await
|
||||
}
|
||||
|
||||
async fn read_thread_by_rollout_path(
|
||||
async fn read_thread_by_rollout_path_params(
|
||||
&self,
|
||||
params: ReadThreadByRolloutPathParams,
|
||||
) -> ThreadStoreResult<StoredThread> {
|
||||
@@ -261,38 +222,89 @@ impl ThreadStore for LocalThreadStore {
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_threads(&self, params: ListThreadsParams) -> ThreadStoreResult<ThreadPage> {
|
||||
list_threads::list_threads(self, params).await
|
||||
impl ThreadStore for LocalThreadStore {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
async fn search_threads(
|
||||
fn create_thread(&self, params: CreateThreadParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { live_writer::create_thread(self, params).await })
|
||||
}
|
||||
|
||||
fn resume_thread(&self, params: ResumeThreadParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { live_writer::resume_thread(self, params).await })
|
||||
}
|
||||
|
||||
fn append_items(&self, params: AppendThreadItemsParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { live_writer::append_items(self, params).await })
|
||||
}
|
||||
|
||||
fn persist_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { live_writer::persist_thread(self, thread_id).await })
|
||||
}
|
||||
|
||||
fn flush_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { live_writer::flush_thread(self, thread_id).await })
|
||||
}
|
||||
|
||||
fn shutdown_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { live_writer::shutdown_thread(self, thread_id).await })
|
||||
}
|
||||
|
||||
fn discard_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { live_writer::discard_thread(self, thread_id).await })
|
||||
}
|
||||
|
||||
fn load_history(
|
||||
&self,
|
||||
params: LoadThreadHistoryParams,
|
||||
) -> ThreadStoreFuture<'_, StoredThreadHistory> {
|
||||
Box::pin(LocalThreadStore::load_history(self, params))
|
||||
}
|
||||
|
||||
fn read_thread(&self, params: ReadThreadParams) -> ThreadStoreFuture<'_, StoredThread> {
|
||||
Box::pin(async move { read_thread::read_thread(self, params).await })
|
||||
}
|
||||
|
||||
fn read_thread_by_rollout_path(
|
||||
&self,
|
||||
params: ReadThreadByRolloutPathParams,
|
||||
) -> ThreadStoreFuture<'_, StoredThread> {
|
||||
Box::pin(LocalThreadStore::read_thread_by_rollout_path_params(
|
||||
self, params,
|
||||
))
|
||||
}
|
||||
|
||||
fn list_threads(&self, params: ListThreadsParams) -> ThreadStoreFuture<'_, ThreadPage> {
|
||||
Box::pin(async move { list_threads::list_threads(self, params).await })
|
||||
}
|
||||
|
||||
fn search_threads(
|
||||
&self,
|
||||
params: SearchThreadsParams,
|
||||
) -> ThreadStoreResult<ThreadSearchPage> {
|
||||
search_threads::search_threads(self, params).await
|
||||
) -> ThreadStoreFuture<'_, ThreadSearchPage> {
|
||||
Box::pin(async move { search_threads::search_threads(self, params).await })
|
||||
}
|
||||
|
||||
async fn update_thread_metadata(
|
||||
fn update_thread_metadata(
|
||||
&self,
|
||||
params: UpdateThreadMetadataParams,
|
||||
) -> ThreadStoreResult<StoredThread> {
|
||||
update_thread_metadata::update_thread_metadata(self, params).await
|
||||
) -> ThreadStoreFuture<'_, StoredThread> {
|
||||
Box::pin(async move { update_thread_metadata::update_thread_metadata(self, params).await })
|
||||
}
|
||||
|
||||
async fn archive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreResult<()> {
|
||||
archive_thread::archive_thread(self, params).await
|
||||
fn archive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { archive_thread::archive_thread(self, params).await })
|
||||
}
|
||||
|
||||
async fn unarchive_thread(
|
||||
&self,
|
||||
params: ArchiveThreadParams,
|
||||
) -> ThreadStoreResult<StoredThread> {
|
||||
unarchive_thread::unarchive_thread(self, params).await
|
||||
fn unarchive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreFuture<'_, StoredThread> {
|
||||
Box::pin(async move { unarchive_thread::unarchive_thread(self, params).await })
|
||||
}
|
||||
|
||||
async fn delete_thread(&self, params: DeleteThreadParams) -> ThreadStoreResult<()> {
|
||||
delete_thread::delete_thread(self, params).await
|
||||
fn delete_thread(&self, params: DeleteThreadParams) -> ThreadStoreFuture<'_, ()> {
|
||||
Box::pin(async move { delete_thread::delete_thread(self, params).await })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::ThreadId;
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::AppendThreadItemsParams;
|
||||
use crate::ArchiveThreadParams;
|
||||
@@ -24,81 +25,89 @@ use crate::ThreadStoreResult;
|
||||
use crate::TurnPage;
|
||||
use crate::UpdateThreadMetadataParams;
|
||||
|
||||
/// Future returned by [`ThreadStore`] operations.
|
||||
pub type ThreadStoreFuture<'a, T> = Pin<Box<dyn Future<Output = ThreadStoreResult<T>> + Send + 'a>>;
|
||||
|
||||
/// Storage-neutral thread persistence boundary.
|
||||
#[async_trait]
|
||||
pub trait ThreadStore: Any + Send + Sync {
|
||||
/// Return this store as [`Any`] for implementation-owned escape hatches.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Creates a new live thread.
|
||||
async fn create_thread(&self, params: CreateThreadParams) -> ThreadStoreResult<()>;
|
||||
fn create_thread(&self, params: CreateThreadParams) -> ThreadStoreFuture<'_, ()>;
|
||||
|
||||
/// Reopens an existing thread for live appends.
|
||||
async fn resume_thread(&self, params: ResumeThreadParams) -> ThreadStoreResult<()>;
|
||||
fn resume_thread(&self, params: ResumeThreadParams) -> ThreadStoreFuture<'_, ()>;
|
||||
|
||||
/// Appends raw rollout items to a live thread.
|
||||
///
|
||||
/// Implementations should apply the shared rollout persistence policy before writing durable
|
||||
/// replay history and before updating any implementation-owned projections.
|
||||
async fn append_items(&self, params: AppendThreadItemsParams) -> ThreadStoreResult<()>;
|
||||
fn append_items(&self, params: AppendThreadItemsParams) -> ThreadStoreFuture<'_, ()>;
|
||||
|
||||
/// Materializes the thread if persistence is lazy, then persists all queued items.
|
||||
async fn persist_thread(&self, thread_id: ThreadId) -> ThreadStoreResult<()>;
|
||||
fn persist_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()>;
|
||||
|
||||
/// Flushes all queued items and returns once they are durable/readable.
|
||||
async fn flush_thread(&self, thread_id: ThreadId) -> ThreadStoreResult<()>;
|
||||
fn flush_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()>;
|
||||
|
||||
/// Flushes pending items and closes the live thread writer.
|
||||
async fn shutdown_thread(&self, thread_id: ThreadId) -> ThreadStoreResult<()>;
|
||||
fn shutdown_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()>;
|
||||
|
||||
/// Discards the live thread writer without forcing pending in-memory items to become durable.
|
||||
///
|
||||
/// Core calls this when session initialization fails after a live writer has been created.
|
||||
/// Implementations should release any live writer resources for the thread while preserving
|
||||
/// already-durable thread data.
|
||||
async fn discard_thread(&self, thread_id: ThreadId) -> ThreadStoreResult<()>;
|
||||
fn discard_thread(&self, thread_id: ThreadId) -> ThreadStoreFuture<'_, ()>;
|
||||
|
||||
/// Loads persisted history for resume, fork, rollback, and memory jobs.
|
||||
async fn load_history(
|
||||
fn load_history(
|
||||
&self,
|
||||
params: LoadThreadHistoryParams,
|
||||
) -> ThreadStoreResult<StoredThreadHistory>;
|
||||
) -> ThreadStoreFuture<'_, StoredThreadHistory>;
|
||||
|
||||
/// Reads a thread summary and optionally its persisted history.
|
||||
async fn read_thread(&self, params: ReadThreadParams) -> ThreadStoreResult<StoredThread>;
|
||||
fn read_thread(&self, params: ReadThreadParams) -> ThreadStoreFuture<'_, StoredThread>;
|
||||
|
||||
/// Reads a rollout-backed thread by path when the store supports path-addressed lookups.
|
||||
///
|
||||
/// Deprecated: new callers should use [`ThreadStore::read_thread`] instead.
|
||||
async fn read_thread_by_rollout_path(
|
||||
fn read_thread_by_rollout_path(
|
||||
&self,
|
||||
params: ReadThreadByRolloutPathParams,
|
||||
) -> ThreadStoreResult<StoredThread>;
|
||||
) -> ThreadStoreFuture<'_, StoredThread>;
|
||||
|
||||
/// Lists stored threads matching the supplied filters.
|
||||
async fn list_threads(&self, params: ListThreadsParams) -> ThreadStoreResult<ThreadPage>;
|
||||
fn list_threads(&self, params: ListThreadsParams) -> ThreadStoreFuture<'_, ThreadPage>;
|
||||
|
||||
/// Searches stored threads and returns search-only preview metadata.
|
||||
async fn search_threads(
|
||||
fn search_threads(
|
||||
&self,
|
||||
_params: SearchThreadsParams,
|
||||
) -> ThreadStoreResult<ThreadSearchPage> {
|
||||
Err(ThreadStoreError::Unsupported {
|
||||
operation: "thread/search",
|
||||
) -> ThreadStoreFuture<'_, ThreadSearchPage> {
|
||||
Box::pin(async {
|
||||
Err(ThreadStoreError::Unsupported {
|
||||
operation: "thread/search",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Lists turns within a stored thread.
|
||||
async fn list_turns(&self, _params: ListTurnsParams) -> ThreadStoreResult<TurnPage> {
|
||||
Err(ThreadStoreError::Unsupported {
|
||||
operation: "list_turns",
|
||||
fn list_turns(&self, _params: ListTurnsParams) -> ThreadStoreFuture<'_, TurnPage> {
|
||||
Box::pin(async {
|
||||
Err(ThreadStoreError::Unsupported {
|
||||
operation: "list_turns",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Lists persisted items within a stored turn.
|
||||
async fn list_items(&self, _params: ListItemsParams) -> ThreadStoreResult<ItemPage> {
|
||||
Err(ThreadStoreError::Unsupported {
|
||||
operation: "list_items",
|
||||
fn list_items(&self, _params: ListItemsParams) -> ThreadStoreFuture<'_, ItemPage> {
|
||||
Box::pin(async {
|
||||
Err(ThreadStoreError::Unsupported {
|
||||
operation: "list_items",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -106,20 +115,17 @@ pub trait ThreadStore: Any + Send + Sync {
|
||||
///
|
||||
/// Implementations should apply the supplied fields directly. Policy such as deciding whether
|
||||
/// an append-derived preview should be emitted belongs above the store.
|
||||
async fn update_thread_metadata(
|
||||
fn update_thread_metadata(
|
||||
&self,
|
||||
params: UpdateThreadMetadataParams,
|
||||
) -> ThreadStoreResult<StoredThread>;
|
||||
) -> ThreadStoreFuture<'_, StoredThread>;
|
||||
|
||||
/// Archives a thread.
|
||||
async fn archive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreResult<()>;
|
||||
fn archive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreFuture<'_, ()>;
|
||||
|
||||
/// Unarchives a thread and returns its updated metadata.
|
||||
async fn unarchive_thread(
|
||||
&self,
|
||||
params: ArchiveThreadParams,
|
||||
) -> ThreadStoreResult<StoredThread>;
|
||||
fn unarchive_thread(&self, params: ArchiveThreadParams) -> ThreadStoreFuture<'_, StoredThread>;
|
||||
|
||||
/// Deletes a thread's persisted rollout data and associated metadata.
|
||||
async fn delete_thread(&self, params: DeleteThreadParams) -> ThreadStoreResult<()>;
|
||||
fn delete_thread(&self, params: DeleteThreadParams) -> ThreadStoreFuture<'_, ()>;
|
||||
}
|
||||
|
||||
@@ -138,9 +138,8 @@ pub(crate) trait WorkspaceCommandExecutor: Send + Sync {
|
||||
/// Runs a workspace command and returns captured output or an app-server request error.
|
||||
///
|
||||
/// Callers should treat errors as infrastructure failures and should treat successful output
|
||||
/// with a non-zero exit code as ordinary command failure. Returning a future instead of using
|
||||
/// `async_trait` keeps the trait object-safe while matching the repo's native async trait
|
||||
/// conventions.
|
||||
/// with a non-zero exit code as ordinary command failure. Returning a boxed future keeps the
|
||||
/// trait object-safe.
|
||||
fn run(
|
||||
&self,
|
||||
command: WorkspaceCommand,
|
||||
|
||||
@@ -5,7 +5,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "time"] }
|
||||
|
||||
@@ -17,7 +17,6 @@ pub struct Token(i32);
|
||||
|
||||
const LOCK_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Readiness: Send + Sync + 'static {
|
||||
/// Returns true if the flag is currently marked ready. At least one token needs to be marked
|
||||
/// as ready before.
|
||||
@@ -27,17 +26,22 @@ pub trait Readiness: Send + Sync + 'static {
|
||||
/// Subscribe to readiness and receive an authorization token.
|
||||
///
|
||||
/// If the flag is already ready, returns `FlagAlreadyReady`.
|
||||
async fn subscribe(&self) -> Result<Token, errors::ReadinessError>;
|
||||
fn subscribe(
|
||||
&self,
|
||||
) -> impl std::future::Future<Output = Result<Token, errors::ReadinessError>> + Send;
|
||||
|
||||
/// Attempt to mark the flag ready, validated by the provided token.
|
||||
///
|
||||
/// Returns `true` iff:
|
||||
/// - `token` is currently subscribed, and
|
||||
/// - the flag was not already ready.
|
||||
async fn mark_ready(&self, token: Token) -> Result<bool, errors::ReadinessError>;
|
||||
fn mark_ready(
|
||||
&self,
|
||||
token: Token,
|
||||
) -> impl std::future::Future<Output = Result<bool, errors::ReadinessError>> + Send;
|
||||
|
||||
/// Asynchronously wait until the flag becomes ready.
|
||||
async fn wait_ready(&self);
|
||||
fn wait_ready(&self) -> impl std::future::Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
pub struct ReadinessFlag {
|
||||
@@ -92,7 +96,6 @@ impl fmt::Debug for ReadinessFlag {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Readiness for ReadinessFlag {
|
||||
fn is_ready(&self) -> bool {
|
||||
if self.load_ready() {
|
||||
|
||||
Reference in New Issue
Block a user