mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
code-mode standalone: extract protocol and add host crate (#27724)
This is phase 1 of a 4 phase stack: 1. **Add protocol and host crates for new IPC code mode implementation** 2. Create the new standalone binary 3. Create a new IPC `CodeModeSessionProvider` to use new binary 4. Remove v8 from core and only use IPC provider ## Add protocol and host crates for new IPC code mode implementation Establish a clean process boundary without changing the existing in-process behavior. - Add the codex-code-mode-protocol crate for shared session, runtime, response, and tool-definition types. - Move protocol-facing code out of the V8-backed implementation. - Add a buildable codex-code-mode-host crate as the foundation for the standalone process. - Keep the existing in-process runtime as the active implementation.
This commit is contained in:
committed by
GitHub
Unverified
parent
216ce03031
commit
aa46f2debf
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
mod description;
|
||||
mod response;
|
||||
mod runtime;
|
||||
mod session;
|
||||
|
||||
pub use description::CODE_MODE_PRAGMA_PREFIX;
|
||||
pub use description::CodeModeToolKind;
|
||||
pub use description::EnabledToolMetadata;
|
||||
pub use description::ToolDefinition;
|
||||
pub use description::ToolNamespaceDescription;
|
||||
pub use description::augment_tool_definition;
|
||||
pub use description::build_exec_tool_description;
|
||||
pub use description::build_wait_tool_description;
|
||||
pub use description::enabled_tool_metadata;
|
||||
pub use description::is_code_mode_nested_tool;
|
||||
pub use description::normalize_code_mode_identifier;
|
||||
pub use description::parse_exec_source;
|
||||
pub use description::render_code_mode_sample;
|
||||
pub use description::render_json_schema_to_typescript;
|
||||
pub use response::DEFAULT_IMAGE_DETAIL;
|
||||
pub use response::FunctionCallOutputContentItem;
|
||||
pub use response::ImageDetail;
|
||||
pub use runtime::CodeModeNestedToolCall;
|
||||
pub use runtime::DEFAULT_EXEC_YIELD_TIME_MS;
|
||||
pub use runtime::DEFAULT_MAX_OUTPUT_TOKENS_PER_EXEC_CALL;
|
||||
pub use runtime::DEFAULT_WAIT_YIELD_TIME_MS;
|
||||
pub use runtime::ExecuteRequest;
|
||||
pub use runtime::ExecuteToPendingOutcome;
|
||||
pub use runtime::RuntimeResponse;
|
||||
pub use runtime::WaitOutcome;
|
||||
pub use runtime::WaitRequest;
|
||||
pub use runtime::WaitToPendingOutcome;
|
||||
pub use runtime::WaitToPendingRequest;
|
||||
pub use session::CellId;
|
||||
pub use session::CodeModeSession;
|
||||
pub use session::CodeModeSessionDelegate;
|
||||
pub use session::CodeModeSessionProvider;
|
||||
pub use session::CodeModeSessionProviderFuture;
|
||||
pub use session::CodeModeSessionResultFuture;
|
||||
pub use session::NotificationFuture;
|
||||
pub use session::StartedCell;
|
||||
pub use session::ToolInvocationFuture;
|
||||
|
||||
pub const PUBLIC_TOOL_NAME: &str = "exec";
|
||||
pub const WAIT_TOOL_NAME: &str = "wait";
|
||||
@@ -0,0 +1,26 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ImageDetail {
|
||||
Auto,
|
||||
Low,
|
||||
High,
|
||||
Original,
|
||||
}
|
||||
|
||||
pub const DEFAULT_IMAGE_DETAIL: ImageDetail = ImageDetail::High;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum FunctionCallOutputContentItem {
|
||||
InputText {
|
||||
text: String,
|
||||
},
|
||||
InputImage {
|
||||
image_url: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
detail: Option<ImageDetail>,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
use codex_protocol::ToolName;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use crate::CellId;
|
||||
use crate::CodeModeToolKind;
|
||||
use crate::FunctionCallOutputContentItem;
|
||||
use crate::ToolDefinition;
|
||||
|
||||
pub const DEFAULT_EXEC_YIELD_TIME_MS: u64 = 10_000;
|
||||
pub const DEFAULT_WAIT_YIELD_TIME_MS: u64 = 10_000;
|
||||
pub const DEFAULT_MAX_OUTPUT_TOKENS_PER_EXEC_CALL: usize = 10_000;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct ExecuteRequest {
|
||||
pub tool_call_id: String,
|
||||
pub enabled_tools: Vec<ToolDefinition>,
|
||||
pub source: String,
|
||||
pub yield_time_ms: Option<u64>,
|
||||
pub max_output_tokens: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct WaitRequest {
|
||||
pub cell_id: CellId,
|
||||
pub yield_time_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct WaitToPendingRequest {
|
||||
pub cell_id: CellId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum WaitOutcome {
|
||||
LiveCell(RuntimeResponse),
|
||||
MissingCell(RuntimeResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum ExecuteToPendingOutcome {
|
||||
Pending {
|
||||
cell_id: CellId,
|
||||
content_items: Vec<FunctionCallOutputContentItem>,
|
||||
pending_tool_call_ids: Vec<String>,
|
||||
},
|
||||
Completed(RuntimeResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum WaitToPendingOutcome {
|
||||
LiveCell(ExecuteToPendingOutcome),
|
||||
MissingCell(RuntimeResponse),
|
||||
}
|
||||
|
||||
impl From<WaitOutcome> for RuntimeResponse {
|
||||
fn from(outcome: WaitOutcome) -> Self {
|
||||
match outcome {
|
||||
WaitOutcome::LiveCell(response) | WaitOutcome::MissingCell(response) => response,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum RuntimeResponse {
|
||||
Yielded {
|
||||
cell_id: CellId,
|
||||
content_items: Vec<FunctionCallOutputContentItem>,
|
||||
},
|
||||
Terminated {
|
||||
cell_id: CellId,
|
||||
content_items: Vec<FunctionCallOutputContentItem>,
|
||||
},
|
||||
Result {
|
||||
cell_id: CellId,
|
||||
content_items: Vec<FunctionCallOutputContentItem>,
|
||||
error_text: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct CodeModeNestedToolCall {
|
||||
pub cell_id: CellId,
|
||||
pub runtime_tool_call_id: String,
|
||||
pub tool_name: ToolName,
|
||||
pub tool_kind: CodeModeToolKind,
|
||||
pub input: Option<JsonValue>,
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::CodeModeNestedToolCall;
|
||||
use crate::ExecuteRequest;
|
||||
use crate::RuntimeResponse;
|
||||
use crate::WaitOutcome;
|
||||
use crate::WaitRequest;
|
||||
|
||||
pub type CodeModeSessionResultFuture<'a, T> =
|
||||
Pin<Box<dyn Future<Output = Result<T, String>> + Send + 'a>>;
|
||||
pub type CodeModeSessionProviderFuture<'a> =
|
||||
CodeModeSessionResultFuture<'a, Arc<dyn CodeModeSession>>;
|
||||
pub type ToolInvocationFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = Result<JsonValue, String>> + Send + 'a>>;
|
||||
pub type NotificationFuture<'a> = Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'a>>;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct CellId(String);
|
||||
|
||||
impl CellId {
|
||||
pub fn new(value: String) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for CellId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CellId {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StartedCell {
|
||||
pub cell_id: CellId,
|
||||
initial_response: CodeModeSessionResultFuture<'static, RuntimeResponse>,
|
||||
}
|
||||
|
||||
impl StartedCell {
|
||||
pub fn new(cell_id: CellId, initial_response_rx: oneshot::Receiver<RuntimeResponse>) -> Self {
|
||||
Self {
|
||||
cell_id,
|
||||
initial_response: Box::pin(async move {
|
||||
initial_response_rx
|
||||
.await
|
||||
.map_err(|_| "exec runtime ended unexpectedly".to_string())
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_result_receiver(
|
||||
cell_id: CellId,
|
||||
initial_response_rx: oneshot::Receiver<Result<RuntimeResponse, String>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cell_id,
|
||||
initial_response: Box::pin(async move {
|
||||
initial_response_rx
|
||||
.await
|
||||
.map_err(|_| "exec runtime ended unexpectedly".to_string())?
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initial_response(self) -> Result<RuntimeResponse, String> {
|
||||
self.initial_response.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Host callbacks used by a code-mode session while cells are executing.
|
||||
pub trait CodeModeSessionDelegate: Send + Sync {
|
||||
fn invoke_tool<'a>(
|
||||
&'a self,
|
||||
invocation: CodeModeNestedToolCall,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> ToolInvocationFuture<'a>;
|
||||
|
||||
fn notify<'a>(
|
||||
&'a self,
|
||||
call_id: String,
|
||||
cell_id: CellId,
|
||||
text: String,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> NotificationFuture<'a>;
|
||||
|
||||
/// Releases delegate state associated with a cell after it reaches a terminal state.
|
||||
fn cell_closed(&self, cell_id: &CellId);
|
||||
}
|
||||
|
||||
/// A durable code-mode session owned by one Codex thread.
|
||||
///
|
||||
/// Cells executed in the same session share stored values. Separate sessions
|
||||
/// must keep those values isolated. Implementations may execute cells
|
||||
/// in-process or remotely.
|
||||
pub trait CodeModeSession: Send + Sync {
|
||||
/// Returns whether the session can still accept requests.
|
||||
///
|
||||
/// Remote implementations should return `false` after their underlying
|
||||
/// connection fails so callers can create a fresh session for later work.
|
||||
fn is_alive(&self) -> bool;
|
||||
|
||||
fn execute<'a>(
|
||||
&'a self,
|
||||
request: ExecuteRequest,
|
||||
) -> CodeModeSessionResultFuture<'a, StartedCell>;
|
||||
|
||||
fn wait<'a>(&'a self, request: WaitRequest) -> CodeModeSessionResultFuture<'a, WaitOutcome>;
|
||||
|
||||
fn terminate<'a>(&'a self, cell_id: CellId) -> CodeModeSessionResultFuture<'a, WaitOutcome>;
|
||||
|
||||
fn shutdown<'a>(&'a self) -> CodeModeSessionResultFuture<'a, ()>;
|
||||
}
|
||||
|
||||
/// Creates code-mode sessions for Codex threads.
|
||||
///
|
||||
/// Implementations may share a remote host process across all sessions created
|
||||
/// by one provider.
|
||||
pub trait CodeModeSessionProvider: Send + Sync {
|
||||
fn create_session<'a>(
|
||||
&'a self,
|
||||
delegate: Arc<dyn CodeModeSessionDelegate>,
|
||||
) -> CodeModeSessionProviderFuture<'a>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "session_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,19 @@
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use super::CellId;
|
||||
use super::StartedCell;
|
||||
|
||||
#[tokio::test]
|
||||
async fn started_cell_preserves_remote_initial_response_errors() {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
response_tx
|
||||
.send(Err("remote runtime failed".to_string()))
|
||||
.expect("initial response receiver should be open");
|
||||
let started = StartedCell::from_result_receiver(CellId::new("1".to_string()), response_rx);
|
||||
|
||||
assert_eq!(
|
||||
started.initial_response().await,
|
||||
Err("remote runtime failed".to_string())
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user