mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
Add a ConfigStore interface
This commit is contained in:
14
codex-rs/Cargo.lock
generated
14
codex-rs/Cargo.lock
generated
@@ -1875,6 +1875,20 @@ dependencies = [
|
||||
"wildmatch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-config-store"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"codex-utils-absolute-path",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-connectors"
|
||||
version = "0.0.0"
|
||||
|
||||
@@ -23,6 +23,7 @@ members = [
|
||||
"collaboration-mode-templates",
|
||||
"connectors",
|
||||
"config",
|
||||
"config-store",
|
||||
"shell-command",
|
||||
"shell-escalation",
|
||||
"skills",
|
||||
@@ -125,6 +126,7 @@ codex-cloud-tasks-client = { path = "cloud-tasks-client" }
|
||||
codex-cloud-tasks-mock-client = { path = "cloud-tasks-mock-client" }
|
||||
codex-code-mode = { path = "code-mode" }
|
||||
codex-config = { path = "config" }
|
||||
codex-config-store = { path = "config-store" }
|
||||
codex-connectors = { path = "connectors" }
|
||||
codex-core = { path = "core" }
|
||||
codex-core-skills = { path = "core-skills" }
|
||||
|
||||
6
codex-rs/config-store/BUILD.bazel
Normal file
6
codex-rs/config-store/BUILD.bazel
Normal file
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "config-store",
|
||||
crate_name = "codex_config_store",
|
||||
)
|
||||
24
codex-rs/config-store/Cargo.toml
Normal file
24
codex-rs/config-store/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "codex-config-store"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "codex_config_store"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt"] }
|
||||
27
codex-rs/config-store/src/error.rs
Normal file
27
codex-rs/config-store/src/error.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
/// Result type returned by config-store operations.
|
||||
pub type ConfigStoreResult<T> = Result<T, ConfigStoreError>;
|
||||
|
||||
/// Error type shared by config-store implementations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConfigStoreError {
|
||||
/// The caller supplied invalid request data.
|
||||
#[error("invalid config-store request: {message}")]
|
||||
InvalidRequest {
|
||||
/// User-facing explanation of the invalid request.
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// A backing source could not be queried.
|
||||
#[error("config-store read failed: {message}")]
|
||||
ReadFailed {
|
||||
/// User-facing explanation of the read failure.
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// Catch-all for implementation failures that do not fit a more specific category.
|
||||
#[error("config-store internal error: {message}")]
|
||||
Internal {
|
||||
/// User-facing explanation of the implementation failure.
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
19
codex-rs/config-store/src/lib.rs
Normal file
19
codex-rs/config-store/src/lib.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
//! Storage-neutral interfaces for loading config-layer documents.
|
||||
//!
|
||||
//! Implementations should report observations from their backing store. Codex config loading
|
||||
//! remains responsible for applying precedence, project trust, path resolution, requirements, and
|
||||
//! final layer merging.
|
||||
//!
|
||||
//! The request and response types in this crate may cross process or network boundaries. Keep them
|
||||
//! wire-friendly: prefer primitive fields over Rust-specific error or filesystem types.
|
||||
|
||||
mod error;
|
||||
mod store;
|
||||
mod types;
|
||||
|
||||
pub use error::ConfigStoreError;
|
||||
pub use error::ConfigStoreResult;
|
||||
pub use store::ConfigDocumentStore;
|
||||
pub use types::ConfigDocumentErrorSpan;
|
||||
pub use types::ConfigDocumentRead;
|
||||
pub use types::ReadConfigDocumentParams;
|
||||
20
codex-rs/config-store/src/store.rs
Normal file
20
codex-rs/config-store/src/store.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::ConfigDocumentRead;
|
||||
use crate::ConfigStoreResult;
|
||||
use crate::ReadConfigDocumentParams;
|
||||
|
||||
/// Storage-neutral reader for path-addressed config documents.
|
||||
///
|
||||
/// Implementations should only read and parse the requested document. Codex config loading remains
|
||||
/// responsible for deciding what the document represents, how missing documents are handled, how
|
||||
/// parse errors interact with project trust, how relative paths are resolved, and how layers are
|
||||
/// ordered and merged.
|
||||
#[async_trait]
|
||||
pub trait ConfigDocumentStore: Send + Sync {
|
||||
/// Reads one config document addressed by path.
|
||||
async fn read_config_document(
|
||||
&self,
|
||||
params: ReadConfigDocumentParams,
|
||||
) -> ConfigStoreResult<ConfigDocumentRead>;
|
||||
}
|
||||
119
codex-rs/config-store/src/types.rs
Normal file
119
codex-rs/config-store/src/types.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::ops::Range;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
/// Request to read one path-addressed config document.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ReadConfigDocumentParams {
|
||||
/// Absolute path to the config document to read.
|
||||
pub path: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
/// Byte span for a config document parse error.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ConfigDocumentErrorSpan {
|
||||
/// Inclusive byte offset where the error starts.
|
||||
pub start: usize,
|
||||
|
||||
/// Exclusive byte offset where the error ends.
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl From<Range<usize>> for ConfigDocumentErrorSpan {
|
||||
fn from(span: Range<usize>) -> Self {
|
||||
Self {
|
||||
start: span.start,
|
||||
end: span.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigDocumentErrorSpan> for Range<usize> {
|
||||
fn from(span: ConfigDocumentErrorSpan) -> Self {
|
||||
span.start..span.end
|
||||
}
|
||||
}
|
||||
|
||||
/// Read and parse state for one config document.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "status", rename_all = "snake_case")]
|
||||
pub enum ConfigDocumentRead {
|
||||
/// The backing source was absent.
|
||||
Missing,
|
||||
|
||||
/// The backing source was present and parsed successfully.
|
||||
Present {
|
||||
/// Parsed TOML document.
|
||||
value: TomlValue,
|
||||
},
|
||||
|
||||
/// The backing source was present but could not be parsed as TOML.
|
||||
///
|
||||
/// This is distinct from ConfigDocumentRead::ReadError because project config parse errors are
|
||||
/// fatal only after Codex applies project trust policy.
|
||||
ParseError {
|
||||
/// Original TOML text that failed to parse.
|
||||
raw_toml: String,
|
||||
|
||||
/// User-facing parse failure message.
|
||||
message: String,
|
||||
|
||||
/// Optional byte span for the parse failure.
|
||||
span: Option<ConfigDocumentErrorSpan>,
|
||||
},
|
||||
|
||||
/// The provider could not read the backing source.
|
||||
ReadError {
|
||||
/// Primitive read failure kind, such as "permission_denied" or "other".
|
||||
kind: String,
|
||||
|
||||
/// User-facing read failure message.
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use async_trait::async_trait;
|
||||
use pretty_assertions::assert_eq;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
use super::*;
|
||||
use crate::ConfigDocumentStore;
|
||||
use crate::ConfigStoreResult;
|
||||
|
||||
struct StaticDocumentStore {
|
||||
document: ConfigDocumentRead,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ConfigDocumentStore for StaticDocumentStore {
|
||||
async fn read_config_document(
|
||||
&self,
|
||||
_params: ReadConfigDocumentParams,
|
||||
) -> ConfigStoreResult<ConfigDocumentRead> {
|
||||
Ok(self.document.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn store_trait_can_return_config_documents() {
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
let path =
|
||||
AbsolutePathBuf::from_absolute_path(temp_dir.path().join("config.toml")).expect("abs");
|
||||
let value = TomlValue::Table(toml::map::Map::new());
|
||||
let document = ConfigDocumentRead::Present { value };
|
||||
let store = StaticDocumentStore {
|
||||
document: document.clone(),
|
||||
};
|
||||
|
||||
let got = store
|
||||
.read_config_document(ReadConfigDocumentParams { path })
|
||||
.await
|
||||
.expect("read document");
|
||||
|
||||
assert_eq!(got, document);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user