codex_runtime/domain/artifact/
mod.rs1use std::sync::Arc;
2
3use crate::plugin::PluginContractVersion;
4use crate::runtime::core::Runtime;
5
6mod adapter;
7mod execution;
8mod lock_policy;
9mod models;
10mod store;
11
12#[cfg(test)]
13pub(crate) use adapter::{ArtifactAdapterFuture, ArtifactTurnOutput};
14pub use adapter::{ArtifactPluginAdapter, RuntimeArtifactAdapter};
15
16#[cfg(test)]
17pub(crate) use models::DocEdit;
18pub use models::{
19 apply_doc_patch, compute_revision, validate_doc_patch, ArtifactMeta, ArtifactSession,
20 ArtifactStore, ArtifactTaskKind, ArtifactTaskResult, ArtifactTaskSpec, DocPatch, DomainError,
21 FsArtifactStore, PatchConflict, SaveMeta, StoreErr, ValidatedPatch,
22};
23
24#[cfg(test)]
25pub(crate) use execution::collect_turn_output_from_live_with_limits;
26#[cfg(test)]
27pub(crate) use execution::debug_with_forced_turn_start_params_serialization_failure;
28#[cfg(test)]
29pub(crate) use execution::{build_turn_prompt, build_turn_start_params};
30#[cfg(test)]
31pub(crate) use store::artifact_key;
32
33#[derive(Clone)]
34pub struct ArtifactSessionManager {
35 adapter: Arc<dyn ArtifactPluginAdapter>,
36 store: Arc<dyn ArtifactStore>,
37 contract_mismatch: Option<ContractMismatch>,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41struct ContractMismatch {
42 expected: PluginContractVersion,
43 actual: PluginContractVersion,
44}
45
46impl ArtifactSessionManager {
47 pub fn new(runtime: Runtime, store: Arc<dyn ArtifactStore>) -> Self {
48 let adapter: Arc<dyn ArtifactPluginAdapter> =
49 Arc::new(RuntimeArtifactAdapter::new(runtime));
50 Self::new_with_adapter(adapter, store)
51 }
52
53 pub fn new_with_adapter(
54 adapter: Arc<dyn ArtifactPluginAdapter>,
55 store: Arc<dyn ArtifactStore>,
56 ) -> Self {
57 let contract_mismatch = detect_contract_mismatch(adapter.as_ref());
58 Self {
59 adapter,
60 store,
61 contract_mismatch,
62 }
63 }
64
65 async fn store_io<T: Send + 'static>(
69 &self,
70 op: impl FnOnce(&dyn ArtifactStore) -> Result<T, StoreErr> + Send + 'static,
71 ) -> Result<T, DomainError> {
72 let store = Arc::clone(&self.store);
73 let joined = tokio::task::spawn_blocking(move || op(store.as_ref()))
74 .await
75 .map_err(|err| {
76 DomainError::Store(StoreErr::Io(format!("store worker join failed: {err}")))
77 })?;
78 joined.map_err(DomainError::from)
79 }
80
81 pub async fn open(&self, artifact_id: &str) -> Result<ArtifactSession, DomainError> {
82 self.ensure_contract_compatible()?;
83
84 let artifact_id_owned = artifact_id.to_owned();
85 let mut meta = self
86 .store_io({
87 let artifact_id = artifact_id_owned.clone();
88 move |store| execution::load_or_default_meta(store, &artifact_id)
89 })
90 .await?;
91
92 let thread_id = match meta.runtime_thread_id.as_deref() {
93 Some(existing) => self.adapter.resume_thread(existing).await?,
94 None => self.adapter.start_thread().await?,
95 };
96 meta.runtime_thread_id = Some(thread_id.clone());
97 self.store_io({
98 let artifact_id = artifact_id_owned.clone();
99 let meta_to_store = meta.clone();
100 move |store| store.set_meta(&artifact_id, meta_to_store)
101 })
102 .await?;
103
104 Ok(ArtifactSession {
105 artifact_id: artifact_id_owned,
106 thread_id,
107 format: meta.format,
108 revision: meta.revision,
109 })
110 }
111
112 pub async fn run_task(
117 &self,
118 spec: ArtifactTaskSpec,
119 ) -> Result<ArtifactTaskResult, DomainError> {
120 execution::run_task(self, spec).await
121 }
122
123 fn ensure_contract_compatible(&self) -> Result<(), DomainError> {
124 if let Some(mismatch) = self.contract_mismatch {
125 return Err(DomainError::IncompatibleContract {
126 expected_major: mismatch.expected.major,
127 expected_minor: mismatch.expected.minor,
128 actual_major: mismatch.actual.major,
129 actual_minor: mismatch.actual.minor,
130 });
131 }
132 Ok(())
133 }
134}
135
136fn detect_contract_mismatch(adapter: &dyn ArtifactPluginAdapter) -> Option<ContractMismatch> {
137 let expected = PluginContractVersion::CURRENT;
138 let actual = adapter.plugin_contract_version();
139 if expected.is_compatible_with(actual) {
140 None
141 } else {
142 Some(ContractMismatch { expected, actual })
143 }
144}
145#[cfg(test)]
146mod tests;