Files
codex/codex-rs/cloud-client/examples/attach.rs
T
Benjamin Carlsson ee80baa38e Support Cloud conversation requests in the app-server client (#59693)
## What changed

- Map thread listing and creation, paginated turn and item history, and turn submission and interruption to Cloud operations.
- Mark listed and activated threads as using paginated history, and warn when history items cannot be displayed.
- Reject unsupported filters, custom settings, and structured input. Require stable submission IDs and avoid returning a running turn for duplicate or queued submissions.
- Forward optional `turn_trigger` values through the Cloud client.

## Testing

Add coverage for resume-picker pagination, unsupported filters, submission IDs and settings, duplicate and queued admissions, and `turn_trigger` wire encoding.

GitOrigin-RevId: dcbd87d8190786da65e343a3a0f8d849b24a5b26
2026-11-05 16:41:33 +00:00

96 lines
3.5 KiB
Rust

//! Standalone consumer; prints event kinds without logging payloads.
use codex_cloud_client::AddUserMessageRequest;
use codex_cloud_client::AdmissionOutcome;
use codex_cloud_client::Client;
use codex_cloud_client::Credentials;
use codex_cloud_client::ResumeRequest;
use codex_cloud_client::SubmissionMode;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use futures::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut args = std::env::args().skip(1);
let endpoint = args
.next()
.ok_or_else(|| anyhow::anyhow!("expected native gRPC origin"))?;
let thread_id = args
.next()
.ok_or_else(|| anyhow::anyhow!("expected existing thread ID"))?;
// Supplying both extra arguments explicitly opts into sending one message.
let submission = match (args.next(), args.next()) {
(None, None) => None,
(Some(client_user_message_id), Some(text)) => Some(AddUserMessageRequest {
thread_id: thread_id.clone(),
client_user_message_id,
text,
turn_trigger: None,
mode: SubmissionMode::StartIfIdle,
shared_access: false,
}),
_ => anyhow::bail!("expected both client message ID and text, or neither"),
};
if args.next().is_some() {
anyhow::bail!("unexpected argument");
}
let credentials = Credentials::new(
&std::env::var("CODEX_CLOUD_ACCESS_TOKEN")?,
&std::env::var("CODEX_CLOUD_ACCOUNT_ID")?,
)?;
let client = Client::new(
&HttpClientFactory::new(OutboundProxyPolicy::RespectSystemProxy),
&endpoint,
credentials,
)?;
client
.resume(&ResumeRequest {
thread_id: thread_id.clone(),
wait: false,
shared_access: false,
})
.await?;
let mut events = client.attach(&thread_id).await?;
if let Some(request) = submission {
// Drain Attach while admission is pending; events can precede its response.
let admission = client.add_user_message(&request);
tokio::pin!(admission);
let (result, stream_error) = loop {
tokio::select! {
result = &mut admission => {
break (result, None);
}
event = events.next() => match event {
Some(Ok(event)) => println!("{event:?}"),
Some(Err(error)) => break (admission.await, Some(anyhow::Error::new(error))),
None => break (admission.await, Some(anyhow::anyhow!("attachment ended"))),
}
}
};
match result {
Ok(admission) => println!(
"admission: {}",
match admission.outcome {
AdmissionOutcome::Started(_) => "started",
AdmissionOutcome::Steered(_) => "steered",
AdmissionOutcome::Duplicate { .. } => "duplicate",
AdmissionOutcome::Queued { .. } => "queued",
}
),
Err(error) => match stream_error {
Some(stream_error) => {
anyhow::bail!("attachment failed: {stream_error}; admission failed: {error}");
}
None => return Err(error.into()),
},
}
if let Some(error) = stream_error {
return Err(error.context("attachment failed"));
}
}
while let Some(event) = events.next().await {
println!("{:?}", event?);
}
Ok(())
}