chore: retire rust ffi scaffold

This commit is contained in:
Haitao Pan 2026-06-06 12:00:13 +08:00
parent b0389e9b2c
commit 05fc574f8b
10 changed files with 15 additions and 603 deletions

View File

@ -126,7 +126,7 @@ rg -n '`_' docs/architecture/public-api/*.md
- `lib/runtime/go_task_service_client.dart` - `lib/runtime/go_task_service_client.dart`
- `lib/runtime/codex_runtime.dart` - `lib/runtime/codex_runtime.dart`
- `lib/app/app_controller_desktop_settings_runtime.dart` - `lib/app/app_controller_desktop_settings_runtime.dart`
- `rust/src/lib.rs` - `rust/src/lib.rs`(历史记录)
## Maintenance Rule ## Maintenance Rule

View File

@ -2,7 +2,9 @@
## Purpose ## Purpose
`rust/src` 当前是一组相对独立、边界清晰的 Codex FFI 草图实现。这里的重点是: `rust/` 目录已退役,下面这份文档仅作为历史记录保留。它描述的是曾经存在的 Codex FFI 草图实现,不代表当前可用能力。
历史上这里的重点是:
- Rust 公开结构体与状态模型 - Rust 公开结构体与状态模型
- C ABI 函数签名 - C ABI 函数签名
@ -170,7 +172,7 @@
## Exported FFI Functions ## Exported FFI Functions
下面这些函数定义在 `rust/src/lib.rs`,是 Flutter / Dart 侧真正可调用的 C ABI 面。 下面这些函数定义在 `rust/src/lib.rs`,是 Flutter / Dart 侧的 C ABI 面。
## `codex_init` ## `codex_init`
@ -280,4 +282,4 @@
3. `rust/src/lib.rs` 3. `rust/src/lib.rs`
4. `rust/src/runtime.rs` 4. `rust/src/runtime.rs`
5. `rust/src/types.rs` 5. `rust/src/types.rs`
- 当前 FFI 面已经具备“结构体/函数签名骨架”但消息收发、event polling、thread lifecycle 仍未完整实现 - 当前 FFI 面仅停留在结构体/函数签名骨架消息收发、event polling、thread lifecycle 都未完成

View File

@ -9,13 +9,13 @@ XWorkmate 当前唯一可交付的 Codex 集成路径是 **external CLI**
- 通过 `CodeAgentNodeOrchestrator` 把 XWorkmate 固定为 `app-mediated cooperative node` - 通过 `CodeAgentNodeOrchestrator` 把 XWorkmate 固定为 `app-mediated cooperative node`
- 通过 `RuntimeCoordinator` 保留多外部 Code Agent CLI 的统一 registry surface - 通过 `RuntimeCoordinator` 保留多外部 Code Agent CLI 的统一 registry surface
Rust FFI / built-in Codex 仍是 future placeholder不应宣传为已完成 Rust FFI / built-in Codex 已退役,不应再作为当前实现宣传
## 能力补全清单(按需求项) ## 能力补全清单(按需求项)
1. 内置 Code Agentbuilt-in 1. 内置 Code Agentbuilt-in
- 已提供运行时模式接入位与桥接流程编排AI Gateway / OpenClaw 协同元数据) - 已提供运行时模式接入位与桥接流程编排AI Gateway / OpenClaw 协同元数据)
- 当前仍属于 experimental受 Rust FFI TODO 约束 - 当前仍属于 experimental且 Rust FFI 路径已退役
2. 外部依赖 Codex CLI 2. 外部依赖 Codex CLI
- 已作为稳定主路径接入 - 已作为稳定主路径接入
- 保持与内置模式相同的桥接能力和模式切换语义 - 保持与内置模式相同的桥接能力和模式切换语义
@ -72,16 +72,15 @@ Rust FFI / built-in Codex 仍是 future placeholder不应宣传为已完成
- 不做第二个 provider 之前的通用 UI - 不做第二个 provider 之前的通用 UI
- 不做复杂调度策略 - 不做复杂调度策略
### Phase 3: 内置 Codex / Rust FFI ### Phase 3: 内置 Codex / Rust FFI(历史)
目标: 目标:
- 仅在 Rust FFI 具备真实可用能力后,再开放 built-in 交付承诺 - 这一路径仅作历史记录保留,不再推进为当前交付面
前置条件: 前置条件:
- `rust/src/lib.rs` 的消息发送 / 轮询 TODO 完成 - 历史文档中的 Rust FFI TODO 不再作为当前实现目标
- `rust/src/runtime.rs` 的进程启动 / 停止 TODO 完成
- 能复用与 external CLI 相同的 coordinator / registry 契约 - 能复用与 external CLI 相同的 coordinator / registry 契约
## truth 收口 ## truth 收口

View File

@ -45,7 +45,7 @@
- 多 provider 调度策略 - 多 provider 调度策略
- 第二个 provider 之前的泛化产品设计 - 第二个 provider 之前的泛化产品设计
## Phase 3: Built-in Codex / Rust FFI ## Phase 3: Built-in Codex / Rust FFI(历史)
目标: 目标:
@ -53,8 +53,7 @@
前置条件: 前置条件:
- `rust/src/lib.rs` 补完消息发送与事件轮询 - 这一路径仅保留历史记录,不再作为当前实施目标
- `rust/src/runtime.rs` 补完进程启动与停止
- 能复用当前 coordinator / registry 契约 - 能复用当前 coordinator / registry 契约
状态: 状态:

View File

@ -66,8 +66,7 @@ XWorkmate 当前唯一可交付的 Codex 集成路径是 **External Codex CLI**
现状: 现状:
- `rust/src/lib.rs` 仍保留消息发送和事件轮询 TODO - `rust/` 目录已退役,曾经的消息发送、事件轮询、进程启动和停止 TODO 仅保留在历史文档里
- `rust/src/runtime.rs` 仍保留进程启动和停止 TODO
- Flutter 侧的 `builtIn` 只是保留枚举位,不会实际走可用 FFI 路径 - Flutter 侧的 `builtIn` 只是保留枚举位,不会实际走可用 FFI 路径
### 2. 其他外部 Provider 的通用选择与调度 ### 2. 其他外部 Provider 的通用选择与调度
@ -117,6 +116,6 @@ flowchart LR
不能宣称的能力: 不能宣称的能力:
- Built-in Codex 已可用 - Built-in Codex 已可用
- Rust FFI 已完成 - Rust FFI 已退役,不作为当前交付能力
- Scheduled Tasks 已支持增删改 - Scheduled Tasks 已支持增删改
- Memory 已支持完整 CRUD - Memory 已支持完整 CRUD

View File

@ -1,30 +0,0 @@
[package]
name = "codex-ffi"
version = "0.1.0"
edition = "2021"
description = "FFI bindings for Codex CLI integration"
license = "Apache-2.0"
[lib]
name = "codex_ffi"
crate-type = ["cdylib", "staticlib"]
[dependencies]
# Minimal dependencies for FFI
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
[features]
default = []
flutter = []
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
[profile.dev]
opt-level = 0
debug = true

View File

@ -1,59 +0,0 @@
//! Error types for Codex FFI.
use std::fmt;
/// Error type for Codex operations.
#[derive(Debug, Clone)]
pub enum CodexError {
/// Invalid argument.
InvalidArgument(String),
/// Runtime not initialized.
NotInitialized,
/// Runtime already initialized.
AlreadyInitialized,
/// IO error.
Io(String),
/// JSON-RPC error.
Rpc { code: i32, message: String },
/// Timeout error.
Timeout(String),
/// Process error.
Process(String),
/// Thread error.
Thread(String),
/// Configuration error.
Config(String),
/// Unknown error.
Unknown(String),
}
impl fmt::Display for CodexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CodexError::InvalidArgument(msg) => write!(f, "Invalid argument: {}", msg),
CodexError::NotInitialized => write!(f, "Runtime not initialized"),
CodexError::AlreadyInitialized => write!(f, "Runtime already initialized"),
CodexError::Io(msg) => write!(f, "IO error: {}", msg),
CodexError::Rpc { code, message } => write!(f, "RPC error ({}): {}", code, message),
CodexError::Timeout(msg) => write!(f, "Timeout: {}", msg),
CodexError::Process(msg) => write!(f, "Process error: {}", msg),
CodexError::Thread(msg) => write!(f, "Thread error: {}", msg),
CodexError::Config(msg) => write!(f, "Configuration error: {}", msg),
CodexError::Unknown(msg) => write!(f, "Unknown error: {}", msg),
}
}
}
impl std::error::Error for CodexError {}
impl From<std::io::Error> for CodexError {
fn from(err: std::io::Error) -> Self {
CodexError::Io(err.to_string())
}
}
impl From<serde_json::Error> for CodexError {
fn from(err: serde_json::Error) -> Self {
CodexError::Config(err.to_string())
}
}

View File

@ -1,139 +0,0 @@
//! FFI bindings for Codex CLI integration.
//!
//! This crate provides C-compatible FFI bindings for embedding Codex CLI
//! into Flutter applications.
mod runtime;
mod error;
mod types;
pub use error::CodexError;
pub use runtime::{CodexRuntime, CodexConfig, CodexConfigRust, ThreadHandle, RuntimeState};
pub use types::{CodexResult, CodexEvent};
use std::ffi::CStr;
use std::os::raw::c_char;
/// FFI-exported initialization function.
///
/// # Safety
/// Must be called before any other FFI functions.
#[no_mangle]
pub unsafe extern "C" fn codex_init() -> i32 {
0 // Success
}
/// FFI-exported runtime creation.
///
/// # Safety
/// Returns a pointer to the runtime. Caller must ensure thread safety.
#[no_mangle]
pub unsafe extern "C" fn codex_runtime_create(config: *const CodexConfig) -> *mut CodexRuntime {
if config.is_null() {
return std::ptr::null_mut();
}
let config = &*config;
let runtime = Box::new(CodexRuntime::new(config.clone()));
Box::into_raw(runtime)
}
/// FFI-exported runtime destruction.
///
/// # Safety
/// Must be called with a valid pointer from `codex_runtime_create`.
#[no_mangle]
pub unsafe extern "C" fn codex_runtime_destroy(runtime: *mut CodexRuntime) {
if !runtime.is_null() {
drop(Box::from_raw(runtime));
}
}
/// FFI-exported start thread function.
///
/// # Safety
/// Must be called with valid pointers.
#[no_mangle]
pub unsafe extern "C" fn codex_start_thread(
_runtime: *mut CodexRuntime,
cwd: *const c_char,
) -> ThreadHandle {
if cwd.is_null() {
return ThreadHandle::null();
}
let _cwd = CStr::from_ptr(cwd);
ThreadHandle::new(0)
}
/// FFI-exported send message function.
///
/// # Safety
/// Must be called with valid pointers.
#[no_mangle]
pub unsafe extern "C" fn codex_send_message(
runtime: *mut CodexRuntime,
_thread: ThreadHandle,
message: *const c_char,
) -> i32 {
if runtime.is_null() || message.is_null() {
return -1;
}
let _runtime = &mut *runtime;
let _message = CStr::from_ptr(message);
// TODO: Implement async message sending
0
}
/// FFI-exported poll events function.
///
/// # Safety
/// Must be called with valid pointers.
#[no_mangle]
pub unsafe extern "C" fn codex_poll_events(
runtime: *mut CodexRuntime,
events: *mut CodexEvent,
max_events: usize,
) -> usize {
if runtime.is_null() || events.is_null() {
return 0;
}
let _runtime = &mut *runtime;
let _events = std::slice::from_raw_parts_mut(events, max_events);
// TODO: Implement event polling
0
}
/// FFI-exported shutdown function.
///
/// # Safety
/// Must be called with a valid runtime pointer.
#[no_mangle]
pub unsafe extern "C" fn codex_shutdown(runtime: *mut CodexRuntime) -> i32 {
if runtime.is_null() {
return -1;
}
let _runtime = &mut *runtime;
// TODO: Implement graceful shutdown
0
}
/// Get the last error message.
///
/// # Safety
/// Returns a pointer to static memory that is valid until the next FFI call.
#[no_mangle]
pub unsafe extern "C" fn codex_last_error(runtime: *mut CodexRuntime) -> *const c_char {
if runtime.is_null() {
return std::ptr::null();
}
let runtime = &mut *runtime;
runtime.last_error.as_ptr()
}

View File

@ -1,291 +0,0 @@
//! Core runtime for Codex FFI.
use std::ffi::CString;
use std::os::raw::c_char;
use std::path::PathBuf;
use crate::error::CodexError;
/// Configuration for Codex runtime.
#[derive(Debug, Clone)]
#[repr(C)]
pub struct CodexConfig {
/// Path to Codex binary.
pub codex_path: *const c_char,
/// Working directory.
pub working_directory: *const c_char,
/// Sandbox mode: 0=read-only, 1=workspace-write, 2=danger-full-access.
pub sandbox_mode: i32,
/// Approval policy: 0=suggest, 1=auto-edit, 2=full-auto.
pub approval_policy: i32,
/// Model to use.
pub model: *const c_char,
/// API key for gateway.
pub api_key: *const c_char,
/// Gateway URL.
pub gateway_url: *const c_char,
/// Enable debug logging.
pub debug: bool,
}
impl Default for CodexConfig {
fn default() -> Self {
CodexConfig {
codex_path: std::ptr::null(),
working_directory: std::ptr::null(),
sandbox_mode: 1, // workspace-write
approval_policy: 0, // suggest
model: std::ptr::null(),
api_key: std::ptr::null(),
gateway_url: std::ptr::null(),
debug: false,
}
}
}
impl CodexConfig {
/// Convert FFI config to Rust types.
pub unsafe fn to_rust(&self) -> Result<CodexConfigRust, CodexError> {
let codex_path = if self.codex_path.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(self.codex_path)
.to_string_lossy()
.into_owned())
};
let working_directory = if self.working_directory.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(self.working_directory)
.to_string_lossy()
.into_owned())
};
let model = if self.model.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(self.model)
.to_string_lossy()
.into_owned())
};
let api_key = if self.api_key.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(self.api_key)
.to_string_lossy()
.into_owned())
};
let gateway_url = if self.gateway_url.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(self.gateway_url)
.to_string_lossy()
.into_owned())
};
Ok(CodexConfigRust {
codex_path,
working_directory,
sandbox_mode: self.sandbox_mode,
approval_policy: self.approval_policy,
model,
api_key,
gateway_url,
debug: self.debug,
})
}
}
/// Rust-native config type.
#[derive(Debug, Clone, Default)]
pub struct CodexConfigRust {
pub codex_path: Option<String>,
pub working_directory: Option<String>,
pub sandbox_mode: i32,
pub approval_policy: i32,
pub model: Option<String>,
pub api_key: Option<String>,
pub gateway_url: Option<String>,
pub debug: bool,
}
/// Opaque handle to a thread.
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ThreadHandle {
pub id: u64,
}
impl ThreadHandle {
pub fn new(id: u64) -> Self {
ThreadHandle { id }
}
pub fn null() -> Self {
ThreadHandle { id: 0 }
}
pub fn is_null(&self) -> bool {
self.id == 0
}
}
/// Codex runtime state.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuntimeState {
Disconnected,
Connecting,
Connected,
Ready,
Error,
}
/// Core runtime for managing Codex process.
pub struct CodexRuntime {
config: CodexConfigRust,
state: RuntimeState,
pub last_error: CString,
}
impl CodexRuntime {
/// Create a new runtime with the given configuration.
pub fn new(config: CodexConfig) -> Self {
let rust_config = unsafe { config.to_rust().unwrap_or_default() };
CodexRuntime {
config: rust_config,
state: RuntimeState::Disconnected,
last_error: CString::new("").unwrap_or_default(),
}
}
/// Create from Rust config.
pub fn with_config(config: CodexConfigRust) -> Self {
CodexRuntime {
config,
state: RuntimeState::Disconnected,
last_error: CString::new("").unwrap_or_default(),
}
}
/// Get the current state.
pub fn state(&self) -> RuntimeState {
self.state
}
/// Set error message.
pub fn set_error(&mut self, message: &str) {
self.last_error = CString::new(message).unwrap_or_default();
self.state = RuntimeState::Error;
}
/// Find the Codex binary.
pub fn find_codex_binary(&self) -> Option<PathBuf> {
// Check config path
if let Some(ref path) = self.config.codex_path {
let path = PathBuf::from(path);
if path.exists() {
return Some(path);
}
}
// Check environment
if let Ok(path) = std::env::var("CODEX_PATH") {
let path = PathBuf::from(path);
if path.exists() {
return Some(path);
}
}
// Check common locations
let home = std::env::var("HOME").unwrap_or_default();
let cargo_path = format!("{}/.cargo/bin/codex", home);
let local_path = format!("{}/.local/bin/codex", home);
let paths = [
"/usr/local/bin/codex",
"/opt/homebrew/bin/codex",
cargo_path.as_str(),
local_path.as_str(),
];
for path in paths {
let path = PathBuf::from(path);
if path.exists() {
return Some(path);
}
}
None
}
/// Start the runtime.
pub async fn start(&mut self) -> Result<(), CodexError> {
if self.state == RuntimeState::Ready {
return Err(CodexError::AlreadyInitialized);
}
self.state = RuntimeState::Connecting;
// Find binary
let _binary = self.find_codex_binary()
.ok_or_else(|| CodexError::Process("Codex binary not found".into()))?;
// TODO: Start process
self.state = RuntimeState::Ready;
Ok(())
}
/// Stop the runtime.
pub async fn stop(&mut self) -> Result<(), CodexError> {
if self.state == RuntimeState::Disconnected {
return Ok(());
}
// TODO: Stop process
self.state = RuntimeState::Disconnected;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let config = CodexConfig::default();
assert!(config.codex_path.is_null());
assert_eq!(config.sandbox_mode, 1);
}
#[test]
fn test_thread_handle() {
let handle = ThreadHandle::new(42);
assert_eq!(handle.id, 42);
assert!(!handle.is_null());
let null_handle = ThreadHandle::null();
assert!(null_handle.is_null());
}
#[test]
fn test_runtime_state() {
let config = CodexConfigRust {
codex_path: None,
working_directory: None,
sandbox_mode: 1,
approval_policy: 0,
model: None,
api_key: None,
gateway_url: None,
debug: false,
};
let runtime = CodexRuntime::with_config(config);
assert_eq!(runtime.state(), RuntimeState::Disconnected);
}
}

View File

@ -1,68 +0,0 @@
//! FFI-safe types for Codex integration.
use std::ffi::CString;
use std::os::raw::c_char;
/// FFI-safe result type.
#[repr(C)]
pub struct CodexResult {
/// Whether the operation was successful.
pub success: bool,
/// Error code if failed.
pub error_code: i32,
/// Error message if failed.
pub error_message: *const c_char,
}
impl CodexResult {
pub fn ok() -> Self {
CodexResult {
success: true,
error_code: 0,
error_message: std::ptr::null(),
}
}
pub fn err(code: i32, message: &str) -> Self {
let c_message = CString::new(message).unwrap_or_default();
CodexResult {
success: false,
error_code: code,
error_message: c_message.as_ptr(),
}
}
}
/// FFI-safe event type.
#[repr(C)]
pub struct CodexEvent {
/// Event type (started, delta, completed, error).
pub event_type: *const c_char,
/// Thread ID.
pub thread_id: *const c_char,
/// Turn ID.
pub turn_id: *const c_char,
/// Event data as JSON.
pub data: *const c_char,
/// Timestamp (Unix millis).
pub timestamp: i64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_result_ok() {
let result = CodexResult::ok();
assert!(result.success);
assert_eq!(result.error_code, 0);
}
#[test]
fn test_result_err() {
let result = CodexResult::err(1, "test error");
assert!(!result.success);
assert_eq!(result.error_code, 1);
}
}