Merge branch 'main' into tibo/rate-limit-unix-reset

This commit is contained in:
Thibault Sottiaux
2025-10-20 11:57:23 -07:00
committed by GitHub
55 changed files with 2404 additions and 435 deletions

View File

@@ -11,6 +11,7 @@ In the codex-rs folder where the rust code lives:
- Always collapse if statements per https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
- Always inline format! args when possible per https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args
- Use method references over closures when possible per https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
- Do not use unsigned integer even if the number cannot be negative.
- When writing tests, prefer comparing the equality of entire objects over fields one by one.
- When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable.

View File

@@ -1,4 +1,4 @@
<p align="center"><code>npm i -g @openai/codex</code><br />or <code>brew install codex</code></p>
<p align="center"><code>npm i -g @openai/codex</code><br />or <code>brew install --cask codex</code></p>
<p align="center"><strong>Codex CLI</strong> is a coding agent from OpenAI that runs locally on your computer.
</br>
@@ -24,7 +24,7 @@ npm install -g @openai/codex
Alternatively, if you use Homebrew:
```shell
brew install codex
brew install --cask codex
```
Then simply run `codex` to get started:

4
codex-rs/Cargo.lock generated
View File

@@ -861,9 +861,11 @@ name = "codex-app-server-protocol"
version = "0.0.0"
dependencies = [
"anyhow",
"clap",
"codex-protocol",
"paste",
"pretty_assertions",
"schemars 0.8.22",
"serde",
"serde_json",
"strum_macros 0.27.2",
@@ -1332,6 +1334,7 @@ dependencies = [
"icu_locale_core",
"mcp-types",
"mime_guess",
"schemars 0.8.22",
"serde",
"serde_json",
"serde_with",
@@ -3579,6 +3582,7 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
name = "mcp-types"
version = "0.0.0"
dependencies = [
"schemars 0.8.22",
"serde",
"serde_json",
"ts-rs",

View File

@@ -11,7 +11,7 @@ npm i -g @openai/codex
codex
```
You can also install via Homebrew (`brew install codex`) or download a platform-specific release directly from our [GitHub Releases](https://github.com/openai/codex/releases).
You can also install via Homebrew (`brew install --cask codex`) or download a platform-specific release directly from our [GitHub Releases](https://github.com/openai/codex/releases).
## Documentation quickstart

View File

@@ -11,8 +11,11 @@ path = "src/lib.rs"
workspace = true
[dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
codex-protocol = { workspace = true }
paste = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
strum_macros = { workspace = true }

View File

@@ -0,0 +1,22 @@
use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(
about = "Generate TypeScript bindings and JSON Schemas for the Codex app-server protocol"
)]
struct Args {
/// Output directory where generated files will be written
#[arg(short = 'o', long = "out", value_name = "DIR")]
out_dir: PathBuf,
/// Optional Prettier executable path to format generated TypeScript files
#[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")]
prettier: Option<PathBuf>,
}
fn main() -> Result<()> {
let args = Args::parse();
codex_app_server_protocol::generate_types(&args.out_dir, args.prettier.as_deref())
}

View File

@@ -0,0 +1,404 @@
use crate::ClientNotification;
use crate::ClientRequest;
use crate::ServerNotification;
use crate::ServerRequest;
use crate::export_client_response_schemas;
use crate::export_client_responses;
use crate::export_server_response_schemas;
use crate::export_server_responses;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use schemars::JsonSchema;
use schemars::schema::RootSchema;
use schemars::schema_for;
use serde::Serialize;
use serde_json::Map;
use serde_json::Value;
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::fs;
use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use ts_rs::TS;
const HEADER: &str = "// GENERATED CODE! DO NOT MODIFY BY HAND!\n\n";
macro_rules! for_each_schema_type {
($macro:ident) => {
$macro!(crate::RequestId);
$macro!(crate::JSONRPCMessage);
$macro!(crate::JSONRPCRequest);
$macro!(crate::JSONRPCNotification);
$macro!(crate::JSONRPCResponse);
$macro!(crate::JSONRPCError);
$macro!(crate::JSONRPCErrorError);
$macro!(crate::AddConversationListenerParams);
$macro!(crate::AddConversationSubscriptionResponse);
$macro!(crate::ApplyPatchApprovalParams);
$macro!(crate::ApplyPatchApprovalResponse);
$macro!(crate::ArchiveConversationParams);
$macro!(crate::ArchiveConversationResponse);
$macro!(crate::AuthMode);
$macro!(crate::AuthStatusChangeNotification);
$macro!(crate::CancelLoginChatGptParams);
$macro!(crate::CancelLoginChatGptResponse);
$macro!(crate::ClientInfo);
$macro!(crate::ClientNotification);
$macro!(crate::ClientRequest);
$macro!(crate::ConversationSummary);
$macro!(crate::ExecCommandApprovalParams);
$macro!(crate::ExecCommandApprovalResponse);
$macro!(crate::ExecOneOffCommandParams);
$macro!(crate::ExecOneOffCommandResponse);
$macro!(crate::FuzzyFileSearchParams);
$macro!(crate::FuzzyFileSearchResponse);
$macro!(crate::FuzzyFileSearchResult);
$macro!(crate::GetAuthStatusParams);
$macro!(crate::GetAuthStatusResponse);
$macro!(crate::GetUserAgentResponse);
$macro!(crate::GetUserSavedConfigResponse);
$macro!(crate::GitDiffToRemoteParams);
$macro!(crate::GitDiffToRemoteResponse);
$macro!(crate::GitSha);
$macro!(crate::InitializeParams);
$macro!(crate::InitializeResponse);
$macro!(crate::InputItem);
$macro!(crate::InterruptConversationParams);
$macro!(crate::InterruptConversationResponse);
$macro!(crate::ListConversationsParams);
$macro!(crate::ListConversationsResponse);
$macro!(crate::LoginApiKeyParams);
$macro!(crate::LoginApiKeyResponse);
$macro!(crate::LoginChatGptCompleteNotification);
$macro!(crate::LoginChatGptResponse);
$macro!(crate::LogoutChatGptParams);
$macro!(crate::LogoutChatGptResponse);
$macro!(crate::NewConversationParams);
$macro!(crate::NewConversationResponse);
$macro!(crate::Profile);
$macro!(crate::RemoveConversationListenerParams);
$macro!(crate::RemoveConversationSubscriptionResponse);
$macro!(crate::ResumeConversationParams);
$macro!(crate::ResumeConversationResponse);
$macro!(crate::SandboxSettings);
$macro!(crate::SendUserMessageParams);
$macro!(crate::SendUserMessageResponse);
$macro!(crate::SendUserTurnParams);
$macro!(crate::SendUserTurnResponse);
$macro!(crate::ServerNotification);
$macro!(crate::ServerRequest);
$macro!(crate::SessionConfiguredNotification);
$macro!(crate::SetDefaultModelParams);
$macro!(crate::SetDefaultModelResponse);
$macro!(crate::Tools);
$macro!(crate::UserInfoResponse);
$macro!(crate::UserSavedConfig);
$macro!(codex_protocol::protocol::EventMsg);
$macro!(codex_protocol::protocol::FileChange);
$macro!(codex_protocol::parse_command::ParsedCommand);
$macro!(codex_protocol::protocol::SandboxPolicy);
};
}
pub fn generate_types(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
generate_ts(out_dir, prettier)?;
generate_json(out_dir)?;
Ok(())
}
pub fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
ensure_dir(out_dir)?;
ClientRequest::export_all_to(out_dir)?;
export_client_responses(out_dir)?;
ClientNotification::export_all_to(out_dir)?;
ServerRequest::export_all_to(out_dir)?;
export_server_responses(out_dir)?;
ServerNotification::export_all_to(out_dir)?;
generate_index_ts(out_dir)?;
let ts_files = ts_files_in(out_dir)?;
for file in &ts_files {
prepend_header_if_missing(file)?;
}
if let Some(prettier_bin) = prettier
&& !ts_files.is_empty()
{
let status = Command::new(prettier_bin)
.arg("--write")
.args(ts_files.iter().map(|p| p.as_os_str()))
.status()
.with_context(|| format!("Failed to invoke Prettier at {}", prettier_bin.display()))?;
if !status.success() {
return Err(anyhow!("Prettier failed with status {status}"));
}
}
Ok(())
}
pub fn generate_json(out_dir: &Path) -> Result<()> {
ensure_dir(out_dir)?;
let mut bundle: BTreeMap<String, RootSchema> = BTreeMap::new();
macro_rules! add_schema {
($ty:path) => {{
let name = type_basename(stringify!($ty));
let schema = write_json_schema_with_return::<$ty>(out_dir, &name)?;
bundle.insert(name, schema);
}};
}
for_each_schema_type!(add_schema);
export_client_response_schemas(out_dir)?;
export_server_response_schemas(out_dir)?;
let mut definitions = Map::new();
const SPECIAL_DEFINITIONS: &[&str] = &[
"ClientNotification",
"ClientRequest",
"EventMsg",
"FileChange",
"InputItem",
"ParsedCommand",
"SandboxPolicy",
"ServerNotification",
"ServerRequest",
];
for (name, schema) in bundle {
let mut schema_value = serde_json::to_value(schema)?;
if let Value::Object(ref mut obj) = schema_value {
if let Some(defs) = obj.remove("definitions")
&& let Value::Object(defs_obj) = defs
{
for (def_name, def_schema) in defs_obj {
if !SPECIAL_DEFINITIONS.contains(&def_name.as_str()) {
definitions.insert(def_name, def_schema);
}
}
}
if let Some(Value::Array(one_of)) = obj.get_mut("oneOf") {
for variant in one_of.iter_mut() {
if let Some(variant_name) = variant_definition_name(&name, variant)
&& let Value::Object(variant_obj) = variant
{
variant_obj.insert("title".into(), Value::String(variant_name));
}
}
}
}
definitions.insert(name, schema_value);
}
let mut root = Map::new();
root.insert(
"$schema".to_string(),
Value::String("http://json-schema.org/draft-07/schema#".into()),
);
root.insert(
"title".to_string(),
Value::String("CodexAppServerProtocol".into()),
);
root.insert("type".to_string(), Value::String("object".into()));
root.insert("definitions".to_string(), Value::Object(definitions));
write_pretty_json(
out_dir.join("codex_app_server_protocol.schemas.json"),
&Value::Object(root),
)?;
Ok(())
}
fn write_json_schema_with_return<T>(out_dir: &Path, name: &str) -> Result<RootSchema>
where
T: JsonSchema,
{
let file_stem = name.trim();
let schema = schema_for!(T);
write_pretty_json(out_dir.join(format!("{file_stem}.json")), &schema)
.with_context(|| format!("Failed to write JSON schema for {file_stem}"))?;
Ok(schema)
}
pub(crate) fn write_json_schema<T>(out_dir: &Path, name: &str) -> Result<()>
where
T: JsonSchema,
{
write_json_schema_with_return::<T>(out_dir, name).map(|_| ())
}
fn write_pretty_json(path: PathBuf, value: &impl Serialize) -> Result<()> {
let json = serde_json::to_vec_pretty(value)
.with_context(|| format!("Failed to serialize JSON schema to {}", path.display()))?;
fs::write(&path, json).with_context(|| format!("Failed to write {}", path.display()))?;
Ok(())
}
fn type_basename(type_path: &str) -> String {
type_path
.rsplit_once("::")
.map(|(_, name)| name)
.unwrap_or(type_path)
.trim()
.to_string()
}
fn variant_definition_name(base: &str, variant: &Value) -> Option<String> {
if let Some(props) = variant.get("properties").and_then(Value::as_object) {
if let Some(method_literal) = literal_from_property(props, "method") {
let pascal = to_pascal_case(method_literal);
return Some(match base {
"ClientRequest" | "ServerRequest" => format!("{pascal}Request"),
"ClientNotification" | "ServerNotification" => format!("{pascal}Notification"),
_ => format!("{pascal}{base}"),
});
}
if let Some(type_literal) = literal_from_property(props, "type") {
let pascal = to_pascal_case(type_literal);
return Some(match base {
"EventMsg" => format!("{pascal}EventMsg"),
_ => format!("{pascal}{base}"),
});
}
if let Some(mode_literal) = literal_from_property(props, "mode") {
let pascal = to_pascal_case(mode_literal);
return Some(match base {
"SandboxPolicy" => format!("{pascal}SandboxPolicy"),
_ => format!("{pascal}{base}"),
});
}
if props.len() == 1
&& let Some(key) = props.keys().next()
{
let pascal = to_pascal_case(key);
return Some(format!("{pascal}{base}"));
}
}
if let Some(required) = variant.get("required").and_then(Value::as_array)
&& required.len() == 1
&& let Some(key) = required[0].as_str()
{
let pascal = to_pascal_case(key);
return Some(format!("{pascal}{base}"));
}
None
}
fn literal_from_property<'a>(props: &'a Map<String, Value>, key: &str) -> Option<&'a str> {
props
.get(key)
.and_then(|value| value.get("enum"))
.and_then(Value::as_array)
.and_then(|arr| arr.first())
.and_then(Value::as_str)
}
fn to_pascal_case(input: &str) -> String {
let mut result = String::new();
let mut capitalize_next = true;
for c in input.chars() {
if c == '_' || c == '-' {
capitalize_next = true;
continue;
}
if capitalize_next {
result.extend(c.to_uppercase());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
fn ensure_dir(dir: &Path) -> Result<()> {
fs::create_dir_all(dir)
.with_context(|| format!("Failed to create output directory {}", dir.display()))
}
fn prepend_header_if_missing(path: &Path) -> Result<()> {
let mut content = String::new();
{
let mut f = fs::File::open(path)
.with_context(|| format!("Failed to open {} for reading", path.display()))?;
f.read_to_string(&mut content)
.with_context(|| format!("Failed to read {}", path.display()))?;
}
if content.starts_with(HEADER) {
return Ok(());
}
let mut f = fs::File::create(path)
.with_context(|| format!("Failed to open {} for writing", path.display()))?;
f.write_all(HEADER.as_bytes())
.with_context(|| format!("Failed to write header to {}", path.display()))?;
f.write_all(content.as_bytes())
.with_context(|| format!("Failed to write content to {}", path.display()))?;
Ok(())
}
fn ts_files_in(dir: &Path) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
for entry in
fs::read_dir(dir).with_context(|| format!("Failed to read dir {}", dir.display()))?
{
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension() == Some(OsStr::new("ts")) {
files.push(path);
}
}
files.sort();
Ok(files)
}
fn generate_index_ts(out_dir: &Path) -> Result<PathBuf> {
let mut entries: Vec<String> = Vec::new();
let mut stems: Vec<String> = ts_files_in(out_dir)?
.into_iter()
.filter_map(|p| {
let stem = p.file_stem()?.to_string_lossy().into_owned();
if stem == "index" { None } else { Some(stem) }
})
.collect();
stems.sort();
stems.dedup();
for name in stems {
entries.push(format!("export type {{ {name} }} from \"./{name}\";\n"));
}
let mut content =
String::with_capacity(HEADER.len() + entries.iter().map(String::len).sum::<usize>());
content.push_str(HEADER);
for line in &entries {
content.push_str(line);
}
let index_path = out_dir.join("index.ts");
let mut f = fs::File::create(&index_path)
.with_context(|| format!("Failed to create {}", index_path.display()))?;
f.write_all(content.as_bytes())
.with_context(|| format!("Failed to write {}", index_path.display()))?;
Ok(index_path)
}

View File

@@ -1,13 +1,14 @@
//! We do not do true JSON-RPC 2.0, as we neither send nor expect the
//! "jsonrpc": "2.0" field.
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
pub const JSONRPC_VERSION: &str = "2.0";
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, JsonSchema, TS)]
#[serde(untagged)]
pub enum RequestId {
String(String),
@@ -18,7 +19,7 @@ pub enum RequestId {
pub type Result = serde_json::Value;
/// Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum JSONRPCMessage {
Request(JSONRPCRequest),
@@ -28,7 +29,7 @@ pub enum JSONRPCMessage {
}
/// A request that expects a response.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCRequest {
pub id: RequestId,
pub method: String,
@@ -37,7 +38,7 @@ pub struct JSONRPCRequest {
}
/// A notification which does not expect a response.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCNotification {
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -45,20 +46,20 @@ pub struct JSONRPCNotification {
}
/// A successful (non-error) response to a request.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCResponse {
pub id: RequestId,
pub result: Result,
}
/// A response to a request that indicates an error occurred.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCError {
pub error: JSONRPCErrorError,
pub id: RequestId,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCErrorError {
pub code: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]

View File

@@ -1,5 +1,9 @@
mod export;
mod jsonrpc_lite;
mod protocol;
pub use export::generate_json;
pub use export::generate_ts;
pub use export::generate_types;
pub use jsonrpc_lite::*;
pub use protocol::*;

View File

@@ -18,13 +18,14 @@ use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::TurnAbortReason;
use paste::paste;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum_macros::Display;
use ts_rs::TS;
use uuid::Uuid;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, TS)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
#[ts(type = "string")]
pub struct GitSha(pub String);
@@ -34,7 +35,7 @@ impl GitSha {
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
pub enum AuthMode {
ApiKey,
@@ -56,7 +57,7 @@ macro_rules! client_request_definitions {
),* $(,)?
) => {
/// Request from the client to the server.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "method", rename_all = "camelCase")]
pub enum ClientRequest {
$(
@@ -78,6 +79,15 @@ macro_rules! client_request_definitions {
)*
Ok(())
}
pub fn export_client_response_schemas(
out_dir: &::std::path::Path,
) -> ::anyhow::Result<()> {
$(
crate::export::write_json_schema::<$response>(out_dir, stringify!($response))?;
)*
Ok(())
}
};
}
@@ -175,13 +185,13 @@ client_request_definitions! {
},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InitializeParams {
pub client_info: ClientInfo,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ClientInfo {
pub name: String,
@@ -190,13 +200,13 @@ pub struct ClientInfo {
pub version: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InitializeResponse {
pub user_agent: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct NewConversationParams {
/// Optional override for the model name (e.g. "o3", "o4-mini").
@@ -239,7 +249,7 @@ pub struct NewConversationParams {
pub include_apply_patch_tool: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct NewConversationResponse {
pub conversation_id: ConversationId,
@@ -250,7 +260,7 @@ pub struct NewConversationResponse {
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ResumeConversationResponse {
pub conversation_id: ConversationId,
@@ -259,7 +269,7 @@ pub struct ResumeConversationResponse {
pub initial_messages: Option<Vec<EventMsg>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ListConversationsParams {
/// Optional page size; defaults to a reasonable server-side value.
@@ -270,7 +280,7 @@ pub struct ListConversationsParams {
pub cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ConversationSummary {
pub conversation_id: ConversationId,
@@ -281,7 +291,7 @@ pub struct ConversationSummary {
pub timestamp: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ListConversationsResponse {
pub items: Vec<ConversationSummary>,
@@ -291,7 +301,7 @@ pub struct ListConversationsResponse {
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ResumeConversationParams {
/// Absolute path to the rollout JSONL file.
@@ -301,78 +311,81 @@ pub struct ResumeConversationParams {
pub overrides: Option<NewConversationParams>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct AddConversationSubscriptionResponse {
#[schemars(with = "String")]
pub subscription_id: Uuid,
}
/// The [`ConversationId`] must match the `rollout_path`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveConversationParams {
pub conversation_id: ConversationId,
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveConversationResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct RemoveConversationSubscriptionResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LoginApiKeyParams {
pub api_key: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LoginApiKeyResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LoginChatGptResponse {
#[schemars(with = "String")]
pub login_id: Uuid,
/// URL the client should open in a browser to initiate the OAuth flow.
pub auth_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GitDiffToRemoteResponse {
pub sha: GitSha,
pub diff: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct CancelLoginChatGptParams {
#[schemars(with = "String")]
pub login_id: Uuid,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GitDiffToRemoteParams {
pub cwd: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct CancelLoginChatGptResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LogoutChatGptParams {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LogoutChatGptResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetAuthStatusParams {
/// If true, include the current auth token (if available) in the response.
@@ -383,7 +396,7 @@ pub struct GetAuthStatusParams {
pub refresh_token: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ExecOneOffCommandParams {
/// Command argv to execute.
@@ -399,7 +412,7 @@ pub struct ExecOneOffCommandParams {
pub sandbox_policy: Option<SandboxPolicy>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ExecOneOffCommandResponse {
pub exit_code: i32,
@@ -407,7 +420,7 @@ pub struct ExecOneOffCommandResponse {
pub stderr: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetAuthStatusResponse {
#[serde(skip_serializing_if = "Option::is_none")]
@@ -422,13 +435,13 @@ pub struct GetAuthStatusResponse {
pub requires_openai_auth: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetUserAgentResponse {
pub user_agent: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct UserInfoResponse {
/// Note: `alleged_user_email` is not currently verified. We read it from
@@ -438,13 +451,13 @@ pub struct UserInfoResponse {
pub alleged_user_email: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetUserSavedConfigResponse {
pub config: UserSavedConfig,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SetDefaultModelParams {
/// If set to None, this means `model` should be cleared in config.toml.
@@ -456,14 +469,14 @@ pub struct SetDefaultModelParams {
pub reasoning_effort: Option<ReasoningEffort>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SetDefaultModelResponse {}
/// UserSavedConfig contains a subset of the config. It is meant to expose mcp
/// client-configurable settings that can be specified in the NewConversation
/// and SendUserTurn requests.
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, TS)]
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct UserSavedConfig {
/// Approvals
@@ -501,7 +514,7 @@ pub struct UserSavedConfig {
}
/// MCP representation of a [`codex_core::config_profile::ConfigProfile`].
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, TS)]
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct Profile {
pub model: Option<String>,
@@ -515,7 +528,7 @@ pub struct Profile {
pub chatgpt_base_url: Option<String>,
}
/// MCP representation of a [`codex_core::config::ToolsToml`].
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, TS)]
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct Tools {
#[serde(skip_serializing_if = "Option::is_none")]
@@ -525,7 +538,7 @@ pub struct Tools {
}
/// MCP representation of a [`codex_core::config_types::SandboxWorkspaceWrite`].
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, TS)]
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SandboxSettings {
#[serde(default)]
@@ -538,14 +551,14 @@ pub struct SandboxSettings {
pub exclude_slash_tmp: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SendUserMessageParams {
pub conversation_id: ConversationId,
pub items: Vec<InputItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SendUserTurnParams {
pub conversation_id: ConversationId,
@@ -559,39 +572,40 @@ pub struct SendUserTurnParams {
pub summary: ReasoningSummary,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SendUserTurnResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InterruptConversationParams {
pub conversation_id: ConversationId,
}
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InterruptConversationResponse {
pub abort_reason: TurnAbortReason,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SendUserMessageResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct AddConversationListenerParams {
pub conversation_id: ConversationId,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct RemoveConversationListenerParams {
#[schemars(with = "String")]
pub subscription_id: Uuid,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type", content = "data")]
pub enum InputItem {
@@ -623,7 +637,7 @@ macro_rules! server_request_definitions {
) => {
paste! {
/// Request initiated from the server and sent to the client.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "method", rename_all = "camelCase")]
pub enum ServerRequest {
$(
@@ -636,7 +650,7 @@ macro_rules! server_request_definitions {
)*
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, JsonSchema)]
pub enum ServerRequestPayload {
$( $variant([<$variant Params>]), )*
}
@@ -658,6 +672,15 @@ macro_rules! server_request_definitions {
}
Ok(())
}
pub fn export_server_response_schemas(
out_dir: &::std::path::Path,
) -> ::anyhow::Result<()> {
paste! {
$(crate::export::write_json_schema::<[<$variant Response>]>(out_dir, stringify!([<$variant Response>]))?;)*
}
Ok(())
}
};
}
@@ -676,7 +699,7 @@ server_request_definitions! {
ExecCommandApproval,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ApplyPatchApprovalParams {
pub conversation_id: ConversationId,
@@ -693,7 +716,7 @@ pub struct ApplyPatchApprovalParams {
pub grant_root: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ExecCommandApprovalParams {
pub conversation_id: ConversationId,
@@ -707,17 +730,17 @@ pub struct ExecCommandApprovalParams {
pub parsed_cmd: Vec<ParsedCommand>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct ExecCommandApprovalResponse {
pub decision: ReviewDecision,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct ApplyPatchApprovalResponse {
pub decision: ReviewDecision,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
pub struct FuzzyFileSearchParams {
@@ -729,7 +752,7 @@ pub struct FuzzyFileSearchParams {
}
/// Superset of [`codex_file_search::FileMatch`]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct FuzzyFileSearchResult {
pub root: String,
pub path: String,
@@ -739,21 +762,22 @@ pub struct FuzzyFileSearchResult {
pub indices: Option<Vec<u32>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct FuzzyFileSearchResponse {
pub files: Vec<FuzzyFileSearchResult>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[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, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SessionConfiguredNotification {
/// Name left as session_id instead of conversation_id for backwards compatibility.
@@ -781,7 +805,7 @@ pub struct SessionConfiguredNotification {
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct AuthStatusChangeNotification {
/// Current authentication method; omitted if signed out.
@@ -790,7 +814,7 @@ pub struct AuthStatusChangeNotification {
}
/// Notification sent from the server to the client.
#[derive(Serialize, Deserialize, Debug, Clone, TS, Display)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS, Display)]
#[serde(tag = "method", content = "params", rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum ServerNotification {
@@ -823,7 +847,7 @@ impl TryFrom<JSONRPCNotification> for ServerNotification {
}
/// Notification sent from the client to the server.
#[derive(Serialize, Deserialize, Debug, Clone, TS, Display)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS, Display)]
#[serde(tag = "method", content = "params", rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum ClientNotification {

View File

@@ -126,10 +126,12 @@ impl ModelClient {
}
}
pub fn get_model_context_window(&self) -> Option<u64> {
pub fn get_model_context_window(&self) -> Option<i64> {
let pct = self.config.model_family.effective_context_window_percent;
self.config
.model_context_window
.or_else(|| get_model_info(&self.config.model_family).map(|info| info.context_window))
.map(|w| w.saturating_mul(pct) / 100)
}
pub fn get_auto_compact_token_limit(&self) -> Option<i64> {
@@ -554,11 +556,11 @@ struct ResponseCompleted {
#[derive(Debug, Deserialize)]
struct ResponseCompletedUsage {
input_tokens: u64,
input_tokens: i64,
input_tokens_details: Option<ResponseCompletedInputTokensDetails>,
output_tokens: u64,
output_tokens: i64,
output_tokens_details: Option<ResponseCompletedOutputTokensDetails>,
total_tokens: u64,
total_tokens: i64,
}
impl From<ResponseCompletedUsage> for TokenUsage {
@@ -581,12 +583,12 @@ impl From<ResponseCompletedUsage> for TokenUsage {
#[derive(Debug, Deserialize)]
struct ResponseCompletedInputTokensDetails {
cached_tokens: u64,
cached_tokens: i64,
}
#[derive(Debug, Deserialize)]
struct ResponseCompletedOutputTokensDetails {
reasoning_tokens: u64,
reasoning_tokens: i64,
}
fn attach_item_ids(payload_json: &mut Value, original_items: &[ResponseItem]) {
@@ -643,7 +645,7 @@ fn parse_rate_limit_window(
let used_percent: Option<f64> = parse_header_f64(headers, used_percent_header);
used_percent.and_then(|used_percent| {
let window_minutes = parse_header_u64(headers, window_minutes_header);
let window_minutes = parse_header_i64(headers, window_minutes_header);
let resets_at = parse_header_str(headers, resets_header)
.map(str::trim)
.filter(|value| !value.is_empty())
@@ -674,8 +676,8 @@ fn parse_header_f64(headers: &HeaderMap, name: &str) -> Option<f64> {
.filter(|v| v.is_finite())
}
fn parse_header_u64(headers: &HeaderMap, name: &str) -> Option<u64> {
parse_header_str(headers, name)?.parse::<u64>().ok()
fn parse_header_i64(headers: &HeaderMap, name: &str) -> Option<i64> {
parse_header_str(headers, name)?.parse::<i64>().ok()
}
fn parse_header_str<'a>(headers: &'a HeaderMap, name: &str) -> Option<&'a str> {

View File

@@ -1778,7 +1778,7 @@ pub(crate) async fn run_task(
.as_ref()
.map(TokenUsage::tokens_in_context_window);
let token_limit_reached = total_usage_tokens
.map(|tokens| (tokens as i64) >= limit)
.map(|tokens| tokens >= limit)
.unwrap_or(false);
let mut items_to_record_in_conversation_history = Vec::<ResponseItem>::new();
let mut responses = Vec::<ResponseInputItem>::new();

View File

@@ -85,10 +85,10 @@ pub struct Config {
pub model_family: ModelFamily,
/// Size of the context window for the model, in tokens.
pub model_context_window: Option<u64>,
pub model_context_window: Option<i64>,
/// Maximum number of output tokens.
pub model_max_output_tokens: Option<u64>,
pub model_max_output_tokens: Option<i64>,
/// Token usage threshold triggering auto-compaction of conversation history.
pub model_auto_compact_token_limit: Option<i64>,
@@ -824,10 +824,10 @@ pub struct ConfigToml {
pub model_provider: Option<String>,
/// Size of the context window for the model, in tokens.
pub model_context_window: Option<u64>,
pub model_context_window: Option<i64>,
/// Maximum number of output tokens.
pub model_max_output_tokens: Option<u64>,
pub model_max_output_tokens: Option<i64>,
/// Token usage threshold triggering auto-compaction of conversation history.
pub model_auto_compact_token_limit: Option<i64>,
@@ -2805,7 +2805,7 @@ model_verbosity = "high"
model_family: find_family_for_model("o3").expect("known model slug"),
model_context_window: Some(200_000),
model_max_output_tokens: Some(100_000),
model_auto_compact_token_limit: None,
model_auto_compact_token_limit: Some(180_000),
model_provider_id: "openai".to_string(),
model_provider: fixture.openai_provider.clone(),
approval_policy: AskForApproval::Never,
@@ -2874,7 +2874,7 @@ model_verbosity = "high"
model_family: find_family_for_model("gpt-3.5-turbo").expect("known model slug"),
model_context_window: Some(16_385),
model_max_output_tokens: Some(4_096),
model_auto_compact_token_limit: None,
model_auto_compact_token_limit: Some(14_746),
model_provider_id: "openai-chat-completions".to_string(),
model_provider: fixture.openai_chat_completions_provider.clone(),
approval_policy: AskForApproval::UnlessTrusted,
@@ -2958,7 +2958,7 @@ model_verbosity = "high"
model_family: find_family_for_model("o3").expect("known model slug"),
model_context_window: Some(200_000),
model_max_output_tokens: Some(100_000),
model_auto_compact_token_limit: None,
model_auto_compact_token_limit: Some(180_000),
model_provider_id: "openai".to_string(),
model_provider: fixture.openai_provider.clone(),
approval_policy: AskForApproval::OnFailure,
@@ -3028,7 +3028,7 @@ model_verbosity = "high"
model_family: find_family_for_model("gpt-5").expect("known model slug"),
model_context_window: Some(272_000),
model_max_output_tokens: Some(128_000),
model_auto_compact_token_limit: None,
model_auto_compact_token_limit: Some(244_800),
model_provider_id: "openai".to_string(),
model_provider: fixture.openai_provider.clone(),
approval_policy: AskForApproval::OnFailure,

View File

@@ -48,6 +48,12 @@ pub struct ModelFamily {
/// Names of beta tools that should be exposed to this model family.
pub experimental_supported_tools: Vec<String>,
/// Percentage of the context window considered usable for inputs, after
/// reserving headroom for system prompts, tool overhead, and model output.
/// This is applied when computing the effective context window seen by
/// consumers.
pub effective_context_window_percent: i64,
}
macro_rules! model_family {
@@ -66,6 +72,7 @@ macro_rules! model_family {
apply_patch_tool_type: None,
base_instructions: BASE_INSTRUCTIONS.to_string(),
experimental_supported_tools: Vec::new(),
effective_context_window_percent: 95,
};
// apply overrides
$(
@@ -175,5 +182,6 @@ pub fn derive_default_model_family(model: &str) -> ModelFamily {
apply_patch_tool_type: None,
base_instructions: BASE_INSTRUCTIONS.to_string(),
experimental_supported_tools: Vec::new(),
effective_context_window_percent: 95,
}
}

View File

@@ -1,5 +1,9 @@
use crate::model_family::ModelFamily;
// Shared constants for commonly used window/token sizes.
pub(crate) const CONTEXT_WINDOW_272K: i64 = 272_000;
pub(crate) const MAX_OUTPUT_TOKENS_128K: i64 = 128_000;
/// Metadata about a model, particularly OpenAI models.
/// We may want to consider including details like the pricing for
/// input tokens, output tokens, etc., though users will need to be able to
@@ -8,10 +12,10 @@ use crate::model_family::ModelFamily;
#[derive(Debug)]
pub(crate) struct ModelInfo {
/// Size of the context window in tokens. This is the maximum size of the input context.
pub(crate) context_window: u64,
pub(crate) context_window: i64,
/// Maximum number of output tokens that can be generated for the model.
pub(crate) max_output_tokens: u64,
pub(crate) max_output_tokens: i64,
/// Token threshold where we should automatically compact conversation history. This considers
/// input tokens + output tokens of this turn.
@@ -19,13 +23,17 @@ pub(crate) struct ModelInfo {
}
impl ModelInfo {
const fn new(context_window: u64, max_output_tokens: u64) -> Self {
const fn new(context_window: i64, max_output_tokens: i64) -> Self {
Self {
context_window,
max_output_tokens,
auto_compact_token_limit: None,
auto_compact_token_limit: Some(Self::default_auto_compact_limit(context_window)),
}
}
const fn default_auto_compact_limit(context_window: i64) -> i64 {
(context_window * 9) / 10
}
}
pub(crate) fn get_model_info(model_family: &ModelFamily) -> Option<ModelInfo> {
@@ -62,15 +70,17 @@ pub(crate) fn get_model_info(model_family: &ModelFamily) -> Option<ModelInfo> {
// https://platform.openai.com/docs/models/gpt-3.5-turbo
"gpt-3.5-turbo" => Some(ModelInfo::new(16_385, 4_096)),
_ if slug.starts_with("gpt-5-codex") => Some(ModelInfo {
context_window: 272_000,
max_output_tokens: 128_000,
auto_compact_token_limit: Some(350_000),
}),
_ if slug.starts_with("gpt-5-codex") => {
Some(ModelInfo::new(CONTEXT_WINDOW_272K, MAX_OUTPUT_TOKENS_128K))
}
_ if slug.starts_with("gpt-5") => Some(ModelInfo::new(272_000, 128_000)),
_ if slug.starts_with("gpt-5") => {
Some(ModelInfo::new(CONTEXT_WINDOW_272K, MAX_OUTPUT_TOKENS_128K))
}
_ if slug.starts_with("codex-") => Some(ModelInfo::new(272_000, 128_000)),
_ if slug.starts_with("codex-") => {
Some(ModelInfo::new(CONTEXT_WINDOW_272K, MAX_OUTPUT_TOKENS_128K))
}
_ => None,
}

View File

@@ -48,7 +48,7 @@ impl SessionState {
pub(crate) fn update_token_info_from_usage(
&mut self,
usage: &TokenUsage,
model_context_window: Option<u64>,
model_context_window: Option<i64>,
) {
self.token_info = TokenUsageInfo::new_or_append(
&self.token_info,
@@ -67,7 +67,7 @@ impl SessionState {
(self.token_info.clone(), self.latest_rate_limits.clone())
}
pub(crate) fn set_token_usage_full(&mut self, context_window: u64) {
pub(crate) fn set_token_usage_full(&mut self, context_window: i64) {
match &mut self.token_info {
Some(info) => info.fill_to_context_window(context_window),
None => {

View File

@@ -138,7 +138,7 @@ pub fn ev_response_created(id: &str) -> Value {
})
}
pub fn ev_completed_with_tokens(id: &str, total_tokens: u64) -> Value {
pub fn ev_completed_with_tokens(id: &str, total_tokens: i64) -> Value {
serde_json::json!({
"type": "response.completed",
"response": {

File diff suppressed because it is too large Load Diff

View File

@@ -858,8 +858,8 @@ async fn token_count_includes_rate_limits_snapshot() {
"reasoning_output_tokens": 0,
"total_tokens": 123
},
// Default model is gpt-5-codex in tests → 272000 context window
"model_context_window": 272000
// Default model is gpt-5-codex in tests → 95% usable context window
"model_context_window": 258400
},
"rate_limits": {
"primary": {
@@ -985,6 +985,8 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
const EFFECTIVE_CONTEXT_WINDOW: i64 = (272_000 * 95) / 100;
responses::mount_sse_once_match(
&server,
body_string_contains("trigger context window"),
@@ -1056,8 +1058,11 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
.info
.expect("token usage info present when context window is exceeded");
assert_eq!(info.model_context_window, Some(272_000));
assert_eq!(info.total_token_usage.total_tokens, 272_000);
assert_eq!(info.model_context_window, Some(EFFECTIVE_CONTEXT_WINDOW));
assert_eq!(
info.total_token_usage.total_tokens,
EFFECTIVE_CONTEXT_WINDOW
);
let error_event = wait_for_event(&codex, |ev| matches!(ev, EventMsg::Error(_))).await;
let expected_context_window_message = CodexErr::ContextWindowExceeded.to_string();

View File

@@ -19,6 +19,7 @@ use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_completed_with_tokens;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::mount_sse_once;
use core_test_support::responses::mount_sse_once_match;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
@@ -43,6 +44,7 @@ const CONTEXT_LIMIT_MESSAGE: &str =
"Your input exceeds the context window of this model. Please adjust your input and try again.";
const DUMMY_FUNCTION_NAME: &str = "unsupported_tool";
const DUMMY_CALL_ID: &str = "call-multi-auto";
const FUNCTION_CALL_LIMIT_MSG: &str = "function call limit push";
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn summarize_context_three_requests_and_instructions() {
@@ -860,3 +862,97 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_
"second auto compact request should include the summarization prompt"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn auto_compact_triggers_after_function_call_over_95_percent_usage() {
skip_if_no_network!();
let server = start_mock_server().await;
let context_window = 100;
let limit = context_window * 90 / 100;
let over_limit_tokens = context_window * 95 / 100 + 1;
let first_turn = sse(vec![
ev_function_call(DUMMY_CALL_ID, DUMMY_FUNCTION_NAME, "{}"),
ev_completed_with_tokens("r1", 50),
]);
let function_call_follow_up = sse(vec![
ev_assistant_message("m2", FINAL_REPLY),
ev_completed_with_tokens("r2", over_limit_tokens),
]);
let auto_compact_turn = sse(vec![
ev_assistant_message("m3", AUTO_SUMMARY_TEXT),
ev_completed_with_tokens("r3", 10),
]);
let post_auto_compact_turn = sse(vec![ev_completed_with_tokens("r4", 10)]);
// Mount responses in order and keep mocks only for the ones we assert on.
let first_turn_mock = mount_sse_once(&server, first_turn).await;
let follow_up_mock = mount_sse_once(&server, function_call_follow_up).await;
let auto_compact_mock = mount_sse_once(&server, auto_compact_turn).await;
// We don't assert on the post-compact request, so no need to keep its mock.
mount_sse_once(&server, post_auto_compact_turn).await;
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.model_context_window = Some(context_window);
config.model_auto_compact_token_limit = Some(limit);
let codex = ConversationManager::with_auth(CodexAuth::from_api_key("dummy"))
.new_conversation(config)
.await
.unwrap()
.conversation;
codex
.submit(Op::UserInput {
items: vec![InputItem::Text {
text: FUNCTION_CALL_LIMIT_MSG.into(),
}],
})
.await
.unwrap();
wait_for_event(&codex, |msg| matches!(msg, EventMsg::TaskComplete(_))).await;
// Assert first request captured expected user message that triggers function call.
let first_request = first_turn_mock.single_request().input();
assert!(
first_request.iter().any(|item| {
item.get("type").and_then(|value| value.as_str()) == Some("message")
&& item
.get("content")
.and_then(|content| content.as_array())
.and_then(|entries| entries.first())
.and_then(|entry| entry.get("text"))
.and_then(|value| value.as_str())
== Some(FUNCTION_CALL_LIMIT_MSG)
}),
"first request should include the user message that triggers the function call"
);
let function_call_output = follow_up_mock
.single_request()
.function_call_output(DUMMY_CALL_ID);
let output_text = function_call_output
.get("output")
.and_then(|value| value.as_str())
.unwrap_or_default();
assert!(
output_text.contains(DUMMY_FUNCTION_NAME),
"function call output should be sent before auto compact"
);
let auto_compact_body = auto_compact_mock.single_request().body_json().to_string();
assert!(
auto_compact_body.contains("You have exceeded the maximum number of tokens"),
"auto compact request should include the summarization prompt after exceeding 95% (limit {limit})"
);
}

View File

@@ -2,6 +2,8 @@
#[cfg(not(target_os = "windows"))]
mod abort_tasks;
#[cfg(not(target_os = "windows"))]
mod approvals;
mod cli_stream;
mod client;
mod compact;

View File

@@ -68,8 +68,8 @@ pub struct Cli {
pub json: bool,
/// Whether to include the plan tool in the conversation.
#[arg(long = "include-plan-tool", default_value_t = false)]
pub include_plan_tool: bool,
#[arg(long = "include-plan-tool")]
pub include_plan_tool: Option<bool>,
/// Specifies file where the last message from the agent should be written.
#[arg(long = "output-last-message", short = 'o', value_name = "FILE")]

View File

@@ -57,11 +57,11 @@ pub struct TurnFailedEvent {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)]
pub struct Usage {
/// The number of input tokens used during the turn.
pub input_tokens: u64,
pub input_tokens: i64,
/// The number of cached input tokens used during the turn.
pub cached_input_tokens: u64,
pub cached_input_tokens: i64,
/// The number of output tokens used during the turn.
pub output_tokens: u64,
pub output_tokens: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]

View File

@@ -74,6 +74,10 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
config_overrides,
} = cli;
if include_plan_tool.is_some() {
eprintln!("include-plan-tool is deprecated. Plan tool is now enabled by default.");
}
// Determine the prompt source (parent or subcommand) and read from stdin if needed.
let prompt_arg = match &command {
// Allow prompt before the subcommand by falling back to the parent-level prompt
@@ -177,7 +181,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
model_provider,
codex_linux_sandbox_exe,
base_instructions: None,
include_plan_tool: Some(include_plan_tool),
include_plan_tool: Some(include_plan_tool.unwrap_or(true)),
include_apply_patch_tool: None,
include_view_image_tool: None,
show_raw_agent_reasoning: oss.then_some(true),

View File

@@ -10,3 +10,4 @@ workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
ts-rs = { workspace = true, features = ["serde-json-impl", "no-serde-warnings"] }
schemars = { workspace = true }

View File

@@ -21,9 +21,9 @@ from typing import Any, Literal
SCHEMA_VERSION = "2025-06-18"
JSONRPC_VERSION = "2.0"
STANDARD_DERIVE = "#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]\n"
STANDARD_DERIVE = "#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]\n"
STANDARD_HASHABLE_DERIVE = (
"#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, TS)]\n"
"#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, JsonSchema, TS)]\n"
)
# Will be populated with the schema's `definitions` map in `main()` so that
@@ -94,6 +94,7 @@ use serde::Serialize;
use serde::de::DeserializeOwned;
use std::convert::TryFrom;
use schemars::JsonSchema;
use ts_rs::TS;
pub const MCP_SCHEMA_VERSION: &str = "{SCHEMA_VERSION}";

View File

@@ -10,6 +10,7 @@ use serde::Serialize;
use serde::de::DeserializeOwned;
use std::convert::TryFrom;
use schemars::JsonSchema;
use ts_rs::TS;
pub const MCP_SCHEMA_VERSION: &str = "2025-06-18";
@@ -33,7 +34,7 @@ fn default_jsonrpc() -> String {
}
/// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Annotations {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audience: Option<Vec<Role>>,
@@ -48,7 +49,7 @@ pub struct Annotations {
}
/// Audio provided to or from an LLM.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct AudioContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@@ -59,14 +60,14 @@ pub struct AudioContent {
}
/// Base interface for metadata with name (identifier) and title (display name) properties.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct BaseMetadata {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct BlobResourceContents {
pub blob: String,
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
@@ -74,7 +75,7 @@ pub struct BlobResourceContents {
pub uri: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct BooleanSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default: Option<bool>,
@@ -85,7 +86,7 @@ pub struct BooleanSchema {
pub r#type: String, // &'static str = "boolean"
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum CallToolRequest {}
impl ModelContextProtocolRequest for CallToolRequest {
@@ -94,7 +95,7 @@ impl ModelContextProtocolRequest for CallToolRequest {
type Result = CallToolResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CallToolRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>,
@@ -102,7 +103,7 @@ pub struct CallToolRequestParams {
}
/// The server's response to a tool call.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CallToolResult {
pub content: Vec<ContentBlock>,
#[serde(rename = "isError", default, skip_serializing_if = "Option::is_none")]
@@ -123,7 +124,7 @@ impl From<CallToolResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum CancelledNotification {}
impl ModelContextProtocolNotification for CancelledNotification {
@@ -131,7 +132,7 @@ impl ModelContextProtocolNotification for CancelledNotification {
type Params = CancelledNotificationParams;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CancelledNotificationParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
@@ -140,7 +141,7 @@ pub struct CancelledNotificationParams {
}
/// Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ClientCapabilities {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub elicitation: Option<serde_json::Value>,
@@ -153,7 +154,7 @@ pub struct ClientCapabilities {
}
/// Present if the client supports listing roots.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ClientCapabilitiesRoots {
#[serde(
rename = "listChanged",
@@ -163,7 +164,7 @@ pub struct ClientCapabilitiesRoots {
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum ClientNotification {
CancelledNotification(CancelledNotification),
@@ -172,7 +173,7 @@ pub enum ClientNotification {
RootsListChangedNotification(RootsListChangedNotification),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(tag = "method", content = "params")]
pub enum ClientRequest {
#[serde(rename = "initialize")]
@@ -205,7 +206,7 @@ pub enum ClientRequest {
CompleteRequest(<CompleteRequest as ModelContextProtocolRequest>::Params),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum ClientResult {
Result(Result),
@@ -214,7 +215,7 @@ pub enum ClientResult {
ElicitResult(ElicitResult),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum CompleteRequest {}
impl ModelContextProtocolRequest for CompleteRequest {
@@ -223,7 +224,7 @@ impl ModelContextProtocolRequest for CompleteRequest {
type Result = CompleteResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CompleteRequestParams {
pub argument: CompleteRequestParamsArgument,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -232,20 +233,20 @@ pub struct CompleteRequestParams {
}
/// Additional, optional context for completions
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CompleteRequestParamsContext {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>,
}
/// The argument's information
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CompleteRequestParamsArgument {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum CompleteRequestParamsRef {
PromptReference(PromptReference),
@@ -253,12 +254,12 @@ pub enum CompleteRequestParamsRef {
}
/// The server's response to a completion/complete request
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CompleteResult {
pub completion: CompleteResultCompletion,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CompleteResultCompletion {
#[serde(rename = "hasMore", default, skip_serializing_if = "Option::is_none")]
pub has_more: Option<bool>,
@@ -275,7 +276,7 @@ impl From<CompleteResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum ContentBlock {
TextContent(TextContent),
@@ -285,7 +286,7 @@ pub enum ContentBlock {
EmbeddedResource(EmbeddedResource),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum CreateMessageRequest {}
impl ModelContextProtocolRequest for CreateMessageRequest {
@@ -294,7 +295,7 @@ impl ModelContextProtocolRequest for CreateMessageRequest {
type Result = CreateMessageResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CreateMessageRequestParams {
#[serde(
rename = "includeContext",
@@ -330,7 +331,7 @@ pub struct CreateMessageRequestParams {
}
/// The client's response to a sampling/create_message request from the server. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct CreateMessageResult {
pub content: CreateMessageResultContent,
pub model: String,
@@ -343,7 +344,7 @@ pub struct CreateMessageResult {
pub stop_reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum CreateMessageResultContent {
TextContent(TextContent),
@@ -359,10 +360,10 @@ impl From<CreateMessageResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Cursor(String);
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ElicitRequest {}
impl ModelContextProtocolRequest for ElicitRequest {
@@ -371,7 +372,7 @@ impl ModelContextProtocolRequest for ElicitRequest {
type Result = ElicitResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ElicitRequestParams {
pub message: String,
#[serde(rename = "requestedSchema")]
@@ -380,7 +381,7 @@ pub struct ElicitRequestParams {
/// A restricted subset of JSON Schema.
/// Only top-level properties are allowed, without nesting.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ElicitRequestParamsRequestedSchema {
pub properties: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -389,7 +390,7 @@ pub struct ElicitRequestParamsRequestedSchema {
}
/// The client's response to an elicitation request.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ElicitResult {
pub action: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -408,7 +409,7 @@ impl From<ElicitResult> for serde_json::Value {
///
/// It is up to the client how best to render embedded resources for the benefit
/// of the LLM and/or the user.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct EmbeddedResource {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@@ -416,7 +417,7 @@ pub struct EmbeddedResource {
pub r#type: String, // &'static str = "resource"
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum EmbeddedResourceResource {
TextResourceContents(TextResourceContents),
@@ -425,7 +426,7 @@ pub enum EmbeddedResourceResource {
pub type EmptyResult = Result;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct EnumSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
@@ -437,7 +438,7 @@ pub struct EnumSchema {
pub r#type: String, // &'static str = "string"
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum GetPromptRequest {}
impl ModelContextProtocolRequest for GetPromptRequest {
@@ -446,7 +447,7 @@ impl ModelContextProtocolRequest for GetPromptRequest {
type Result = GetPromptResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct GetPromptRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>,
@@ -454,7 +455,7 @@ pub struct GetPromptRequestParams {
}
/// The server's response to a prompts/get request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct GetPromptResult {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
@@ -470,7 +471,7 @@ impl From<GetPromptResult> for serde_json::Value {
}
/// An image provided to or from an LLM.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ImageContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@@ -481,7 +482,7 @@ pub struct ImageContent {
}
/// Describes the name and version of an MCP implementation, with an optional title for UI representation.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Implementation {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -492,7 +493,7 @@ pub struct Implementation {
pub user_agent: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum InitializeRequest {}
impl ModelContextProtocolRequest for InitializeRequest {
@@ -501,7 +502,7 @@ impl ModelContextProtocolRequest for InitializeRequest {
type Result = InitializeResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct InitializeRequestParams {
pub capabilities: ClientCapabilities,
#[serde(rename = "clientInfo")]
@@ -511,7 +512,7 @@ pub struct InitializeRequestParams {
}
/// After receiving an initialize request from the client, the server sends this response.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct InitializeResult {
pub capabilities: ServerCapabilities,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -530,7 +531,7 @@ impl From<InitializeResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum InitializedNotification {}
impl ModelContextProtocolNotification for InitializedNotification {
@@ -539,7 +540,7 @@ impl ModelContextProtocolNotification for InitializedNotification {
}
/// A response to a request that indicates an error occurred.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCError {
pub error: JSONRPCErrorError,
pub id: RequestId,
@@ -547,7 +548,7 @@ pub struct JSONRPCError {
pub jsonrpc: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCErrorError {
pub code: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -556,7 +557,7 @@ pub struct JSONRPCErrorError {
}
/// Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum JSONRPCMessage {
Request(JSONRPCRequest),
@@ -566,7 +567,7 @@ pub enum JSONRPCMessage {
}
/// A notification which does not expect a response.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCNotification {
#[serde(rename = "jsonrpc", default = "default_jsonrpc")]
pub jsonrpc: String,
@@ -576,7 +577,7 @@ pub struct JSONRPCNotification {
}
/// A request that expects a response.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCRequest {
pub id: RequestId,
#[serde(rename = "jsonrpc", default = "default_jsonrpc")]
@@ -587,7 +588,7 @@ pub struct JSONRPCRequest {
}
/// A successful (non-error) response to a request.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct JSONRPCResponse {
pub id: RequestId,
#[serde(rename = "jsonrpc", default = "default_jsonrpc")]
@@ -595,7 +596,7 @@ pub struct JSONRPCResponse {
pub result: Result,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ListPromptsRequest {}
impl ModelContextProtocolRequest for ListPromptsRequest {
@@ -604,14 +605,14 @@ impl ModelContextProtocolRequest for ListPromptsRequest {
type Result = ListPromptsResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListPromptsRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
/// The server's response to a prompts/list request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListPromptsResult {
#[serde(
rename = "nextCursor",
@@ -630,7 +631,7 @@ impl From<ListPromptsResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ListResourceTemplatesRequest {}
impl ModelContextProtocolRequest for ListResourceTemplatesRequest {
@@ -639,14 +640,14 @@ impl ModelContextProtocolRequest for ListResourceTemplatesRequest {
type Result = ListResourceTemplatesResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListResourceTemplatesRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
/// The server's response to a resources/templates/list request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListResourceTemplatesResult {
#[serde(
rename = "nextCursor",
@@ -666,7 +667,7 @@ impl From<ListResourceTemplatesResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ListResourcesRequest {}
impl ModelContextProtocolRequest for ListResourcesRequest {
@@ -675,14 +676,14 @@ impl ModelContextProtocolRequest for ListResourcesRequest {
type Result = ListResourcesResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListResourcesRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
/// The server's response to a resources/list request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListResourcesResult {
#[serde(
rename = "nextCursor",
@@ -701,7 +702,7 @@ impl From<ListResourcesResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ListRootsRequest {}
impl ModelContextProtocolRequest for ListRootsRequest {
@@ -713,7 +714,7 @@ impl ModelContextProtocolRequest for ListRootsRequest {
/// The client's response to a roots/list request from the server.
/// This result contains an array of Root objects, each representing a root directory
/// or file that the server can operate on.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListRootsResult {
pub roots: Vec<Root>,
}
@@ -726,7 +727,7 @@ impl From<ListRootsResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ListToolsRequest {}
impl ModelContextProtocolRequest for ListToolsRequest {
@@ -735,14 +736,14 @@ impl ModelContextProtocolRequest for ListToolsRequest {
type Result = ListToolsResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListToolsRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
/// The server's response to a tools/list request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListToolsResult {
#[serde(
rename = "nextCursor",
@@ -765,7 +766,7 @@ impl From<ListToolsResult> for serde_json::Value {
///
/// These map to syslog message severities, as specified in RFC-5424:
/// https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum LoggingLevel {
#[serde(rename = "alert")]
Alert,
@@ -785,7 +786,7 @@ pub enum LoggingLevel {
Warning,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum LoggingMessageNotification {}
impl ModelContextProtocolNotification for LoggingMessageNotification {
@@ -793,7 +794,7 @@ impl ModelContextProtocolNotification for LoggingMessageNotification {
type Params = LoggingMessageNotificationParams;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct LoggingMessageNotificationParams {
pub data: serde_json::Value,
pub level: LoggingLevel,
@@ -805,7 +806,7 @@ pub struct LoggingMessageNotificationParams {
///
/// Keys not declared here are currently left unspecified by the spec and are up
/// to the client to interpret.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ModelHint {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
@@ -822,7 +823,7 @@ pub struct ModelHint {
/// These preferences are always advisory. The client MAY ignore them. It is also
/// up to the client to decide how to interpret these preferences and how to
/// balance them against other considerations.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ModelPreferences {
#[serde(
rename = "costPriority",
@@ -846,14 +847,14 @@ pub struct ModelPreferences {
pub speed_priority: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Notification {
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct NumberSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
@@ -866,20 +867,20 @@ pub struct NumberSchema {
pub r#type: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct PaginatedRequest {
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<PaginatedRequestParams>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct PaginatedRequestParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct PaginatedResult {
#[serde(
rename = "nextCursor",
@@ -897,7 +898,7 @@ impl From<PaginatedResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum PingRequest {}
impl ModelContextProtocolRequest for PingRequest {
@@ -908,7 +909,7 @@ impl ModelContextProtocolRequest for PingRequest {
/// Restricted schema definitions that only allow primitive types
/// without nested objects or arrays.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum PrimitiveSchemaDefinition {
StringSchema(StringSchema),
@@ -917,7 +918,7 @@ pub enum PrimitiveSchemaDefinition {
EnumSchema(EnumSchema),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ProgressNotification {}
impl ModelContextProtocolNotification for ProgressNotification {
@@ -925,7 +926,7 @@ impl ModelContextProtocolNotification for ProgressNotification {
type Params = ProgressNotificationParams;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ProgressNotificationParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
@@ -936,7 +937,7 @@ pub struct ProgressNotificationParams {
pub total: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, JsonSchema, TS)]
#[serde(untagged)]
pub enum ProgressToken {
String(String),
@@ -944,7 +945,7 @@ pub enum ProgressToken {
}
/// A prompt or prompt template that the server offers.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Prompt {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<Vec<PromptArgument>>,
@@ -956,7 +957,7 @@ pub struct Prompt {
}
/// Describes an argument that a prompt can accept.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct PromptArgument {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
@@ -967,7 +968,7 @@ pub struct PromptArgument {
pub title: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum PromptListChangedNotification {}
impl ModelContextProtocolNotification for PromptListChangedNotification {
@@ -979,14 +980,14 @@ impl ModelContextProtocolNotification for PromptListChangedNotification {
///
/// This is similar to `SamplingMessage`, but also supports the embedding of
/// resources from the MCP server.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct PromptMessage {
pub content: ContentBlock,
pub role: Role,
}
/// Identifies a prompt.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct PromptReference {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -994,7 +995,7 @@ pub struct PromptReference {
pub r#type: String, // &'static str = "ref/prompt"
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ReadResourceRequest {}
impl ModelContextProtocolRequest for ReadResourceRequest {
@@ -1003,18 +1004,18 @@ impl ModelContextProtocolRequest for ReadResourceRequest {
type Result = ReadResourceResult;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ReadResourceRequestParams {
pub uri: String,
}
/// The server's response to a resources/read request from the client.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ReadResourceResult {
pub contents: Vec<ReadResourceResultContents>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum ReadResourceResultContents {
TextResourceContents(TextResourceContents),
@@ -1029,14 +1030,14 @@ impl From<ReadResourceResult> for serde_json::Value {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Request {
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, JsonSchema, TS)]
#[serde(untagged)]
pub enum RequestId {
String(String),
@@ -1044,7 +1045,7 @@ pub enum RequestId {
}
/// A known resource that the server is capable of reading.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Resource {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@@ -1061,7 +1062,7 @@ pub struct Resource {
}
/// The contents of a specific resource or sub-resource.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResourceContents {
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
@@ -1071,7 +1072,7 @@ pub struct ResourceContents {
/// A resource that the server is capable of reading, included in a prompt or tool call result.
///
/// Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResourceLink {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@@ -1088,7 +1089,7 @@ pub struct ResourceLink {
pub uri: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ResourceListChangedNotification {}
impl ModelContextProtocolNotification for ResourceListChangedNotification {
@@ -1097,7 +1098,7 @@ impl ModelContextProtocolNotification for ResourceListChangedNotification {
}
/// A template description for resources available on the server.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResourceTemplate {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@@ -1113,13 +1114,13 @@ pub struct ResourceTemplate {
}
/// A reference to a resource or resource template definition.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResourceTemplateReference {
pub r#type: String, // &'static str = "ref/resource"
pub uri: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ResourceUpdatedNotification {}
impl ModelContextProtocolNotification for ResourceUpdatedNotification {
@@ -1127,7 +1128,7 @@ impl ModelContextProtocolNotification for ResourceUpdatedNotification {
type Params = ResourceUpdatedNotificationParams;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResourceUpdatedNotificationParams {
pub uri: String,
}
@@ -1135,7 +1136,7 @@ pub struct ResourceUpdatedNotificationParams {
pub type Result = serde_json::Value;
/// The sender or recipient of messages and data in a conversation.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum Role {
#[serde(rename = "assistant")]
Assistant,
@@ -1144,14 +1145,14 @@ pub enum Role {
}
/// Represents a root directory or file that the server can operate on.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Root {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub uri: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum RootsListChangedNotification {}
impl ModelContextProtocolNotification for RootsListChangedNotification {
@@ -1160,13 +1161,13 @@ impl ModelContextProtocolNotification for RootsListChangedNotification {
}
/// Describes a message issued to or received from an LLM API.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct SamplingMessage {
pub content: SamplingMessageContent,
pub role: Role,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum SamplingMessageContent {
TextContent(TextContent),
@@ -1175,7 +1176,7 @@ pub enum SamplingMessageContent {
}
/// Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ServerCapabilities {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completions: Option<serde_json::Value>,
@@ -1192,7 +1193,7 @@ pub struct ServerCapabilities {
}
/// Present if the server offers any tools to call.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ServerCapabilitiesTools {
#[serde(
rename = "listChanged",
@@ -1203,7 +1204,7 @@ pub struct ServerCapabilitiesTools {
}
/// Present if the server offers any resources to read.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ServerCapabilitiesResources {
#[serde(
rename = "listChanged",
@@ -1216,7 +1217,7 @@ pub struct ServerCapabilitiesResources {
}
/// Present if the server offers any prompt templates.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ServerCapabilitiesPrompts {
#[serde(
rename = "listChanged",
@@ -1226,7 +1227,7 @@ pub struct ServerCapabilitiesPrompts {
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(tag = "method", content = "params")]
pub enum ServerNotification {
#[serde(rename = "notifications/cancelled")]
@@ -1255,7 +1256,7 @@ pub enum ServerNotification {
),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum ServerRequest {
PingRequest(PingRequest),
@@ -1264,7 +1265,7 @@ pub enum ServerRequest {
ElicitRequest(ElicitRequest),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum ServerResult {
@@ -1280,7 +1281,7 @@ pub enum ServerResult {
CompleteResult(CompleteResult),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum SetLevelRequest {}
impl ModelContextProtocolRequest for SetLevelRequest {
@@ -1289,12 +1290,12 @@ impl ModelContextProtocolRequest for SetLevelRequest {
type Result = Result;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct SetLevelRequestParams {
pub level: LoggingLevel,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct StringSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
@@ -1309,7 +1310,7 @@ pub struct StringSchema {
pub r#type: String, // &'static str = "string"
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum SubscribeRequest {}
impl ModelContextProtocolRequest for SubscribeRequest {
@@ -1318,13 +1319,13 @@ impl ModelContextProtocolRequest for SubscribeRequest {
type Result = Result;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct SubscribeRequestParams {
pub uri: String,
}
/// Text provided to or from an LLM.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct TextContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
@@ -1332,7 +1333,7 @@ pub struct TextContent {
pub r#type: String, // &'static str = "text"
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct TextResourceContents {
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
@@ -1341,7 +1342,7 @@ pub struct TextResourceContents {
}
/// Definition for a tool the client can call.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct Tool {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<ToolAnnotations>,
@@ -1362,7 +1363,7 @@ pub struct Tool {
/// An optional JSON Schema object defining the structure of the tool's output returned in
/// the structuredContent field of a CallToolResult.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ToolOutputSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Value>,
@@ -1372,7 +1373,7 @@ pub struct ToolOutputSchema {
}
/// A JSON Schema object defining the expected parameters for the tool.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ToolInputSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Value>,
@@ -1389,7 +1390,7 @@ pub struct ToolInputSchema {
///
/// Clients should never make tool use decisions based on ToolAnnotations
/// received from untrusted servers.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct ToolAnnotations {
#[serde(
rename = "destructiveHint",
@@ -1419,7 +1420,7 @@ pub struct ToolAnnotations {
pub title: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum ToolListChangedNotification {}
impl ModelContextProtocolNotification for ToolListChangedNotification {
@@ -1427,7 +1428,7 @@ impl ModelContextProtocolNotification for ToolListChangedNotification {
type Params = Option<serde_json::Value>;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub enum UnsubscribeRequest {}
impl ModelContextProtocolRequest for UnsubscribeRequest {
@@ -1436,7 +1437,7 @@ impl ModelContextProtocolRequest for UnsubscribeRequest {
type Result = Result;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct UnsubscribeRequestParams {
pub uri: String,
}

View File

@@ -86,8 +86,8 @@ impl OtelEventManager {
provider_name: &str,
reasoning_effort: Option<ReasoningEffort>,
reasoning_summary: ReasoningSummary,
context_window: Option<u64>,
max_output_tokens: Option<u64>,
context_window: Option<i64>,
max_output_tokens: Option<i64>,
auto_compact_token_limit: Option<i64>,
approval_policy: AskForApproval,
sandbox_policy: SandboxPolicy,
@@ -281,11 +281,11 @@ impl OtelEventManager {
pub fn sse_event_completed(
&self,
input_token_count: u64,
output_token_count: u64,
cached_token_count: Option<u64>,
reasoning_token_count: Option<u64>,
tool_token_count: u64,
input_token_count: i64,
output_token_count: i64,
cached_token_count: Option<i64>,
reasoning_token_count: Option<i64>,
tool_token_count: i64,
) {
tracing::event!(
tracing::Level::INFO,

View File

@@ -19,6 +19,7 @@ mime_guess = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_with = { workspace = true, features = ["macros", "base64"] }
schemars = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
sys-locale = { workspace = true }

View File

@@ -1,3 +1,4 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum_macros::Display;
@@ -6,7 +7,18 @@ use ts_rs::TS;
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
#[derive(
Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, TS, EnumIter,
Debug,
Serialize,
Deserialize,
Default,
Clone,
Copy,
PartialEq,
Eq,
Display,
JsonSchema,
TS,
EnumIter,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
@@ -21,7 +33,9 @@ pub enum ReasoningEffort {
/// A summary of the reasoning performed by the model. This can be useful for
/// debugging and understanding the model's reasoning process.
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, TS)]
#[derive(
Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ReasoningSummary {
@@ -35,7 +49,9 @@ pub enum ReasoningSummary {
/// Controls output length/detail on GPT-5 models via the Responses API.
/// Serialized with lowercase values to match the OpenAI API.
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, TS)]
#[derive(
Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum Verbosity {
@@ -45,7 +61,9 @@ pub enum Verbosity {
High,
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Default, Serialize, Display, TS)]
#[derive(
Deserialize, Debug, Clone, Copy, PartialEq, Default, Serialize, Display, JsonSchema, TS,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum SandboxMode {
@@ -60,7 +78,7 @@ pub enum SandboxMode {
DangerFullAccess,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, TS)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ForcedLoginMethod {

View File

@@ -1,5 +1,8 @@
use std::fmt::Display;
use schemars::JsonSchema;
use schemars::r#gen::SchemaGenerator;
use schemars::schema::Schema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
@@ -57,6 +60,16 @@ impl<'de> Deserialize<'de> for ConversationId {
}
}
impl JsonSchema for ConversationId {
fn schema_name() -> String {
"ConversationId".to_string()
}
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
<String>::json_schema(generator)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,3 +1,4 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::path::PathBuf;
@@ -9,7 +10,7 @@ use ts_rs::TS;
/// - Full slash prefix: `"/{PROMPTS_CMD_PREFIX}:"`
pub const PROMPTS_CMD_PREFIX: &str = "prompts";
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
pub struct CustomPrompt {
pub name: String,
pub path: PathBuf,

View File

@@ -1,8 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
pub struct HistoryEntry {
pub conversation_id: String,
pub ts: u64,

View File

@@ -9,8 +9,9 @@ use serde::ser::Serializer;
use ts_rs::TS;
use crate::protocol::InputItem;
use schemars::JsonSchema;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseInputItem {
Message {
@@ -31,7 +32,7 @@ pub enum ResponseInputItem {
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentItem {
InputText { text: String },
@@ -39,7 +40,7 @@ pub enum ContentItem {
OutputText { text: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseItem {
Message {
@@ -159,7 +160,7 @@ impl From<ResponseInputItem> for ResponseItem {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum LocalShellStatus {
Completed,
@@ -167,13 +168,13 @@ pub enum LocalShellStatus {
Incomplete,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum LocalShellAction {
Exec(LocalShellExecAction),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
pub struct LocalShellExecAction {
pub command: Vec<String>,
pub timeout_ms: Option<u64>,
@@ -182,7 +183,7 @@ pub struct LocalShellExecAction {
pub user: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum WebSearchAction {
Search {
@@ -192,13 +193,13 @@ pub enum WebSearchAction {
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ReasoningItemReasoningSummary {
SummaryText { text: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ReasoningItemContent {
ReasoningText { text: String },
@@ -242,7 +243,7 @@ impl From<Vec<InputItem>> 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, TS)]
#[derive(Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct ShellToolCallParams {
pub command: Vec<String>,
pub workdir: Option<String>,
@@ -256,7 +257,7 @@ pub struct ShellToolCallParams {
pub justification: Option<String>,
}
#[derive(Debug, Clone, PartialEq, TS)]
#[derive(Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct FunctionCallOutputPayload {
pub content: String,
// TODO(jif) drop this.

View File

@@ -22,27 +22,28 @@ fn formatter() -> &'static DecimalFormatter {
FORMATTER.get_or_init(|| make_local_formatter().unwrap_or_else(make_en_us_formatter))
}
/// Format a u64 with locale-aware digit separators (e.g. "12345" -> "12,345"
/// Format an i64 with locale-aware digit separators (e.g. "12345" -> "12,345"
/// for en-US).
pub fn format_with_separators(n: u64) -> String {
pub fn format_with_separators(n: i64) -> String {
formatter().format(&Decimal::from(n)).to_string()
}
fn format_si_suffix_with_formatter(n: u64, formatter: &DecimalFormatter) -> String {
fn format_si_suffix_with_formatter(n: i64, formatter: &DecimalFormatter) -> String {
let n = n.max(0);
if n < 1000 {
return formatter.format(&Decimal::from(n)).to_string();
}
// Format `n / scale` with the requested number of fractional digits.
let format_scaled = |n: u64, scale: u64, frac_digits: u32| -> String {
let format_scaled = |n: i64, scale: i64, frac_digits: u32| -> String {
let value = n as f64 / scale as f64;
let scaled: u64 = (value * 10f64.powi(frac_digits as i32)).round() as u64;
let scaled: i64 = (value * 10f64.powi(frac_digits as i32)).round() as i64;
let mut dec = Decimal::from(scaled);
dec.multiply_pow10(-(frac_digits as i16));
formatter.format(&dec).to_string()
};
const UNITS: [(u64, &str); 3] = [(1_000, "K"), (1_000_000, "M"), (1_000_000_000, "G")];
const UNITS: [(i64, &str); 3] = [(1_000, "K"), (1_000_000, "M"), (1_000_000_000, "G")];
let f = n as f64;
for &(scale, suffix) in &UNITS {
if (100.0 * f / scale as f64).round() < 1000.0 {
@@ -57,7 +58,7 @@ fn format_si_suffix_with_formatter(n: u64, formatter: &DecimalFormatter) -> Stri
// Above 1000G, keep wholeG precision.
format!(
"{}G",
format_with_separators(((n as f64) / 1e9).round() as u64)
format_with_separators(((n as f64) / 1e9).round() as i64)
)
}
@@ -67,7 +68,7 @@ fn format_si_suffix_with_formatter(n: u64, formatter: &DecimalFormatter) -> Stri
/// - 999 -> "999"
/// - 1200 -> "1.20K"
/// - 123456789 -> "123M"
pub fn format_si_suffix(n: u64) -> String {
pub fn format_si_suffix(n: i64) -> String {
format_si_suffix_with_formatter(n, formatter())
}
@@ -78,7 +79,7 @@ mod tests {
#[test]
fn kmg() {
let formatter = make_en_us_formatter();
let fmt = |n: u64| format_si_suffix_with_formatter(n, &formatter);
let fmt = |n: i64| format_si_suffix_with_formatter(n, &formatter);
assert_eq!(fmt(0), "0");
assert_eq!(fmt(999), "999");
assert_eq!(fmt(1_000), "1.00K");

View File

@@ -1,9 +1,10 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::path::PathBuf;
use ts_rs::TS;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ParsedCommand {
Read {

View File

@@ -1,9 +1,10 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
// Types for the TODO tool arguments matching codex-vscode/todo-mcp/src/main.rs
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum StepStatus {
Pending,
@@ -11,14 +12,14 @@ pub enum StepStatus {
Completed,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, TS)]
#[serde(deny_unknown_fields)]
pub struct PlanItemArg {
pub step: String,
pub status: StepStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, TS)]
#[serde(deny_unknown_fields)]
pub struct UpdatePlanArgs {
#[serde(default)]

View File

@@ -24,6 +24,7 @@ use mcp_types::CallToolResult;
use mcp_types::Resource as McpResource;
use mcp_types::ResourceTemplate as McpResourceTemplate;
use mcp_types::Tool as McpTool;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
@@ -40,7 +41,7 @@ pub const ENVIRONMENT_CONTEXT_CLOSE_TAG: &str = "</environment_context>";
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
/// Submission Queue Entry - requests from user
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct Submission {
/// Unique id for this Submission to correlate with Events
pub id: String,
@@ -49,7 +50,7 @@ pub struct Submission {
}
/// Submission operation
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
#[non_exhaustive]
@@ -182,7 +183,20 @@ pub enum Op {
/// Determines the conditions under which the user is consulted to approve
/// running the command proposed by Codex.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, Display, TS)]
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
Display,
JsonSchema,
TS,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum AskForApproval {
@@ -209,7 +223,7 @@ pub enum AskForApproval {
}
/// Determines execution restrictions for model shell commands.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display, TS)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display, JsonSchema, TS)]
#[strum(serialize_all = "kebab-case")]
#[serde(tag = "mode", rename_all = "kebab-case")]
pub enum SandboxPolicy {
@@ -252,7 +266,7 @@ pub enum SandboxPolicy {
/// readonly even when the root is writable. This is primarily used to ensure
/// toplevel VCS metadata directories (e.g. `.git`) under a writable root are
/// not modified by the agent.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, JsonSchema)]
pub struct WritableRoot {
/// Absolute path, by construction.
pub root: PathBuf,
@@ -391,7 +405,7 @@ impl SandboxPolicy {
/// User input
#[non_exhaustive]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum InputItem {
Text {
@@ -410,7 +424,7 @@ pub enum InputItem {
}
/// Event Queue Entry - events from agent
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct Event {
/// Submission `id` that this event is correlated with.
pub id: String,
@@ -420,7 +434,7 @@ pub struct Event {
/// Response event from the agent
/// NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.
#[derive(Debug, Clone, Deserialize, Serialize, Display, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, Display, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum EventMsg {
@@ -526,55 +540,55 @@ pub enum EventMsg {
ExitedReviewMode(ExitedReviewModeEvent),
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ExitedReviewModeEvent {
pub review_output: Option<ReviewOutputEvent>,
}
// Individual event payload types matching each `EventMsg` variant.
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ErrorEvent {
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct TaskCompleteEvent {
pub last_agent_message: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct TaskStartedEvent {
pub model_context_window: Option<u64>,
pub model_context_window: Option<i64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, Default, JsonSchema, TS)]
pub struct TokenUsage {
#[ts(type = "number")]
pub input_tokens: u64,
pub input_tokens: i64,
#[ts(type = "number")]
pub cached_input_tokens: u64,
pub cached_input_tokens: i64,
#[ts(type = "number")]
pub output_tokens: u64,
pub output_tokens: i64,
#[ts(type = "number")]
pub reasoning_output_tokens: u64,
pub reasoning_output_tokens: i64,
#[ts(type = "number")]
pub total_tokens: u64,
pub total_tokens: i64,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct TokenUsageInfo {
pub total_token_usage: TokenUsage,
pub last_token_usage: TokenUsage,
#[ts(type = "number | null")]
pub model_context_window: Option<u64>,
pub model_context_window: Option<i64>,
}
impl TokenUsageInfo {
pub fn new_or_append(
info: &Option<TokenUsageInfo>,
last: &Option<TokenUsage>,
model_context_window: Option<u64>,
model_context_window: Option<i64>,
) -> Option<Self> {
if info.is_none() && last.is_none() {
return None;
@@ -599,9 +613,9 @@ impl TokenUsageInfo {
self.last_token_usage = last.clone();
}
pub fn fill_to_context_window(&mut self, context_window: u64) {
pub fn fill_to_context_window(&mut self, context_window: i64) {
let previous_total = self.total_token_usage.total_tokens;
let delta = context_window.saturating_sub(previous_total);
let delta = (context_window - previous_total).max(0);
self.model_context_window = Some(context_window);
self.total_token_usage = TokenUsage {
@@ -614,7 +628,7 @@ impl TokenUsageInfo {
};
}
pub fn full_context_window(context_window: u64) -> Self {
pub fn full_context_window(context_window: i64) -> Self {
let mut info = Self {
total_token_usage: TokenUsage::default(),
last_token_usage: TokenUsage::default(),
@@ -625,19 +639,19 @@ impl TokenUsageInfo {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct TokenCountEvent {
pub info: Option<TokenUsageInfo>,
pub rate_limits: Option<RateLimitSnapshot>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct RateLimitSnapshot {
pub primary: Option<RateLimitWindow>,
pub secondary: Option<RateLimitWindow>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct RateLimitWindow {
/// Percentage (0-100) of the window that has been consumed.
pub used_percent: f64,
@@ -650,33 +664,32 @@ pub struct RateLimitWindow {
}
// Includes prompts, tools and space to call compact.
const BASELINE_TOKENS: u64 = 12000;
const BASELINE_TOKENS: i64 = 12000;
impl TokenUsage {
pub fn is_zero(&self) -> bool {
self.total_tokens == 0
}
pub fn cached_input(&self) -> u64 {
self.cached_input_tokens
pub fn cached_input(&self) -> i64 {
self.cached_input_tokens.max(0)
}
pub fn non_cached_input(&self) -> u64 {
self.input_tokens.saturating_sub(self.cached_input())
pub fn non_cached_input(&self) -> i64 {
(self.input_tokens - self.cached_input()).max(0)
}
/// Primary count for display as a single absolute value: non-cached input + output.
pub fn blended_total(&self) -> u64 {
self.non_cached_input() + self.output_tokens
pub fn blended_total(&self) -> i64 {
(self.non_cached_input() + self.output_tokens.max(0)).max(0)
}
/// For estimating what % of the model's context window is used, we need to account
/// for reasoning output tokens from prior turns being dropped from the context window.
/// We approximate this here by subtracting reasoning output tokens from the total.
/// This will be off for the current turn and pending function calls.
pub fn tokens_in_context_window(&self) -> u64 {
self.total_tokens
.saturating_sub(self.reasoning_output_tokens)
pub fn tokens_in_context_window(&self) -> i64 {
(self.total_tokens - self.reasoning_output_tokens).max(0)
}
/// Estimate the remaining user-controllable percentage of the model's context window.
@@ -689,17 +702,17 @@ impl TokenUsage {
/// This normalizes both the numerator and denominator by subtracting the
/// baseline, so immediately after the first prompt the UI shows 100% left
/// and trends toward 0% as the user fills the effective window.
pub fn percent_of_context_window_remaining(&self, context_window: u64) -> u8 {
pub fn percent_of_context_window_remaining(&self, context_window: i64) -> i64 {
if context_window <= BASELINE_TOKENS {
return 0;
}
let effective_window = context_window - BASELINE_TOKENS;
let used = self
.tokens_in_context_window()
.saturating_sub(BASELINE_TOKENS);
let remaining = effective_window.saturating_sub(used);
((remaining as f32 / effective_window as f32) * 100.0).clamp(0.0, 100.0) as u8
let used = (self.tokens_in_context_window() - BASELINE_TOKENS).max(0);
let remaining = (effective_window - used).max(0);
((remaining as f64 / effective_window as f64) * 100.0)
.clamp(0.0, 100.0)
.round() as i64
}
/// In-place element-wise sum of token counts.
@@ -712,7 +725,7 @@ impl TokenUsage {
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct FinalOutput {
pub token_usage: TokenUsage,
}
@@ -753,12 +766,12 @@ impl fmt::Display for FinalOutput {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct AgentMessageEvent {
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum InputMessageKind {
/// Plain user text (default)
@@ -769,7 +782,7 @@ pub enum InputMessageKind {
EnvironmentContext,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct UserMessageEvent {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -821,35 +834,35 @@ fn ends_with_ignore_ascii_case(text: &str, suffix: &str) -> bool {
.all(|(a, b)| a.eq_ignore_ascii_case(b))
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct AgentMessageDeltaEvent {
pub delta: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct AgentReasoningEvent {
pub text: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct AgentReasoningRawContentEvent {
pub text: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct AgentReasoningRawContentDeltaEvent {
pub delta: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct AgentReasoningSectionBreakEvent {}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct AgentReasoningDeltaEvent {
pub delta: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct McpInvocation {
/// Name of the MCP server as defined in the config.
pub server: String,
@@ -859,14 +872,14 @@ pub struct McpInvocation {
pub arguments: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct McpToolCallBeginEvent {
/// Identifier so this can be paired with the McpToolCallEnd event.
pub call_id: String,
pub invocation: McpInvocation,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct McpToolCallEndEvent {
/// Identifier for the corresponding McpToolCallBegin that finished.
pub call_id: String,
@@ -886,12 +899,12 @@ impl McpToolCallEndEvent {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct WebSearchBeginEvent {
pub call_id: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct WebSearchEndEvent {
pub call_id: String,
pub query: String,
@@ -899,20 +912,20 @@ pub struct WebSearchEndEvent {
/// Response payload for `Op::GetHistory` containing the current session's
/// in-memory transcript.
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ConversationPathResponseEvent {
pub conversation_id: ConversationId,
pub path: PathBuf,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ResumedHistory {
pub conversation_id: ConversationId,
pub history: Vec<RolloutItem>,
pub rollout_path: PathBuf,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub enum InitialHistory {
New,
Resumed(ResumedHistory),
@@ -954,7 +967,7 @@ impl InitialHistory {
}
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, TS, Default)]
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
#[serde(rename_all = "lowercase")]
#[ts(rename_all = "lowercase")]
pub enum SessionSource {
@@ -967,7 +980,7 @@ pub enum SessionSource {
Unknown,
}
#[derive(Serialize, Deserialize, Clone, Debug, TS)]
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
pub struct SessionMeta {
pub id: ConversationId,
pub timestamp: String,
@@ -993,7 +1006,7 @@ impl Default for SessionMeta {
}
}
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
pub struct SessionMetaLine {
#[serde(flatten)]
pub meta: SessionMeta,
@@ -1001,7 +1014,7 @@ pub struct SessionMetaLine {
pub git: Option<GitInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
pub enum RolloutItem {
SessionMeta(SessionMetaLine),
@@ -1011,7 +1024,7 @@ pub enum RolloutItem {
EventMsg(EventMsg),
}
#[derive(Serialize, Deserialize, Clone, Debug, TS)]
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
pub struct CompactedItem {
pub message: String,
}
@@ -1028,7 +1041,7 @@ impl From<CompactedItem> for ResponseItem {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, TS)]
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
pub struct TurnContextItem {
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
@@ -1039,14 +1052,14 @@ pub struct TurnContextItem {
pub summary: ReasoningSummaryConfig,
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct RolloutLine {
pub timestamp: String,
#[serde(flatten)]
pub item: RolloutItem,
}
#[derive(Serialize, Deserialize, Clone, Debug, TS)]
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
pub struct GitInfo {
/// Current commit hash (SHA)
#[serde(skip_serializing_if = "Option::is_none")]
@@ -1060,14 +1073,14 @@ pub struct GitInfo {
}
/// Review request sent to the review session.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct ReviewRequest {
pub prompt: String,
pub user_facing_hint: String,
}
/// Structured review result produced by a child review session.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct ReviewOutputEvent {
pub findings: Vec<ReviewFinding>,
pub overall_correctness: String,
@@ -1087,7 +1100,7 @@ impl Default for ReviewOutputEvent {
}
/// A single review finding describing an observed issue or recommendation.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct ReviewFinding {
pub title: String,
pub body: String,
@@ -1097,20 +1110,20 @@ pub struct ReviewFinding {
}
/// Location of the code related to a review finding.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct ReviewCodeLocation {
pub absolute_file_path: PathBuf,
pub line_range: ReviewLineRange,
}
/// Inclusive line range in a file associated with the finding.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct ReviewLineRange {
pub start: u32,
pub end: u32,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ExecCommandBeginEvent {
/// Identifier so this can be paired with the ExecCommandEnd event.
pub call_id: String,
@@ -1121,7 +1134,7 @@ pub struct ExecCommandBeginEvent {
pub parsed_cmd: Vec<ParsedCommand>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ExecCommandEndEvent {
/// Identifier for the ExecCommandBegin that finished.
pub call_id: String,
@@ -1141,7 +1154,7 @@ pub struct ExecCommandEndEvent {
pub formatted_output: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ViewImageToolCallEvent {
/// Identifier for the originating tool call.
pub call_id: String,
@@ -1149,7 +1162,7 @@ pub struct ViewImageToolCallEvent {
pub path: PathBuf,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum ExecOutputStream {
Stdout,
@@ -1157,7 +1170,7 @@ pub enum ExecOutputStream {
}
#[serde_as]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct ExecCommandOutputDeltaEvent {
/// Identifier for the ExecCommandBegin that produced this chunk.
pub call_id: String,
@@ -1165,11 +1178,12 @@ pub struct ExecCommandOutputDeltaEvent {
pub stream: ExecOutputStream,
/// Raw bytes from the stream (may not be valid UTF-8).
#[serde_as(as = "serde_with::base64::Base64")]
#[schemars(with = "String")]
#[ts(type = "string")]
pub chunk: Vec<u8>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ExecApprovalRequestEvent {
/// Identifier for the associated exec call, if available.
pub call_id: String,
@@ -1183,7 +1197,7 @@ pub struct ExecApprovalRequestEvent {
pub parsed_cmd: Vec<ParsedCommand>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ApplyPatchApprovalRequestEvent {
/// Responses API call id for the associated patch apply call, if available.
pub call_id: String,
@@ -1196,22 +1210,22 @@ pub struct ApplyPatchApprovalRequestEvent {
pub grant_root: Option<PathBuf>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct BackgroundEventEvent {
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct StreamErrorEvent {
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct StreamInfoEvent {
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct PatchApplyBeginEvent {
/// Identifier so this can be paired with the PatchApplyEnd event.
pub call_id: String,
@@ -1221,7 +1235,7 @@ pub struct PatchApplyBeginEvent {
pub changes: HashMap<PathBuf, FileChange>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct PatchApplyEndEvent {
/// Identifier for the PatchApplyBegin that finished.
pub call_id: String,
@@ -1233,12 +1247,12 @@ pub struct PatchApplyEndEvent {
pub success: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct TurnDiffEvent {
pub unified_diff: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct GetHistoryEntryResponseEvent {
pub offset: usize,
pub log_id: u64,
@@ -1247,7 +1261,7 @@ pub struct GetHistoryEntryResponseEvent {
pub entry: Option<HistoryEntry>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct McpListToolsResponseEvent {
/// Fully qualified tool name -> tool definition.
pub tools: std::collections::HashMap<String, McpTool>,
@@ -1259,7 +1273,7 @@ pub struct McpListToolsResponseEvent {
pub auth_statuses: std::collections::HashMap<String, McpAuthStatus>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
pub enum McpAuthStatus {
@@ -1282,12 +1296,12 @@ impl fmt::Display for McpAuthStatus {
}
/// Response payload for `Op::ListCustomPrompts`.
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ListCustomPromptsResponseEvent {
pub custom_prompts: Vec<CustomPrompt>,
}
#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct SessionConfiguredEvent {
/// Name left as session_id instead of conversation_id for backwards compatibility.
pub session_id: ConversationId,
@@ -1314,7 +1328,9 @@ pub struct SessionConfiguredEvent {
}
/// User's decision in response to an ExecApprovalRequest.
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Display, TS)]
#[derive(
Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Display, JsonSchema, TS,
)]
#[serde(rename_all = "snake_case")]
pub enum ReviewDecision {
/// User has approved this command and the agent should execute it.
@@ -1335,7 +1351,7 @@ pub enum ReviewDecision {
Abort,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum FileChange {
Add {
@@ -1350,7 +1366,7 @@ pub enum FileChange {
},
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct Chunk {
/// 1-based line index of the first line in the original file
pub orig_index: u32,
@@ -1358,12 +1374,12 @@ pub struct Chunk {
pub inserted_lines: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct TurnAbortedEvent {
pub reason: TurnAbortReason,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum TurnAbortReason {
Interrupted,

View File

@@ -485,11 +485,10 @@ mod tests {
})
.collect();
let expected = vec![
"✔ You approved codex to".to_string(),
" run /bin/zsh -lc 'git add".to_string(),
" tui/src/render/mod.rs tui/".to_string(),
" src/render/renderable.rs'".to_string(),
" this time".to_string(),
"✔ You approved codex to run".to_string(),
" git add tui/src/render/".to_string(),
" mod.rs tui/src/render/".to_string(),
" renderable.rs this time".to_string(),
];
assert_eq!(rendered, expected);
}

View File

@@ -108,7 +108,7 @@ pub(crate) struct ChatComposer {
custom_prompts: Vec<CustomPrompt>,
footer_mode: FooterMode,
footer_hint_override: Option<Vec<(String, String)>>,
context_window_percent: Option<u8>,
context_window_percent: Option<i64>,
}
/// Popup state at most one can be visible at any time.
@@ -1511,7 +1511,7 @@ impl ChatComposer {
self.is_task_running = running;
}
pub(crate) fn set_context_window_percent(&mut self, percent: Option<u8>) {
pub(crate) fn set_context_window_percent(&mut self, percent: Option<i64>) {
if self.context_window_percent != percent {
self.context_window_percent = percent;
}

View File

@@ -17,7 +17,7 @@ pub(crate) struct FooterProps {
pub(crate) esc_backtrack_hint: bool,
pub(crate) use_shift_enter_hint: bool,
pub(crate) is_task_running: bool,
pub(crate) context_window_percent: Option<u8>,
pub(crate) context_window_percent: Option<i64>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -221,8 +221,8 @@ fn build_columns(entries: Vec<Line<'static>>) -> Vec<Line<'static>> {
.collect()
}
fn context_window_line(percent: Option<u8>) -> Line<'static> {
let percent = percent.unwrap_or(100);
fn context_window_line(percent: Option<i64>) -> Line<'static> {
let percent = percent.unwrap_or(100).clamp(0, 100);
Line::from(vec![Span::from(format!("{percent}% context left")).dim()])
}

View File

@@ -70,7 +70,7 @@ pub(crate) struct BottomPane {
status: Option<StatusIndicatorWidget>,
/// Queued user messages to show under the status indicator.
queued_user_messages: Vec<String>,
context_window_percent: Option<u8>,
context_window_percent: Option<i64>,
}
pub(crate) struct BottomPaneParams {
@@ -357,7 +357,7 @@ impl BottomPane {
}
}
pub(crate) fn set_context_window_percent(&mut self, percent: Option<u8>) {
pub(crate) fn set_context_window_percent(&mut self, percent: Option<i64>) {
if self.context_window_percent == percent {
return;
}

View File

@@ -142,9 +142,9 @@ impl RateLimitWarningState {
fn take_warnings(
&mut self,
secondary_used_percent: Option<f64>,
secondary_window_minutes: Option<u64>,
secondary_window_minutes: Option<i64>,
primary_used_percent: Option<f64>,
primary_window_minutes: Option<u64>,
primary_window_minutes: Option<i64>,
) -> Vec<String> {
let reached_secondary_cap =
matches!(secondary_used_percent, Some(percent) if percent == 100.0);
@@ -195,12 +195,14 @@ impl RateLimitWarningState {
}
}
pub(crate) fn get_limits_duration(windows_minutes: u64) -> String {
const MINUTES_PER_HOUR: u64 = 60;
const MINUTES_PER_DAY: u64 = 24 * MINUTES_PER_HOUR;
const MINUTES_PER_WEEK: u64 = 7 * MINUTES_PER_DAY;
const MINUTES_PER_MONTH: u64 = 30 * MINUTES_PER_DAY;
const ROUNDING_BIAS_MINUTES: u64 = 3;
pub(crate) fn get_limits_duration(windows_minutes: i64) -> String {
const MINUTES_PER_HOUR: i64 = 60;
const MINUTES_PER_DAY: i64 = 24 * MINUTES_PER_HOUR;
const MINUTES_PER_WEEK: i64 = 7 * MINUTES_PER_DAY;
const MINUTES_PER_MONTH: i64 = 30 * MINUTES_PER_DAY;
const ROUNDING_BIAS_MINUTES: i64 = 3;
let windows_minutes = windows_minutes.max(0);
if windows_minutes <= MINUTES_PER_DAY.saturating_add(ROUNDING_BIAS_MINUTES) {
let adjusted = windows_minutes.saturating_add(ROUNDING_BIAS_MINUTES);

View File

@@ -8,9 +8,17 @@ pub(crate) fn escape_command(command: &[String]) -> String {
try_join(command.iter().map(String::as_str)).unwrap_or_else(|_| command.join(" "))
}
fn is_login_shell_with_lc(shell: &str) -> bool {
let shell_name = std::path::Path::new(shell)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or(shell);
matches!(shell_name, "bash" | "zsh")
}
pub(crate) fn strip_bash_lc_and_escape(command: &[String]) -> String {
match command {
[first, second, third] if first == "bash" && second == "-lc" => third.clone(),
[first, second, third] if is_login_shell_with_lc(first) && second == "-lc" => third.clone(),
_ => escape_command(command),
}
}
@@ -46,8 +54,24 @@ mod tests {
#[test]
fn test_strip_bash_lc_and_escape() {
// Test bash
let args = vec!["bash".into(), "-lc".into(), "echo hello".into()];
let cmdline = strip_bash_lc_and_escape(&args);
assert_eq!(cmdline, "echo hello");
// Test zsh
let args = vec!["zsh".into(), "-lc".into(), "echo hello".into()];
let cmdline = strip_bash_lc_and_escape(&args);
assert_eq!(cmdline, "echo hello");
// Test absolute path to zsh
let args = vec!["/usr/bin/zsh".into(), "-lc".into(), "echo hello".into()];
let cmdline = strip_bash_lc_and_escape(&args);
assert_eq!(cmdline, "echo hello");
// Test absolute path to bash
let args = vec!["/bin/bash".into(), "-lc".into(), "echo hello".into()];
let cmdline = strip_bash_lc_and_escape(&args);
assert_eq!(cmdline, "echo hello");
}
}

View File

@@ -31,16 +31,16 @@ use super::rate_limits::render_status_limit_progress_bar;
#[derive(Debug, Clone)]
struct StatusContextWindowData {
percent_remaining: u8,
tokens_in_context: u64,
window: u64,
percent_remaining: i64,
tokens_in_context: i64,
window: i64,
}
#[derive(Debug, Clone)]
pub(crate) struct StatusTokenUsageData {
total: u64,
input: u64,
output: u64,
total: i64,
input: i64,
output: i64,
context_window: Option<StatusContextWindowData>,
}

View File

@@ -103,7 +103,8 @@ pub(crate) fn compose_account_display(config: &Config) -> Option<StatusAccountDi
None
}
pub(crate) fn format_tokens_compact(value: u64) -> String {
pub(crate) fn format_tokens_compact(value: i64) -> String {
let value = value.max(0);
if value == 0 {
return "0".to_string();
}
@@ -111,14 +112,15 @@ pub(crate) fn format_tokens_compact(value: u64) -> String {
return value.to_string();
}
let value_f64 = value as f64;
let (scaled, suffix) = if value >= 1_000_000_000_000 {
(value as f64 / 1_000_000_000_000.0, "T")
(value_f64 / 1_000_000_000_000.0, "T")
} else if value >= 1_000_000_000 {
(value as f64 / 1_000_000_000.0, "B")
(value_f64 / 1_000_000_000.0, "B")
} else if value >= 1_000_000 {
(value as f64 / 1_000_000.0, "M")
(value_f64 / 1_000_000.0, "M")
} else {
(value as f64 / 1_000.0, "K")
(value_f64 / 1_000.0, "K")
};
let decimals = if scaled < 10.0 {

View File

@@ -29,7 +29,7 @@ pub(crate) enum StatusRateLimitData {
pub(crate) struct RateLimitWindowDisplay {
pub used_percent: f64,
pub resets_at: Option<String>,
pub window_minutes: Option<u64>,
pub window_minutes: Option<i64>,
}
impl RateLimitWindowDisplay {

View File

@@ -40,17 +40,16 @@ Send a `tools/list` request and you will see that there are two tools available:
**`codex`** - Run a Codex session. Accepts configuration parameters matching the Codex Config struct. The `codex` tool takes the following properties:
| Property | Type | Description |
| ----------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`prompt`** (required) | string | The initial user prompt to start the Codex conversation. |
| `approval-policy` | string | Approval policy for shell commands generated by the model: `untrusted`, `on-failure`, `never`. |
| `base-instructions` | string | The set of instructions to use instead of the default ones. |
| `config` | object | Individual [config settings](https://github.com/openai/codex/blob/main/docs/config.md#config) that will override what is in `$CODEX_HOME/config.toml`. |
| `cwd` | string | Working directory for the session. If relative, resolved against the server process's current directory. |
| `include-plan-tool` | boolean | Whether to include the plan tool in the conversation. |
| `model` | string | Optional override for the model name (e.g. `o3`, `o4-mini`). |
| `profile` | string | Configuration profile from `config.toml` to specify default options. |
| `sandbox` | string | Sandbox mode: `read-only`, `workspace-write`, or `danger-full-access`. |
| Property | Type | Description |
| ----------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --- |
| **`prompt`** (required) | string | The initial user prompt to start the Codex conversation. |
| `approval-policy` | string | Approval policy for shell commands generated by the model: `untrusted`, `on-failure`, `never`. |
| `base-instructions` | string | The set of instructions to use instead of the default ones. |
| `config` | object | Individual [config settings](https://github.com/openai/codex/blob/main/docs/config.md#config) that will override what is in `$CODEX_HOME/config.toml`. |
| `cwd` | string | Working directory for the session. If relative, resolved against the server process's current directory. | |
| `model` | string | Optional override for the model name (e.g. `o3`, `o4-mini`). |
| `profile` | string | Configuration profile from `config.toml` to specify default options. |
| `sandbox` | string | Sandbox mode: `read-only`, `workspace-write`, or `danger-full-access`. |
**`codex-reply`** - Continue a Codex session by providing the conversation id and prompt. The `codex-reply` tool takes the following properties:

View File

@@ -87,7 +87,7 @@ This will push the commit using the tag `rust-v${VERSION}`, which in turn kicks
If everything looks good in the generated GitHub Release, uncheck the **pre-release** box so it is the latest release.
Create a PR to update [`Formula/c/codex.rb`](https://github.com/Homebrew/homebrew-core/blob/main/Formula/c/codex.rb) on Homebrew.
Create a PR to update [`Cask/c/codex.rb`](https://github.com/Homebrew/homebrew-cask/blob/main/Formula/c/codex.rb) on Homebrew.
### Security & responsible AI

View File

@@ -4,7 +4,7 @@ Currently, we made Codex binaries available in three places:
- GitHub Releases https://github.com/openai/codex/releases/
- `@openai/codex` on npm: https://www.npmjs.com/package/@openai/codex
- `codex` on Homebrew: https://formulae.brew.sh/formula/codex
- `codex` on Homebrew: https://formulae.brew.sh/cask/codex
# Cutting a Release
@@ -34,14 +34,12 @@ The GitHub Action is responsible for publishing to npm.
## Publishing to Homebrew
For Homebrew, we are properly set up with their automation system, so every few hours or so it will check our GitHub repo to see if there is a new release. When it finds one, it will put up a PR to create the equivalent Homebrew release, which entails building Codex CLI from source on various versions of macOS.
For Homebrew, we ship Codex as a cask. Homebrew's automation system checks our GitHub repo every few hours for a new release and will open a PR to update the cask with the latest binary.
Inevitably, you just have to refresh this page periodically to see if the release has been picked up by their automation system:
https://github.com/Homebrew/homebrew-core/pulls?q=%3Apr+codex
https://github.com/Homebrew/homebrew-cask/pulls?q=%3Apr+codex
Once everything builds, a Homebrew admin has to approve the PR. Again, the whole process takes several hours and we don't have total control over it, but it seems to work pretty well.
For reference, our Homebrew cask lives at:
For reference, our Homebrew formula lives at:
https://github.com/Homebrew/homebrew-core/blob/main/Formula/c/codex.rb
https://github.com/Homebrew/homebrew-cask/blob/main/Casks/c/codex.rb

View File

@@ -83,6 +83,18 @@ const turn = await thread.run("Summarize repository status", {
console.log(turn.finalResponse);
```
### Attaching images
Provide structured input entries when you need to include images alongside text. Text entries are concatenated into the final prompt while image entries are passed to the Codex CLI via `--image`.
```typescript
const turn = await thread.run([
{ type: "text", text: "Describe these screenshots" },
{ type: "local_image", path: "./ui.png" },
{ type: "local_image", path: "./diagram.jpg" },
]);
```
### Resuming an existing thread
Threads are persisted in `~/.codex/sessions`. If you lose the in-memory `Thread` object, reconstruct it with `resumeThread()` and keep going.
@@ -95,7 +107,7 @@ await thread.run("Implement the fix");
### Working directory controls
Codex runs in the current working directory by default. To avoid unrecoverable errors, Codex requires the working directory to be a Git repository. You can skip the Git repository check by passing the `skipGitRepoCheck` option when creating a thread.
Codex runs in the current working directory by default. To avoid unrecoverable errors, Codex requires the working directory to be a Git repository. You can skip the Git repository check by passing the `skipGitRepoCheck` option when creating a thread.
```typescript
const thread = codex.startThread({

View File

@@ -11,6 +11,7 @@ export type CodexExecArgs = {
baseUrl?: string;
apiKey?: string;
threadId?: string | null;
images?: string[];
// --model
model?: string;
// --sandbox
@@ -55,6 +56,12 @@ export class CodexExec {
commandArgs.push("--output-schema", args.outputSchemaFile);
}
if (args.images?.length) {
for (const image of args.images) {
commandArgs.push("--image", image);
}
}
if (args.threadId) {
commandArgs.push("resume", args.threadId);
}

View File

@@ -24,7 +24,7 @@ export type {
} from "./items";
export { Thread } from "./thread";
export type { RunResult, RunStreamedResult, Input } from "./thread";
export type { RunResult, RunStreamedResult, Input, UserInput } from "./thread";
export { Codex } from "./codex";

View File

@@ -25,7 +25,17 @@ export type StreamedTurn = {
export type RunStreamedResult = StreamedTurn;
/** An input to send to the agent. */
export type Input = string;
export type UserInput =
| {
type: "text";
text: string;
}
| {
type: "local_image";
path: string;
};
export type Input = string | UserInput[];
/** Respesent a thread of conversation with the agent. One thread can have multiple consecutive turns. */
export class Thread {
@@ -53,21 +63,23 @@ export class Thread {
}
/** Provides the input to the agent and streams events as they are produced during the turn. */
async runStreamed(input: string, turnOptions: TurnOptions = {}): Promise<StreamedTurn> {
async runStreamed(input: Input, turnOptions: TurnOptions = {}): Promise<StreamedTurn> {
return { events: this.runStreamedInternal(input, turnOptions) };
}
private async *runStreamedInternal(
input: string,
input: Input,
turnOptions: TurnOptions = {},
): AsyncGenerator<ThreadEvent> {
const { schemaPath, cleanup } = await createOutputSchemaFile(turnOptions.outputSchema);
const options = this._threadOptions;
const { prompt, images } = normalizeInput(input);
const generator = this._exec.run({
input,
input: prompt,
baseUrl: this._options.baseUrl,
apiKey: this._options.apiKey,
threadId: this._id,
images,
model: options?.model,
sandboxMode: options?.sandboxMode,
workingDirectory: options?.workingDirectory,
@@ -93,7 +105,7 @@ export class Thread {
}
/** Provides the input to the agent and returns the completed turn. */
async run(input: string, turnOptions: TurnOptions = {}): Promise<Turn> {
async run(input: Input, turnOptions: TurnOptions = {}): Promise<Turn> {
const generator = this.runStreamedInternal(input, turnOptions);
const items: ThreadItem[] = [];
let finalResponse: string = "";
@@ -118,3 +130,19 @@ export class Thread {
return { items, finalResponse, usage };
}
}
function normalizeInput(input: Input): { prompt: string; images: string[] } {
if (typeof input === "string") {
return { prompt: input, images: [] };
}
const promptParts: string[] = [];
const images: string[] = [];
for (const item of input) {
if (item.type === "text") {
promptParts.push(item.text);
} else if (item.type === "local_image") {
images.push(item.path);
}
}
return { prompt: promptParts.join("\n\n"), images };
}

View File

@@ -279,6 +279,82 @@ describe("Codex", () => {
await close();
}
});
it("combines structured text input segments", async () => {
const { url, close, requests } = await startResponsesTestProxy({
statusCode: 200,
responseBodies: [
sse(
responseStarted("response_1"),
assistantMessage("Combined input applied", "item_1"),
responseCompleted("response_1"),
),
],
});
try {
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
const thread = client.startThread();
await thread.run([
{ type: "text", text: "Describe file changes" },
{ type: "text", text: "Focus on impacted tests" },
]);
const payload = requests[0];
expect(payload).toBeDefined();
const lastUser = payload!.json.input.at(-1);
expect(lastUser?.content?.[0]?.text).toBe("Describe file changes\n\nFocus on impacted tests");
} finally {
await close();
}
});
it("forwards images to exec", async () => {
const { url, close } = await startResponsesTestProxy({
statusCode: 200,
responseBodies: [
sse(
responseStarted("response_1"),
assistantMessage("Images applied", "item_1"),
responseCompleted("response_1"),
),
],
});
const { args: spawnArgs, restore } = codexExecSpy();
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-images-"));
const imagesDirectoryEntries: [string, string] = [
path.join(tempDir, "first.png"),
path.join(tempDir, "second.jpg"),
];
imagesDirectoryEntries.forEach((image, index) => {
fs.writeFileSync(image, `image-${index}`);
});
try {
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
const thread = client.startThread();
await thread.run([
{ type: "text", text: "describe the images" },
{ type: "local_image", path: imagesDirectoryEntries[0] },
{ type: "local_image", path: imagesDirectoryEntries[1] },
]);
const commandArgs = spawnArgs[0];
expect(commandArgs).toBeDefined();
const forwardedImages: string[] = [];
for (let i = 0; i < commandArgs!.length; i += 1) {
if (commandArgs![i] === "--image") {
forwardedImages.push(commandArgs![i + 1] ?? "");
}
}
expect(forwardedImages).toEqual(imagesDirectoryEntries);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
restore();
await close();
}
});
it("runs in provided working directory", async () => {
const { url, close } = await startResponsesTestProxy({
statusCode: 200,