Files
codex/codex-rs/cloud-client/examples/attach.rs
T
Benjamin Carlsson b279cfb170 Enable conversation-scoped speed selection for Cloud turns (#61178)
## What changed

- Stage Cloud speed choices for the next new turn in the conversation, alongside pending model and effort selections. Update the displayed conversation speed after admission without changing the local default.
- Offer speed commands supported by the pending model. Clear an incompatible pending speed change with a warning when switching models.
- Carry `service_tier` through Cloud message requests, preserving omission to inherit the thread tier and explicit null to clear it. Translate `default` to null and reject tier overrides on queued requests.

## Testing

Add tests for Cloud speed toggling, model-dependent speed choices, admission-time updates, and preserving the conversation tier when selecting a model. Extend request and wire tests to cover service-tier values, explicit clearing, and queue rejection.

GitOrigin-RevId: b4daa701f1fe29593d90a938e60cc20cb980298a
2026-11-12 20:56:33 +00:00

103 lines
3.8 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_cloud_client::UserMessageInput;
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,
input: vec![UserMessageInput::Text {
text: text,
text_elements: vec![],
}],
turn_trigger: None,
model: None,
effort: None,
service_tier: 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(())
}