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>(
66 &self,
67 op: impl FnOnce(&dyn ArtifactStore) -> Result<T, StoreErr> + Send + 'static,
68 ) -> Result<T, DomainError> {
69 let store = Arc::clone(&self.store);
70 let joined = tokio::task::spawn_blocking(move || op(store.as_ref()))
71 .await
72 .map_err(|err| {
73 DomainError::Store(StoreErr::Io(format!("store worker join failed: {err}")))
74 })?;
75 joined.map_err(DomainError::from)
76 }
77
78 pub async fn open(&self, artifact_id: &str) -> Result<ArtifactSession, DomainError> {
79 self.ensure_contract_compatible()?;
80
81 let artifact_id_owned = artifact_id.to_owned();
82 let mut meta = self
83 .store_io({
84 let artifact_id = artifact_id_owned.clone();
85 move |store| execution::load_or_default_meta(store, &artifact_id)
86 })
87 .await?;
88
89 let thread_id = match meta.runtime_thread_id.as_deref() {
90 Some(existing) => self.adapter.resume_thread(existing).await?,
91 None => self.adapter.start_thread().await?,
92 };
93 meta.runtime_thread_id = Some(thread_id.clone());
94 self.store_io({
95 let artifact_id = artifact_id_owned.clone();
96 let meta_to_store = meta.clone();
97 move |store| store.set_meta(&artifact_id, meta_to_store)
98 })
99 .await?;
100
101 Ok(ArtifactSession {
102 artifact_id: artifact_id_owned,
103 thread_id,
104 format: meta.format,
105 revision: meta.revision,
106 })
107 }
108
109 pub async fn run_task(
114 &self,
115 spec: ArtifactTaskSpec,
116 ) -> Result<ArtifactTaskResult, DomainError> {
117 execution::run_task(self, spec).await
118 }
119
120 fn ensure_contract_compatible(&self) -> Result<(), DomainError> {
121 if let Some(mismatch) = self.contract_mismatch {
122 return Err(DomainError::IncompatibleContract {
123 expected_major: mismatch.expected.major,
124 expected_minor: mismatch.expected.minor,
125 actual_major: mismatch.actual.major,
126 actual_minor: mismatch.actual.minor,
127 });
128 }
129 Ok(())
130 }
131}
132
133fn detect_contract_mismatch(adapter: &dyn ArtifactPluginAdapter) -> Option<ContractMismatch> {
134 let expected = PluginContractVersion::CURRENT;
135 let actual = adapter.plugin_contract_version();
136 if expected.is_compatible_with(actual) {
137 None
138 } else {
139 Some(ContractMismatch { expected, actual })
140 }
141}
142#[cfg(test)]
143mod tests;