Add missing "nullable" macro to protocol structs that contain optional fields (#5901)
This PR addresses a current hole in the TypeScript code generation for
the API server protocol. Fields that are marked as "Optional<>" in the
Rust code are serialized such that the value is omitted when it is
deserialized — appearing as `undefined`, but the TS type indicates
(incorrectly) that it is always defined but possibly `null`. This can
lead to subtle errors that the TypeScript compiler doesn't catch. The
fix is to include the `#[ts(optional_fields = nullable)]` macro for all
protocol structs that contain one or more `Optional<>` fields.
This PR also includes a new test that validates that all TS protocol
code containing "| null" in its type is marked optional ("?") to catch
cases where `#[ts(optional_fields = nullable)]` is omitted.
This commit is contained in:
@@ -534,3 +534,150 @@ fn generate_index_ts(out_dir: &Path) -> Result<PathBuf> {
|
||||
.with_context(|| format!("Failed to write {}", index_path.display()))?;
|
||||
Ok(index_path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[test]
|
||||
fn generated_ts_omits_undefined_unions_for_optionals() -> Result<()> {
|
||||
let output_dir = std::env::temp_dir().join(format!("codex_ts_types_{}", Uuid::now_v7()));
|
||||
fs::create_dir(&output_dir)?;
|
||||
|
||||
struct TempDirGuard(PathBuf);
|
||||
|
||||
impl Drop for TempDirGuard {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
let _guard = TempDirGuard(output_dir.clone());
|
||||
|
||||
generate_ts(&output_dir, None)?;
|
||||
|
||||
let mut undefined_offenders = Vec::new();
|
||||
let mut missing_optional_marker = BTreeSet::new();
|
||||
let mut stack = vec![output_dir];
|
||||
while let Some(dir) = stack.pop() {
|
||||
for entry in fs::read_dir(&dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
stack.push(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if matches!(path.extension().and_then(|ext| ext.to_str()), Some("ts")) {
|
||||
let contents = fs::read_to_string(&path)?;
|
||||
if contents.contains("| undefined") {
|
||||
undefined_offenders.push(path.clone());
|
||||
}
|
||||
|
||||
const SKIP_PREFIXES: &[&str] = &[
|
||||
"const ",
|
||||
"let ",
|
||||
"var ",
|
||||
"export const ",
|
||||
"export let ",
|
||||
"export var ",
|
||||
];
|
||||
|
||||
let mut search_start = 0;
|
||||
while let Some(idx) = contents[search_start..].find("| null") {
|
||||
let abs_idx = search_start + idx;
|
||||
let Some(colon_idx) = contents[..abs_idx].rfind(':') else {
|
||||
search_start = abs_idx + 5;
|
||||
continue;
|
||||
};
|
||||
|
||||
let line_start_idx = contents[..colon_idx]
|
||||
.rfind('\n')
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut segment_start_idx = line_start_idx;
|
||||
if let Some(rel_idx) = contents[line_start_idx..colon_idx].rfind(',') {
|
||||
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
|
||||
}
|
||||
if let Some(rel_idx) = contents[line_start_idx..colon_idx].rfind('{') {
|
||||
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
|
||||
}
|
||||
if let Some(rel_idx) = contents[line_start_idx..colon_idx].rfind('}') {
|
||||
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
|
||||
}
|
||||
|
||||
let mut field_prefix = contents[segment_start_idx..colon_idx].trim();
|
||||
if field_prefix.is_empty() {
|
||||
search_start = abs_idx + 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(comment_idx) = field_prefix.rfind("*/") {
|
||||
field_prefix = field_prefix[comment_idx + 2..].trim_start();
|
||||
}
|
||||
|
||||
if field_prefix.is_empty() {
|
||||
search_start = abs_idx + 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
if SKIP_PREFIXES
|
||||
.iter()
|
||||
.any(|prefix| field_prefix.starts_with(prefix))
|
||||
{
|
||||
search_start = abs_idx + 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
if field_prefix.contains('(') {
|
||||
search_start = abs_idx + 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
if field_prefix.chars().rev().find(|c| !c.is_whitespace()) == Some('?') {
|
||||
search_start = abs_idx + 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
let line_number =
|
||||
contents[..abs_idx].chars().filter(|c| *c == '\n').count() + 1;
|
||||
let offending_line_end = contents[line_start_idx..]
|
||||
.find('\n')
|
||||
.map(|i| line_start_idx + i)
|
||||
.unwrap_or(contents.len());
|
||||
let offending_snippet = contents[line_start_idx..offending_line_end].trim();
|
||||
|
||||
missing_optional_marker.insert(format!(
|
||||
"{}:{}: {offending_snippet}",
|
||||
path.display(),
|
||||
line_number
|
||||
));
|
||||
|
||||
search_start = abs_idx + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
undefined_offenders.is_empty(),
|
||||
"Generated TypeScript still includes unions with `undefined` in {undefined_offenders:?}"
|
||||
);
|
||||
|
||||
// If this test fails, it means that a struct field that is `Option<T>` in Rust
|
||||
// is being generated as `T | null` in TypeScript, without the optional marker
|
||||
// (`?`). To fix this, add #[ts(optional_fields = nullable)] to the struct definition.
|
||||
assert!(
|
||||
missing_optional_marker.is_empty(),
|
||||
"Generated TypeScript has nullable fields without an optional marker: {missing_optional_marker:?}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ pub enum JSONRPCMessage {
|
||||
|
||||
/// A request that expects a response.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct JSONRPCRequest {
|
||||
pub id: RequestId,
|
||||
pub method: String,
|
||||
@@ -39,6 +40,7 @@ pub struct JSONRPCRequest {
|
||||
|
||||
/// A notification which does not expect a response.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct JSONRPCNotification {
|
||||
pub method: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -60,6 +62,7 @@ pub struct JSONRPCError {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct JSONRPCErrorError {
|
||||
pub code: i64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -248,6 +248,7 @@ pub enum Account {
|
||||
#[serde(rename = "chatgpt", rename_all = "camelCase")]
|
||||
#[ts(rename = "chatgpt", rename_all = "camelCase")]
|
||||
ChatGpt {
|
||||
#[ts(optional = nullable)]
|
||||
email: Option<String>,
|
||||
plan_type: PlanType,
|
||||
},
|
||||
@@ -266,6 +267,7 @@ pub struct InitializeParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClientInfo {
|
||||
pub name: String,
|
||||
@@ -281,6 +283,7 @@ pub struct InitializeResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NewConversationParams {
|
||||
/// Optional override for the model name (e.g. "o3", "o4-mini").
|
||||
@@ -324,6 +327,7 @@ pub struct NewConversationParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NewConversationResponse {
|
||||
pub conversation_id: ConversationId,
|
||||
@@ -335,6 +339,7 @@ pub struct NewConversationResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResumeConversationResponse {
|
||||
pub conversation_id: ConversationId,
|
||||
@@ -368,6 +373,7 @@ pub struct GetConversationSummaryResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListConversationsParams {
|
||||
/// Optional page size; defaults to a reasonable server-side value.
|
||||
@@ -385,6 +391,7 @@ pub struct ListConversationsParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConversationSummary {
|
||||
pub conversation_id: ConversationId,
|
||||
@@ -398,6 +405,7 @@ pub struct ConversationSummary {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListConversationsResponse {
|
||||
pub items: Vec<ConversationSummary>,
|
||||
@@ -408,6 +416,7 @@ pub struct ListConversationsResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListModelsParams {
|
||||
/// Optional page size; defaults to a reasonable server-side value.
|
||||
@@ -439,6 +448,7 @@ pub struct ReasoningEffortOption {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListModelsResponse {
|
||||
pub items: Vec<Model>,
|
||||
@@ -449,6 +459,7 @@ pub struct ListModelsResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UploadFeedbackParams {
|
||||
pub classification: String,
|
||||
@@ -482,6 +493,7 @@ pub enum LoginAccountParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoginAccountResponse {
|
||||
/// Only set if the login method is ChatGPT.
|
||||
@@ -498,6 +510,7 @@ pub struct LoginAccountResponse {
|
||||
pub struct LogoutAccountResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResumeConversationParams {
|
||||
/// Absolute path to the rollout JSONL file, when explicitly resuming a known rollout.
|
||||
@@ -589,6 +602,7 @@ pub struct LogoutChatGptParams {}
|
||||
pub struct LogoutChatGptResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetAuthStatusParams {
|
||||
/// If true, include the current auth token (if available) in the response.
|
||||
@@ -600,6 +614,7 @@ pub struct GetAuthStatusParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExecOneOffCommandParams {
|
||||
/// Command argv to execute.
|
||||
@@ -631,6 +646,7 @@ pub struct GetAccountRateLimitsResponse {
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct GetAuthStatusResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub auth_method: Option<AuthMode>,
|
||||
@@ -651,6 +667,7 @@ pub struct GetUserAgentResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserInfoResponse {
|
||||
/// Note: `alleged_user_email` is not currently verified. We read it from
|
||||
@@ -667,6 +684,7 @@ pub struct GetUserSavedConfigResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetDefaultModelParams {
|
||||
/// If set to None, this means `model` should be cleared in config.toml.
|
||||
@@ -686,6 +704,7 @@ pub struct SetDefaultModelResponse {}
|
||||
/// client-configurable settings that can be specified in the NewConversation
|
||||
/// and SendUserTurn requests.
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserSavedConfig {
|
||||
/// Approvals
|
||||
@@ -724,6 +743,7 @@ pub struct UserSavedConfig {
|
||||
|
||||
/// MCP representation of a [`codex_core::config_profile::ConfigProfile`].
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Profile {
|
||||
pub model: Option<String>,
|
||||
@@ -738,6 +758,7 @@ pub struct Profile {
|
||||
}
|
||||
/// MCP representation of a [`codex_core::config::ToolsToml`].
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Tools {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -748,6 +769,7 @@ pub struct Tools {
|
||||
|
||||
/// MCP representation of a [`codex_core::config_types::SandboxWorkspaceWrite`].
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SandboxSettings {
|
||||
#[serde(default)]
|
||||
@@ -768,6 +790,7 @@ pub struct SendUserMessageParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SendUserTurnParams {
|
||||
pub conversation_id: ConversationId,
|
||||
@@ -911,6 +934,7 @@ server_request_definitions! {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApplyPatchApprovalParams {
|
||||
pub conversation_id: ConversationId,
|
||||
@@ -928,6 +952,7 @@ pub struct ApplyPatchApprovalParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExecCommandApprovalParams {
|
||||
pub conversation_id: ConversationId,
|
||||
@@ -954,6 +979,7 @@ pub struct ApplyPatchApprovalResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
pub struct FuzzyFileSearchParams {
|
||||
@@ -966,6 +992,7 @@ pub struct FuzzyFileSearchParams {
|
||||
|
||||
/// Superset of [`codex_file_search::FileMatch`]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct FuzzyFileSearchResult {
|
||||
pub root: String,
|
||||
pub path: String,
|
||||
@@ -981,6 +1008,7 @@ pub struct FuzzyFileSearchResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoginChatGptCompleteNotification {
|
||||
#[schemars(with = "String")]
|
||||
@@ -991,6 +1019,7 @@ pub struct LoginChatGptCompleteNotification {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SessionConfiguredNotification {
|
||||
/// Name left as session_id instead of conversation_id for backwards compatibility.
|
||||
@@ -1019,6 +1048,7 @@ pub struct SessionConfiguredNotification {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthStatusChangeNotification {
|
||||
/// Current authentication method; omitted if signed out.
|
||||
|
||||
@@ -61,6 +61,7 @@ impl SandboxRiskCategory {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct ExecApprovalRequestEvent {
|
||||
/// Identifier for the associated exec call, if available.
|
||||
pub call_id: String,
|
||||
@@ -78,6 +79,7 @@ pub struct ExecApprovalRequestEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct ApplyPatchApprovalRequestEvent {
|
||||
/// Responses API call id for the associated patch apply call, if available.
|
||||
pub call_id: String,
|
||||
|
||||
@@ -11,6 +11,7 @@ use ts_rs::TS;
|
||||
pub const PROMPTS_CMD_PREFIX: &str = "prompts";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct CustomPrompt {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
|
||||
@@ -49,6 +49,7 @@ pub enum ContentItem {
|
||||
pub enum ResponseItem {
|
||||
Message {
|
||||
#[serde(skip_serializing)]
|
||||
#[ts(optional = nullable)]
|
||||
id: Option<String>,
|
||||
role: String,
|
||||
content: Vec<ContentItem>,
|
||||
@@ -58,20 +59,25 @@ pub enum ResponseItem {
|
||||
id: String,
|
||||
summary: Vec<ReasoningItemReasoningSummary>,
|
||||
#[serde(default, skip_serializing_if = "should_serialize_reasoning_content")]
|
||||
#[ts(optional = nullable)]
|
||||
content: Option<Vec<ReasoningItemContent>>,
|
||||
#[ts(optional = nullable)]
|
||||
encrypted_content: Option<String>,
|
||||
},
|
||||
LocalShellCall {
|
||||
/// Set when using the chat completions API.
|
||||
#[serde(skip_serializing)]
|
||||
#[ts(optional = nullable)]
|
||||
id: Option<String>,
|
||||
/// Set when using the Responses API.
|
||||
#[ts(optional = nullable)]
|
||||
call_id: Option<String>,
|
||||
status: LocalShellStatus,
|
||||
action: LocalShellAction,
|
||||
},
|
||||
FunctionCall {
|
||||
#[serde(skip_serializing)]
|
||||
#[ts(optional = nullable)]
|
||||
id: Option<String>,
|
||||
name: String,
|
||||
// The Responses API returns the function call arguments as a *string* that contains
|
||||
@@ -92,8 +98,10 @@ pub enum ResponseItem {
|
||||
},
|
||||
CustomToolCall {
|
||||
#[serde(skip_serializing)]
|
||||
#[ts(optional = nullable)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional = nullable)]
|
||||
status: Option<String>,
|
||||
|
||||
call_id: String,
|
||||
@@ -114,8 +122,10 @@ pub enum ResponseItem {
|
||||
// }
|
||||
WebSearchCall {
|
||||
#[serde(skip_serializing)]
|
||||
#[ts(optional = nullable)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional = nullable)]
|
||||
status: Option<String>,
|
||||
action: WebSearchAction,
|
||||
},
|
||||
@@ -193,6 +203,7 @@ pub enum LocalShellAction {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct LocalShellExecAction {
|
||||
pub command: Vec<String>,
|
||||
pub timeout_ms: Option<u64>,
|
||||
@@ -285,6 +296,7 @@ impl From<Vec<UserInput>> for ResponseInputItem {
|
||||
/// If the `name` of a `ResponseItem::FunctionCall` is either `container.exec`
|
||||
/// or shell`, the `arguments` field should deserialize to this struct.
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct ShellToolCallParams {
|
||||
pub command: Vec<String>,
|
||||
pub workdir: Option<String>,
|
||||
@@ -317,6 +329,7 @@ pub enum FunctionCallOutputContentItem {
|
||||
/// `content_items` with the structured form that the Responses/Chat
|
||||
/// Completions APIs understand.
|
||||
#[derive(Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct FunctionCallOutputPayload {
|
||||
pub content: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -18,11 +18,14 @@ pub enum ParsedCommand {
|
||||
},
|
||||
ListFiles {
|
||||
cmd: String,
|
||||
#[ts(optional = nullable)]
|
||||
path: Option<String>,
|
||||
},
|
||||
Search {
|
||||
cmd: String,
|
||||
#[ts(optional = nullable)]
|
||||
query: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
path: Option<String>,
|
||||
},
|
||||
Unknown {
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct PlanItemArg {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, TS)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct UpdatePlanArgs {
|
||||
#[serde(default)]
|
||||
pub explanation: Option<String>,
|
||||
|
||||
@@ -563,6 +563,7 @@ pub struct ItemCompletedEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct ExitedReviewModeEvent {
|
||||
pub review_output: Option<ReviewOutputEvent>,
|
||||
}
|
||||
@@ -575,11 +576,13 @@ pub struct ErrorEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct TaskCompleteEvent {
|
||||
pub last_agent_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct TaskStartedEvent {
|
||||
pub model_context_window: Option<i64>,
|
||||
}
|
||||
@@ -599,9 +602,11 @@ pub struct TokenUsage {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct TokenUsageInfo {
|
||||
pub total_token_usage: TokenUsage,
|
||||
pub last_token_usage: TokenUsage,
|
||||
#[ts(optional = nullable)]
|
||||
#[ts(type = "number | null")]
|
||||
pub model_context_window: Option<i64>,
|
||||
}
|
||||
@@ -662,25 +667,30 @@ impl TokenUsageInfo {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct TokenCountEvent {
|
||||
pub info: Option<TokenUsageInfo>,
|
||||
pub rate_limits: Option<RateLimitSnapshot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct RateLimitSnapshot {
|
||||
pub primary: Option<RateLimitWindow>,
|
||||
pub secondary: Option<RateLimitWindow>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct RateLimitWindow {
|
||||
/// Percentage (0-100) of the window that has been consumed.
|
||||
pub used_percent: f64,
|
||||
/// Rolling window duration, in minutes.
|
||||
#[ts(optional = nullable)]
|
||||
#[ts(type = "number | null")]
|
||||
pub window_minutes: Option<i64>,
|
||||
/// Unix timestamp (seconds since epoch) when the window resets.
|
||||
#[ts(optional = nullable)]
|
||||
#[ts(type = "number | null")]
|
||||
pub resets_at: Option<i64>,
|
||||
}
|
||||
@@ -794,6 +804,7 @@ pub struct AgentMessageEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct UserMessageEvent {
|
||||
pub message: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -829,6 +840,7 @@ pub struct AgentReasoningDeltaEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS, PartialEq)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct McpInvocation {
|
||||
/// Name of the MCP server as defined in the config.
|
||||
pub server: String,
|
||||
@@ -947,6 +959,7 @@ pub enum SessionSource {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct SessionMeta {
|
||||
pub id: ConversationId,
|
||||
pub timestamp: String,
|
||||
@@ -975,6 +988,7 @@ impl Default for SessionMeta {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct SessionMetaLine {
|
||||
#[serde(flatten)]
|
||||
pub meta: SessionMeta,
|
||||
@@ -1010,6 +1024,7 @@ impl From<CompactedItem> for ResponseItem {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct TurnContextItem {
|
||||
pub cwd: PathBuf,
|
||||
pub approval_policy: AskForApproval,
|
||||
@@ -1028,6 +1043,7 @@ pub struct RolloutLine {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct GitInfo {
|
||||
/// Current commit hash (SHA)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -1161,6 +1177,7 @@ pub struct BackgroundEventEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct DeprecationNoticeEvent {
|
||||
/// Concise summary of what is deprecated.
|
||||
pub summary: String,
|
||||
@@ -1170,12 +1187,14 @@ pub struct DeprecationNoticeEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct UndoStartedEvent {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct UndoCompletedEvent {
|
||||
pub success: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -1220,6 +1239,7 @@ pub struct TurnDiffEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct GetHistoryEntryResponseEvent {
|
||||
pub offset: usize,
|
||||
pub log_id: u64,
|
||||
@@ -1269,6 +1289,7 @@ pub struct ListCustomPromptsResponseEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct SessionConfiguredEvent {
|
||||
/// Name left as session_id instead of conversation_id for backwards compatibility.
|
||||
pub session_id: ConversationId,
|
||||
@@ -1329,6 +1350,7 @@ pub enum FileChange {
|
||||
},
|
||||
Update {
|
||||
unified_diff: String,
|
||||
#[ts(optional = nullable)]
|
||||
move_path: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type CommitID = String;
|
||||
|
||||
/// Details of a ghost commit created from a repository state.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
|
||||
#[ts(optional_fields = nullable)]
|
||||
pub struct GhostCommit {
|
||||
id: CommitID,
|
||||
parent: Option<CommitID>,
|
||||
|
||||
Reference in New Issue
Block a user