Merge remote-tracking branch 'origin/main' into jif/client-work

# Conflicts:
#	codex-rs/core/tests/suite/client.rs
This commit is contained in:
jif-oai
2025-10-30 19:02:00 +00:00
33 changed files with 1688 additions and 223 deletions
+94 -40
View File
@@ -545,7 +545,7 @@ mod tests {
use uuid::Uuid;
#[test]
fn generated_ts_omits_undefined_unions_for_optionals() -> Result<()> {
fn generated_ts_has_no_optional_nullable_fields() -> Result<()> {
let output_dir = std::env::temp_dir().join(format!("codex_ts_types_{}", Uuid::now_v7()));
fs::create_dir(&output_dir)?;
@@ -562,7 +562,7 @@ mod tests {
generate_ts(&output_dir, None)?;
let mut undefined_offenders = Vec::new();
let mut missing_optional_marker = BTreeSet::new();
let mut optional_nullable_offenders = BTreeSet::new();
let mut stack = vec![output_dir];
while let Some(dir) = stack.pop() {
for entry in fs::read_dir(&dir)? {
@@ -591,27 +591,80 @@ mod tests {
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 {
// Find the property-colon for this field by scanning forward
// from the start of the segment and ignoring nested braces,
// brackets, and parens. This avoids colons inside nested
// type literals like `{ [k in string]?: string }`.
let line_start_idx =
contents[..abs_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..abs_idx].rfind(',') {
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
}
if let Some(rel_idx) = contents[line_start_idx..abs_idx].rfind('{') {
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
}
if let Some(rel_idx) = contents[line_start_idx..abs_idx].rfind('}') {
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
}
// Scan forward for the colon that separates the field name from its type.
let mut level_brace = 0_i32;
let mut level_brack = 0_i32;
let mut level_paren = 0_i32;
let mut in_single = false;
let mut in_double = false;
let mut escape = false;
let mut prop_colon_idx = None;
for (i, ch) in contents[segment_start_idx..abs_idx].char_indices() {
let idx_abs = segment_start_idx + i;
if escape {
escape = false;
continue;
}
match ch {
'\\' => {
// Only treat as escape when inside a string.
if in_single || in_double {
escape = true;
}
}
'\'' => {
if !in_double {
in_single = !in_single;
}
}
'"' => {
if !in_single {
in_double = !in_double;
}
}
'{' if !in_single && !in_double => level_brace += 1,
'}' if !in_single && !in_double => level_brace -= 1,
'[' if !in_single && !in_double => level_brack += 1,
']' if !in_single && !in_double => level_brack -= 1,
'(' if !in_single && !in_double => level_paren += 1,
')' if !in_single && !in_double => level_paren -= 1,
':' if !in_single
&& !in_double
&& level_brace == 0
&& level_brack == 0
&& level_paren == 0 =>
{
prop_colon_idx = Some(idx_abs);
break;
}
_ => {}
}
}
let Some(colon_idx) = prop_colon_idx 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;
@@ -640,25 +693,26 @@ mod tests {
continue;
}
// If the last non-whitespace before ':' is '?', then this is an
// optional field with a nullable type (i.e., "?: T | null"),
// which we explicitly disallow.
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();
optional_nullable_offenders.insert(format!(
"{}:{}: {offending_snippet}",
path.display(),
line_number
));
}
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;
}
}
@@ -670,12 +724,12 @@ mod tests {
"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.
// If this assertion fails, it means a field was generated as
// "?: T | null" — i.e., both optional (undefined) and nullable (null).
// We only want either "?: T" or ": T | null".
assert!(
missing_optional_marker.is_empty(),
"Generated TypeScript has nullable fields without an optional marker: {missing_optional_marker:?}"
optional_nullable_offenders.is_empty(),
"Generated TypeScript has optional fields with nullable types (disallowed '?: T | null'), add #[ts(optional)] to fix:\n{optional_nullable_offenders:?}"
);
Ok(())
@@ -30,20 +30,20 @@ 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,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
}
/// 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")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
}
@@ -62,10 +62,10 @@ 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")]
#[ts(optional)]
pub data: Option<serde_json::Value>,
pub message: String,
}
+24 -95
View File
@@ -248,7 +248,6 @@ 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,
},
@@ -267,11 +266,9 @@ 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,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub version: String,
}
@@ -283,68 +280,62 @@ 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").
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
/// Override the model provider to use for this session.
#[serde(skip_serializing_if = "Option::is_none")]
pub model_provider: Option<String>,
/// Configuration profile from config.toml to specify default options.
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
/// Working directory for the session. If relative, it is resolved against
/// the server process's current working directory.
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<String>,
/// Approval policy for shell commands generated by the model:
/// `untrusted`, `on-failure`, `on-request`, `never`.
#[serde(skip_serializing_if = "Option::is_none")]
pub approval_policy: Option<AskForApproval>,
/// Sandbox mode: `read-only`, `workspace-write`, or `danger-full-access`.
#[serde(skip_serializing_if = "Option::is_none")]
pub sandbox: Option<SandboxMode>,
/// Individual config settings that will override what is in
/// CODEX_HOME/config.toml.
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<HashMap<String, serde_json::Value>>,
/// The set of instructions to use instead of the default ones.
#[serde(skip_serializing_if = "Option::is_none")]
pub base_instructions: Option<String>,
/// Whether to include the apply patch tool in the conversation.
/// Developer instructions that will be sent as a `developer` role message.
#[serde(skip_serializing_if = "Option::is_none")]
pub developer_instructions: Option<String>,
/// Prompt used during conversation compaction.
#[serde(skip_serializing_if = "Option::is_none")]
pub compact_prompt: Option<String>,
/// Whether to include the apply patch tool in the conversation.
pub include_apply_patch_tool: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
#[serde(rename_all = "camelCase")]
pub struct NewConversationResponse {
pub conversation_id: ConversationId,
pub model: String,
/// Note this could be ignored by the model.
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<ReasoningEffort>,
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
#[serde(rename_all = "camelCase")]
pub struct ResumeConversationResponse {
pub conversation_id: ConversationId,
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_messages: Option<Vec<EventMsg>>,
pub rollout_path: PathBuf,
}
@@ -373,57 +364,46 @@ 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub page_size: Option<usize>,
/// Opaque pagination cursor returned by a previous call.
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
/// Optional model provider filter (matches against session metadata).
/// - None => filter by the server's default model provider
/// - Some([]) => no filtering, include all providers
/// - Some([...]) => only include sessions with one of the specified providers
#[serde(skip_serializing_if = "Option::is_none")]
pub model_providers: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
#[serde(rename_all = "camelCase")]
pub struct ConversationSummary {
pub conversation_id: ConversationId,
pub path: PathBuf,
pub preview: String,
/// RFC3339 timestamp string for the session start, if available.
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<String>,
/// Model provider recorded for the session (resolved when absent in metadata).
pub model_provider: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
#[serde(rename_all = "camelCase")]
pub struct ListConversationsResponse {
pub items: Vec<ConversationSummary>,
/// Opaque cursor to pass to the next call to continue after the last item.
/// if None, there are no more items to return.
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[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.
#[serde(skip_serializing_if = "Option::is_none")]
pub page_size: Option<usize>,
/// Opaque pagination cursor returned by a previous call.
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
@@ -448,24 +428,19 @@ 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>,
/// Opaque cursor to pass to the next call to continue after the last item.
/// if None, there are no more items to return.
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
#[serde(rename_all = "camelCase")]
pub struct UploadFeedbackParams {
pub classification: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<ConversationId>,
pub include_logs: bool,
}
@@ -493,7 +468,6 @@ 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.
@@ -510,20 +484,15 @@ 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>,
/// If the rollout path is not known, it can be discovered via the conversation id at the cost of extra latency.
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<ConversationId>,
/// if the rollout path or conversation id is not known, it can be resumed from given history
#[serde(skip_serializing_if = "Option::is_none")]
pub history: Option<Vec<ResponseItem>>,
/// Optional overrides to apply when spawning the resumed session.
#[serde(skip_serializing_if = "Option::is_none")]
pub overrides: Option<NewConversationParams>,
}
@@ -602,19 +571,15 @@ 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub include_token: Option<bool>,
/// If true, attempt to refresh the token before returning status.
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
#[serde(rename_all = "camelCase")]
pub struct ExecOneOffCommandParams {
/// Command argv to execute.
@@ -623,10 +588,8 @@ pub struct ExecOneOffCommandParams {
/// If not specified, a sensible default is used server-side.
pub timeout_ms: Option<u64>,
/// Optional working directory for the process. Defaults to server config cwd.
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathBuf>,
/// Optional explicit sandbox policy overriding the server default.
#[serde(skip_serializing_if = "Option::is_none")]
pub sandbox_policy: Option<SandboxPolicy>,
}
@@ -646,17 +609,13 @@ 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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth_token: Option<String>,
// Indicates that auth method must be valid to use the server.
// This can be false if using a custom provider that is configured
// with requires_openai_auth == false.
#[serde(skip_serializing_if = "Option::is_none")]
pub requires_openai_auth: Option<bool>,
}
@@ -667,7 +626,6 @@ 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
@@ -684,15 +642,12 @@ 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.
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
/// If set to None, this means `model_reasoning_effort` should be cleared
/// in config.toml.
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<ReasoningEffort>,
}
@@ -704,46 +659,32 @@ 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
#[serde(skip_serializing_if = "Option::is_none")]
pub approval_policy: Option<AskForApproval>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sandbox_mode: Option<SandboxMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sandbox_settings: Option<SandboxSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub forced_chatgpt_workspace_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub forced_login_method: Option<ForcedLoginMethod>,
/// Model-specific configuration
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_reasoning_effort: Option<ReasoningEffort>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_reasoning_summary: Option<ReasoningSummary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_verbosity: Option<Verbosity>,
/// Tools
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Tools>,
/// Profiles
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
#[serde(default)]
pub profiles: HashMap<String, Profile>,
}
/// 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>,
@@ -756,29 +697,23 @@ pub struct Profile {
pub model_verbosity: Option<Verbosity>,
pub chatgpt_base_url: Option<String>,
}
/// 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")]
pub web_search: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub view_image: Option<bool>,
}
/// 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)]
pub writable_roots: Vec<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub network_access: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude_tmpdir_env_var: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude_slash_tmp: Option<bool>,
}
@@ -790,7 +725,6 @@ 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,
@@ -799,7 +733,6 @@ pub struct SendUserTurnParams {
pub approval_policy: AskForApproval,
pub sandbox_policy: SandboxPolicy,
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<ReasoningEffort>,
pub summary: ReasoningSummary,
}
@@ -934,7 +867,6 @@ 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,
@@ -943,16 +875,13 @@ pub struct ApplyPatchApprovalParams {
pub call_id: String,
pub file_changes: HashMap<PathBuf, FileChange>,
/// Optional explanatory reason (e.g. request for extra write access).
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
/// When set, the agent is asking the user to allow writes under this root
/// for the remainder of the session (unclear if this is honored today).
#[serde(skip_serializing_if = "Option::is_none")]
pub grant_root: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
#[serde(rename_all = "camelCase")]
pub struct ExecCommandApprovalParams {
pub conversation_id: ConversationId,
@@ -961,9 +890,7 @@ pub struct ExecCommandApprovalParams {
pub call_id: String,
pub command: Vec<String>,
pub cwd: PathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub risk: Option<SandboxCommandAssessment>,
pub parsed_cmd: Vec<ParsedCommand>,
}
@@ -979,26 +906,22 @@ 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 {
pub query: String,
pub roots: Vec<String>,
// if provided, will cancel any previous request that used the same value
#[serde(skip_serializing_if = "Option::is_none")]
pub cancellation_token: Option<String>,
}
/// 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,
pub file_name: String,
pub score: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub indices: Option<Vec<u32>>,
}
@@ -1008,18 +931,15 @@ 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")]
pub login_id: Uuid,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[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.
@@ -1029,7 +949,6 @@ pub struct SessionConfiguredNotification {
pub model: String,
/// The effort the model is putting into reasoning about the user's request.
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<ReasoningEffort>,
/// Identifier of the history log file (inode on Unix, 0 otherwise).
@@ -1041,18 +960,15 @@ pub struct SessionConfiguredNotification {
/// Optional initial messages (as events) for resumed sessions.
/// When present, UIs can use these to seed the history.
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_messages: Option<Vec<EventMsg>>,
pub rollout_path: PathBuf,
}
#[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.
#[serde(skip_serializing_if = "Option::is_none")]
pub auth_method: Option<AuthMode>,
}
@@ -1125,6 +1041,8 @@ mod tests {
sandbox: None,
config: None,
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
include_apply_patch_tool: None,
},
};
@@ -1134,7 +1052,14 @@ mod tests {
"id": 42,
"params": {
"model": "gpt-5-codex",
"approvalPolicy": "on-request"
"modelProvider": null,
"profile": null,
"cwd": null,
"approvalPolicy": "on-request",
"sandbox": null,
"config": null,
"baseInstructions": null,
"includeApplyPatchTool": null
}
}),
serde_json::to_value(&request)?,
@@ -1207,6 +1132,7 @@ mod tests {
"command": ["echo", "hello"],
"cwd": "/tmp",
"reason": "because tests",
"risk": null,
"parsedCmd": [
{
"type": "unknown",
@@ -1351,7 +1277,10 @@ mod tests {
json!({
"method": "model/list",
"id": 6,
"params": {}
"params": {
"pageSize": null,
"cursor": null
}
}),
serde_json::to_value(&request)?,
);
@@ -1760,6 +1760,8 @@ async fn derive_config_from_params(
sandbox: sandbox_mode,
config: cli_overrides,
base_instructions,
developer_instructions,
compact_prompt,
include_apply_patch_tool,
} = params;
let overrides = ConfigOverrides {
@@ -1772,6 +1774,8 @@ async fn derive_config_from_params(
model_provider,
codex_linux_sandbox_exe,
base_instructions,
developer_instructions,
compact_prompt,
include_apply_patch_tool,
include_view_image_tool: None,
show_raw_agent_reasoning: None,
@@ -166,6 +166,7 @@ mod tests {
"params": {
"loginId": Uuid::nil(),
"success": true,
"error": null,
},
}),
serde_json::to_value(jsonrpc_notification)
@@ -44,7 +44,9 @@ async fn test_send_message_success() -> Result<()> {
// Start a conversation using the new wire API.
let new_conv_id = mcp
.send_new_conversation_request(NewConversationParams::default())
.send_new_conversation_request(NewConversationParams {
..Default::default()
})
.await?;
let new_conv_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
@@ -143,7 +145,10 @@ async fn test_send_message_raw_notifications_opt_in() -> Result<()> {
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let new_conv_id = mcp
.send_new_conversation_request(NewConversationParams::default())
.send_new_conversation_request(NewConversationParams {
developer_instructions: Some("Use the test harness tools.".to_string()),
..Default::default()
})
.await?;
let new_conv_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
@@ -177,6 +182,9 @@ async fn test_send_message_raw_notifications_opt_in() -> Result<()> {
})
.await?;
let developer = read_raw_response_item(&mut mcp, conversation_id).await;
assert_developer_message(&developer, "Use the test harness tools.");
let instructions = read_raw_response_item(&mut mcp, conversation_id).await;
assert_instructions_message(&instructions);
@@ -316,6 +324,21 @@ fn assert_instructions_message(item: &ResponseItem) {
}
}
fn assert_developer_message(item: &ResponseItem, expected_text: &str) {
match item {
ResponseItem::Message { role, content, .. } => {
assert_eq!(role, "developer");
let texts = content_texts(content);
assert_eq!(
texts,
vec![expected_text],
"expected developer instructions message, got {texts:?}"
);
}
other => panic!("expected developer instructions message, got {other:?}"),
}
}
fn assert_environment_message(item: &ResponseItem) {
match item {
ResponseItem::Message { role, content, .. } => {
+1 -3
View File
@@ -347,9 +347,7 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
.context("failed to load configuration")?;
if !config.features.enabled(Feature::RmcpClient) {
bail!(
"OAuth login is only supported when experimental_use_rmcp_client is true in config.toml."
);
bail!("OAuth login is only supported when [feature].rmcp_client is true in config.toml.");
}
let LoginArgs { name, scopes } = login_args;
+31 -3
View File
@@ -114,6 +114,7 @@ use crate::tools::spec::ToolsConfig;
use crate::tools::spec::ToolsConfigParams;
use crate::turn_diff_tracker::TurnDiffTracker;
use crate::unified_exec::UnifiedExecSessionManager;
use crate::user_instructions::DeveloperInstructions;
use crate::user_instructions::UserInstructions;
use crate::user_notification::UserNotification;
use crate::util::backoff;
@@ -173,8 +174,10 @@ impl Codex {
model: config.model.clone(),
model_reasoning_effort: config.model_reasoning_effort,
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
user_instructions,
base_instructions: config.base_instructions.clone(),
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.approval_policy,
sandbox_policy: config.sandbox_policy.clone(),
cwd: config.cwd.clone(),
@@ -266,7 +269,9 @@ pub(crate) struct TurnContext {
/// the model as well as sandbox policies are resolved against this path
/// instead of `std::env::current_dir()`.
pub(crate) cwd: PathBuf,
pub(crate) developer_instructions: Option<String>,
pub(crate) base_instructions: Option<String>,
pub(crate) compact_prompt: Option<String>,
pub(crate) user_instructions: Option<String>,
pub(crate) approval_policy: AskForApproval,
pub(crate) sandbox_policy: SandboxPolicy,
@@ -283,6 +288,12 @@ impl TurnContext {
.map(PathBuf::from)
.map_or_else(|| self.cwd.clone(), |p| self.cwd.join(p))
}
pub(crate) fn compact_prompt(&self) -> &str {
self.compact_prompt
.as_deref()
.unwrap_or(compact::SUMMARIZATION_PROMPT)
}
}
#[allow(dead_code)]
@@ -297,12 +308,18 @@ pub(crate) struct SessionConfiguration {
model_reasoning_effort: Option<ReasoningEffortConfig>,
model_reasoning_summary: ReasoningSummaryConfig,
/// Developer instructions that supplement the base instructions.
developer_instructions: Option<String>,
/// Model instructions that are appended to the base instructions.
user_instructions: Option<String>,
/// Base instructions override.
base_instructions: Option<String>,
/// Compact prompt override.
compact_prompt: Option<String>,
/// When to escalate for approval for execution
approval_policy: AskForApproval,
/// How to sandbox commands executed in the system
@@ -414,7 +431,9 @@ impl Session {
sub_id,
client,
cwd: session_configuration.cwd.clone(),
developer_instructions: session_configuration.developer_instructions.clone(),
base_instructions: session_configuration.base_instructions.clone(),
compact_prompt: session_configuration.compact_prompt.clone(),
user_instructions: session_configuration.user_instructions.clone(),
approval_policy: session_configuration.approval_policy,
sandbox_policy: session_configuration.sandbox_policy.clone(),
@@ -1057,7 +1076,10 @@ impl Session {
}
pub(crate) fn build_initial_context(&self, turn_context: &TurnContext) -> Vec<ResponseItem> {
let mut items = Vec::<ResponseItem>::with_capacity(2);
let mut items = Vec::<ResponseItem>::with_capacity(3);
if let Some(developer_instructions) = turn_context.developer_instructions.as_deref() {
items.push(DeveloperInstructions::new(developer_instructions.to_string()).into());
}
if let Some(user_instructions) = turn_context.user_instructions.as_deref() {
items.push(UserInstructions::new(user_instructions.to_string()).into());
}
@@ -1391,7 +1413,7 @@ mod handlers {
use crate::codex::Session;
use crate::codex::SessionSettingsUpdate;
use crate::codex::TurnContext;
use crate::codex::compact;
use crate::codex::spawn_review_thread;
use crate::config::Config;
use crate::mcp::auth::compute_auth_statuses;
@@ -1618,7 +1640,7 @@ mod handlers {
// Attempt to inject input into current task
if let Err(items) = sess
.inject_input(vec![UserInput::Text {
text: compact::SUMMARIZATION_PROMPT.to_string(),
text: turn_context.compact_prompt().to_string(),
}])
.await
{
@@ -1740,8 +1762,10 @@ async fn spawn_review_thread(
sub_id: sub_id.to_string(),
client,
tools_config,
developer_instructions: None,
user_instructions: None,
base_instructions: Some(base_instructions.clone()),
compact_prompt: parent_turn_context.compact_prompt.clone(),
approval_policy: parent_turn_context.approval_policy,
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
@@ -2623,8 +2647,10 @@ mod tests {
model: config.model.clone(),
model_reasoning_effort: config.model_reasoning_effort,
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
user_instructions: config.user_instructions.clone(),
base_instructions: config.base_instructions.clone(),
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.approval_policy,
sandbox_policy: config.sandbox_policy.clone(),
cwd: config.cwd.clone(),
@@ -2697,8 +2723,10 @@ mod tests {
model: config.model.clone(),
model_reasoning_effort: config.model_reasoning_effort,
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
user_instructions: config.user_instructions.clone(),
base_instructions: config.base_instructions.clone(),
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.approval_policy,
sandbox_policy: config.sandbox_policy.clone(),
cwd: config.cwd.clone(),
+2 -3
View File
@@ -40,9 +40,8 @@ pub(crate) async fn run_inline_auto_compact_task(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
) {
let input = vec![UserInput::Text {
text: SUMMARIZATION_PROMPT.to_string(),
}];
let prompt = turn_context.compact_prompt().to_string();
let input = vec![UserInput::Text { text: prompt }];
run_compact_task_inner(sess, turn_context, input).await;
}
+4 -1
View File
@@ -36,6 +36,7 @@ pub(crate) async fn run_codex_conversation_interactive(
parent_session: Arc<Session>,
parent_ctx: Arc<TurnContext>,
cancel_token: CancellationToken,
initial_history: Option<InitialHistory>,
) -> Result<Codex, CodexErr> {
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
let (tx_ops, rx_ops) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
@@ -43,7 +44,7 @@ pub(crate) async fn run_codex_conversation_interactive(
let CodexSpawnOk { codex, .. } = Codex::spawn(
config,
auth_manager,
InitialHistory::New,
initial_history.unwrap_or(InitialHistory::New),
SessionSource::SubAgent(SubAgentSource::Review),
)
.await?;
@@ -93,6 +94,7 @@ pub(crate) async fn run_codex_conversation_one_shot(
parent_session: Arc<Session>,
parent_ctx: Arc<TurnContext>,
cancel_token: CancellationToken,
initial_history: Option<InitialHistory>,
) -> Result<Codex, CodexErr> {
// Use a child token so we can stop the delegate after completion without
// requiring the caller to cancel the parent token.
@@ -103,6 +105,7 @@ pub(crate) async fn run_codex_conversation_one_shot(
parent_session,
parent_ctx,
child_cancel.clone(),
initial_history,
)
.await?;
+115 -17
View File
@@ -128,6 +128,12 @@ pub struct Config {
/// Base instructions override.
pub base_instructions: Option<String>,
/// Developer instructions override injected as a separate message.
pub developer_instructions: Option<String>,
/// Compact prompt override.
pub compact_prompt: Option<String>,
/// Optional external notifier command. When set, Codex will spawn this
/// program after each completed *turn* (i.e. when the agent finishes
/// processing a user submission). The value must be the full command
@@ -541,6 +547,13 @@ pub struct ConfigToml {
/// System instructions.
pub instructions: Option<String>,
/// Developer instructions inserted as a `developer` role message.
#[serde(default)]
pub developer_instructions: Option<String>,
/// Compact prompt used for history compaction.
pub compact_prompt: Option<String>,
/// When set, restricts ChatGPT login to a specific workspace identifier.
#[serde(default)]
pub forced_chatgpt_workspace_id: Option<String>,
@@ -644,6 +657,7 @@ pub struct ConfigToml {
/// Legacy, now use features
pub experimental_instructions_file: Option<PathBuf>,
pub experimental_compact_prompt_file: Option<PathBuf>,
pub experimental_use_exec_command_tool: Option<bool>,
pub experimental_use_unified_exec_tool: Option<bool>,
pub experimental_use_rmcp_client: Option<bool>,
@@ -824,6 +838,8 @@ pub struct ConfigOverrides {
pub config_profile: Option<String>,
pub codex_linux_sandbox_exe: Option<PathBuf>,
pub base_instructions: Option<String>,
pub developer_instructions: Option<String>,
pub compact_prompt: Option<String>,
pub include_apply_patch_tool: Option<bool>,
pub include_view_image_tool: Option<bool>,
pub show_raw_agent_reasoning: Option<bool>,
@@ -854,6 +870,8 @@ impl Config {
config_profile: config_profile_key,
codex_linux_sandbox_exe,
base_instructions,
developer_instructions,
compact_prompt,
include_apply_patch_tool: include_apply_patch_tool_override,
include_view_image_tool: include_view_image_tool_override,
show_raw_agent_reasoning,
@@ -1030,6 +1048,15 @@ impl Config {
.and_then(|info| info.auto_compact_token_limit)
});
let compact_prompt = compact_prompt.or(cfg.compact_prompt).and_then(|value| {
let trimmed = value.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
});
// Load base instructions override from a file if specified. If the
// path is relative, resolve it against the effective cwd so the
// behaviour matches other path-like config values.
@@ -1037,9 +1064,24 @@ impl Config {
.experimental_instructions_file
.as_ref()
.or(cfg.experimental_instructions_file.as_ref());
let file_base_instructions =
Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?;
let file_base_instructions = Self::load_override_from_file(
experimental_instructions_path,
&resolved_cwd,
"experimental instructions file",
)?;
let base_instructions = base_instructions.or(file_base_instructions);
let developer_instructions = developer_instructions.or(cfg.developer_instructions);
let experimental_compact_prompt_path = config_profile
.experimental_compact_prompt_file
.as_ref()
.or(cfg.experimental_compact_prompt_file.as_ref());
let file_compact_prompt = Self::load_override_from_file(
experimental_compact_prompt_path,
&resolved_cwd,
"experimental compact prompt file",
)?;
let compact_prompt = compact_prompt.or(file_compact_prompt);
// Default review model when not set in config; allow CLI override to take precedence.
let review_model = override_review_model
@@ -1064,6 +1106,8 @@ impl Config {
notify: cfg.notify,
user_instructions,
base_instructions,
developer_instructions,
compact_prompt,
// The config.toml omits "_mode" because it's a config file. However, "_mode"
// is important in code to differentiate the mode from the store implementation.
cli_auth_credentials_store_mode: cfg.cli_auth_credentials_store.unwrap_or_default(),
@@ -1160,18 +1204,15 @@ impl Config {
None
}
fn get_base_instructions(
fn load_override_from_file(
path: Option<&PathBuf>,
cwd: &Path,
description: &str,
) -> std::io::Result<Option<String>> {
let p = match path.as_ref() {
None => return Ok(None),
Some(p) => p,
let Some(p) = path else {
return Ok(None);
};
// Resolve relative paths against the provided cwd to make CLI
// overrides consistent regardless of where the process was launched
// from.
let full_path = if p.is_relative() {
cwd.join(p)
} else {
@@ -1181,10 +1222,7 @@ impl Config {
let contents = std::fs::read_to_string(&full_path).map_err(|e| {
std::io::Error::new(
e.kind(),
format!(
"failed to read experimental instructions file {}: {e}",
full_path.display()
),
format!("failed to read {description} {}: {e}", full_path.display()),
)
})?;
@@ -1192,10 +1230,7 @@ impl Config {
if s.is_empty() {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"experimental instructions file is empty: {}",
full_path.display()
),
format!("{description} is empty: {}", full_path.display()),
))
} else {
Ok(Some(s))
@@ -2653,6 +2688,61 @@ model = "gpt-5-codex"
}
}
#[test]
fn cli_override_sets_compact_prompt() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let overrides = ConfigOverrides {
compact_prompt: Some("Use the compact override".to_string()),
..Default::default()
};
let config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
overrides,
codex_home.path().to_path_buf(),
)?;
assert_eq!(
config.compact_prompt.as_deref(),
Some("Use the compact override")
);
Ok(())
}
#[test]
fn loads_compact_prompt_from_file() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let workspace = codex_home.path().join("workspace");
std::fs::create_dir_all(&workspace)?;
let prompt_path = workspace.join("compact_prompt.txt");
std::fs::write(&prompt_path, " summarize differently ")?;
let cfg = ConfigToml {
experimental_compact_prompt_file: Some(PathBuf::from("compact_prompt.txt")),
..Default::default()
};
let overrides = ConfigOverrides {
cwd: Some(workspace),
..Default::default()
};
let config = Config::load_from_base_config_with_overrides(
cfg,
overrides,
codex_home.path().to_path_buf(),
)?;
assert_eq!(
config.compact_prompt.as_deref(),
Some("summarize differently")
);
Ok(())
}
fn create_test_fixture() -> std::io::Result<PrecedenceTestFixture> {
let toml = r#"
model = "o3"
@@ -2808,6 +2898,8 @@ model_verbosity = "high"
model_verbosity: None,
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
include_apply_patch_tool: false,
@@ -2879,6 +2971,8 @@ model_verbosity = "high"
model_verbosity: None,
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
include_apply_patch_tool: false,
@@ -2965,6 +3059,8 @@ model_verbosity = "high"
model_verbosity: None,
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
include_apply_patch_tool: false,
@@ -3037,6 +3133,8 @@ model_verbosity = "high"
model_verbosity: Some(Verbosity::High),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
include_apply_patch_tool: false,
+1
View File
@@ -22,6 +22,7 @@ pub struct ConfigProfile {
pub model_verbosity: Option<Verbosity>,
pub chatgpt_base_url: Option<String>,
pub experimental_instructions_file: Option<PathBuf>,
pub experimental_compact_prompt_file: Option<PathBuf>,
pub include_apply_patch_tool: Option<bool>,
pub include_view_image_tool: Option<bool>,
pub experimental_use_unified_exec_tool: Option<bool>,
+1
View File
@@ -90,6 +90,7 @@ async fn start_review_conversation(
session.clone_session(),
ctx.clone(),
cancellation_token,
None,
)
.await)
.ok()
+28
View File
@@ -40,3 +40,31 @@ impl From<UserInstructions> for ResponseItem {
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename = "developer_instructions", rename_all = "snake_case")]
pub(crate) struct DeveloperInstructions {
text: String,
}
impl DeveloperInstructions {
pub fn new<T: Into<String>>(text: T) -> Self {
Self { text: text.into() }
}
pub fn into_text(self) -> String {
self.text
}
}
impl From<DeveloperInstructions> for ResponseItem {
fn from(di: DeveloperInstructions) -> Self {
ResponseItem::Message {
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: di.into_text(),
}],
}
}
}
+24
View File
@@ -240,6 +240,30 @@ impl TestCodexHarness {
.expect("output string")
.to_string()
}
pub async fn custom_tool_call_output(&self, call_id: &str) -> String {
let bodies = self.request_bodies().await;
custom_tool_call_output(&bodies, call_id)
.get("output")
.and_then(Value::as_str)
.expect("output string")
.to_string()
}
}
fn custom_tool_call_output<'a>(bodies: &'a [Value], call_id: &str) -> &'a Value {
for body in bodies {
if let Some(items) = body.get("input").and_then(Value::as_array) {
for item in items {
if item.get("type").and_then(Value::as_str) == Some("custom_tool_call_output")
&& item.get("call_id").and_then(Value::as_str) == Some(call_id)
{
return item;
}
}
}
}
panic!("custom_tool_call_output {call_id} not found");
}
fn function_call_output<'a>(bodies: &'a [Value], call_id: &str) -> &'a Value {
File diff suppressed because it is too large Load Diff
+70
View File
@@ -59,6 +59,18 @@ fn assert_message_role(request_body: &serde_json::Value, role: &str) {
assert_eq!(request_body["role"].as_str().unwrap(), role);
}
#[expect(clippy::expect_used)]
fn assert_message_equals(request_body: &serde_json::Value, text: &str) {
let content = request_body["content"][0]["text"]
.as_str()
.expect("invalid message content");
assert_eq!(
content, text,
"expected message content '{content}' to equal '{text}'"
);
}
#[expect(clippy::expect_used)]
fn assert_message_starts_with(request_body: &serde_json::Value, text: &str) {
let content = request_body["content"][0]["text"]
@@ -609,6 +621,64 @@ async fn includes_user_instructions_message_in_request() {
assert_message_ends_with(&request_body["input"][1], "</environment_context>");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn includes_developer_instructions_message_in_request() {
skip_if_no_network!();
let server = MockServer::start().await;
let resp_mock =
responses::mount_sse_once_match(&server, path("/v1/responses"), sse_completed("resp1"))
.await;
let model_provider = ModelProviderInfo {
base_url: Some(format!("{}/v1", server.uri())),
..built_in_model_providers()["openai"].clone()
};
let codex_home = TempDir::new().unwrap();
let mut config = load_default_config_for_test(&codex_home);
config.model_provider = model_provider;
config.user_instructions = Some("be nice".to_string());
config.developer_instructions = Some("be useful".to_string());
let conversation_manager =
ConversationManager::with_auth(CodexAuth::from_api_key("Test API Key"));
let codex = conversation_manager
.new_conversation(config)
.await
.expect("create new conversation")
.conversation;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "hello".into(),
}],
})
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
let request = resp_mock.single_request();
let request_body = request.body_json();
assert!(
!request_body["instructions"]
.as_str()
.unwrap()
.contains("be nice")
);
assert_message_role(&request_body["input"][0], "developer");
assert_message_equals(&request_body["input"][0], "be useful");
assert_message_role(&request_body["input"][1], "user");
assert_message_starts_with(&request_body["input"][1], "<user_instructions>");
assert_message_ends_with(&request_body["input"][1], "</user_instructions>");
assert_message_role(&request_body["input"][2], "user");
assert_message_starts_with(&request_body["input"][2], "<environment_context>");
assert_message_ends_with(&request_body["input"][2], "</environment_context>");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn responses_api_chaining_sets_store_and_previous_id() {
skip_if_no_network!();
+59
View File
@@ -261,6 +261,65 @@ async fn summarize_context_three_requests_and_instructions() {
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn manual_compact_uses_custom_prompt() {
skip_if_no_network!();
let server = start_mock_server().await;
let sse_stream = sse(vec![ev_completed("r1")]);
mount_sse_once(&server, sse_stream).await;
let custom_prompt = "Use this compact prompt instead";
let model_provider = ModelProviderInfo {
base_url: Some(format!("{}/v1", server.uri())),
..built_in_model_providers()["openai"].clone()
};
let home = TempDir::new().unwrap();
let mut config = load_default_config_for_test(&home);
config.model_provider = model_provider;
config.compact_prompt = Some(custom_prompt.to_string());
let conversation_manager = ConversationManager::with_auth(CodexAuth::from_api_key("dummy"));
let codex = conversation_manager
.new_conversation(config)
.await
.expect("create conversation")
.conversation;
codex.submit(Op::Compact).await.expect("trigger compact");
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
let requests = server.received_requests().await.expect("collect requests");
let body = requests
.iter()
.find_map(|req| req.body_json::<serde_json::Value>().ok())
.expect("summary request body");
let input = body
.get("input")
.and_then(|v| v.as_array())
.expect("input array");
let mut found_custom_prompt = false;
let mut found_default_prompt = false;
for item in input {
if item["type"].as_str() != Some("message") {
continue;
}
let text = item["content"][0]["text"].as_str().unwrap_or_default();
if text == custom_prompt {
found_custom_prompt = true;
}
if text == SUMMARIZATION_PROMPT {
found_default_prompt = true;
}
}
assert!(found_custom_prompt, "custom prompt should be injected");
assert!(!found_default_prompt, "default prompt should be replaced");
}
// Windows CI only: bump to 4 workers to prevent SSE/event starvation and test timeouts.
#[cfg_attr(windows, tokio::test(flavor = "multi_thread", worker_threads = 4))]
#[cfg_attr(not(windows), tokio::test(flavor = "multi_thread", worker_threads = 2))]
+2
View File
@@ -5,6 +5,8 @@ mod abort_tasks;
#[cfg(not(target_os = "windows"))]
mod apply_patch_cli;
#[cfg(not(target_os = "windows"))]
mod apply_patch_freeform;
#[cfg(not(target_os = "windows"))]
mod approvals;
mod cli_stream;
mod client;
+1
View File
@@ -61,6 +61,7 @@ Request `newConversation` params (subset):
- `sandbox`: `read-only` | `workspace-write` | `danger-full-access`
- `config`: map of additional config overrides
- `baseInstructions`: optional instruction override
- `compactPrompt`: optional replacement for the default compaction prompt
- `includePlanTool` / `includeApplyPatchTool`: booleans
Response: `{ conversationId, model, reasoningEffort?, rolloutPath }`
+2
View File
@@ -174,6 +174,8 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
model_provider,
codex_linux_sandbox_exe,
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
include_apply_patch_tool: None,
include_view_image_tool: None,
show_raw_agent_reasoning: oss.then_some(true),
@@ -49,6 +49,14 @@ pub struct CodexToolCallParam {
/// The set of instructions to use instead of the default ones.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_instructions: Option<String>,
/// Developer instructions that should be injected as a developer role message.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub developer_instructions: Option<String>,
/// Prompt used when compacting the conversation.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub compact_prompt: Option<String>,
}
/// Custom enum mirroring [`AskForApproval`], but has an extra dependency on
@@ -141,6 +149,8 @@ impl CodexToolCallParam {
sandbox,
config: cli_overrides,
base_instructions,
developer_instructions,
compact_prompt,
} = self;
// Build the `ConfigOverrides` recognized by codex-core.
@@ -154,6 +164,8 @@ impl CodexToolCallParam {
model_provider: None,
codex_linux_sandbox_exe,
base_instructions,
developer_instructions,
compact_prompt,
include_apply_patch_tool: None,
include_view_image_tool: None,
show_raw_agent_reasoning: None,
@@ -288,6 +300,14 @@ mod tests {
"description": "The set of instructions to use instead of the default ones.",
"type": "string"
},
"developer-instructions": {
"description": "Developer instructions that should be injected as a developer role message.",
"type": "string"
},
"compact-prompt": {
"description": "Prompt used when compacting the conversation.",
"type": "string"
},
},
"required": [
"prompt"
+20 -1
View File
@@ -341,6 +341,7 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> {
.send_codex_tool_call(CodexToolCallParam {
prompt: "How are you?".to_string(),
base_instructions: Some("You are a helpful assistant.".to_string()),
developer_instructions: Some("Foreshadow upcoming tool calls.".to_string()),
..Default::default()
})
.await?;
@@ -367,10 +368,28 @@ async fn codex_tool_passes_base_instructions() -> anyhow::Result<()> {
);
let requests = server.received_requests().await.unwrap();
let request = requests[0].body_json::<serde_json::Value>().unwrap();
let request = requests[0].body_json::<serde_json::Value>()?;
let instructions = request["messages"][0]["content"].as_str().unwrap();
assert!(instructions.starts_with("You are a helpful assistant."));
let developer_msg = request["messages"]
.as_array()
.and_then(|messages| {
messages
.iter()
.find(|msg| msg.get("role").and_then(|role| role.as_str()) == Some("developer"))
})
.unwrap();
let developer_content = developer_msg
.get("content")
.and_then(|value| value.as_str())
.unwrap();
assert!(
!developer_content.contains('<'),
"expected developer instructions without XML tags, got `{developer_content}`"
);
assert_eq!(developer_content, "Foreshadow upcoming tool calls.");
Ok(())
}
+17 -7
View File
@@ -332,6 +332,7 @@ class StructField:
name: str
type_name: str
serde: str | None = None
ts: str | None = None
comment: str | None = None
def append(self, out: list[str], supports_const: bool) -> None:
@@ -339,6 +340,8 @@ class StructField:
out.append(f" // {self.comment}\n")
if self.serde:
out.append(f" {self.serde}\n")
if self.ts:
out.append(f" {self.ts}\n")
if self.viz == "const":
if supports_const:
out.append(f" const {self.name}: {self.type_name};\n")
@@ -378,9 +381,9 @@ def define_struct(
prop_type = f"Option<{prop_type}>"
rs_prop = rust_prop_name(prop_name, is_optional)
if prop_type.startswith("&'static str"):
fields.append(StructField("const", rs_prop.name, prop_type, rs_prop.serde))
fields.append(StructField("const", rs_prop.name, prop_type, rs_prop.serde, rs_prop.ts))
else:
fields.append(StructField("pub", rs_prop.name, prop_type, rs_prop.serde))
fields.append(StructField("pub", rs_prop.name, prop_type, rs_prop.serde, rs_prop.ts))
# Special-case: add Codex-specific user_agent to Implementation
if name == "Implementation":
@@ -390,6 +393,7 @@ def define_struct(
"user_agent",
"Option<String>",
'#[serde(default, skip_serializing_if = "Option::is_none")]',
'#[ts(optional)]',
"This is an extra field that the Codex MCP server sends as part of InitializeResult.",
)
)
@@ -474,7 +478,6 @@ def define_string_enum(
out.append(f" {capitalize(value)},\n")
out.append("}\n\n")
return out
def define_untagged_enum(name: str, type_list: list[str], out: list[str]) -> None:
@@ -590,7 +593,7 @@ def get_serde_annotation_for_anyof_type(type_name: str) -> str | None:
def map_type(
typedef: dict[str, any],
typedef: dict[str, Any],
prop_name: str | None = None,
struct_name: str | None = None,
) -> str:
@@ -665,7 +668,8 @@ class RustProp:
name: str
# serde annotation, if necessary
serde: str | None = None
# ts annotation, if necessary
ts: str | None = None
def rust_prop_name(name: str, is_optional: bool) -> RustProp:
"""Convert a JSON property name to a Rust property name."""
@@ -684,6 +688,7 @@ def rust_prop_name(name: str, is_optional: bool) -> RustProp:
prop_name = name
serde_annotations = []
ts_str = None
if is_rename:
serde_annotations.append(f'rename = "{name}"')
if is_optional:
@@ -691,13 +696,18 @@ def rust_prop_name(name: str, is_optional: bool) -> RustProp:
serde_annotations.append('skip_serializing_if = "Option::is_none"')
if serde_annotations:
# Also mark optional fields for ts-rs generation.
serde_str = f"#[serde({', '.join(serde_annotations)})]"
else:
serde_str = None
return RustProp(prop_name, serde_str)
if is_optional and serde_str:
ts_str = "#[ts(optional)]"
return RustProp(prop_name, serde_str, ts_str)
def to_snake_case(name: str) -> str:
def to_snake_case(name: str) -> str | None:
"""Convert a camelCase or PascalCase name to snake_case."""
snake_case = name[0].lower() + "".join("_" + c.lower() if c.isupper() else c for c in name[1:])
if snake_case != name:
+122
View File
@@ -37,14 +37,17 @@ fn default_jsonrpc() -> String {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Annotations {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub audience: Option<Vec<Role>>,
#[serde(
rename = "lastModified",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub last_modified: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub priority: Option<f64>,
}
@@ -52,6 +55,7 @@ pub struct Annotations {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct AudioContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<Annotations>,
pub data: String,
#[serde(rename = "mimeType")]
@@ -64,6 +68,7 @@ pub struct AudioContent {
pub struct BaseMetadata {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
}
@@ -71,6 +76,7 @@ pub struct BaseMetadata {
pub struct BlobResourceContents {
pub blob: String,
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mime_type: Option<String>,
pub uri: String,
}
@@ -78,10 +84,13 @@ pub struct BlobResourceContents {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct BooleanSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub r#type: String, // &'static str = "boolean"
}
@@ -98,6 +107,7 @@ impl ModelContextProtocolRequest for CallToolRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CallToolRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub arguments: Option<serde_json::Value>,
pub name: String,
}
@@ -107,12 +117,14 @@ pub struct CallToolRequestParams {
pub struct CallToolResult {
pub content: Vec<ContentBlock>,
#[serde(rename = "isError", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub is_error: Option<bool>,
#[serde(
rename = "structuredContent",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub structured_content: Option<serde_json::Value>,
}
@@ -135,6 +147,7 @@ impl ModelContextProtocolNotification for CancelledNotification {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CancelledNotificationParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub reason: Option<String>,
#[serde(rename = "requestId")]
pub request_id: RequestId,
@@ -144,12 +157,16 @@ pub struct CancelledNotificationParams {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ClientCapabilities {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub elicitation: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub experimental: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub roots: Option<ClientCapabilitiesRoots>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub sampling: Option<serde_json::Value>,
}
@@ -161,6 +178,7 @@ pub struct ClientCapabilitiesRoots {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub list_changed: Option<bool>,
}
@@ -228,6 +246,7 @@ impl ModelContextProtocolRequest for CompleteRequest {
pub struct CompleteRequestParams {
pub argument: CompleteRequestParamsArgument,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub context: Option<CompleteRequestParamsContext>,
pub r#ref: CompleteRequestParamsRef,
}
@@ -236,6 +255,7 @@ pub struct CompleteRequestParams {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CompleteRequestParamsContext {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub arguments: Option<serde_json::Value>,
}
@@ -262,8 +282,10 @@ pub struct CompleteResult {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CompleteResultCompletion {
#[serde(rename = "hasMore", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub has_more: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub total: Option<i64>,
pub values: Vec<String>,
}
@@ -302,31 +324,37 @@ pub struct CreateMessageRequestParams {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub include_context: Option<String>,
#[serde(rename = "maxTokens")]
pub max_tokens: i64,
pub messages: Vec<SamplingMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub metadata: Option<serde_json::Value>,
#[serde(
rename = "modelPreferences",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub model_preferences: Option<ModelPreferences>,
#[serde(
rename = "stopSequences",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub stop_sequences: Option<Vec<String>>,
#[serde(
rename = "systemPrompt",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub system_prompt: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub temperature: Option<f64>,
}
@@ -341,6 +369,7 @@ pub struct CreateMessageResult {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub stop_reason: Option<String>,
}
@@ -385,6 +414,7 @@ pub struct ElicitRequestParams {
pub struct ElicitRequestParamsRequestedSchema {
pub properties: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub required: Option<Vec<String>>,
pub r#type: String, // &'static str = "object"
}
@@ -394,6 +424,7 @@ pub struct ElicitRequestParamsRequestedSchema {
pub struct ElicitResult {
pub action: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub content: Option<serde_json::Value>,
}
@@ -412,6 +443,7 @@ impl From<ElicitResult> for serde_json::Value {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct EmbeddedResource {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<Annotations>,
pub resource: EmbeddedResourceResource,
pub r#type: String, // &'static str = "resource"
@@ -429,11 +461,14 @@ pub type EmptyResult = Result;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct EnumSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
pub r#enum: Vec<String>,
#[serde(rename = "enumNames", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub enum_names: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub r#type: String, // &'static str = "string"
}
@@ -450,6 +485,7 @@ impl ModelContextProtocolRequest for GetPromptRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct GetPromptRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub arguments: Option<serde_json::Value>,
pub name: String,
}
@@ -458,6 +494,7 @@ pub struct GetPromptRequestParams {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct GetPromptResult {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
pub messages: Vec<PromptMessage>,
}
@@ -474,6 +511,7 @@ impl From<GetPromptResult> for serde_json::Value {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ImageContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<Annotations>,
pub data: String,
#[serde(rename = "mimeType")]
@@ -486,10 +524,12 @@ pub struct ImageContent {
pub struct Implementation {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub version: String,
// This is an extra field that the Codex MCP server sends as part of InitializeResult.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub user_agent: Option<String>,
}
@@ -516,6 +556,7 @@ pub struct InitializeRequestParams {
pub struct InitializeResult {
pub capabilities: ServerCapabilities,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub instructions: Option<String>,
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
@@ -552,6 +593,7 @@ pub struct JSONRPCError {
pub struct JSONRPCErrorError {
pub code: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub data: Option<serde_json::Value>,
pub message: String,
}
@@ -573,6 +615,7 @@ pub struct JSONRPCNotification {
pub jsonrpc: String,
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
}
@@ -584,6 +627,7 @@ pub struct JSONRPCRequest {
pub jsonrpc: String,
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
}
@@ -608,6 +652,7 @@ impl ModelContextProtocolRequest for ListPromptsRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListPromptsRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub cursor: Option<String>,
}
@@ -619,6 +664,7 @@ pub struct ListPromptsResult {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub next_cursor: Option<String>,
pub prompts: Vec<Prompt>,
}
@@ -643,6 +689,7 @@ impl ModelContextProtocolRequest for ListResourceTemplatesRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListResourceTemplatesRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub cursor: Option<String>,
}
@@ -654,6 +701,7 @@ pub struct ListResourceTemplatesResult {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub next_cursor: Option<String>,
#[serde(rename = "resourceTemplates")]
pub resource_templates: Vec<ResourceTemplate>,
@@ -679,6 +727,7 @@ impl ModelContextProtocolRequest for ListResourcesRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListResourcesRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub cursor: Option<String>,
}
@@ -690,6 +739,7 @@ pub struct ListResourcesResult {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub next_cursor: Option<String>,
pub resources: Vec<Resource>,
}
@@ -739,6 +789,7 @@ impl ModelContextProtocolRequest for ListToolsRequest {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListToolsRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub cursor: Option<String>,
}
@@ -750,6 +801,7 @@ pub struct ListToolsResult {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub next_cursor: Option<String>,
pub tools: Vec<Tool>,
}
@@ -799,6 +851,7 @@ pub struct LoggingMessageNotificationParams {
pub data: serde_json::Value,
pub level: LoggingLevel,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub logger: Option<String>,
}
@@ -809,6 +862,7 @@ pub struct LoggingMessageNotificationParams {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ModelHint {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub name: Option<String>,
}
@@ -830,20 +884,24 @@ pub struct ModelPreferences {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub cost_priority: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub hints: Option<Vec<ModelHint>>,
#[serde(
rename = "intelligencePriority",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub intelligence_priority: Option<f64>,
#[serde(
rename = "speedPriority",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub speed_priority: Option<f64>,
}
@@ -851,18 +909,23 @@ pub struct ModelPreferences {
pub struct Notification {
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct NumberSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub maximum: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub minimum: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub r#type: String,
}
@@ -871,12 +934,14 @@ pub struct NumberSchema {
pub struct PaginatedRequest {
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<PaginatedRequestParams>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct PaginatedRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub cursor: Option<String>,
}
@@ -887,6 +952,7 @@ pub struct PaginatedResult {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub next_cursor: Option<String>,
}
@@ -929,11 +995,13 @@ impl ModelContextProtocolNotification for ProgressNotification {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ProgressNotificationParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub message: Option<String>,
pub progress: f64,
#[serde(rename = "progressToken")]
pub progress_token: ProgressToken,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub total: Option<f64>,
}
@@ -948,11 +1016,14 @@ pub enum ProgressToken {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Prompt {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub arguments: Option<Vec<PromptArgument>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
}
@@ -960,11 +1031,14 @@ pub struct Prompt {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct PromptArgument {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub required: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
}
@@ -991,6 +1065,7 @@ pub struct PromptMessage {
pub struct PromptReference {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub r#type: String, // &'static str = "ref/prompt"
}
@@ -1034,6 +1109,7 @@ impl From<ReadResourceResult> for serde_json::Value {
pub struct Request {
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
}
@@ -1048,15 +1124,20 @@ pub enum RequestId {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Resource {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<Annotations>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mime_type: Option<String>,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub size: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub uri: String,
}
@@ -1065,6 +1146,7 @@ pub struct Resource {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResourceContents {
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mime_type: Option<String>,
pub uri: String,
}
@@ -1075,15 +1157,20 @@ pub struct ResourceContents {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResourceLink {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<Annotations>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mime_type: Option<String>,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub size: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub r#type: String, // &'static str = "resource_link"
pub uri: String,
@@ -1101,13 +1188,17 @@ impl ModelContextProtocolNotification for ResourceListChangedNotification {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResourceTemplate {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<Annotations>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mime_type: Option<String>,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(rename = "uriTemplate")]
pub uri_template: String,
@@ -1148,6 +1239,7 @@ pub enum Role {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Root {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub name: Option<String>,
pub uri: String,
}
@@ -1179,16 +1271,22 @@ pub enum SamplingMessageContent {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ServerCapabilities {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub completions: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub experimental: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub logging: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub prompts: Option<ServerCapabilitiesPrompts>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub resources: Option<ServerCapabilitiesResources>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub tools: Option<ServerCapabilitiesTools>,
}
@@ -1200,6 +1298,7 @@ pub struct ServerCapabilitiesTools {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub list_changed: Option<bool>,
}
@@ -1211,8 +1310,10 @@ pub struct ServerCapabilitiesResources {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub list_changed: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub subscribe: Option<bool>,
}
@@ -1224,6 +1325,7 @@ pub struct ServerCapabilitiesPrompts {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub list_changed: Option<bool>,
}
@@ -1298,14 +1400,19 @@ pub struct SetLevelRequestParams {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct StringSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub format: Option<String>,
#[serde(rename = "maxLength", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub max_length: Option<i64>,
#[serde(rename = "minLength", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub min_length: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
pub r#type: String, // &'static str = "string"
}
@@ -1328,6 +1435,7 @@ pub struct SubscribeRequestParams {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct TextContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<Annotations>,
pub text: String,
pub r#type: String, // &'static str = "text"
@@ -1336,6 +1444,7 @@ pub struct TextContent {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct TextResourceContents {
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mime_type: Option<String>,
pub text: String,
pub uri: String,
@@ -1345,8 +1454,10 @@ pub struct TextResourceContents {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Tool {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub annotations: Option<ToolAnnotations>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "inputSchema")]
pub input_schema: ToolInputSchema,
@@ -1356,8 +1467,10 @@ pub struct Tool {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub output_schema: Option<ToolOutputSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
}
@@ -1366,8 +1479,10 @@ pub struct Tool {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ToolOutputSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub properties: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub required: Option<Vec<String>>,
pub r#type: String, // &'static str = "object"
}
@@ -1376,8 +1491,10 @@ pub struct ToolOutputSchema {
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ToolInputSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub properties: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub required: Option<Vec<String>>,
pub r#type: String, // &'static str = "object"
}
@@ -1397,26 +1514,31 @@ pub struct ToolAnnotations {
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub destructive_hint: Option<bool>,
#[serde(
rename = "idempotentHint",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub idempotent_hint: Option<bool>,
#[serde(
rename = "openWorldHint",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub open_world_hint: Option<bool>,
#[serde(
rename = "readOnlyHint",
default,
skip_serializing_if = "Option::is_none"
)]
#[ts(optional)]
pub read_only_hint: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
}
-2
View File
@@ -61,7 +61,6 @@ 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,
@@ -79,7 +78,6 @@ 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,
-1
View File
@@ -11,7 +11,6 @@ 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,
+14 -18
View File
@@ -48,36 +48,35 @@ pub enum ContentItem {
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseItem {
Message {
#[serde(skip_serializing)]
#[ts(optional = nullable)]
#[serde(default, skip_serializing)]
#[ts(skip)]
id: Option<String>,
role: String,
content: Vec<ContentItem>,
},
Reasoning {
#[serde(default, skip_serializing)]
#[ts(skip)]
id: String,
summary: Vec<ReasoningItemReasoningSummary>,
#[serde(default, skip_serializing_if = "should_serialize_reasoning_content")]
#[ts(optional = nullable)]
#[ts(optional)]
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)]
#[serde(default, skip_serializing)]
#[ts(skip)]
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)]
#[serde(default, skip_serializing)]
#[ts(skip)]
id: Option<String>,
name: String,
// The Responses API returns the function call arguments as a *string* that contains
@@ -97,11 +96,11 @@ pub enum ResponseItem {
output: FunctionCallOutputPayload,
},
CustomToolCall {
#[serde(skip_serializing)]
#[ts(optional = nullable)]
#[serde(default, skip_serializing)]
#[ts(skip)]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
#[ts(optional)]
status: Option<String>,
call_id: String,
@@ -121,11 +120,11 @@ pub enum ResponseItem {
// "action": {"type":"search","query":"weather: San Francisco, CA"}
// }
WebSearchCall {
#[serde(skip_serializing)]
#[ts(optional = nullable)]
#[serde(default, skip_serializing)]
#[ts(skip)]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
#[ts(optional)]
status: Option<String>,
action: WebSearchAction,
},
@@ -203,7 +202,6 @@ 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>,
@@ -296,7 +294,6 @@ 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>,
@@ -329,7 +326,6 @@ 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")]
-3
View File
@@ -18,14 +18,11 @@ 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 {
-1
View File
@@ -21,7 +21,6 @@ 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>,
-22
View File
@@ -661,7 +661,6 @@ impl HasLegacyEvent for EventMsg {
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
pub struct ExitedReviewModeEvent {
pub review_output: Option<ReviewOutputEvent>,
}
@@ -674,13 +673,11 @@ 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>,
}
@@ -700,11 +697,9 @@ 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>,
}
@@ -765,30 +760,25 @@ 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>,
}
@@ -902,7 +892,6 @@ 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")]
@@ -938,7 +927,6 @@ 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,
@@ -1067,7 +1055,6 @@ pub enum SubAgentSource {
}
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
pub struct SessionMeta {
pub id: ConversationId,
pub timestamp: String,
@@ -1096,7 +1083,6 @@ impl Default for SessionMeta {
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
pub struct SessionMetaLine {
#[serde(flatten)]
pub meta: SessionMeta,
@@ -1132,7 +1118,6 @@ 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,
@@ -1151,7 +1136,6 @@ 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")]
@@ -1285,7 +1269,6 @@ 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,
@@ -1295,14 +1278,12 @@ 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")]
@@ -1347,7 +1328,6 @@ pub struct TurnDiffEvent {
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
#[ts(optional_fields = nullable)]
pub struct GetHistoryEntryResponseEvent {
pub offset: usize,
pub log_id: u64,
@@ -1397,7 +1377,6 @@ 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,
@@ -1458,7 +1437,6 @@ pub enum FileChange {
},
Update {
unified_diff: String,
#[ts(optional = nullable)]
move_path: Option<PathBuf>,
},
}
+2
View File
@@ -144,6 +144,8 @@ pub async fn run_main(
config_profile: cli.config_profile.clone(),
codex_linux_sandbox_exe,
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
include_apply_patch_tool: None,
include_view_image_tool: None,
show_raw_agent_reasoning: cli.oss.then_some(true),
-1
View File
@@ -28,7 +28,6 @@ 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>,