Compare commits

...

1 Commits

Author SHA1 Message Date
viyatb-oai
3c48a3bf03 feat: add thread share export plumbing
Co-authored-by: Codex <noreply@openai.com>
2026-04-12 16:23:54 -07:00
11 changed files with 591 additions and 1 deletions

View File

@@ -317,6 +317,16 @@ client_request_definitions! {
params: v2::ThreadReadParams,
response: v2::ThreadReadResponse,
},
#[experimental("thread/share")]
ThreadShare => "thread/share" {
params: v2::ThreadShareParams,
response: v2::ThreadShareResponse,
},
#[experimental("share/revoke")]
ShareRevoke => "share/revoke" {
params: v2::ShareRevokeParams,
response: v2::ShareRevokeResponse,
},
SkillsList => "skills/list" {
params: v2::SkillsListParams,
response: v2::SkillsListResponse,

View File

@@ -3245,6 +3245,35 @@ pub struct ThreadReadResponse {
pub thread: Thread,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadShareParams {
pub thread_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadShareResponse {
pub share_id: String,
pub share_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ShareRevokeParams {
pub share_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ShareRevokeResponse {
pub revoked: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]

View File

@@ -53,6 +53,8 @@ use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ShareRevokeParams;
use codex_app_server_protocol::ShareRevokeResponse;
use codex_app_server_protocol::ThreadDecrementElicitationParams;
use codex_app_server_protocol::ThreadDecrementElicitationResponse;
use codex_app_server_protocol::ThreadIncrementElicitationParams;
@@ -62,6 +64,8 @@ use codex_app_server_protocol::ThreadListParams;
use codex_app_server_protocol::ThreadListResponse;
use codex_app_server_protocol::ThreadResumeParams;
use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadShareParams;
use codex_app_server_protocol::ThreadShareResponse;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::TurnStartParams;
@@ -272,6 +276,18 @@ enum CliCommand {
#[arg(long, default_value_t = 15)]
hold_seconds: u64,
},
/// Create a shareable snapshot link for a persisted thread.
#[command(name = "thread-share")]
ThreadShare {
/// Existing thread id to share.
thread_id: String,
},
/// Revoke a previously created share link.
#[command(name = "share-revoke")]
ShareRevoke {
/// Share id returned by thread-share.
share_id: String,
},
}
pub async fn run() -> Result<()> {
@@ -423,6 +439,16 @@ pub async fn run() -> Result<()> {
hold_seconds,
)
}
CliCommand::ThreadShare { thread_id } => {
ensure_dynamic_tools_unused(&dynamic_tools, "thread-share")?;
let endpoint = resolve_endpoint(codex_bin, url)?;
thread_share(&endpoint, &config_overrides, thread_id).await
}
CliCommand::ShareRevoke { share_id } => {
ensure_dynamic_tools_unused(&dynamic_tools, "share-revoke")?;
let endpoint = resolve_endpoint(codex_bin, url)?;
share_revoke(&endpoint, &config_overrides, share_id).await
}
}
}
@@ -1137,6 +1163,40 @@ async fn thread_list(endpoint: &Endpoint, config_overrides: &[String], limit: u3
.await
}
async fn thread_share(
endpoint: &Endpoint,
config_overrides: &[String],
thread_id: String,
) -> Result<()> {
with_client("thread-share", endpoint, config_overrides, |client| {
let initialize = client.initialize_with_experimental_api(true)?;
println!("< initialize response: {initialize:?}");
let response = client.thread_share(ThreadShareParams { thread_id })?;
println!("< thread/share response: {response:?}");
Ok(())
})
.await
}
async fn share_revoke(
endpoint: &Endpoint,
config_overrides: &[String],
share_id: String,
) -> Result<()> {
with_client("share-revoke", endpoint, config_overrides, |client| {
let initialize = client.initialize_with_experimental_api(true)?;
println!("< initialize response: {initialize:?}");
let response = client.share_revoke(ShareRevokeParams { share_id })?;
println!("< share/revoke response: {response:?}");
Ok(())
})
.await
}
async fn with_client<T>(
command_name: &'static str,
endpoint: &Endpoint,
@@ -1681,6 +1741,26 @@ impl CodexClient {
self.send_request(request, request_id, "thread/decrement_elicitation")
}
fn thread_share(&mut self, params: ThreadShareParams) -> Result<ThreadShareResponse> {
let request_id = self.request_id();
let request = ClientRequest::ThreadShare {
request_id: request_id.clone(),
params,
};
self.send_request(request, request_id, "thread/share")
}
fn share_revoke(&mut self, params: ShareRevokeParams) -> Result<ShareRevokeResponse> {
let request_id = self.request_id();
let request = ClientRequest::ShareRevoke {
request_id: request_id.clone(),
params,
};
self.send_request(request, request_id, "share/revoke")
}
fn wait_for_account_login_completion(
&mut self,
expected_login_id: &str,

View File

@@ -367,6 +367,25 @@ Use `thread/read` to fetch a stored thread by id without resuming it. Pass `incl
} }
```
### Example: Create a shareable snapshot link (experimental)
`thread/share` creates a markdown snapshot of a persisted thread, publishes it through the Codex backend, and returns a share URL. This method rejects threads that have not materialized a rollout yet and requires ChatGPT authentication.
```json
{ "method": "thread/share", "id": 24, "params": { "threadId": "thr_123" } }
{ "id": 24, "result": {
"shareId": "019cbc6b-bf5e-74e4-86c5-8e2b0d3ad2a1",
"shareUrl": "https://chatgpt.com/s/019cbc6b-bf5e-74e4-86c5-8e2b0d3ad2a1"
} }
```
`share/revoke` revokes a share previously created by the authenticated user.
```json
{ "method": "share/revoke", "id": 25, "params": { "shareId": "019cbc6b-bf5e-74e4-86c5-8e2b0d3ad2a1" } }
{ "id": 25, "result": { "revoked": true } }
```
### Example: Update stored thread metadata
Use `thread/metadata/update` to patch sqlite-backed metadata for a thread without resuming it. Today this supports persisted `gitInfo`; omitted fields are left unchanged, while explicit `null` clears a stored value.

View File

@@ -18,6 +18,7 @@ use crate::outgoing_message::RequestContext;
use crate::outgoing_message::ThreadScopedOutgoingMessageSender;
use crate::thread_status::ThreadWatchManager;
use crate::thread_status::resolve_thread_status;
use anyhow::Context;
use chrono::DateTime;
use chrono::SecondsFormat;
use chrono::Utc;
@@ -112,6 +113,8 @@ use codex_app_server_protocol::ReviewTarget as ApiReviewTarget;
use codex_app_server_protocol::SandboxMode;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequestResolvedNotification;
use codex_app_server_protocol::ShareRevokeParams;
use codex_app_server_protocol::ShareRevokeResponse;
use codex_app_server_protocol::SkillSummary;
use codex_app_server_protocol::SkillsConfigWriteParams;
use codex_app_server_protocol::SkillsConfigWriteResponse;
@@ -159,6 +162,7 @@ use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadRollbackParams;
use codex_app_server_protocol::ThreadSetNameParams;
use codex_app_server_protocol::ThreadSetNameResponse;
use codex_app_server_protocol::ThreadShareParams;
use codex_app_server_protocol::ThreadShellCommandParams;
use codex_app_server_protocol::ThreadShellCommandResponse;
use codex_app_server_protocol::ThreadSortKey;
@@ -189,6 +193,7 @@ use codex_app_server_protocol::WindowsSandboxSetupStartResponse;
use codex_app_server_protocol::build_turns_from_rollout_items;
use codex_arg0::Arg0DispatchPaths;
use codex_backend_client::Client as BackendClient;
use codex_backend_client::CreateThreadShareRequest;
use codex_chatgpt::connectors;
use codex_cloud_requirements::cloud_requirements_loader;
use codex_config::types::McpServerTransportConfig;
@@ -197,7 +202,6 @@ use codex_core::Cursor as RolloutCursor;
use codex_core::ForkSnapshot;
use codex_core::NewThread;
use codex_core::RolloutRecorder;
use codex_core::SessionMeta;
use codex_core::SteerInputError;
use codex_core::ThreadConfigSnapshot;
use codex_core::ThreadManager;
@@ -293,6 +297,7 @@ use codex_protocol::protocol::ReviewRequest;
use codex_protocol::protocol::ReviewTarget as CoreReviewTarget;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::SessionConfiguredEvent;
use codex_protocol::protocol::SessionMeta;
use codex_protocol::protocol::SessionMetaLine;
use codex_protocol::protocol::ThreadNameUpdatedEvent;
use codex_protocol::protocol::USER_MESSAGE_BEGIN;
@@ -789,6 +794,14 @@ impl CodexMessageProcessor {
self.thread_shell_command(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadShare { request_id, params } => {
self.thread_share(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ShareRevoke { request_id, params } => {
self.share_revoke(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::SkillsList { request_id, params } => {
self.skills_list(to_connection_request_id(request_id), params)
.await;
@@ -3714,6 +3727,212 @@ impl CodexMessageProcessor {
self.outgoing.send_response(request_id, response).await;
}
async fn thread_share(&self, request_id: ConnectionRequestId, params: ThreadShareParams) {
let thread_uuid = match ThreadId::from_string(&params.thread_id) {
Ok(id) => id,
Err(err) => {
self.send_invalid_request_error(request_id, format!("invalid thread id: {err}"))
.await;
return;
}
};
let thread_id = thread_uuid.to_string();
let rollout_path = match find_thread_path_by_id_str(&self.config.codex_home, &thread_id)
.await
{
Ok(Some(path)) => path,
Ok(None) => {
match find_archived_thread_path_by_id_str(&self.config.codex_home, &thread_id).await
{
Ok(Some(path)) => path,
Ok(None) => {
self.outgoing
.send_error(
request_id,
JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!(
"thread {thread_uuid} is not materialized yet; thread/share is only available for persisted conversations"
),
data: Some(serde_json::json!({
"reason": "thread_not_persisted"
})),
},
)
.await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to locate archived thread {thread_uuid}: {err}"),
)
.await;
return;
}
}
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to locate thread {thread_uuid}: {err}"),
)
.await;
return;
}
};
let mut thread = match load_thread_summary_for_rollout(
&self.config,
thread_uuid,
rollout_path.as_path(),
self.config.model_provider_id.as_str(),
/*persisted_metadata*/ None,
)
.await
{
Ok(thread) => thread,
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to snapshot thread {thread_uuid} for sharing: {err}"),
)
.await;
return;
}
};
if let Err(message) = populate_thread_turns(
&mut thread,
ThreadTurnSource::RolloutPath(rollout_path.as_path()),
/*active_turn*/ None,
)
.await
{
self.send_internal_error(request_id, message).await;
return;
}
let auth = match self.auth_manager.auth().await {
Some(auth) if auth.is_chatgpt_auth() => auth,
Some(_) | None => {
self.send_invalid_request_error(
request_id,
"chatgpt authentication required to create share link".to_string(),
)
.await;
return;
}
};
let title = thread
.name
.clone()
.filter(|name| !name.trim().is_empty())
.unwrap_or_else(|| {
if thread.preview.trim().is_empty() {
format!("Codex thread {}", thread.id)
} else {
thread.preview.clone()
}
});
let markdown = match serde_json::to_string_pretty(&thread)
.map(|snapshot| format!("# {title}\n\n```json\n{snapshot}\n```\n"))
.context("serialize thread snapshot")
{
Ok(markdown) => markdown,
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to render thread {thread_uuid} for sharing: {err}"),
)
.await;
return;
}
};
let client = match BackendClient::from_auth(self.config.chatgpt_base_url.clone(), &auth) {
Ok(client) => client,
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to construct backend client: {err}"),
)
.await;
return;
}
};
let request = CreateThreadShareRequest { title, markdown };
match client.create_thread_share(&request).await {
Ok(response) => {
self.outgoing
.send_response(
request_id,
codex_app_server_protocol::ThreadShareResponse {
share_id: response.share_id,
share_url: response.share_url,
},
)
.await;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to create backend share link for thread {thread_uuid}: {err}"),
)
.await;
}
}
}
async fn share_revoke(&self, request_id: ConnectionRequestId, params: ShareRevokeParams) {
let share_id = params.share_id.trim();
let has_path_chars = share_id.chars().any(|ch| matches!(ch, '/' | '?' | '#'));
if share_id.is_empty() || has_path_chars {
self.send_invalid_request_error(request_id, "shareId is invalid".to_string())
.await;
return;
}
let auth = match self.auth_manager.auth().await {
Some(auth) if auth.is_chatgpt_auth() => auth,
Some(_) | None => {
self.send_invalid_request_error(
request_id,
"chatgpt authentication required to revoke share link".to_string(),
)
.await;
return;
}
};
let client = match BackendClient::from_auth(self.config.chatgpt_base_url.clone(), &auth) {
Ok(client) => client,
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to construct backend client: {err}"),
)
.await;
return;
}
};
match client.revoke_thread_share(share_id).await {
Ok(response) => {
self.outgoing
.send_response(
request_id,
ShareRevokeResponse {
revoked: response.revoked,
},
)
.await;
}
Err(err) => {
self.send_internal_error(request_id, format!("failed to revoke share link: {err}"))
.await;
}
}
}
pub(crate) fn thread_created_receiver(&self) -> broadcast::Receiver<ThreadId> {
self.thread_manager.subscribe_thread_created()
}
@@ -9253,11 +9472,14 @@ mod tests {
use crate::outgoing_message::OutgoingEnvelope;
use crate::outgoing_message::OutgoingMessage;
use anyhow::Result;
use chrono::Utc;
use codex_app_server_protocol::ServerRequestPayload;
use codex_app_server_protocol::ToolRequestUserInputParams;
use codex_core::SessionMeta;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::USER_MESSAGE_BEGIN;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::PathBuf;

View File

@@ -58,6 +58,7 @@ use codex_app_server_protocol::PluginUninstallParams;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ReviewStartParams;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ShareRevokeParams;
use codex_app_server_protocol::SkillsListParams;
use codex_app_server_protocol::ThreadArchiveParams;
use codex_app_server_protocol::ThreadCompactStartParams;
@@ -74,6 +75,7 @@ use codex_app_server_protocol::ThreadRealtimeStopParams;
use codex_app_server_protocol::ThreadResumeParams;
use codex_app_server_protocol::ThreadRollbackParams;
use codex_app_server_protocol::ThreadSetNameParams;
use codex_app_server_protocol::ThreadShareParams;
use codex_app_server_protocol::ThreadShellCommandParams;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadUnarchiveParams;
@@ -451,6 +453,24 @@ impl McpProcess {
self.send_request("thread/read", params).await
}
/// Send a `thread/share` JSON-RPC request.
pub async fn send_thread_share_request(
&mut self,
params: ThreadShareParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/share", params).await
}
/// Send a `share/revoke` JSON-RPC request.
pub async fn send_share_revoke_request(
&mut self,
params: ShareRevokeParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("share/revoke", params).await
}
/// Send a `model/list` JSON-RPC request.
pub async fn send_list_models_request(
&mut self,

View File

@@ -42,6 +42,7 @@ mod thread_name_websocket;
mod thread_read;
mod thread_resume;
mod thread_rollback;
mod thread_share;
mod thread_shell_command;
mod thread_start;
mod thread_status;

View File

@@ -0,0 +1,154 @@
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::create_fake_rollout;
use app_test_support::to_response;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ShareRevokeParams;
use codex_app_server_protocol::ThreadShareParams;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[tokio::test]
async fn thread_share_requires_chatgpt_auth_for_persisted_threads() -> Result<()> {
let codex_home = TempDir::new()?;
create_minimal_config(codex_home.path())?;
let filename_ts = "2025-01-05T12-00-00";
let thread_id = create_fake_rollout(
codex_home.path(),
filename_ts,
"2025-01-05T12:00:00Z",
"Saved user message",
Some("mock_provider"),
None,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let share_request_id = mcp
.send_thread_share_request(ThreadShareParams {
thread_id: thread_id.clone(),
})
.await?;
let share_error: JSONRPCError = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(share_request_id)),
)
.await??;
assert!(
share_error
.error
.message
.contains("chatgpt authentication required to create share link"),
"unexpected error: {}",
share_error.error.message
);
Ok(())
}
#[tokio::test]
async fn thread_share_rejects_unmaterialized_threads() -> Result<()> {
let codex_home = TempDir::new()?;
create_minimal_config(codex_home.path())?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let start_request_id = mcp
.send_thread_start_request(ThreadStartParams {
model: Some("mock-model".to_string()),
..Default::default()
})
.await?;
let start_response: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(start_request_id)),
)
.await??;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_response)?;
assert!(
!thread.path.as_ref().expect("thread path").exists(),
"fresh thread rollout should not be materialized yet"
);
let share_request_id = mcp
.send_thread_share_request(ThreadShareParams {
thread_id: thread.id.clone(),
})
.await?;
let share_error: JSONRPCError = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(share_request_id)),
)
.await??;
assert!(
share_error
.error
.message
.contains("thread/share is only available for persisted conversations"),
"unexpected error: {}",
share_error.error.message
);
assert_eq!(
share_error.error.data,
Some(json!({"reason": "thread_not_persisted"}))
);
Ok(())
}
#[tokio::test]
async fn share_revoke_requires_chatgpt_auth() -> Result<()> {
let codex_home = TempDir::new()?;
create_minimal_config(codex_home.path())?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let revoke_request_id = mcp
.send_share_revoke_request(ShareRevokeParams {
share_id: "share_123".to_string(),
})
.await?;
let revoke_error: JSONRPCError = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(revoke_request_id)),
)
.await??;
assert!(
revoke_error
.error
.message
.contains("chatgpt authentication required to revoke share link"),
"unexpected error: {}",
revoke_error.error.message
);
Ok(())
}
fn create_minimal_config(codex_home: &Path) -> std::io::Result<()> {
let config_toml = codex_home.join("config.toml");
std::fs::write(
config_toml,
r#"
model = "mock-model"
approval_policy = "never"
"#,
)?;
Ok(())
}

View File

@@ -1,7 +1,10 @@
use crate::types::CodeTaskDetailsResponse;
use crate::types::ConfigFileResponse;
use crate::types::CreateThreadShareRequest;
use crate::types::CreateThreadShareResponse;
use crate::types::PaginatedListTaskListItem;
use crate::types::RateLimitStatusPayload;
use crate::types::RevokeThreadShareResponse;
use crate::types::TurnAttemptsSiblingTurnsResponse;
use anyhow::Result;
use codex_client::build_reqwest_client_with_custom_ca;
@@ -357,6 +360,34 @@ impl Client {
.map_err(RequestError::from)
}
pub async fn create_thread_share(
&self,
request: &CreateThreadShareRequest,
) -> Result<CreateThreadShareResponse> {
let url = match self.path_style {
PathStyle::CodexApi => format!("{}/api/codex/thread-shares", self.base_url),
PathStyle::ChatGptApi => format!("{}/wham/thread-shares", self.base_url),
};
let req = self
.http
.post(&url)
.headers(self.headers())
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
.json(request);
let (body, ct) = self.exec_request(req, "POST", &url).await?;
self.decode_json::<CreateThreadShareResponse>(&url, &ct, &body)
}
pub async fn revoke_thread_share(&self, share_id: &str) -> Result<RevokeThreadShareResponse> {
let url = match self.path_style {
PathStyle::CodexApi => format!("{}/api/codex/thread-shares/{share_id}", self.base_url),
PathStyle::ChatGptApi => format!("{}/wham/thread-shares/{share_id}", self.base_url),
};
let req = self.http.delete(&url).headers(self.headers());
let (body, ct) = self.exec_request(req, "DELETE", &url).await?;
self.decode_json::<RevokeThreadShareResponse>(&url, &ct, &body)
}
/// Create a new task (user turn) by POSTing to the appropriate backend path
/// based on `path_style`. Returns the created task id.
pub async fn create_task(&self, request_body: serde_json::Value) -> Result<String> {

View File

@@ -6,6 +6,9 @@ pub use client::RequestError;
pub use types::CodeTaskDetailsResponse;
pub use types::CodeTaskDetailsResponseExt;
pub use types::ConfigFileResponse;
pub use types::CreateThreadShareRequest;
pub use types::CreateThreadShareResponse;
pub use types::PaginatedListTaskListItem;
pub use types::RevokeThreadShareResponse;
pub use types::TaskListItem;
pub use types::TurnAttemptsSiblingTurnsResponse;

View File

@@ -8,10 +8,31 @@ pub use codex_backend_openapi_models::models::RateLimitWindowSnapshot;
pub use codex_backend_openapi_models::models::TaskListItem;
use serde::Deserialize;
use serde::Serialize;
use serde::de::Deserializer;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateThreadShareRequest {
pub title: String,
pub markdown: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateThreadShareResponse {
pub share_id: String,
pub share_url: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RevokeThreadShareResponse {
pub revoked: bool,
}
/// Hand-rolled models for the Cloud Tasks task-details response.
/// The generated OpenAPI models are pretty bad. This is a half-step
/// towards hand-rolling them.