///|
/// Configuration shared by every Codex session opened by this adapter.
pub struct CodexAgentOptions {
codex_path_override : @path.Path?
base_url : String?
api_key : String?
config : @sdk.CodexConfigObject?
resume_thread_id : String?
model : String?
sandbox : @sdk.SandboxMode?
skip_git : Bool?
reasoning : @sdk.ModelReasoningEffort?
web_search : @sdk.WebSearchMode?
}
///|
/// Creates Codex adapter options with the SDK's defaults.
pub fn CodexAgentOptions::CodexAgentOptions(
codex_path_override? : @path.Path,
base_url? : String,
api_key? : String,
config? : @sdk.CodexConfigObject,
resume_thread_id? : String,
model? : String,
sandbox? : @sdk.SandboxMode,
skip_git? : Bool,
reasoning? : @sdk.ModelReasoningEffort,
web_search? : @sdk.WebSearchMode,
) -> CodexAgentOptions {
{
codex_path_override,
base_url,
api_key,
config,
resume_thread_id,
model,
sandbox,
skip_git,
reasoning,
web_search,
}
}
///|
/// Raised when execution is requested after a session has closed.
pub suberror CodexAdapterError {
SessionClosed
} derive(Debug)
///|
priv struct CodexAgentSession {
thread : @sdk.Thread
mutex : @async.Mutex
mut closed : Bool
}
///|
fn approval_mode(policy : @coding.ApprovalPolicy) -> @sdk.ApprovalMode {
match policy {
@coding.Never => @sdk.Never
@coding.OnRequest => @sdk.OnRequest
@coding.OnFailure => @sdk.OnFailure
@coding.Untrusted => @sdk.Untrusted
}
}
///|
fn network_enabled(policy : @coding.NetworkPolicy) -> Bool {
match policy {
@coding.Disabled => false
@coding.Enabled => true
}
}
///|
fn request_input(request : @coding.CodingAgentRequest) -> @sdk.Input {
if request.context_files.is_empty() {
return @sdk.Prompt(request.instruction)
}
let files = request.context_files
.iter()
.map(path => path.to_string())
.join("\n")
@sdk.Prompt(
"\{request.instruction}\n\nContext files supplied by the caller:\n\{files}",
)
}
///|
fn changed_files(
items : ReadOnlyArray[@sdk.ThreadItem],
) -> ReadOnlyArray[@path.Path] {
let files : Array[@path.Path] = []
for item in items {
match item {
@sdk.FileChange(change) =>
match change.status {
@sdk.PatchCompleted =>
for update in change.changes {
files.push(update.path)
}
_ => ()
}
_ => ()
}
}
ReadOnlyArray::from_array(files)
}
///|
impl @coding.CodingAgentSession for CodexAgentSession with fn id(self) {
match self.thread.id() {
Some(id) =>
Some(@coding.SessionId::SessionId(id) catch { _ => return None })
None => None
}
}
///|
impl @coding.CodingAgentSession for CodexAgentSession with fn execute(
self,
request,
) {
self.mutex.acquire()
if self.closed {
self.mutex.release()
raise CodexAdapterError::SessionClosed
}
let result = Ok(self.thread.run(request_input(request))) catch {
error => Err(error)
}
self.mutex.release()
let turn = match result {
Ok(turn) => turn
Err(error) => raise error
}
@coding.CodingAgentResponse::CodingAgentResponse(
status=@coding.Succeeded,
summary=Some(turn.final_response),
continuation_id=self.thread.id(),
changed_files=changed_files(turn.items),
)
}
///|
impl @coding.CodingAgentSession for CodexAgentSession with fn close(self) {
self.mutex.acquire()
self.closed = true
self.mutex.release()
}
///|
/// Creates a coding agent backed by the pinned Codex SDK.
pub fn codex_agent(
id : @coding.CodingAgentId,
options : CodexAgentOptions,
) -> @coding.CodingAgent {
@coding.CodingAgent::CodingAgent(id, async fn(context) {
@async.pause()
let client = @sdk.Codex::Codex(
options=@sdk.CodexOptions::CodexOptions(
codex_path_override?=options.codex_path_override,
base_url?=options.base_url,
api_key?=options.api_key,
config?=options.config,
env=Map(context.environment.to_array()),
),
)
let thread_options = @sdk.ThreadOptions::ThreadOptions(
model?=options.model,
sandbox_mode?=options.sandbox,
working_directory=context.workspace.root,
skip_git_repo_check?=options.skip_git,
model_reasoning_effort?=options.reasoning,
network_access_enabled=network_enabled(context.network),
web_search_mode?=options.web_search,
approval_policy=approval_mode(context.approval),
additional_directories=[..context.workspace.additional_writable_roots],
)
let thread = match options.resume_thread_id {
Some(thread_id) => client.resume_thread(thread_id, options=thread_options)
None => client.start_thread(options=thread_options)
}
let session = CodexAgentSession::{
thread,
mutex: @async.Mutex::Mutex(),
closed: false,
}
(session : &@coding.CodingAgentSession)
})
}