From 8dd41e229b43d44004ab8f1a90b9a788344f3bcc Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 2 Feb 2026 18:00:35 -0800 Subject: [PATCH 1/9] Fixed sandbox mode inconsistency if untrusted is selected (#10415) This PR addresses #10395 When a user is asked to pick the trust level of a project, the code currently reloads the config if they select "trusted". It doesn't reload the config in the "untrusted" case but should. This causes the sandbox mode to be reported incorrectly in `/status` during the first run (it's displayed as `read-only` even though it acts as though it's `workspace-write`). --- codex-rs/tui/src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index dc29030434..862f93eacf 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -114,7 +114,6 @@ mod wrapping; #[cfg(test)] pub mod test_backend; -use crate::onboarding::TrustDirectorySelection; use crate::onboarding::onboarding_screen::OnboardingScreenArgs; use crate::onboarding::onboarding_screen::run_onboarding_app; use crate::tui::Tui; @@ -494,12 +493,9 @@ async fn run_ratatui_app( exit_reason: ExitReason::UserRequested, }); } - // if the user acknowledged windows or made an explicit decision ato trust the directory, reload the config accordingly - if onboarding_result - .directory_trust_decision - .map(|d| d == TrustDirectorySelection::Trust) - .unwrap_or(false) - { + // If the user made an explicit trust decision, reload config so current + // process state reflects what was persisted to config.toml. + if onboarding_result.directory_trust_decision.is_some() { load_config_or_exit( cli_kv_overrides.clone(), overrides.clone(), From 97ff0901047dfba97f4b6c2d2a42a598782db1e1 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 2 Feb 2026 18:29:20 -0800 Subject: [PATCH 2/9] Hide short worked-for label in final separator (#10452) - Hide the "Worked for" label in the final message separator unless elapsed time is over one minute.\n- Update/add tests to cover both hidden (<60s) and shown (>=61s) behavior. --- codex-rs/tui/src/history_cell.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index a91b62a754..49ccc481bf 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -2147,6 +2147,7 @@ impl HistoryCell for FinalMessageSeparator { let mut label_parts = Vec::new(); if let Some(elapsed_seconds) = self .elapsed_seconds + .filter(|seconds| *seconds > 60) .map(super::status_indicator_widget::fmt_elapsed_compact) { label_parts.push(format!("Worked for {elapsed_seconds}")); @@ -2358,7 +2359,7 @@ mod tests { } #[test] - fn final_message_separator_includes_runtime_metrics() { + fn final_message_separator_hides_short_worked_label_and_includes_runtime_metrics() { let summary = RuntimeMetricsSummary { tool_calls: RuntimeMetricTotals { count: 3, @@ -2385,7 +2386,7 @@ mod tests { let rendered = render_lines(&cell.display_lines(200)); assert_eq!(rendered.len(), 1); - assert!(rendered[0].contains("Worked for 12s")); + assert!(!rendered[0].contains("Worked for")); assert!(rendered[0].contains("Local tools: 3 calls (2.5s)")); assert!(rendered[0].contains("Inference: 2 calls (1.2s)")); assert!(rendered[0].contains("WebSocket: 1 events send (700ms)")); @@ -2393,6 +2394,15 @@ mod tests { assert!(rendered[0].contains("4 events received (1.2s)")); } + #[test] + fn final_message_separator_includes_worked_label_after_one_minute() { + let cell = FinalMessageSeparator::new(Some(61), None); + let rendered = render_lines(&cell.display_lines(200)); + + assert_eq!(rendered.len(), 1); + assert!(rendered[0].contains("Worked for")); + } + #[test] fn ps_output_empty_snapshot() { let cell = new_unified_exec_processes_output(Vec::new()); From 891ed87409249441fac5af20f9a2ec1fe767b38d Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Mon, 2 Feb 2026 18:33:16 -0800 Subject: [PATCH 3/9] chore: remove deprecated mcp-types crate (#10357) https://github.com/openai/codex/pull/10349 migrated us off of `mcp-types`, so this PR deletes the code. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10357). * __->__ #10357 * #10349 * #10356 --- .github/workflows/rust-ci.yml | 2 - codex-rs/Cargo.lock | 10 - codex-rs/Cargo.toml | 1 - codex-rs/mcp-types/BUILD.bazel | 6 - codex-rs/mcp-types/Cargo.toml | 14 - codex-rs/mcp-types/README.md | 8 - codex-rs/mcp-types/check_lib_rs.py | 21 - codex-rs/mcp-types/generate_mcp_types.py | 780 ----- .../mcp-types/schema/2025-03-26/schema.json | 2138 -------------- .../mcp-types/schema/2025-06-18/schema.json | 2516 ----------------- codex-rs/mcp-types/src/lib.rs | 1714 ----------- codex-rs/mcp-types/tests/all.rs | 3 - codex-rs/mcp-types/tests/suite/initialize.rs | 70 - codex-rs/mcp-types/tests/suite/mod.rs | 3 - .../tests/suite/progress_notification.rs | 43 - 15 files changed, 7329 deletions(-) delete mode 100644 codex-rs/mcp-types/BUILD.bazel delete mode 100644 codex-rs/mcp-types/Cargo.toml delete mode 100644 codex-rs/mcp-types/README.md delete mode 100755 codex-rs/mcp-types/check_lib_rs.py delete mode 100755 codex-rs/mcp-types/generate_mcp_types.py delete mode 100644 codex-rs/mcp-types/schema/2025-03-26/schema.json delete mode 100644 codex-rs/mcp-types/schema/2025-06-18/schema.json delete mode 100644 codex-rs/mcp-types/src/lib.rs delete mode 100644 codex-rs/mcp-types/tests/all.rs delete mode 100644 codex-rs/mcp-types/tests/suite/initialize.rs delete mode 100644 codex-rs/mcp-types/tests/suite/mod.rs delete mode 100644 codex-rs/mcp-types/tests/suite/progress_notification.rs diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 7a2ef1f2df..ec6319f8da 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -64,8 +64,6 @@ jobs: components: rustfmt - name: cargo fmt run: cargo fmt -- --config imports_granularity=Item --check - - name: Verify codegen for mcp-types - run: ./mcp-types/check_lib_rs.py cargo_shear: name: cargo shear diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index bc8cac244c..992a710a12 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -4649,16 +4649,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3eede3bdf92f3b4f9dc04072a9ce5ab557d5ec9038773bf9ffcd5588b3cc05b" -[[package]] -name = "mcp-types" -version = "0.0.0" -dependencies = [ - "schemars 0.8.22", - "serde", - "serde_json", - "ts-rs", -] - [[package]] name = "mcp_test_support" version = "0.0.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index a452e98e7c..2ea3624d38 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -27,7 +27,6 @@ members = [ "lmstudio", "login", "mcp-server", - "mcp-types", "network-proxy", "ollama", "process-hardening", diff --git a/codex-rs/mcp-types/BUILD.bazel b/codex-rs/mcp-types/BUILD.bazel deleted file mode 100644 index 6286bda4d2..0000000000 --- a/codex-rs/mcp-types/BUILD.bazel +++ /dev/null @@ -1,6 +0,0 @@ -load("//:defs.bzl", "codex_rust_crate") - -codex_rust_crate( - name = "mcp-types", - crate_name = "mcp_types", -) diff --git a/codex-rs/mcp-types/Cargo.toml b/codex-rs/mcp-types/Cargo.toml deleted file mode 100644 index 92cf539611..0000000000 --- a/codex-rs/mcp-types/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "mcp-types" -version.workspace = true -edition.workspace = true -license.workspace = true - -[lints] -workspace = true - -[dependencies] -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -ts-rs = { workspace = true, features = ["serde-json-impl", "no-serde-warnings"] } -schemars = { workspace = true } diff --git a/codex-rs/mcp-types/README.md b/codex-rs/mcp-types/README.md deleted file mode 100644 index 66ea540cc4..0000000000 --- a/codex-rs/mcp-types/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# mcp-types - -Types for Model Context Protocol. Inspired by https://crates.io/crates/lsp-types. - -As documented on https://modelcontextprotocol.io/specification/2025-06-18/basic: - -- TypeScript schema is the source of truth: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-06-18/schema.ts -- JSON schema is amenable to automated tooling: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-06-18/schema.json diff --git a/codex-rs/mcp-types/check_lib_rs.py b/codex-rs/mcp-types/check_lib_rs.py deleted file mode 100755 index 37b623a260..0000000000 --- a/codex-rs/mcp-types/check_lib_rs.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess -import sys -from pathlib import Path - - -def main() -> int: - crate_dir = Path(__file__).resolve().parent - generator = crate_dir / "generate_mcp_types.py" - - result = subprocess.run( - [sys.executable, str(generator), "--check"], - cwd=crate_dir, - check=False, - ) - return result.returncode - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/codex-rs/mcp-types/generate_mcp_types.py b/codex-rs/mcp-types/generate_mcp_types.py deleted file mode 100755 index 5aaac81cd7..0000000000 --- a/codex-rs/mcp-types/generate_mcp_types.py +++ /dev/null @@ -1,780 +0,0 @@ -#!/usr/bin/env python3 -# flake8: noqa: E501 - -import argparse -import json -import subprocess -import sys -import tempfile - -from dataclasses import ( - dataclass, -) -from difflib import unified_diff -from pathlib import Path -from shutil import copy2 - -# Helper first so it is defined when other functions call it. -from typing import Any, Literal - - -SCHEMA_VERSION = "2025-06-18" -JSONRPC_VERSION = "2.0" - -STANDARD_DERIVE = "#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]\n" -STANDARD_HASHABLE_DERIVE = ( - "#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, JsonSchema, TS)]\n" -) - -# Will be populated with the schema's `definitions` map in `main()` so that -# helper functions (for example `define_any_of`) can perform look-ups while -# generating code. -DEFINITIONS: dict[str, Any] = {} -# Names of the concrete *Request types that make up the ClientRequest enum. -CLIENT_REQUEST_TYPE_NAMES: list[str] = [] -# Concrete *Notification types that make up the ServerNotification enum. -SERVER_NOTIFICATION_TYPE_NAMES: list[str] = [] -# Enum types that will need a `allow(clippy::large_enum_variant)` annotation in -# order to compile without warnings. -LARGE_ENUMS = {"ServerResult"} - -# some types need setting a default value for `r#type` -# ref: [#7417](https://github.com/openai/codex/pull/7417) -default_type_values: dict[str, str] = { - "ToolInputSchema": "object", - "ToolOutputSchema": "object", -} - - -def main() -> int: - parser = argparse.ArgumentParser( - description="Embed, cluster and analyse text prompts via the OpenAI API.", - ) - - default_schema_file = ( - Path(__file__).resolve().parent / "schema" / SCHEMA_VERSION / "schema.json" - ) - default_lib_rs = Path(__file__).resolve().parent / "src/lib.rs" - parser.add_argument( - "schema_file", - nargs="?", - default=default_schema_file, - help="schema.json file to process", - ) - parser.add_argument( - "--check", - action="store_true", - help="Regenerate lib.rs in a sandbox and ensure the checked-in file matches", - ) - args = parser.parse_args() - schema_file = Path(args.schema_file) - crate_dir = Path(__file__).resolve().parent - - if args.check: - return run_check(schema_file, crate_dir, default_lib_rs) - - generate_lib_rs(schema_file, default_lib_rs, fmt=True) - return 0 - - -def generate_lib_rs(schema_file: Path, lib_rs: Path, fmt: bool) -> None: - lib_rs.parent.mkdir(parents=True, exist_ok=True) - - global DEFINITIONS # Allow helper functions to access the schema. - - with schema_file.open(encoding="utf-8") as f: - schema_json = json.load(f) - - DEFINITIONS = schema_json["definitions"] - - out = [ - f""" -// @generated -// DO NOT EDIT THIS FILE DIRECTLY. -// Run the following in the crate root to regenerate this file: -// -// ```shell -// ./generate_mcp_types.py -// ``` -use serde::Deserialize; -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}"; -pub const JSONRPC_VERSION: &str = "{JSONRPC_VERSION}"; - -/// Paired request/response types for the Model Context Protocol (MCP). -pub trait ModelContextProtocolRequest {{ - const METHOD: &'static str; - type Params: DeserializeOwned + Serialize + Send + Sync + 'static; - type Result: DeserializeOwned + Serialize + Send + Sync + 'static; -}} - -/// One-way message in the Model Context Protocol (MCP). -pub trait ModelContextProtocolNotification {{ - const METHOD: &'static str; - type Params: DeserializeOwned + Serialize + Send + Sync + 'static; -}} - -fn default_jsonrpc() -> String {{ JSONRPC_VERSION.to_owned() }} - -""" - ] - definitions = schema_json["definitions"] - # Keep track of every *Request type so we can generate the TryFrom impl at - # the end. - # The concrete *Request types referenced by the ClientRequest enum will be - # captured dynamically while we are processing that definition. - for name, definition in definitions.items(): - add_definition(name, definition, out) - # No-op: list collected via define_any_of("ClientRequest"). - - # Generate TryFrom impl string and append to out before writing to file. - try_from_impl_lines: list[str] = [] - try_from_impl_lines.append("impl TryFrom for ClientRequest {\n") - try_from_impl_lines.append(" type Error = serde_json::Error;\n") - try_from_impl_lines.append( - " fn try_from(req: JSONRPCRequest) -> std::result::Result {\n" - ) - try_from_impl_lines.append(" match req.method.as_str() {\n") - - for req_name in CLIENT_REQUEST_TYPE_NAMES: - defn = definitions[req_name] - method_const = defn.get("properties", {}).get("method", {}).get("const", req_name) - payload_type = f"<{req_name} as ModelContextProtocolRequest>::Params" - try_from_impl_lines.append(f' "{method_const}" => {{\n') - try_from_impl_lines.append( - " let params_json = req.params.unwrap_or(serde_json::Value::Null);\n" - ) - try_from_impl_lines.append( - f" let params: {payload_type} = serde_json::from_value(params_json)?;\n" - ) - try_from_impl_lines.append(f" Ok(ClientRequest::{req_name}(params))\n") - try_from_impl_lines.append(" },\n") - - try_from_impl_lines.append( - ' _ => Err(serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Unknown method: {}", req.method)))),\n' - ) - try_from_impl_lines.append(" }\n") - try_from_impl_lines.append(" }\n") - try_from_impl_lines.append("}\n\n") - - out.extend(try_from_impl_lines) - - # Generate TryFrom for ServerNotification - notif_impl_lines: list[str] = [] - notif_impl_lines.append("impl TryFrom for ServerNotification {\n") - notif_impl_lines.append(" type Error = serde_json::Error;\n") - notif_impl_lines.append( - " fn try_from(n: JSONRPCNotification) -> std::result::Result {\n" - ) - notif_impl_lines.append(" match n.method.as_str() {\n") - - for notif_name in SERVER_NOTIFICATION_TYPE_NAMES: - n_def = definitions[notif_name] - method_const = n_def.get("properties", {}).get("method", {}).get("const", notif_name) - payload_type = f"<{notif_name} as ModelContextProtocolNotification>::Params" - notif_impl_lines.append(f' "{method_const}" => {{\n') - # params may be optional - notif_impl_lines.append( - " let params_json = n.params.unwrap_or(serde_json::Value::Null);\n" - ) - notif_impl_lines.append( - f" let params: {payload_type} = serde_json::from_value(params_json)?;\n" - ) - notif_impl_lines.append(f" Ok(ServerNotification::{notif_name}(params))\n") - notif_impl_lines.append(" },\n") - - notif_impl_lines.append( - ' _ => Err(serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Unknown method: {}", n.method)))),\n' - ) - notif_impl_lines.append(" }\n") - notif_impl_lines.append(" }\n") - notif_impl_lines.append("}\n") - - out.extend(notif_impl_lines) - - with open(lib_rs, "w", encoding="utf-8") as f: - for chunk in out: - f.write(chunk) - - if fmt: - subprocess.check_call( - ["cargo", "fmt", "--", "--config", "imports_granularity=Item"], - cwd=lib_rs.parent.parent, - stderr=subprocess.DEVNULL, - ) - - -def run_check(schema_file: Path, crate_dir: Path, checked_in_lib: Path) -> int: - config_path = crate_dir.parent / "rustfmt.toml" - eprint(f"Running --check with schema {schema_file}") - - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_path = Path(tmp_dir) - eprint(f"Created temporary workspace at {tmp_path}") - manifest_path = tmp_path / "Cargo.toml" - eprint(f"Copying Cargo.toml into {manifest_path}") - copy2(crate_dir / "Cargo.toml", manifest_path) - manifest_text = manifest_path.read_text(encoding="utf-8") - manifest_text = manifest_text.replace( - "version = { workspace = true }", - 'version = "0.0.0"', - ) - manifest_text = manifest_text.replace("\n[lints]\nworkspace = true\n", "\n") - manifest_path.write_text(manifest_text, encoding="utf-8") - src_dir = tmp_path / "src" - src_dir.mkdir(parents=True, exist_ok=True) - eprint(f"Generating lib.rs into {src_dir}") - generated_lib = src_dir / "lib.rs" - - generate_lib_rs(schema_file, generated_lib, fmt=False) - - eprint("Formatting generated lib.rs with rustfmt") - subprocess.check_call( - [ - "rustfmt", - "--config-path", - str(config_path), - str(generated_lib), - ], - cwd=tmp_path, - stderr=subprocess.DEVNULL, - ) - - eprint("Comparing generated lib.rs with checked-in version") - checked_in_contents = checked_in_lib.read_text(encoding="utf-8") - generated_contents = generated_lib.read_text(encoding="utf-8") - - if checked_in_contents == generated_contents: - eprint("lib.rs matches checked-in version") - return 0 - - diff = unified_diff( - checked_in_contents.splitlines(keepends=True), - generated_contents.splitlines(keepends=True), - fromfile=str(checked_in_lib), - tofile=str(generated_lib), - ) - diff_text = "".join(diff) - eprint("Generated lib.rs does not match the checked-in version. Diff:") - if diff_text: - eprint(diff_text, end="") - eprint("Re-run generate_mcp_types.py without --check to update src/lib.rs.") - return 1 - - -def add_definition(name: str, definition: dict[str, Any], out: list[str]) -> None: - if name == "Result": - out.append("pub type Result = serde_json::Value;\n\n") - return - - # Capture description - description = definition.get("description") - - properties = definition.get("properties", {}) - if properties: - required_props = set(definition.get("required", [])) - out.extend(define_struct(name, properties, required_props, description)) - - # Special carve-out for Result types: - if name.endswith("Result"): - out.extend(f"impl From<{name}> for serde_json::Value {{\n") - out.append(f" fn from(value: {name}) -> Self {{\n") - out.append(" // Leave this as it should never fail\n") - out.append(" #[expect(clippy::unwrap_used)]\n") - out.append(" serde_json::to_value(value).unwrap()\n") - out.append(" }\n") - out.append("}\n\n") - return - - enum_values = definition.get("enum", []) - if enum_values: - assert definition.get("type") == "string" - define_string_enum(name, enum_values, out, description) - return - - any_of = definition.get("anyOf", []) - if any_of: - assert isinstance(any_of, list) - out.extend(define_any_of(name, any_of, description)) - return - - type_prop = definition.get("type", None) - if type_prop: - if type_prop == "string": - # Newtype pattern - out.append(STANDARD_DERIVE) - out.append(f"pub struct {name}(String);\n\n") - return - elif types := check_string_list(type_prop): - define_untagged_enum(name, types, out) - return - elif type_prop == "array": - item_name = name + "Item" - out.extend(define_any_of(item_name, definition["items"]["anyOf"])) - out.append(f"pub type {name} = Vec<{item_name}>;\n\n") - return - raise ValueError(f"Unknown type: {type_prop} in {name}") - - ref_prop = definition.get("$ref", None) - if ref_prop: - ref = type_from_ref(ref_prop) - out.extend(f"pub type {name} = {ref};\n\n") - return - - raise ValueError(f"Definition for {name} could not be processed.") - - -extra_defs = [] - - -@dataclass -class StructField: - viz: Literal["pub"] | Literal["const"] - name: str - type_name: str - serde: str | None = None - ts: str | None = None - comment: str | None = None - - def append(self, out: list[str], supports_const: bool) -> None: - if self.comment: - out.append(f" // {self.comment}\n") - if self.serde: - out.append(f" {self.serde}\n") - if self.ts: - out.append(f" {self.ts}\n") - if self.viz == "const": - if supports_const: - out.append(f" const {self.name}: {self.type_name};\n") - else: - out.append(f" pub {self.name}: String, // {self.type_name}\n") - else: - out.append(f" pub {self.name}: {self.type_name},\n") - - -def append_serde_attr(existing: str | None, fragment: str) -> str: - if existing is None: - return f"#[serde({fragment})]" - assert existing.startswith("#[serde(") and existing.endswith(")]"), existing - body = existing[len("#[serde(") : -2] - return f"#[serde({body}, {fragment})]" - - -def define_struct( - name: str, - properties: dict[str, Any], - required_props: set[str], - description: str | None, -) -> list[str]: - out: list[str] = [] - - type_default_fn: str | None = None - if name in default_type_values: - snake_name = to_snake_case(name) or name - type_default_fn = f"{snake_name}_type_default_str" - out.append(f"fn {type_default_fn}() -> String {{\n") - out.append(f' "{default_type_values[name]}".to_string()\n') - out.append("}\n\n") - - fields: list[StructField] = [] - for prop_name, prop in properties.items(): - if prop_name == "_meta": - # TODO? - continue - elif prop_name == "jsonrpc": - fields.append( - StructField( - "pub", - "jsonrpc", - "String", # cannot use `&'static str` because of Deserialize - '#[serde(rename = "jsonrpc", default = "default_jsonrpc")]', - ) - ) - continue - - prop_type = map_type(prop, prop_name, name) - is_optional = prop_name not in required_props - if is_optional: - prop_type = f"Option<{prop_type}>" - rs_prop = rust_prop_name(prop_name, is_optional) - - if prop_name == "type" and type_default_fn: - rs_prop.serde = append_serde_attr(rs_prop.serde, f'default = "{type_default_fn}"') - - if prop_type.startswith("&'static str"): - fields.append(StructField("const", rs_prop.name, prop_type, rs_prop.serde, rs_prop.ts)) - else: - fields.append(StructField("pub", rs_prop.name, prop_type, rs_prop.serde, rs_prop.ts)) - - # Special-case: add Codex-specific user_agent to Implementation - if name == "Implementation": - fields.append( - StructField( - "pub", - "user_agent", - "Option", - '#[serde(default, skip_serializing_if = "Option::is_none")]', - '#[ts(optional)]', - "This is an extra field that the Codex MCP server sends as part of InitializeResult.", - ) - ) - - if implements_request_trait(name): - add_trait_impl(name, "ModelContextProtocolRequest", fields, out) - elif implements_notification_trait(name): - add_trait_impl(name, "ModelContextProtocolNotification", fields, out) - else: - # Add doc comment if available. - emit_doc_comment(description, out) - out.append(STANDARD_DERIVE) - out.append(f"pub struct {name} {{\n") - for field in fields: - field.append(out, supports_const=False) - out.append("}\n\n") - - # Declare any extra structs after the main struct. - if extra_defs: - out.extend(extra_defs) - # Clear the extra structs for the next definition. - extra_defs.clear() - return out - - -def infer_result_type(request_type_name: str) -> str: - """Return the corresponding Result type name for a given *Request name.""" - if not request_type_name.endswith("Request"): - return "Result" # fallback - candidate = request_type_name[:-7] + "Result" - if candidate in DEFINITIONS: - return candidate - # Fallback to generic Result if specific one missing. - return "Result" - - -def implements_request_trait(name: str) -> bool: - return name.endswith("Request") and name not in ( - "Request", - "JSONRPCRequest", - "PaginatedRequest", - ) - - -def implements_notification_trait(name: str) -> bool: - return name.endswith("Notification") and name not in ( - "Notification", - "JSONRPCNotification", - ) - - -def add_trait_impl( - type_name: str, trait_name: str, fields: list[StructField], out: list[str] -) -> None: - out.append(STANDARD_DERIVE) - out.append(f"pub enum {type_name} {{}}\n\n") - - out.append(f"impl {trait_name} for {type_name} {{\n") - for field in fields: - if field.name == "method": - field.name = "METHOD" - field.append(out, supports_const=True) - elif field.name == "params": - out.append(f" type Params = {field.type_name};\n") - else: - print(f"Warning: {type_name} has unexpected field {field.name}.") - if trait_name == "ModelContextProtocolRequest": - result_type = infer_result_type(type_name) - out.append(f" type Result = {result_type};\n") - out.append("}\n\n") - - -def define_string_enum( - name: str, enum_values: Any, out: list[str], description: str | None -) -> None: - emit_doc_comment(description, out) - out.append(STANDARD_DERIVE) - out.append(f"pub enum {name} {{\n") - for value in enum_values: - assert isinstance(value, str) - out.append(f' #[serde(rename = "{value}")]\n') - out.append(f" {capitalize(value)},\n") - - out.append("}\n\n") - - -def define_untagged_enum(name: str, type_list: list[str], out: list[str]) -> None: - out.append(STANDARD_HASHABLE_DERIVE) - out.append("#[serde(untagged)]\n") - out.append(f"pub enum {name} {{\n") - for simple_type in type_list: - match simple_type: - case "string": - out.append(" String(String),\n") - case "integer": - out.append(" Integer(i64),\n") - case _: - raise ValueError(f"Unknown type in untagged enum: {simple_type} in {name}") - out.append("}\n\n") - - -def define_any_of(name: str, list_of_refs: list[Any], description: str | None = None) -> list[str]: - """Generate a Rust enum for a JSON-Schema `anyOf` union. - - For most types we simply map each `$ref` inside the `anyOf` list to a - similarly named enum variant that holds the referenced type as its - payload. For certain well-known composite types (currently only - `ClientRequest`) we need a little bit of extra intelligence: - - * The JSON shape of a request is `{ "method": , "params": }`. - * We want to deserialize directly into `ClientRequest` using Serde's - `#[serde(tag = "method", content = "params")]` representation so that - the enum payload is **only** the request's `params` object. - * Therefore each enum variant needs to carry the dedicated `…Params` type - (wrapped in `Option<…>` if the `params` field is not required), not the - full `…Request` struct from the schema definition. - """ - - # Verify each item in list_of_refs is a dict with a $ref key. - refs = [item["$ref"] for item in list_of_refs if isinstance(item, dict)] - - out: list[str] = [] - if description: - emit_doc_comment(description, out) - out.append(STANDARD_DERIVE) - - if serde := get_serde_annotation_for_anyof_type(name): - out.append(serde + "\n") - - if name in LARGE_ENUMS: - out.append("#[allow(clippy::large_enum_variant)]\n") - out.append(f"pub enum {name} {{\n") - - if name == "ClientRequest": - # Record the set of request type names so we can later generate a - # `TryFrom` implementation. - global CLIENT_REQUEST_TYPE_NAMES - CLIENT_REQUEST_TYPE_NAMES = [type_from_ref(r) for r in refs] - - if name == "ServerNotification": - global SERVER_NOTIFICATION_TYPE_NAMES - SERVER_NOTIFICATION_TYPE_NAMES = [type_from_ref(r) for r in refs] - - for ref in refs: - ref_name = type_from_ref(ref) - - # For JSONRPCMessage variants, drop the common "JSONRPC" prefix to - # make the enum easier to read (e.g. `Request` instead of - # `JSONRPCRequest`). The payload type remains unchanged. - variant_name = ( - ref_name[len("JSONRPC") :] - if name == "JSONRPCMessage" and ref_name.startswith("JSONRPC") - else ref_name - ) - - # Special-case for `ClientRequest` and `ServerNotification` so the enum - # variant's payload is the *Params type rather than the full *Request / - # *Notification marker type. - if name in ("ClientRequest", "ServerNotification"): - # Rely on the trait implementation to tell us the exact Rust type - # of the `params` payload. This guarantees we stay in sync with any - # special-case logic used elsewhere (e.g. objects with - # `additionalProperties` mapping to `serde_json::Value`). - if name == "ClientRequest": - payload_type = f"<{ref_name} as ModelContextProtocolRequest>::Params" - else: - payload_type = f"<{ref_name} as ModelContextProtocolNotification>::Params" - - # Determine the wire value for `method` so we can annotate the - # variant appropriately. If for some reason the schema does not - # specify a constant we fall back to the type name, which will at - # least compile (although deserialization will likely fail). - request_def = DEFINITIONS.get(ref_name, {}) - method_const = ( - request_def.get("properties", {}).get("method", {}).get("const", ref_name) - ) - - out.append(f' #[serde(rename = "{method_const}")]\n') - out.append(f" {variant_name}({payload_type}),\n") - else: - # The regular/straight-forward case. - out.append(f" {variant_name}({ref_name}),\n") - - out.append("}\n\n") - return out - - -def get_serde_annotation_for_anyof_type(type_name: str) -> str | None: - # TODO: Solve this in a more generic way. - match type_name: - case "ClientRequest": - return '#[serde(tag = "method", content = "params")]' - case "ServerNotification": - return '#[serde(tag = "method", content = "params")]' - case _: - return "#[serde(untagged)]" - - -def map_type( - typedef: dict[str, Any], - prop_name: str | None = None, - struct_name: str | None = None, -) -> str: - """typedef must have a `type` key, but may also have an `items`key.""" - ref_prop = typedef.get("$ref", None) - if ref_prop: - return type_from_ref(ref_prop) - - any_of = typedef.get("anyOf", None) - if any_of: - assert prop_name is not None - assert struct_name is not None - custom_type = struct_name + capitalize(prop_name) - extra_defs.extend(define_any_of(custom_type, any_of)) - return custom_type - - type_prop = typedef.get("type", None) - if type_prop is None: - # Likely `unknown` in TypeScript, like the JSONRPCError.data property. - return "serde_json::Value" - - if type_prop == "string": - if const_prop := typedef.get("const", None): - assert isinstance(const_prop, str) - return f'&\'static str = "{const_prop}"' - else: - return "String" - elif type_prop == "integer": - return "i64" - elif type_prop == "number": - return "f64" - elif type_prop == "boolean": - return "bool" - elif type_prop == "array": - item_type = typedef.get("items", None) - if item_type: - item_type = map_type(item_type, prop_name, struct_name) - assert isinstance(item_type, str) - return f"Vec<{item_type}>" - else: - raise ValueError("Array type without items.") - elif type_prop == "object": - # If the schema says `additionalProperties: {}` this is effectively an - # open-ended map, so deserialize into `serde_json::Value` for maximum - # flexibility. - if typedef.get("additionalProperties") is not None: - return "serde_json::Value" - - # If there are *no* properties declared treat it similarly. - if not typedef.get("properties"): - return "serde_json::Value" - - # Otherwise, synthesize a nested struct for the inline object. - assert prop_name is not None - assert struct_name is not None - custom_type = struct_name + capitalize(prop_name) - extra_defs.extend( - define_struct( - custom_type, - typedef["properties"], - set(typedef.get("required", [])), - typedef.get("description"), - ) - ) - return custom_type - else: - raise ValueError(f"Unknown type: {type_prop} in {typedef}") - - -@dataclass -class RustProp: - name: str - # serde annotation, if necessary - serde: str | None = None - # ts annotation, if necessary - ts: str | None = None - -def rust_prop_name(name: str, is_optional: bool) -> RustProp: - """Convert a JSON property name to a Rust property name.""" - prop_name: str - is_rename = False - if name == "type": - prop_name = "r#type" - elif name == "ref": - prop_name = "r#ref" - elif name == "enum": - prop_name = "r#enum" - elif snake_case := to_snake_case(name): - prop_name = snake_case - is_rename = True - else: - prop_name = name - - serde_annotations = [] - ts_str = None - if is_rename: - serde_annotations.append(f'rename = "{name}"') - if is_optional: - serde_annotations.append("default") - serde_annotations.append('skip_serializing_if = "Option::is_none"') - - if serde_annotations: - # Also mark optional fields for ts-rs generation. - serde_str = f"#[serde({', '.join(serde_annotations)})]" - else: - serde_str = None - - if is_optional and serde_str: - ts_str = "#[ts(optional)]" - - return RustProp(prop_name, serde_str, ts_str) - - -def to_snake_case(name: str) -> str | None: - """Convert a camelCase or PascalCase name to snake_case.""" - snake_case = name[0].lower() + "".join("_" + c.lower() if c.isupper() else c for c in name[1:]) - if snake_case != name: - return snake_case - else: - return None - - -def capitalize(name: str) -> str: - """Capitalize the first letter of a name.""" - return name[0].upper() + name[1:] - - -def check_string_list(value: Any) -> list[str] | None: - """If the value is a list of strings, return it. Otherwise, return None.""" - if not isinstance(value, list): - return None - for item in value: - if not isinstance(item, str): - return None - return value - - -def type_from_ref(ref: str) -> str: - """Convert a JSON reference to a Rust type.""" - assert ref.startswith("#/definitions/") - return ref.split("/")[-1] - - -def emit_doc_comment(text: str | None, out: list[str]) -> None: - """Append Rust doc comments derived from the JSON-schema description.""" - if not text: - return - for line in text.strip().split("\n"): - out.append(f"/// {line.rstrip()}\n") - - -def eprint(*args: Any, **kwargs: Any) -> None: - print(*args, file=sys.stderr, **kwargs) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/codex-rs/mcp-types/schema/2025-03-26/schema.json b/codex-rs/mcp-types/schema/2025-03-26/schema.json deleted file mode 100644 index 328ff95f4b..0000000000 --- a/codex-rs/mcp-types/schema/2025-03-26/schema.json +++ /dev/null @@ -1,2138 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "description": "Describes who the intended customer of this object or data is.\n\nIt can include multiple entries to indicate content useful for multiple audiences (e.g., `[\"user\", \"assistant\"]`).", - "items": { - "$ref": "#/definitions/Role" - }, - "type": "array" - }, - "priority": { - "description": "Describes how important this data is for operating the server.\n\nA value of 1 means \"most important,\" and indicates that the data is\neffectively required, while 0 means \"least important,\" and indicates that\nthe data is entirely optional.", - "maximum": 1, - "minimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "data": { - "description": "The base64-encoded audio data.", - "format": "byte", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of the audio. Different providers may support different audio types.", - "type": "string" - }, - "type": { - "const": "audio", - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "blob": { - "description": "A base64-encoded string representing the binary data of the item.", - "format": "byte", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, - "CallToolRequest": { - "description": "Used by the client to invoke a tool provided by the server.", - "properties": { - "method": { - "const": "tools/call", - "type": "string" - }, - "params": { - "properties": { - "arguments": { - "additionalProperties": {}, - "type": "object" - }, - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "CallToolResult": { - "description": "The server's response to a tool call.\n\nAny errors that originate from the tool SHOULD be reported inside the result\nobject, with `isError` set to true, _not_ as an MCP protocol-level error\nresponse. Otherwise, the LLM would not be able to see that an error occurred\nand self-correct.\n\nHowever, any errors in _finding_ the tool, an error indicating that the\nserver does not support tool calls, or any other exceptional conditions,\nshould be reported as an MCP error response.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "content": { - "items": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "type": "array" - }, - "isError": { - "description": "Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).", - "type": "boolean" - } - }, - "required": [ - "content" - ], - "type": "object" - }, - "CancelledNotification": { - "description": "This notification can be sent by either side to indicate that it is cancelling a previously-issued request.\n\nThe request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.\n\nThis notification indicates that the result will be unused, so any associated processing SHOULD cease.\n\nA client MUST NOT attempt to cancel its `initialize` request.", - "properties": { - "method": { - "const": "notifications/cancelled", - "type": "string" - }, - "params": { - "properties": { - "reason": { - "description": "An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.", - "type": "string" - }, - "requestId": { - "$ref": "#/definitions/RequestId", - "description": "The ID of the request to cancel.\n\nThis MUST correspond to the ID of a request previously issued in the same direction." - } - }, - "required": [ - "requestId" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ClientCapabilities": { - "description": "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.", - "properties": { - "experimental": { - "additionalProperties": { - "additionalProperties": true, - "properties": {}, - "type": "object" - }, - "description": "Experimental, non-standard capabilities that the client supports.", - "type": "object" - }, - "roots": { - "description": "Present if the client supports listing roots.", - "properties": { - "listChanged": { - "description": "Whether the client supports notifications for changes to the roots list.", - "type": "boolean" - } - }, - "type": "object" - }, - "sampling": { - "additionalProperties": true, - "description": "Present if the client supports sampling from an LLM.", - "properties": {}, - "type": "object" - } - }, - "type": "object" - }, - "ClientNotification": { - "anyOf": [ - { - "$ref": "#/definitions/CancelledNotification" - }, - { - "$ref": "#/definitions/InitializedNotification" - }, - { - "$ref": "#/definitions/ProgressNotification" - }, - { - "$ref": "#/definitions/RootsListChangedNotification" - } - ] - }, - "ClientRequest": { - "anyOf": [ - { - "$ref": "#/definitions/InitializeRequest" - }, - { - "$ref": "#/definitions/PingRequest" - }, - { - "$ref": "#/definitions/ListResourcesRequest" - }, - { - "$ref": "#/definitions/ListResourceTemplatesRequest" - }, - { - "$ref": "#/definitions/ReadResourceRequest" - }, - { - "$ref": "#/definitions/SubscribeRequest" - }, - { - "$ref": "#/definitions/UnsubscribeRequest" - }, - { - "$ref": "#/definitions/ListPromptsRequest" - }, - { - "$ref": "#/definitions/GetPromptRequest" - }, - { - "$ref": "#/definitions/ListToolsRequest" - }, - { - "$ref": "#/definitions/CallToolRequest" - }, - { - "$ref": "#/definitions/SetLevelRequest" - }, - { - "$ref": "#/definitions/CompleteRequest" - } - ] - }, - "ClientResult": { - "anyOf": [ - { - "$ref": "#/definitions/Result" - }, - { - "$ref": "#/definitions/CreateMessageResult" - }, - { - "$ref": "#/definitions/ListRootsResult" - } - ] - }, - "CompleteRequest": { - "description": "A request from the client to the server, to ask for completion options.", - "properties": { - "method": { - "const": "completion/complete", - "type": "string" - }, - "params": { - "properties": { - "argument": { - "description": "The argument's information", - "properties": { - "name": { - "description": "The name of the argument", - "type": "string" - }, - "value": { - "description": "The value of the argument to use for completion matching.", - "type": "string" - } - }, - "required": [ - "name", - "value" - ], - "type": "object" - }, - "ref": { - "anyOf": [ - { - "$ref": "#/definitions/PromptReference" - }, - { - "$ref": "#/definitions/ResourceReference" - } - ] - } - }, - "required": [ - "argument", - "ref" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "CompleteResult": { - "description": "The server's response to a completion/complete request", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "completion": { - "properties": { - "hasMore": { - "description": "Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.", - "type": "boolean" - }, - "total": { - "description": "The total number of completion options available. This can exceed the number of values actually sent in the response.", - "type": "integer" - }, - "values": { - "description": "An array of completion values. Must not exceed 100 items.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "values" - ], - "type": "object" - } - }, - "required": [ - "completion" - ], - "type": "object" - }, - "CreateMessageRequest": { - "description": "A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.", - "properties": { - "method": { - "const": "sampling/createMessage", - "type": "string" - }, - "params": { - "properties": { - "includeContext": { - "description": "A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request.", - "enum": [ - "allServers", - "none", - "thisServer" - ], - "type": "string" - }, - "maxTokens": { - "description": "The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested.", - "type": "integer" - }, - "messages": { - "items": { - "$ref": "#/definitions/SamplingMessage" - }, - "type": "array" - }, - "metadata": { - "additionalProperties": true, - "description": "Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.", - "properties": {}, - "type": "object" - }, - "modelPreferences": { - "$ref": "#/definitions/ModelPreferences", - "description": "The server's preferences for which model to select. The client MAY ignore these preferences." - }, - "stopSequences": { - "items": { - "type": "string" - }, - "type": "array" - }, - "systemPrompt": { - "description": "An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt.", - "type": "string" - }, - "temperature": { - "type": "number" - } - }, - "required": [ - "maxTokens", - "messages" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "CreateMessageResult": { - "description": "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.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "content": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - } - ] - }, - "model": { - "description": "The name of the model that generated the message.", - "type": "string" - }, - "role": { - "$ref": "#/definitions/Role" - }, - "stopReason": { - "description": "The reason why sampling stopped, if known.", - "type": "string" - } - }, - "required": [ - "content", - "model", - "role" - ], - "type": "object" - }, - "Cursor": { - "description": "An opaque token used to represent a cursor for pagination.", - "type": "string" - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit\nof the LLM and/or the user.", - "properties": { - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "resource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, - "type": { - "const": "resource", - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmptyResult": { - "$ref": "#/definitions/Result" - }, - "GetPromptRequest": { - "description": "Used by the client to get a prompt provided by the server.", - "properties": { - "method": { - "const": "prompts/get", - "type": "string" - }, - "params": { - "properties": { - "arguments": { - "additionalProperties": { - "type": "string" - }, - "description": "Arguments to use for templating the prompt.", - "type": "object" - }, - "name": { - "description": "The name of the prompt or prompt template.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "GetPromptResult": { - "description": "The server's response to a prompts/get request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "description": { - "description": "An optional description for the prompt.", - "type": "string" - }, - "messages": { - "items": { - "$ref": "#/definitions/PromptMessage" - }, - "type": "array" - } - }, - "required": [ - "messages" - ], - "type": "object" - }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "data": { - "description": "The base64-encoded image data.", - "format": "byte", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of the image. Different providers may support different image types.", - "type": "string" - }, - "type": { - "const": "image", - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "Implementation": { - "description": "Describes the name and version of an MCP implementation.", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "name", - "version" - ], - "type": "object" - }, - "InitializeRequest": { - "description": "This request is sent from the client to the server when it first connects, asking it to begin initialization.", - "properties": { - "method": { - "const": "initialize", - "type": "string" - }, - "params": { - "properties": { - "capabilities": { - "$ref": "#/definitions/ClientCapabilities" - }, - "clientInfo": { - "$ref": "#/definitions/Implementation" - }, - "protocolVersion": { - "description": "The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well.", - "type": "string" - } - }, - "required": [ - "capabilities", - "clientInfo", - "protocolVersion" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "InitializeResult": { - "description": "After receiving an initialize request from the client, the server sends this response.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "capabilities": { - "$ref": "#/definitions/ServerCapabilities" - }, - "instructions": { - "description": "Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a \"hint\" to the model. For example, this information MAY be added to the system prompt.", - "type": "string" - }, - "protocolVersion": { - "description": "The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect.", - "type": "string" - }, - "serverInfo": { - "$ref": "#/definitions/Implementation" - } - }, - "required": [ - "capabilities", - "protocolVersion", - "serverInfo" - ], - "type": "object" - }, - "InitializedNotification": { - "description": "This notification is sent from the client to the server after initialization has finished.", - "properties": { - "method": { - "const": "notifications/initialized", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "JSONRPCBatchRequest": { - "description": "A JSON-RPC batch request, as described in https://www.jsonrpc.org/specification#batch.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/JSONRPCRequest" - }, - { - "$ref": "#/definitions/JSONRPCNotification" - } - ] - }, - "type": "array" - }, - "JSONRPCBatchResponse": { - "description": "A JSON-RPC batch response, as described in https://www.jsonrpc.org/specification#batch.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/JSONRPCResponse" - }, - { - "$ref": "#/definitions/JSONRPCError" - } - ] - }, - "type": "array" - }, - "JSONRPCError": { - "description": "A response to a request that indicates an error occurred.", - "properties": { - "error": { - "properties": { - "code": { - "description": "The error type that occurred.", - "type": "integer" - }, - "data": { - "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." - }, - "message": { - "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", - "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "type": "object" - }, - "id": { - "$ref": "#/definitions/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - } - }, - "required": [ - "error", - "id", - "jsonrpc" - ], - "type": "object" - }, - "JSONRPCMessage": { - "anyOf": [ - { - "$ref": "#/definitions/JSONRPCRequest" - }, - { - "$ref": "#/definitions/JSONRPCNotification" - }, - { - "description": "A JSON-RPC batch request, as described in https://www.jsonrpc.org/specification#batch.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/JSONRPCRequest" - }, - { - "$ref": "#/definitions/JSONRPCNotification" - } - ] - }, - "type": "array" - }, - { - "$ref": "#/definitions/JSONRPCResponse" - }, - { - "$ref": "#/definitions/JSONRPCError" - }, - { - "description": "A JSON-RPC batch response, as described in https://www.jsonrpc.org/specification#batch.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/JSONRPCResponse" - }, - { - "$ref": "#/definitions/JSONRPCError" - } - ] - }, - "type": "array" - } - ], - "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent." - }, - "JSONRPCNotification": { - "description": "A notification which does not expect a response.", - "properties": { - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "method": { - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "jsonrpc", - "method" - ], - "type": "object" - }, - "JSONRPCRequest": { - "description": "A request that expects a response.", - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "method": { - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "properties": { - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "id", - "jsonrpc", - "method" - ], - "type": "object" - }, - "JSONRPCResponse": { - "description": "A successful (non-error) response to a request.", - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "result": { - "$ref": "#/definitions/Result" - } - }, - "required": [ - "id", - "jsonrpc", - "result" - ], - "type": "object" - }, - "ListPromptsRequest": { - "description": "Sent from the client to request a list of prompts and prompt templates the server has.", - "properties": { - "method": { - "const": "prompts/list", - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListPromptsResult": { - "description": "The server's response to a prompts/list request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - }, - "prompts": { - "items": { - "$ref": "#/definitions/Prompt" - }, - "type": "array" - } - }, - "required": [ - "prompts" - ], - "type": "object" - }, - "ListResourceTemplatesRequest": { - "description": "Sent from the client to request a list of resource templates the server has.", - "properties": { - "method": { - "const": "resources/templates/list", - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListResourceTemplatesResult": { - "description": "The server's response to a resources/templates/list request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - }, - "resourceTemplates": { - "items": { - "$ref": "#/definitions/ResourceTemplate" - }, - "type": "array" - } - }, - "required": [ - "resourceTemplates" - ], - "type": "object" - }, - "ListResourcesRequest": { - "description": "Sent from the client to request a list of resources the server has.", - "properties": { - "method": { - "const": "resources/list", - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListResourcesResult": { - "description": "The server's response to a resources/list request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - }, - "resources": { - "items": { - "$ref": "#/definitions/Resource" - }, - "type": "array" - } - }, - "required": [ - "resources" - ], - "type": "object" - }, - "ListRootsRequest": { - "description": "Sent from the server to request a list of root URIs from the client. Roots allow\nservers to ask for specific directories or files to operate on. A common example\nfor roots is providing a set of repositories or directories a server should operate\non.\n\nThis request is typically used when the server needs to understand the file system\nstructure or access specific locations that the client has permission to read from.", - "properties": { - "method": { - "const": "roots/list", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "properties": { - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListRootsResult": { - "description": "The client's response to a roots/list request from the server.\nThis result contains an array of Root objects, each representing a root directory\nor file that the server can operate on.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "roots": { - "items": { - "$ref": "#/definitions/Root" - }, - "type": "array" - } - }, - "required": [ - "roots" - ], - "type": "object" - }, - "ListToolsRequest": { - "description": "Sent from the client to request a list of tools the server has.", - "properties": { - "method": { - "const": "tools/list", - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListToolsResult": { - "description": "The server's response to a tools/list request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - }, - "tools": { - "items": { - "$ref": "#/definitions/Tool" - }, - "type": "array" - } - }, - "required": [ - "tools" - ], - "type": "object" - }, - "LoggingLevel": { - "description": "The severity of a log message.\n\nThese map to syslog message severities, as specified in RFC-5424:\nhttps://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1", - "enum": [ - "alert", - "critical", - "debug", - "emergency", - "error", - "info", - "notice", - "warning" - ], - "type": "string" - }, - "LoggingMessageNotification": { - "description": "Notification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.", - "properties": { - "method": { - "const": "notifications/message", - "type": "string" - }, - "params": { - "properties": { - "data": { - "description": "The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here." - }, - "level": { - "$ref": "#/definitions/LoggingLevel", - "description": "The severity of this log message." - }, - "logger": { - "description": "An optional name of the logger issuing this message.", - "type": "string" - } - }, - "required": [ - "data", - "level" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ModelHint": { - "description": "Hints to use for model selection.\n\nKeys not declared here are currently left unspecified by the spec and are up\nto the client to interpret.", - "properties": { - "name": { - "description": "A hint for a model name.\n\nThe client SHOULD treat this as a substring of a model name; for example:\n - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022`\n - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc.\n - `claude` should match any Claude model\n\nThe client MAY also map the string to a different provider's model name or a different model info, as long as it fills a similar niche; for example:\n - `gemini-1.5-flash` could match `claude-3-haiku-20240307`", - "type": "string" - } - }, - "type": "object" - }, - "ModelPreferences": { - "description": "The server's preferences for model selection, requested of the client during sampling.\n\nBecause LLMs can vary along multiple dimensions, choosing the \"best\" model is\nrarely straightforward. Different models excel in different areas—some are\nfaster but less capable, others are more capable but more expensive, and so\non. This interface allows servers to express their priorities across multiple\ndimensions to help clients make an appropriate selection for their use case.\n\nThese preferences are always advisory. The client MAY ignore them. It is also\nup to the client to decide how to interpret these preferences and how to\nbalance them against other considerations.", - "properties": { - "costPriority": { - "description": "How much to prioritize cost when selecting a model. A value of 0 means cost\nis not important, while a value of 1 means cost is the most important\nfactor.", - "maximum": 1, - "minimum": 0, - "type": "number" - }, - "hints": { - "description": "Optional hints to use for model selection.\n\nIf multiple hints are specified, the client MUST evaluate them in order\n(such that the first match is taken).\n\nThe client SHOULD prioritize these hints over the numeric priorities, but\nMAY still use the priorities to select from ambiguous matches.", - "items": { - "$ref": "#/definitions/ModelHint" - }, - "type": "array" - }, - "intelligencePriority": { - "description": "How much to prioritize intelligence and capabilities when selecting a\nmodel. A value of 0 means intelligence is not important, while a value of 1\nmeans intelligence is the most important factor.", - "maximum": 1, - "minimum": 0, - "type": "number" - }, - "speedPriority": { - "description": "How much to prioritize sampling speed (latency) when selecting a model. A\nvalue of 0 means speed is not important, while a value of 1 means speed is\nthe most important factor.", - "maximum": 1, - "minimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "Notification": { - "properties": { - "method": { - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "PaginatedRequest": { - "properties": { - "method": { - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "PaginatedResult": { - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - } - }, - "type": "object" - }, - "PingRequest": { - "description": "A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.", - "properties": { - "method": { - "const": "ping", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "properties": { - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ProgressNotification": { - "description": "An out-of-band notification used to inform the receiver of a progress update for a long-running request.", - "properties": { - "method": { - "const": "notifications/progress", - "type": "string" - }, - "params": { - "properties": { - "message": { - "description": "An optional message describing the current progress.", - "type": "string" - }, - "progress": { - "description": "The progress thus far. This should increase every time progress is made, even if the total is unknown.", - "type": "number" - }, - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "The progress token which was given in the initial request, used to associate this notification with the request that is proceeding." - }, - "total": { - "description": "Total number of items to process (or total progress required), if known.", - "type": "number" - } - }, - "required": [ - "progress", - "progressToken" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ProgressToken": { - "description": "A progress token, used to associate progress notifications with the original request.", - "type": [ - "string", - "integer" - ] - }, - "Prompt": { - "description": "A prompt or prompt template that the server offers.", - "properties": { - "arguments": { - "description": "A list of arguments to use for templating the prompt.", - "items": { - "$ref": "#/definitions/PromptArgument" - }, - "type": "array" - }, - "description": { - "description": "An optional description of what this prompt provides", - "type": "string" - }, - "name": { - "description": "The name of the prompt or prompt template.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "PromptArgument": { - "description": "Describes an argument that a prompt can accept.", - "properties": { - "description": { - "description": "A human-readable description of the argument.", - "type": "string" - }, - "name": { - "description": "The name of the argument.", - "type": "string" - }, - "required": { - "description": "Whether this argument must be provided.", - "type": "boolean" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "PromptListChangedNotification": { - "description": "An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.", - "properties": { - "method": { - "const": "notifications/prompts/list_changed", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "PromptMessage": { - "description": "Describes a message returned as part of a prompt.\n\nThis is similar to `SamplingMessage`, but also supports the embedding of\nresources from the MCP server.", - "properties": { - "content": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "role": { - "$ref": "#/definitions/Role" - } - }, - "required": [ - "content", - "role" - ], - "type": "object" - }, - "PromptReference": { - "description": "Identifies a prompt.", - "properties": { - "name": { - "description": "The name of the prompt or prompt template", - "type": "string" - }, - "type": { - "const": "ref/prompt", - "type": "string" - } - }, - "required": [ - "name", - "type" - ], - "type": "object" - }, - "ReadResourceRequest": { - "description": "Sent from the client to the server, to read a specific resource URI.", - "properties": { - "method": { - "const": "resources/read", - "type": "string" - }, - "params": { - "properties": { - "uri": { - "description": "The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ReadResourceResult": { - "description": "The server's response to a resources/read request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - }, - "contents": { - "items": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, - "type": "array" - } - }, - "required": [ - "contents" - ], - "type": "object" - }, - "Request": { - "properties": { - "method": { - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "properties": { - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "RequestId": { - "description": "A uniquely identifying ID for a request in JSON-RPC.", - "type": [ - "string", - "integer" - ] - }, - "Resource": { - "description": "A known resource that the server is capable of reading.", - "properties": { - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "description": { - "description": "A description of what this resource represents.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "name": { - "description": "A human-readable name for this resource.\n\nThis can be used by clients to populate UI elements.", - "type": "string" - }, - "size": { - "description": "The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.\n\nThis can be used by Hosts to display file sizes and estimate context window usage.", - "type": "integer" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "name", - "uri" - ], - "type": "object" - }, - "ResourceContents": { - "description": "The contents of a specific resource or sub-resource.", - "properties": { - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - }, - "ResourceListChangedNotification": { - "description": "An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.", - "properties": { - "method": { - "const": "notifications/resources/list_changed", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ResourceReference": { - "description": "A reference to a resource or resource template definition.", - "properties": { - "type": { - "const": "ref/resource", - "type": "string" - }, - "uri": { - "description": "The URI or URI template of the resource.", - "format": "uri-template", - "type": "string" - } - }, - "required": [ - "type", - "uri" - ], - "type": "object" - }, - "ResourceTemplate": { - "description": "A template description for resources available on the server.", - "properties": { - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "description": { - "description": "A description of what this template is for.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", - "type": "string" - }, - "mimeType": { - "description": "The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.", - "type": "string" - }, - "name": { - "description": "A human-readable name for the type of resource this template refers to.\n\nThis can be used by clients to populate UI elements.", - "type": "string" - }, - "uriTemplate": { - "description": "A URI template (according to RFC 6570) that can be used to construct resource URIs.", - "format": "uri-template", - "type": "string" - } - }, - "required": [ - "name", - "uriTemplate" - ], - "type": "object" - }, - "ResourceUpdatedNotification": { - "description": "A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.", - "properties": { - "method": { - "const": "notifications/resources/updated", - "type": "string" - }, - "params": { - "properties": { - "uri": { - "description": "The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "Result": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", - "type": "object" - } - }, - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "Root": { - "description": "Represents a root directory or file that the server can operate on.", - "properties": { - "name": { - "description": "An optional name for the root. This can be used to provide a human-readable\nidentifier for the root, which may be useful for display purposes or for\nreferencing the root in other parts of the application.", - "type": "string" - }, - "uri": { - "description": "The URI identifying the root. This *must* start with file:// for now.\nThis restriction may be relaxed in future versions of the protocol to allow\nother URI schemes.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - }, - "RootsListChangedNotification": { - "description": "A notification from the client to the server, informing it that the list of roots has changed.\nThis notification should be sent whenever the client adds, removes, or modifies any root.\nThe server should then request an updated list of roots using the ListRootsRequest.", - "properties": { - "method": { - "const": "notifications/roots/list_changed", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "SamplingMessage": { - "description": "Describes a message issued to or received from an LLM API.", - "properties": { - "content": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - } - ] - }, - "role": { - "$ref": "#/definitions/Role" - } - }, - "required": [ - "content", - "role" - ], - "type": "object" - }, - "ServerCapabilities": { - "description": "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.", - "properties": { - "completions": { - "additionalProperties": true, - "description": "Present if the server supports argument autocompletion suggestions.", - "properties": {}, - "type": "object" - }, - "experimental": { - "additionalProperties": { - "additionalProperties": true, - "properties": {}, - "type": "object" - }, - "description": "Experimental, non-standard capabilities that the server supports.", - "type": "object" - }, - "logging": { - "additionalProperties": true, - "description": "Present if the server supports sending log messages to the client.", - "properties": {}, - "type": "object" - }, - "prompts": { - "description": "Present if the server offers any prompt templates.", - "properties": { - "listChanged": { - "description": "Whether this server supports notifications for changes to the prompt list.", - "type": "boolean" - } - }, - "type": "object" - }, - "resources": { - "description": "Present if the server offers any resources to read.", - "properties": { - "listChanged": { - "description": "Whether this server supports notifications for changes to the resource list.", - "type": "boolean" - }, - "subscribe": { - "description": "Whether this server supports subscribing to resource updates.", - "type": "boolean" - } - }, - "type": "object" - }, - "tools": { - "description": "Present if the server offers any tools to call.", - "properties": { - "listChanged": { - "description": "Whether this server supports notifications for changes to the tool list.", - "type": "boolean" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "ServerNotification": { - "anyOf": [ - { - "$ref": "#/definitions/CancelledNotification" - }, - { - "$ref": "#/definitions/ProgressNotification" - }, - { - "$ref": "#/definitions/ResourceListChangedNotification" - }, - { - "$ref": "#/definitions/ResourceUpdatedNotification" - }, - { - "$ref": "#/definitions/PromptListChangedNotification" - }, - { - "$ref": "#/definitions/ToolListChangedNotification" - }, - { - "$ref": "#/definitions/LoggingMessageNotification" - } - ] - }, - "ServerRequest": { - "anyOf": [ - { - "$ref": "#/definitions/PingRequest" - }, - { - "$ref": "#/definitions/CreateMessageRequest" - }, - { - "$ref": "#/definitions/ListRootsRequest" - } - ] - }, - "ServerResult": { - "anyOf": [ - { - "$ref": "#/definitions/Result" - }, - { - "$ref": "#/definitions/InitializeResult" - }, - { - "$ref": "#/definitions/ListResourcesResult" - }, - { - "$ref": "#/definitions/ListResourceTemplatesResult" - }, - { - "$ref": "#/definitions/ReadResourceResult" - }, - { - "$ref": "#/definitions/ListPromptsResult" - }, - { - "$ref": "#/definitions/GetPromptResult" - }, - { - "$ref": "#/definitions/ListToolsResult" - }, - { - "$ref": "#/definitions/CallToolResult" - }, - { - "$ref": "#/definitions/CompleteResult" - } - ] - }, - "SetLevelRequest": { - "description": "A request from the client to the server, to enable or adjust logging.", - "properties": { - "method": { - "const": "logging/setLevel", - "type": "string" - }, - "params": { - "properties": { - "level": { - "$ref": "#/definitions/LoggingLevel", - "description": "The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message." - } - }, - "required": [ - "level" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "SubscribeRequest": { - "description": "Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.", - "properties": { - "method": { - "const": "resources/subscribe", - "type": "string" - }, - "params": { - "properties": { - "uri": { - "description": "The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "text": { - "description": "The text content of the message.", - "type": "string" - }, - "type": { - "const": "text", - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, - "TextResourceContents": { - "properties": { - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "text": { - "description": "The text of the item. This must only be set if the item can actually be represented as text (not binary data).", - "type": "string" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, - "Tool": { - "description": "Definition for a tool the client can call.", - "properties": { - "annotations": { - "$ref": "#/definitions/ToolAnnotations", - "description": "Optional additional tool information." - }, - "description": { - "description": "A human-readable description of the tool.\n\nThis can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a \"hint\" to the model.", - "type": "string" - }, - "inputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": { - "additionalProperties": { - "additionalProperties": true, - "properties": {}, - "type": "object" - }, - "type": "object" - }, - "required": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "const": "object", - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - }, - "name": { - "description": "The name of the tool.", - "type": "string" - } - }, - "required": [ - "inputSchema", - "name" - ], - "type": "object" - }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**.\nThey are not guaranteed to provide a faithful description of\ntool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations\nreceived from untrusted servers.", - "properties": { - "destructiveHint": { - "description": "If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: true", - "type": "boolean" - }, - "idempotentHint": { - "description": "If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on the its environment.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: false", - "type": "boolean" - }, - "openWorldHint": { - "description": "If true, this tool may interact with an \"open world\" of external\nentities. If false, the tool's domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true", - "type": "boolean" - }, - "readOnlyHint": { - "description": "If true, the tool does not modify its environment.\n\nDefault: false", - "type": "boolean" - }, - "title": { - "description": "A human-readable title for the tool.", - "type": "string" - } - }, - "type": "object" - }, - "ToolListChangedNotification": { - "description": "An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.", - "properties": { - "method": { - "const": "notifications/tools/list_changed", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "UnsubscribeRequest": { - "description": "Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.", - "properties": { - "method": { - "const": "resources/unsubscribe", - "type": "string" - }, - "params": { - "properties": { - "uri": { - "description": "The URI of the resource to unsubscribe from.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - } - } -} diff --git a/codex-rs/mcp-types/schema/2025-06-18/schema.json b/codex-rs/mcp-types/schema/2025-06-18/schema.json deleted file mode 100644 index d5faee82cd..0000000000 --- a/codex-rs/mcp-types/schema/2025-06-18/schema.json +++ /dev/null @@ -1,2516 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "Annotations": { - "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", - "properties": { - "audience": { - "description": "Describes who the intended customer of this object or data is.\n\nIt can include multiple entries to indicate content useful for multiple audiences (e.g., `[\"user\", \"assistant\"]`).", - "items": { - "$ref": "#/definitions/Role" - }, - "type": "array" - }, - "lastModified": { - "description": "The moment the resource was last modified, as an ISO 8601 formatted string.\n\nShould be an ISO 8601 formatted string (e.g., \"2025-01-12T15:00:58Z\").\n\nExamples: last activity timestamp in an open file, timestamp when the resource\nwas attached, etc.", - "type": "string" - }, - "priority": { - "description": "Describes how important this data is for operating the server.\n\nA value of 1 means \"most important,\" and indicates that the data is\neffectively required, while 0 means \"least important,\" and indicates that\nthe data is entirely optional.", - "maximum": 1, - "minimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "AudioContent": { - "description": "Audio provided to or from an LLM.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "data": { - "description": "The base64-encoded audio data.", - "format": "byte", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of the audio. Different providers may support different audio types.", - "type": "string" - }, - "type": { - "const": "audio", - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "BaseMetadata": { - "description": "Base interface for metadata with name (identifier) and title (display name) properties.", - "properties": { - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "BlobResourceContents": { - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "blob": { - "description": "A base64-encoded string representing the binary data of the item.", - "format": "byte", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "blob", - "uri" - ], - "type": "object" - }, - "BooleanSchema": { - "properties": { - "default": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "const": "boolean", - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - }, - "CallToolRequest": { - "description": "Used by the client to invoke a tool provided by the server.", - "properties": { - "method": { - "const": "tools/call", - "type": "string" - }, - "params": { - "properties": { - "arguments": { - "additionalProperties": {}, - "type": "object" - }, - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "CallToolResult": { - "description": "The server's response to a tool call.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "content": { - "description": "A list of content objects that represent the unstructured result of the tool call.", - "items": { - "$ref": "#/definitions/ContentBlock" - }, - "type": "array" - }, - "isError": { - "description": "Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).\n\nAny errors that originate from the tool SHOULD be reported inside the result\nobject, with `isError` set to true, _not_ as an MCP protocol-level error\nresponse. Otherwise, the LLM would not be able to see that an error occurred\nand self-correct.\n\nHowever, any errors in _finding_ the tool, an error indicating that the\nserver does not support tool calls, or any other exceptional conditions,\nshould be reported as an MCP error response.", - "type": "boolean" - }, - "structuredContent": { - "additionalProperties": {}, - "description": "An optional JSON object that represents the structured result of the tool call.", - "type": "object" - } - }, - "required": [ - "content" - ], - "type": "object" - }, - "CancelledNotification": { - "description": "This notification can be sent by either side to indicate that it is cancelling a previously-issued request.\n\nThe request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.\n\nThis notification indicates that the result will be unused, so any associated processing SHOULD cease.\n\nA client MUST NOT attempt to cancel its `initialize` request.", - "properties": { - "method": { - "const": "notifications/cancelled", - "type": "string" - }, - "params": { - "properties": { - "reason": { - "description": "An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.", - "type": "string" - }, - "requestId": { - "$ref": "#/definitions/RequestId", - "description": "The ID of the request to cancel.\n\nThis MUST correspond to the ID of a request previously issued in the same direction." - } - }, - "required": [ - "requestId" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ClientCapabilities": { - "description": "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.", - "properties": { - "elicitation": { - "additionalProperties": true, - "description": "Present if the client supports elicitation from the server.", - "properties": {}, - "type": "object" - }, - "experimental": { - "additionalProperties": { - "additionalProperties": true, - "properties": {}, - "type": "object" - }, - "description": "Experimental, non-standard capabilities that the client supports.", - "type": "object" - }, - "roots": { - "description": "Present if the client supports listing roots.", - "properties": { - "listChanged": { - "description": "Whether the client supports notifications for changes to the roots list.", - "type": "boolean" - } - }, - "type": "object" - }, - "sampling": { - "additionalProperties": true, - "description": "Present if the client supports sampling from an LLM.", - "properties": {}, - "type": "object" - } - }, - "type": "object" - }, - "ClientNotification": { - "anyOf": [ - { - "$ref": "#/definitions/CancelledNotification" - }, - { - "$ref": "#/definitions/InitializedNotification" - }, - { - "$ref": "#/definitions/ProgressNotification" - }, - { - "$ref": "#/definitions/RootsListChangedNotification" - } - ] - }, - "ClientRequest": { - "anyOf": [ - { - "$ref": "#/definitions/InitializeRequest" - }, - { - "$ref": "#/definitions/PingRequest" - }, - { - "$ref": "#/definitions/ListResourcesRequest" - }, - { - "$ref": "#/definitions/ListResourceTemplatesRequest" - }, - { - "$ref": "#/definitions/ReadResourceRequest" - }, - { - "$ref": "#/definitions/SubscribeRequest" - }, - { - "$ref": "#/definitions/UnsubscribeRequest" - }, - { - "$ref": "#/definitions/ListPromptsRequest" - }, - { - "$ref": "#/definitions/GetPromptRequest" - }, - { - "$ref": "#/definitions/ListToolsRequest" - }, - { - "$ref": "#/definitions/CallToolRequest" - }, - { - "$ref": "#/definitions/SetLevelRequest" - }, - { - "$ref": "#/definitions/CompleteRequest" - } - ] - }, - "ClientResult": { - "anyOf": [ - { - "$ref": "#/definitions/Result" - }, - { - "$ref": "#/definitions/CreateMessageResult" - }, - { - "$ref": "#/definitions/ListRootsResult" - }, - { - "$ref": "#/definitions/ElicitResult" - } - ] - }, - "CompleteRequest": { - "description": "A request from the client to the server, to ask for completion options.", - "properties": { - "method": { - "const": "completion/complete", - "type": "string" - }, - "params": { - "properties": { - "argument": { - "description": "The argument's information", - "properties": { - "name": { - "description": "The name of the argument", - "type": "string" - }, - "value": { - "description": "The value of the argument to use for completion matching.", - "type": "string" - } - }, - "required": [ - "name", - "value" - ], - "type": "object" - }, - "context": { - "description": "Additional, optional context for completions", - "properties": { - "arguments": { - "additionalProperties": { - "type": "string" - }, - "description": "Previously-resolved variables in a URI template or prompt.", - "type": "object" - } - }, - "type": "object" - }, - "ref": { - "anyOf": [ - { - "$ref": "#/definitions/PromptReference" - }, - { - "$ref": "#/definitions/ResourceTemplateReference" - } - ] - } - }, - "required": [ - "argument", - "ref" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "CompleteResult": { - "description": "The server's response to a completion/complete request", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "completion": { - "properties": { - "hasMore": { - "description": "Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.", - "type": "boolean" - }, - "total": { - "description": "The total number of completion options available. This can exceed the number of values actually sent in the response.", - "type": "integer" - }, - "values": { - "description": "An array of completion values. Must not exceed 100 items.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "values" - ], - "type": "object" - } - }, - "required": [ - "completion" - ], - "type": "object" - }, - "ContentBlock": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - }, - { - "$ref": "#/definitions/ResourceLink" - }, - { - "$ref": "#/definitions/EmbeddedResource" - } - ] - }, - "CreateMessageRequest": { - "description": "A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.", - "properties": { - "method": { - "const": "sampling/createMessage", - "type": "string" - }, - "params": { - "properties": { - "includeContext": { - "description": "A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request.", - "enum": [ - "allServers", - "none", - "thisServer" - ], - "type": "string" - }, - "maxTokens": { - "description": "The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested.", - "type": "integer" - }, - "messages": { - "items": { - "$ref": "#/definitions/SamplingMessage" - }, - "type": "array" - }, - "metadata": { - "additionalProperties": true, - "description": "Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.", - "properties": {}, - "type": "object" - }, - "modelPreferences": { - "$ref": "#/definitions/ModelPreferences", - "description": "The server's preferences for which model to select. The client MAY ignore these preferences." - }, - "stopSequences": { - "items": { - "type": "string" - }, - "type": "array" - }, - "systemPrompt": { - "description": "An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt.", - "type": "string" - }, - "temperature": { - "type": "number" - } - }, - "required": [ - "maxTokens", - "messages" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "CreateMessageResult": { - "description": "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.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "content": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - } - ] - }, - "model": { - "description": "The name of the model that generated the message.", - "type": "string" - }, - "role": { - "$ref": "#/definitions/Role" - }, - "stopReason": { - "description": "The reason why sampling stopped, if known.", - "type": "string" - } - }, - "required": [ - "content", - "model", - "role" - ], - "type": "object" - }, - "Cursor": { - "description": "An opaque token used to represent a cursor for pagination.", - "type": "string" - }, - "ElicitRequest": { - "description": "A request from the server to elicit additional information from the user via the client.", - "properties": { - "method": { - "const": "elicitation/create", - "type": "string" - }, - "params": { - "properties": { - "message": { - "description": "The message to present to the user.", - "type": "string" - }, - "requestedSchema": { - "description": "A restricted subset of JSON Schema.\nOnly top-level properties are allowed, without nesting.", - "properties": { - "properties": { - "additionalProperties": { - "$ref": "#/definitions/PrimitiveSchemaDefinition" - }, - "type": "object" - }, - "required": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "const": "object", - "type": "string" - } - }, - "required": [ - "properties", - "type" - ], - "type": "object" - } - }, - "required": [ - "message", - "requestedSchema" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ElicitResult": { - "description": "The client's response to an elicitation request.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "action": { - "description": "The user action in response to the elicitation.\n- \"accept\": User submitted the form/confirmed the action\n- \"decline\": User explicitly declined the action\n- \"cancel\": User dismissed without making an explicit choice", - "enum": [ - "accept", - "cancel", - "decline" - ], - "type": "string" - }, - "content": { - "additionalProperties": { - "type": [ - "string", - "integer", - "boolean" - ] - }, - "description": "The submitted form data, only present when action is \"accept\".\nContains values matching the requested schema.", - "type": "object" - } - }, - "required": [ - "action" - ], - "type": "object" - }, - "EmbeddedResource": { - "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit\nof the LLM and/or the user.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "resource": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, - "type": { - "const": "resource", - "type": "string" - } - }, - "required": [ - "resource", - "type" - ], - "type": "object" - }, - "EmptyResult": { - "$ref": "#/definitions/Result" - }, - "EnumSchema": { - "properties": { - "description": { - "type": "string" - }, - "enum": { - "items": { - "type": "string" - }, - "type": "array" - }, - "enumNames": { - "items": { - "type": "string" - }, - "type": "array" - }, - "title": { - "type": "string" - }, - "type": { - "const": "string", - "type": "string" - } - }, - "required": [ - "enum", - "type" - ], - "type": "object" - }, - "GetPromptRequest": { - "description": "Used by the client to get a prompt provided by the server.", - "properties": { - "method": { - "const": "prompts/get", - "type": "string" - }, - "params": { - "properties": { - "arguments": { - "additionalProperties": { - "type": "string" - }, - "description": "Arguments to use for templating the prompt.", - "type": "object" - }, - "name": { - "description": "The name of the prompt or prompt template.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "GetPromptResult": { - "description": "The server's response to a prompts/get request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "description": { - "description": "An optional description for the prompt.", - "type": "string" - }, - "messages": { - "items": { - "$ref": "#/definitions/PromptMessage" - }, - "type": "array" - } - }, - "required": [ - "messages" - ], - "type": "object" - }, - "ImageContent": { - "description": "An image provided to or from an LLM.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "data": { - "description": "The base64-encoded image data.", - "format": "byte", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of the image. Different providers may support different image types.", - "type": "string" - }, - "type": { - "const": "image", - "type": "string" - } - }, - "required": [ - "data", - "mimeType", - "type" - ], - "type": "object" - }, - "Implementation": { - "description": "Describes the name and version of an MCP implementation, with an optional title for UI representation.", - "properties": { - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "name", - "version" - ], - "type": "object" - }, - "InitializeRequest": { - "description": "This request is sent from the client to the server when it first connects, asking it to begin initialization.", - "properties": { - "method": { - "const": "initialize", - "type": "string" - }, - "params": { - "properties": { - "capabilities": { - "$ref": "#/definitions/ClientCapabilities" - }, - "clientInfo": { - "$ref": "#/definitions/Implementation" - }, - "protocolVersion": { - "description": "The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well.", - "type": "string" - } - }, - "required": [ - "capabilities", - "clientInfo", - "protocolVersion" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "InitializeResult": { - "description": "After receiving an initialize request from the client, the server sends this response.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "capabilities": { - "$ref": "#/definitions/ServerCapabilities" - }, - "instructions": { - "description": "Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a \"hint\" to the model. For example, this information MAY be added to the system prompt.", - "type": "string" - }, - "protocolVersion": { - "description": "The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect.", - "type": "string" - }, - "serverInfo": { - "$ref": "#/definitions/Implementation" - } - }, - "required": [ - "capabilities", - "protocolVersion", - "serverInfo" - ], - "type": "object" - }, - "InitializedNotification": { - "description": "This notification is sent from the client to the server after initialization has finished.", - "properties": { - "method": { - "const": "notifications/initialized", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "JSONRPCError": { - "description": "A response to a request that indicates an error occurred.", - "properties": { - "error": { - "properties": { - "code": { - "description": "The error type that occurred.", - "type": "integer" - }, - "data": { - "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." - }, - "message": { - "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", - "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "type": "object" - }, - "id": { - "$ref": "#/definitions/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - } - }, - "required": [ - "error", - "id", - "jsonrpc" - ], - "type": "object" - }, - "JSONRPCMessage": { - "anyOf": [ - { - "$ref": "#/definitions/JSONRPCRequest" - }, - { - "$ref": "#/definitions/JSONRPCNotification" - }, - { - "$ref": "#/definitions/JSONRPCResponse" - }, - { - "$ref": "#/definitions/JSONRPCError" - } - ], - "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent." - }, - "JSONRPCNotification": { - "description": "A notification which does not expect a response.", - "properties": { - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "method": { - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "jsonrpc", - "method" - ], - "type": "object" - }, - "JSONRPCRequest": { - "description": "A request that expects a response.", - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "method": { - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "properties": { - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "id", - "jsonrpc", - "method" - ], - "type": "object" - }, - "JSONRPCResponse": { - "description": "A successful (non-error) response to a request.", - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "result": { - "$ref": "#/definitions/Result" - } - }, - "required": [ - "id", - "jsonrpc", - "result" - ], - "type": "object" - }, - "ListPromptsRequest": { - "description": "Sent from the client to request a list of prompts and prompt templates the server has.", - "properties": { - "method": { - "const": "prompts/list", - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListPromptsResult": { - "description": "The server's response to a prompts/list request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - }, - "prompts": { - "items": { - "$ref": "#/definitions/Prompt" - }, - "type": "array" - } - }, - "required": [ - "prompts" - ], - "type": "object" - }, - "ListResourceTemplatesRequest": { - "description": "Sent from the client to request a list of resource templates the server has.", - "properties": { - "method": { - "const": "resources/templates/list", - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListResourceTemplatesResult": { - "description": "The server's response to a resources/templates/list request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - }, - "resourceTemplates": { - "items": { - "$ref": "#/definitions/ResourceTemplate" - }, - "type": "array" - } - }, - "required": [ - "resourceTemplates" - ], - "type": "object" - }, - "ListResourcesRequest": { - "description": "Sent from the client to request a list of resources the server has.", - "properties": { - "method": { - "const": "resources/list", - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListResourcesResult": { - "description": "The server's response to a resources/list request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - }, - "resources": { - "items": { - "$ref": "#/definitions/Resource" - }, - "type": "array" - } - }, - "required": [ - "resources" - ], - "type": "object" - }, - "ListRootsRequest": { - "description": "Sent from the server to request a list of root URIs from the client. Roots allow\nservers to ask for specific directories or files to operate on. A common example\nfor roots is providing a set of repositories or directories a server should operate\non.\n\nThis request is typically used when the server needs to understand the file system\nstructure or access specific locations that the client has permission to read from.", - "properties": { - "method": { - "const": "roots/list", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "properties": { - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListRootsResult": { - "description": "The client's response to a roots/list request from the server.\nThis result contains an array of Root objects, each representing a root directory\nor file that the server can operate on.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "roots": { - "items": { - "$ref": "#/definitions/Root" - }, - "type": "array" - } - }, - "required": [ - "roots" - ], - "type": "object" - }, - "ListToolsRequest": { - "description": "Sent from the client to request a list of tools the server has.", - "properties": { - "method": { - "const": "tools/list", - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ListToolsResult": { - "description": "The server's response to a tools/list request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - }, - "tools": { - "items": { - "$ref": "#/definitions/Tool" - }, - "type": "array" - } - }, - "required": [ - "tools" - ], - "type": "object" - }, - "LoggingLevel": { - "description": "The severity of a log message.\n\nThese map to syslog message severities, as specified in RFC-5424:\nhttps://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1", - "enum": [ - "alert", - "critical", - "debug", - "emergency", - "error", - "info", - "notice", - "warning" - ], - "type": "string" - }, - "LoggingMessageNotification": { - "description": "Notification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.", - "properties": { - "method": { - "const": "notifications/message", - "type": "string" - }, - "params": { - "properties": { - "data": { - "description": "The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here." - }, - "level": { - "$ref": "#/definitions/LoggingLevel", - "description": "The severity of this log message." - }, - "logger": { - "description": "An optional name of the logger issuing this message.", - "type": "string" - } - }, - "required": [ - "data", - "level" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ModelHint": { - "description": "Hints to use for model selection.\n\nKeys not declared here are currently left unspecified by the spec and are up\nto the client to interpret.", - "properties": { - "name": { - "description": "A hint for a model name.\n\nThe client SHOULD treat this as a substring of a model name; for example:\n - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022`\n - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc.\n - `claude` should match any Claude model\n\nThe client MAY also map the string to a different provider's model name or a different model info, as long as it fills a similar niche; for example:\n - `gemini-1.5-flash` could match `claude-3-haiku-20240307`", - "type": "string" - } - }, - "type": "object" - }, - "ModelPreferences": { - "description": "The server's preferences for model selection, requested of the client during sampling.\n\nBecause LLMs can vary along multiple dimensions, choosing the \"best\" model is\nrarely straightforward. Different models excel in different areas—some are\nfaster but less capable, others are more capable but more expensive, and so\non. This interface allows servers to express their priorities across multiple\ndimensions to help clients make an appropriate selection for their use case.\n\nThese preferences are always advisory. The client MAY ignore them. It is also\nup to the client to decide how to interpret these preferences and how to\nbalance them against other considerations.", - "properties": { - "costPriority": { - "description": "How much to prioritize cost when selecting a model. A value of 0 means cost\nis not important, while a value of 1 means cost is the most important\nfactor.", - "maximum": 1, - "minimum": 0, - "type": "number" - }, - "hints": { - "description": "Optional hints to use for model selection.\n\nIf multiple hints are specified, the client MUST evaluate them in order\n(such that the first match is taken).\n\nThe client SHOULD prioritize these hints over the numeric priorities, but\nMAY still use the priorities to select from ambiguous matches.", - "items": { - "$ref": "#/definitions/ModelHint" - }, - "type": "array" - }, - "intelligencePriority": { - "description": "How much to prioritize intelligence and capabilities when selecting a\nmodel. A value of 0 means intelligence is not important, while a value of 1\nmeans intelligence is the most important factor.", - "maximum": 1, - "minimum": 0, - "type": "number" - }, - "speedPriority": { - "description": "How much to prioritize sampling speed (latency) when selecting a model. A\nvalue of 0 means speed is not important, while a value of 1 means speed is\nthe most important factor.", - "maximum": 1, - "minimum": 0, - "type": "number" - } - }, - "type": "object" - }, - "Notification": { - "properties": { - "method": { - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "NumberSchema": { - "properties": { - "description": { - "type": "string" - }, - "maximum": { - "type": "integer" - }, - "minimum": { - "type": "integer" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integer", - "number" - ], - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - }, - "PaginatedRequest": { - "properties": { - "method": { - "type": "string" - }, - "params": { - "properties": { - "cursor": { - "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "PaginatedResult": { - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "nextCursor": { - "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", - "type": "string" - } - }, - "type": "object" - }, - "PingRequest": { - "description": "A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.", - "properties": { - "method": { - "const": "ping", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "properties": { - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "PrimitiveSchemaDefinition": { - "anyOf": [ - { - "$ref": "#/definitions/StringSchema" - }, - { - "$ref": "#/definitions/NumberSchema" - }, - { - "$ref": "#/definitions/BooleanSchema" - }, - { - "$ref": "#/definitions/EnumSchema" - } - ], - "description": "Restricted schema definitions that only allow primitive types\nwithout nested objects or arrays." - }, - "ProgressNotification": { - "description": "An out-of-band notification used to inform the receiver of a progress update for a long-running request.", - "properties": { - "method": { - "const": "notifications/progress", - "type": "string" - }, - "params": { - "properties": { - "message": { - "description": "An optional message describing the current progress.", - "type": "string" - }, - "progress": { - "description": "The progress thus far. This should increase every time progress is made, even if the total is unknown.", - "type": "number" - }, - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "The progress token which was given in the initial request, used to associate this notification with the request that is proceeding." - }, - "total": { - "description": "Total number of items to process (or total progress required), if known.", - "type": "number" - } - }, - "required": [ - "progress", - "progressToken" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ProgressToken": { - "description": "A progress token, used to associate progress notifications with the original request.", - "type": [ - "string", - "integer" - ] - }, - "Prompt": { - "description": "A prompt or prompt template that the server offers.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "arguments": { - "description": "A list of arguments to use for templating the prompt.", - "items": { - "$ref": "#/definitions/PromptArgument" - }, - "type": "array" - }, - "description": { - "description": "An optional description of what this prompt provides", - "type": "string" - }, - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "PromptArgument": { - "description": "Describes an argument that a prompt can accept.", - "properties": { - "description": { - "description": "A human-readable description of the argument.", - "type": "string" - }, - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "required": { - "description": "Whether this argument must be provided.", - "type": "boolean" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "PromptListChangedNotification": { - "description": "An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.", - "properties": { - "method": { - "const": "notifications/prompts/list_changed", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "PromptMessage": { - "description": "Describes a message returned as part of a prompt.\n\nThis is similar to `SamplingMessage`, but also supports the embedding of\nresources from the MCP server.", - "properties": { - "content": { - "$ref": "#/definitions/ContentBlock" - }, - "role": { - "$ref": "#/definitions/Role" - } - }, - "required": [ - "content", - "role" - ], - "type": "object" - }, - "PromptReference": { - "description": "Identifies a prompt.", - "properties": { - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - }, - "type": { - "const": "ref/prompt", - "type": "string" - } - }, - "required": [ - "name", - "type" - ], - "type": "object" - }, - "ReadResourceRequest": { - "description": "Sent from the client to the server, to read a specific resource URI.", - "properties": { - "method": { - "const": "resources/read", - "type": "string" - }, - "params": { - "properties": { - "uri": { - "description": "The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "ReadResourceResult": { - "description": "The server's response to a resources/read request from the client.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "contents": { - "items": { - "anyOf": [ - { - "$ref": "#/definitions/TextResourceContents" - }, - { - "$ref": "#/definitions/BlobResourceContents" - } - ] - }, - "type": "array" - } - }, - "required": [ - "contents" - ], - "type": "object" - }, - "Request": { - "properties": { - "method": { - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "properties": { - "progressToken": { - "$ref": "#/definitions/ProgressToken", - "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "RequestId": { - "description": "A uniquely identifying ID for a request in JSON-RPC.", - "type": [ - "string", - "integer" - ] - }, - "Resource": { - "description": "A known resource that the server is capable of reading.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "description": { - "description": "A description of what this resource represents.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "size": { - "description": "The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.\n\nThis can be used by Hosts to display file sizes and estimate context window usage.", - "type": "integer" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "name", - "uri" - ], - "type": "object" - }, - "ResourceContents": { - "description": "The contents of a specific resource or sub-resource.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - }, - "ResourceLink": { - "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "description": { - "description": "A description of what this resource represents.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "size": { - "description": "The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.\n\nThis can be used by Hosts to display file sizes and estimate context window usage.", - "type": "integer" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - }, - "type": { - "const": "resource_link", - "type": "string" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "name", - "type", - "uri" - ], - "type": "object" - }, - "ResourceListChangedNotification": { - "description": "An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.", - "properties": { - "method": { - "const": "notifications/resources/list_changed", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "ResourceTemplate": { - "description": "A template description for resources available on the server.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "description": { - "description": "A description of what this template is for.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", - "type": "string" - }, - "mimeType": { - "description": "The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.", - "type": "string" - }, - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - }, - "uriTemplate": { - "description": "A URI template (according to RFC 6570) that can be used to construct resource URIs.", - "format": "uri-template", - "type": "string" - } - }, - "required": [ - "name", - "uriTemplate" - ], - "type": "object" - }, - "ResourceTemplateReference": { - "description": "A reference to a resource or resource template definition.", - "properties": { - "type": { - "const": "ref/resource", - "type": "string" - }, - "uri": { - "description": "The URI or URI template of the resource.", - "format": "uri-template", - "type": "string" - } - }, - "required": [ - "type", - "uri" - ], - "type": "object" - }, - "ResourceUpdatedNotification": { - "description": "A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.", - "properties": { - "method": { - "const": "notifications/resources/updated", - "type": "string" - }, - "params": { - "properties": { - "uri": { - "description": "The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "Result": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - } - }, - "type": "object" - }, - "Role": { - "description": "The sender or recipient of messages and data in a conversation.", - "enum": [ - "assistant", - "user" - ], - "type": "string" - }, - "Root": { - "description": "Represents a root directory or file that the server can operate on.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "name": { - "description": "An optional name for the root. This can be used to provide a human-readable\nidentifier for the root, which may be useful for display purposes or for\nreferencing the root in other parts of the application.", - "type": "string" - }, - "uri": { - "description": "The URI identifying the root. This *must* start with file:// for now.\nThis restriction may be relaxed in future versions of the protocol to allow\nother URI schemes.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - }, - "RootsListChangedNotification": { - "description": "A notification from the client to the server, informing it that the list of roots has changed.\nThis notification should be sent whenever the client adds, removes, or modifies any root.\nThe server should then request an updated list of roots using the ListRootsRequest.", - "properties": { - "method": { - "const": "notifications/roots/list_changed", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "SamplingMessage": { - "description": "Describes a message issued to or received from an LLM API.", - "properties": { - "content": { - "anyOf": [ - { - "$ref": "#/definitions/TextContent" - }, - { - "$ref": "#/definitions/ImageContent" - }, - { - "$ref": "#/definitions/AudioContent" - } - ] - }, - "role": { - "$ref": "#/definitions/Role" - } - }, - "required": [ - "content", - "role" - ], - "type": "object" - }, - "ServerCapabilities": { - "description": "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.", - "properties": { - "completions": { - "additionalProperties": true, - "description": "Present if the server supports argument autocompletion suggestions.", - "properties": {}, - "type": "object" - }, - "experimental": { - "additionalProperties": { - "additionalProperties": true, - "properties": {}, - "type": "object" - }, - "description": "Experimental, non-standard capabilities that the server supports.", - "type": "object" - }, - "logging": { - "additionalProperties": true, - "description": "Present if the server supports sending log messages to the client.", - "properties": {}, - "type": "object" - }, - "prompts": { - "description": "Present if the server offers any prompt templates.", - "properties": { - "listChanged": { - "description": "Whether this server supports notifications for changes to the prompt list.", - "type": "boolean" - } - }, - "type": "object" - }, - "resources": { - "description": "Present if the server offers any resources to read.", - "properties": { - "listChanged": { - "description": "Whether this server supports notifications for changes to the resource list.", - "type": "boolean" - }, - "subscribe": { - "description": "Whether this server supports subscribing to resource updates.", - "type": "boolean" - } - }, - "type": "object" - }, - "tools": { - "description": "Present if the server offers any tools to call.", - "properties": { - "listChanged": { - "description": "Whether this server supports notifications for changes to the tool list.", - "type": "boolean" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "ServerNotification": { - "anyOf": [ - { - "$ref": "#/definitions/CancelledNotification" - }, - { - "$ref": "#/definitions/ProgressNotification" - }, - { - "$ref": "#/definitions/ResourceListChangedNotification" - }, - { - "$ref": "#/definitions/ResourceUpdatedNotification" - }, - { - "$ref": "#/definitions/PromptListChangedNotification" - }, - { - "$ref": "#/definitions/ToolListChangedNotification" - }, - { - "$ref": "#/definitions/LoggingMessageNotification" - } - ] - }, - "ServerRequest": { - "anyOf": [ - { - "$ref": "#/definitions/PingRequest" - }, - { - "$ref": "#/definitions/CreateMessageRequest" - }, - { - "$ref": "#/definitions/ListRootsRequest" - }, - { - "$ref": "#/definitions/ElicitRequest" - } - ] - }, - "ServerResult": { - "anyOf": [ - { - "$ref": "#/definitions/Result" - }, - { - "$ref": "#/definitions/InitializeResult" - }, - { - "$ref": "#/definitions/ListResourcesResult" - }, - { - "$ref": "#/definitions/ListResourceTemplatesResult" - }, - { - "$ref": "#/definitions/ReadResourceResult" - }, - { - "$ref": "#/definitions/ListPromptsResult" - }, - { - "$ref": "#/definitions/GetPromptResult" - }, - { - "$ref": "#/definitions/ListToolsResult" - }, - { - "$ref": "#/definitions/CallToolResult" - }, - { - "$ref": "#/definitions/CompleteResult" - } - ] - }, - "SetLevelRequest": { - "description": "A request from the client to the server, to enable or adjust logging.", - "properties": { - "method": { - "const": "logging/setLevel", - "type": "string" - }, - "params": { - "properties": { - "level": { - "$ref": "#/definitions/LoggingLevel", - "description": "The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message." - } - }, - "required": [ - "level" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "StringSchema": { - "properties": { - "description": { - "type": "string" - }, - "format": { - "enum": [ - "date", - "date-time", - "email", - "uri" - ], - "type": "string" - }, - "maxLength": { - "type": "integer" - }, - "minLength": { - "type": "integer" - }, - "title": { - "type": "string" - }, - "type": { - "const": "string", - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - }, - "SubscribeRequest": { - "description": "Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.", - "properties": { - "method": { - "const": "resources/subscribe", - "type": "string" - }, - "params": { - "properties": { - "uri": { - "description": "The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, - "TextContent": { - "description": "Text provided to or from an LLM.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "annotations": { - "$ref": "#/definitions/Annotations", - "description": "Optional annotations for the client." - }, - "text": { - "description": "The text content of the message.", - "type": "string" - }, - "type": { - "const": "text", - "type": "string" - } - }, - "required": [ - "text", - "type" - ], - "type": "object" - }, - "TextResourceContents": { - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "mimeType": { - "description": "The MIME type of this resource, if known.", - "type": "string" - }, - "text": { - "description": "The text of the item. This must only be set if the item can actually be represented as text (not binary data).", - "type": "string" - }, - "uri": { - "description": "The URI of this resource.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "text", - "uri" - ], - "type": "object" - }, - "Tool": { - "description": "Definition for a tool the client can call.", - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - }, - "annotations": { - "$ref": "#/definitions/ToolAnnotations", - "description": "Optional additional tool information.\n\nDisplay name precedence order is: title, annotations.title, then name." - }, - "description": { - "description": "A human-readable description of the tool.\n\nThis can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a \"hint\" to the model.", - "type": "string" - }, - "inputSchema": { - "description": "A JSON Schema object defining the expected parameters for the tool.", - "properties": { - "properties": { - "additionalProperties": { - "additionalProperties": true, - "properties": {}, - "type": "object" - }, - "type": "object" - }, - "required": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "const": "object", - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - }, - "name": { - "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", - "type": "string" - }, - "outputSchema": { - "description": "An optional JSON Schema object defining the structure of the tool's output returned in\nthe structuredContent field of a CallToolResult.", - "properties": { - "properties": { - "additionalProperties": { - "additionalProperties": true, - "properties": {}, - "type": "object" - }, - "type": "object" - }, - "required": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "const": "object", - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" - }, - "title": { - "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", - "type": "string" - } - }, - "required": [ - "inputSchema", - "name" - ], - "type": "object" - }, - "ToolAnnotations": { - "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**.\nThey are not guaranteed to provide a faithful description of\ntool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations\nreceived from untrusted servers.", - "properties": { - "destructiveHint": { - "description": "If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: true", - "type": "boolean" - }, - "idempotentHint": { - "description": "If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on the its environment.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: false", - "type": "boolean" - }, - "openWorldHint": { - "description": "If true, this tool may interact with an \"open world\" of external\nentities. If false, the tool's domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true", - "type": "boolean" - }, - "readOnlyHint": { - "description": "If true, the tool does not modify its environment.\n\nDefault: false", - "type": "boolean" - }, - "title": { - "description": "A human-readable title for the tool.", - "type": "string" - } - }, - "type": "object" - }, - "ToolListChangedNotification": { - "description": "An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.", - "properties": { - "method": { - "const": "notifications/tools/list_changed", - "type": "string" - }, - "params": { - "additionalProperties": {}, - "properties": { - "_meta": { - "additionalProperties": {}, - "description": "See [specification/2025-06-18/basic/index#general-fields] for notes on _meta usage.", - "type": "object" - } - }, - "type": "object" - } - }, - "required": [ - "method" - ], - "type": "object" - }, - "UnsubscribeRequest": { - "description": "Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.", - "properties": { - "method": { - "const": "resources/unsubscribe", - "type": "string" - }, - "params": { - "properties": { - "uri": { - "description": "The URI of the resource to unsubscribe from.", - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri" - ], - "type": "object" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - } - } -} diff --git a/codex-rs/mcp-types/src/lib.rs b/codex-rs/mcp-types/src/lib.rs deleted file mode 100644 index 7418bea854..0000000000 --- a/codex-rs/mcp-types/src/lib.rs +++ /dev/null @@ -1,1714 +0,0 @@ -// @generated -// DO NOT EDIT THIS FILE DIRECTLY. -// Run the following in the crate root to regenerate this file: -// -// ```shell -// ./generate_mcp_types.py -// ``` -use serde::Deserialize; -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"; -pub const JSONRPC_VERSION: &str = "2.0"; - -/// Paired request/response types for the Model Context Protocol (MCP). -pub trait ModelContextProtocolRequest { - const METHOD: &'static str; - type Params: DeserializeOwned + Serialize + Send + Sync + 'static; - type Result: DeserializeOwned + Serialize + Send + Sync + 'static; -} - -/// One-way message in the Model Context Protocol (MCP). -pub trait ModelContextProtocolNotification { - const METHOD: &'static str; - type Params: DeserializeOwned + Serialize + Send + Sync + 'static; -} - -fn default_jsonrpc() -> String { - JSONRPC_VERSION.to_owned() -} - -/// Optional annotations for the client. The client can use annotations to inform how objects are used or displayed -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Annotations { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub audience: Option>, - #[serde( - rename = "lastModified", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub last_modified: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub priority: Option, -} - -/// Audio provided to or from an LLM. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct AudioContent { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub annotations: Option, - pub data: String, - #[serde(rename = "mimeType")] - pub mime_type: String, - pub r#type: String, // &'static str = "audio" -} - -/// Base interface for metadata with name (identifier) and title (display name) properties. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct BaseMetadata { - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct BlobResourceContents { - pub blob: String, - #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub mime_type: Option, - pub uri: String, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct BooleanSchema { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub default: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - pub r#type: String, // &'static str = "boolean" -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum CallToolRequest {} - -impl ModelContextProtocolRequest for CallToolRequest { - const METHOD: &'static str = "tools/call"; - type Params = CallToolRequestParams; - type Result = CallToolResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CallToolRequestParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub arguments: Option, - pub name: String, -} - -/// The server's response to a tool call. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CallToolResult { - pub content: Vec, - #[serde(rename = "isError", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub is_error: Option, - #[serde( - rename = "structuredContent", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub structured_content: Option, -} - -impl From for serde_json::Value { - fn from(value: CallToolResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum CancelledNotification {} - -impl ModelContextProtocolNotification for CancelledNotification { - const METHOD: &'static str = "notifications/cancelled"; - type Params = CancelledNotificationParams; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CancelledNotificationParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub reason: Option, - #[serde(rename = "requestId")] - pub request_id: RequestId, -} - -/// 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, JsonSchema, TS)] -pub struct ClientCapabilities { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub elicitation: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub experimental: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub roots: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub sampling: Option, -} - -/// Present if the client supports listing roots. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ClientCapabilitiesRoots { - #[serde( - rename = "listChanged", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub list_changed: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum ClientNotification { - CancelledNotification(CancelledNotification), - InitializedNotification(InitializedNotification), - ProgressNotification(ProgressNotification), - RootsListChangedNotification(RootsListChangedNotification), -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(tag = "method", content = "params")] -pub enum ClientRequest { - #[serde(rename = "initialize")] - InitializeRequest(::Params), - #[serde(rename = "ping")] - PingRequest(::Params), - #[serde(rename = "resources/list")] - ListResourcesRequest(::Params), - #[serde(rename = "resources/templates/list")] - ListResourceTemplatesRequest( - ::Params, - ), - #[serde(rename = "resources/read")] - ReadResourceRequest(::Params), - #[serde(rename = "resources/subscribe")] - SubscribeRequest(::Params), - #[serde(rename = "resources/unsubscribe")] - UnsubscribeRequest(::Params), - #[serde(rename = "prompts/list")] - ListPromptsRequest(::Params), - #[serde(rename = "prompts/get")] - GetPromptRequest(::Params), - #[serde(rename = "tools/list")] - ListToolsRequest(::Params), - #[serde(rename = "tools/call")] - CallToolRequest(::Params), - #[serde(rename = "logging/setLevel")] - SetLevelRequest(::Params), - #[serde(rename = "completion/complete")] - CompleteRequest(::Params), -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum ClientResult { - Result(Result), - CreateMessageResult(CreateMessageResult), - ListRootsResult(ListRootsResult), - ElicitResult(ElicitResult), -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum CompleteRequest {} - -impl ModelContextProtocolRequest for CompleteRequest { - const METHOD: &'static str = "completion/complete"; - type Params = CompleteRequestParams; - type Result = CompleteResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CompleteRequestParams { - pub argument: CompleteRequestParamsArgument, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub context: Option, - pub r#ref: CompleteRequestParamsRef, -} - -/// Additional, optional context for completions -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CompleteRequestParamsContext { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub arguments: Option, -} - -/// The argument's information -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CompleteRequestParamsArgument { - pub name: String, - pub value: String, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum CompleteRequestParamsRef { - PromptReference(PromptReference), - ResourceTemplateReference(ResourceTemplateReference), -} - -/// The server's response to a completion/complete request -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CompleteResult { - pub completion: CompleteResultCompletion, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CompleteResultCompletion { - #[serde(rename = "hasMore", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub has_more: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub total: Option, - pub values: Vec, -} - -impl From for serde_json::Value { - fn from(value: CompleteResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum ContentBlock { - TextContent(TextContent), - ImageContent(ImageContent), - AudioContent(AudioContent), - ResourceLink(ResourceLink), - EmbeddedResource(EmbeddedResource), -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum CreateMessageRequest {} - -impl ModelContextProtocolRequest for CreateMessageRequest { - const METHOD: &'static str = "sampling/createMessage"; - type Params = CreateMessageRequestParams; - type Result = CreateMessageResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct CreateMessageRequestParams { - #[serde( - rename = "includeContext", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub include_context: Option, - #[serde(rename = "maxTokens")] - pub max_tokens: i64, - pub messages: Vec, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub metadata: Option, - #[serde( - rename = "modelPreferences", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub model_preferences: Option, - #[serde( - rename = "stopSequences", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub stop_sequences: Option>, - #[serde( - rename = "systemPrompt", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub system_prompt: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub temperature: Option, -} - -/// 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, JsonSchema, TS)] -pub struct CreateMessageResult { - pub content: CreateMessageResultContent, - pub model: String, - pub role: Role, - #[serde( - rename = "stopReason", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub stop_reason: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum CreateMessageResultContent { - TextContent(TextContent), - ImageContent(ImageContent), - AudioContent(AudioContent), -} - -impl From for serde_json::Value { - fn from(value: CreateMessageResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Cursor(String); - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ElicitRequest {} - -impl ModelContextProtocolRequest for ElicitRequest { - const METHOD: &'static str = "elicitation/create"; - type Params = ElicitRequestParams; - type Result = ElicitResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ElicitRequestParams { - pub message: String, - #[serde(rename = "requestedSchema")] - pub requested_schema: ElicitRequestParamsRequestedSchema, -} - -/// A restricted subset of JSON Schema. -/// Only top-level properties are allowed, without nesting. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ElicitRequestParamsRequestedSchema { - pub properties: serde_json::Value, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub required: Option>, - pub r#type: String, // &'static str = "object" -} - -/// The client's response to an elicitation request. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ElicitResult { - pub action: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub content: Option, -} - -impl From for serde_json::Value { - fn from(value: ElicitResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -/// The contents of a resource, embedded into a prompt or tool call result. -/// -/// 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, JsonSchema, TS)] -pub struct EmbeddedResource { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub annotations: Option, - pub resource: EmbeddedResourceResource, - pub r#type: String, // &'static str = "resource" -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum EmbeddedResourceResource { - TextResourceContents(TextResourceContents), - BlobResourceContents(BlobResourceContents), -} - -pub type EmptyResult = Result; - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct EnumSchema { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - pub r#enum: Vec, - #[serde(rename = "enumNames", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub enum_names: Option>, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - pub r#type: String, // &'static str = "string" -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum GetPromptRequest {} - -impl ModelContextProtocolRequest for GetPromptRequest { - const METHOD: &'static str = "prompts/get"; - type Params = GetPromptRequestParams; - type Result = GetPromptResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct GetPromptRequestParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub arguments: Option, - pub name: String, -} - -/// The server's response to a prompts/get request from the client. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct GetPromptResult { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - pub messages: Vec, -} - -impl From for serde_json::Value { - fn from(value: GetPromptResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -/// An image provided to or from an LLM. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ImageContent { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub annotations: Option, - pub data: String, - #[serde(rename = "mimeType")] - pub mime_type: String, - pub r#type: String, // &'static str = "image" -} - -/// Describes the name and version of an MCP implementation, with an optional title for UI representation. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Implementation { - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - pub version: String, - // This is an extra field that the Codex MCP server sends as part of InitializeResult. - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub user_agent: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum InitializeRequest {} - -impl ModelContextProtocolRequest for InitializeRequest { - const METHOD: &'static str = "initialize"; - type Params = InitializeRequestParams; - type Result = InitializeResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct InitializeRequestParams { - pub capabilities: ClientCapabilities, - #[serde(rename = "clientInfo")] - pub client_info: Implementation, - #[serde(rename = "protocolVersion")] - pub protocol_version: String, -} - -/// After receiving an initialize request from the client, the server sends this response. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct InitializeResult { - pub capabilities: ServerCapabilities, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub instructions: Option, - #[serde(rename = "protocolVersion")] - pub protocol_version: String, - #[serde(rename = "serverInfo")] - pub server_info: Implementation, -} - -impl From for serde_json::Value { - fn from(value: InitializeResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum InitializedNotification {} - -impl ModelContextProtocolNotification for InitializedNotification { - const METHOD: &'static str = "notifications/initialized"; - type Params = Option; -} - -/// A response to a request that indicates an error occurred. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct JSONRPCError { - pub error: JSONRPCErrorError, - pub id: RequestId, - #[serde(rename = "jsonrpc", default = "default_jsonrpc")] - pub jsonrpc: String, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct JSONRPCErrorError { - pub code: i64, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub data: Option, - pub message: String, -} - -/// 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, JsonSchema, TS)] -#[serde(untagged)] -pub enum JSONRPCMessage { - Request(JSONRPCRequest), - Notification(JSONRPCNotification), - Response(JSONRPCResponse), - Error(JSONRPCError), -} - -/// A notification which does not expect a response. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct JSONRPCNotification { - #[serde(rename = "jsonrpc", default = "default_jsonrpc")] - pub jsonrpc: String, - pub method: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub params: Option, -} - -/// A request that expects a response. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct JSONRPCRequest { - pub id: RequestId, - #[serde(rename = "jsonrpc", default = "default_jsonrpc")] - pub jsonrpc: String, - pub method: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub params: Option, -} - -/// A successful (non-error) response to a request. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct JSONRPCResponse { - pub id: RequestId, - #[serde(rename = "jsonrpc", default = "default_jsonrpc")] - pub jsonrpc: String, - pub result: Result, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ListPromptsRequest {} - -impl ModelContextProtocolRequest for ListPromptsRequest { - const METHOD: &'static str = "prompts/list"; - type Params = Option; - type Result = ListPromptsResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ListPromptsRequestParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub cursor: Option, -} - -/// The server's response to a prompts/list request from the client. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ListPromptsResult { - #[serde( - rename = "nextCursor", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub next_cursor: Option, - pub prompts: Vec, -} - -impl From for serde_json::Value { - fn from(value: ListPromptsResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ListResourceTemplatesRequest {} - -impl ModelContextProtocolRequest for ListResourceTemplatesRequest { - const METHOD: &'static str = "resources/templates/list"; - type Params = Option; - type Result = ListResourceTemplatesResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ListResourceTemplatesRequestParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub cursor: Option, -} - -/// The server's response to a resources/templates/list request from the client. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ListResourceTemplatesResult { - #[serde( - rename = "nextCursor", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub next_cursor: Option, - #[serde(rename = "resourceTemplates")] - pub resource_templates: Vec, -} - -impl From for serde_json::Value { - fn from(value: ListResourceTemplatesResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ListResourcesRequest {} - -impl ModelContextProtocolRequest for ListResourcesRequest { - const METHOD: &'static str = "resources/list"; - type Params = Option; - type Result = ListResourcesResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ListResourcesRequestParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub cursor: Option, -} - -/// The server's response to a resources/list request from the client. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ListResourcesResult { - #[serde( - rename = "nextCursor", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub next_cursor: Option, - pub resources: Vec, -} - -impl From for serde_json::Value { - fn from(value: ListResourcesResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ListRootsRequest {} - -impl ModelContextProtocolRequest for ListRootsRequest { - const METHOD: &'static str = "roots/list"; - type Params = Option; - type Result = ListRootsResult; -} - -/// 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, JsonSchema, TS)] -pub struct ListRootsResult { - pub roots: Vec, -} - -impl From for serde_json::Value { - fn from(value: ListRootsResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ListToolsRequest {} - -impl ModelContextProtocolRequest for ListToolsRequest { - const METHOD: &'static str = "tools/list"; - type Params = Option; - type Result = ListToolsResult; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ListToolsRequestParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub cursor: Option, -} - -/// The server's response to a tools/list request from the client. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ListToolsResult { - #[serde( - rename = "nextCursor", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub next_cursor: Option, - pub tools: Vec, -} - -impl From for serde_json::Value { - fn from(value: ListToolsResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -/// The severity of a log message. -/// -/// 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, JsonSchema, TS)] -pub enum LoggingLevel { - #[serde(rename = "alert")] - Alert, - #[serde(rename = "critical")] - Critical, - #[serde(rename = "debug")] - Debug, - #[serde(rename = "emergency")] - Emergency, - #[serde(rename = "error")] - Error, - #[serde(rename = "info")] - Info, - #[serde(rename = "notice")] - Notice, - #[serde(rename = "warning")] - Warning, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum LoggingMessageNotification {} - -impl ModelContextProtocolNotification for LoggingMessageNotification { - const METHOD: &'static str = "notifications/message"; - type Params = LoggingMessageNotificationParams; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct LoggingMessageNotificationParams { - pub data: serde_json::Value, - pub level: LoggingLevel, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub logger: Option, -} - -/// Hints to use for model selection. -/// -/// 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, JsonSchema, TS)] -pub struct ModelHint { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub name: Option, -} - -/// The server's preferences for model selection, requested of the client during sampling. -/// -/// Because LLMs can vary along multiple dimensions, choosing the "best" model is -/// rarely straightforward. Different models excel in different areas—some are -/// faster but less capable, others are more capable but more expensive, and so -/// on. This interface allows servers to express their priorities across multiple -/// dimensions to help clients make an appropriate selection for their use case. -/// -/// 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, JsonSchema, TS)] -pub struct ModelPreferences { - #[serde( - rename = "costPriority", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub cost_priority: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub hints: Option>, - #[serde( - rename = "intelligencePriority", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub intelligence_priority: Option, - #[serde( - rename = "speedPriority", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub speed_priority: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Notification { - pub method: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub params: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct NumberSchema { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub maximum: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub minimum: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - pub r#type: String, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct PaginatedRequest { - pub method: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub params: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct PaginatedRequestParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub cursor: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct PaginatedResult { - #[serde( - rename = "nextCursor", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub next_cursor: Option, -} - -impl From for serde_json::Value { - fn from(value: PaginatedResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum PingRequest {} - -impl ModelContextProtocolRequest for PingRequest { - const METHOD: &'static str = "ping"; - type Params = Option; - type Result = Result; -} - -/// Restricted schema definitions that only allow primitive types -/// without nested objects or arrays. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum PrimitiveSchemaDefinition { - StringSchema(StringSchema), - NumberSchema(NumberSchema), - BooleanSchema(BooleanSchema), - EnumSchema(EnumSchema), -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ProgressNotification {} - -impl ModelContextProtocolNotification for ProgressNotification { - const METHOD: &'static str = "notifications/progress"; - type Params = ProgressNotificationParams; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ProgressNotificationParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub message: Option, - pub progress: f64, - #[serde(rename = "progressToken")] - pub progress_token: ProgressToken, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub total: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, JsonSchema, TS)] -#[serde(untagged)] -pub enum ProgressToken { - String(String), - Integer(i64), -} - -/// A prompt or prompt template that the server offers. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Prompt { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub arguments: Option>, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, -} - -/// Describes an argument that a prompt can accept. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct PromptArgument { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub required: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum PromptListChangedNotification {} - -impl ModelContextProtocolNotification for PromptListChangedNotification { - const METHOD: &'static str = "notifications/prompts/list_changed"; - type Params = Option; -} - -/// Describes a message returned as part of a prompt. -/// -/// This is similar to `SamplingMessage`, but also supports the embedding of -/// resources from the MCP server. -#[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, JsonSchema, TS)] -pub struct PromptReference { - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - pub r#type: String, // &'static str = "ref/prompt" -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ReadResourceRequest {} - -impl ModelContextProtocolRequest for ReadResourceRequest { - const METHOD: &'static str = "resources/read"; - type Params = ReadResourceRequestParams; - type Result = ReadResourceResult; -} - -#[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, JsonSchema, TS)] -pub struct ReadResourceResult { - pub contents: Vec, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum ReadResourceResultContents { - TextResourceContents(TextResourceContents), - BlobResourceContents(BlobResourceContents), -} - -impl From for serde_json::Value { - fn from(value: ReadResourceResult) -> Self { - // Leave this as it should never fail - #[expect(clippy::unwrap_used)] - serde_json::to_value(value).unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Request { - pub method: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub params: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq, JsonSchema, TS)] -#[serde(untagged)] -pub enum RequestId { - String(String), - Integer(i64), -} - -/// A known resource that the server is capable of reading. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Resource { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub annotations: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub mime_type: Option, - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub size: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - pub uri: String, -} - -/// The contents of a specific resource or sub-resource. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ResourceContents { - #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub mime_type: Option, - pub uri: String, -} - -/// 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, JsonSchema, TS)] -pub struct ResourceLink { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub annotations: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub mime_type: Option, - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub size: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - pub r#type: String, // &'static str = "resource_link" - pub uri: String, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ResourceListChangedNotification {} - -impl ModelContextProtocolNotification for ResourceListChangedNotification { - const METHOD: &'static str = "notifications/resources/list_changed"; - type Params = Option; -} - -/// A template description for resources available on the server. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ResourceTemplate { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub annotations: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub mime_type: Option, - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - #[serde(rename = "uriTemplate")] - pub uri_template: String, -} - -/// A reference to a resource or resource template definition. -#[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, JsonSchema, TS)] -pub enum ResourceUpdatedNotification {} - -impl ModelContextProtocolNotification for ResourceUpdatedNotification { - const METHOD: &'static str = "notifications/resources/updated"; - type Params = ResourceUpdatedNotificationParams; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ResourceUpdatedNotificationParams { - pub uri: String, -} - -pub type Result = serde_json::Value; - -/// The sender or recipient of messages and data in a conversation. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum Role { - #[serde(rename = "assistant")] - Assistant, - #[serde(rename = "user")] - User, -} - -/// Represents a root directory or file that the server can operate on. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Root { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub name: Option, - pub uri: String, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum RootsListChangedNotification {} - -impl ModelContextProtocolNotification for RootsListChangedNotification { - const METHOD: &'static str = "notifications/roots/list_changed"; - type Params = Option; -} - -/// Describes a message issued to or received from an LLM API. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct SamplingMessage { - pub content: SamplingMessageContent, - pub role: Role, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum SamplingMessageContent { - TextContent(TextContent), - ImageContent(ImageContent), - AudioContent(AudioContent), -} - -/// 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, JsonSchema, TS)] -pub struct ServerCapabilities { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub completions: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub experimental: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub logging: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub prompts: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub resources: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub tools: Option, -} - -/// Present if the server offers any tools to call. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ServerCapabilitiesTools { - #[serde( - rename = "listChanged", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub list_changed: Option, -} - -/// Present if the server offers any resources to read. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ServerCapabilitiesResources { - #[serde( - rename = "listChanged", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub list_changed: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub subscribe: Option, -} - -/// Present if the server offers any prompt templates. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ServerCapabilitiesPrompts { - #[serde( - rename = "listChanged", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub list_changed: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(tag = "method", content = "params")] -pub enum ServerNotification { - #[serde(rename = "notifications/cancelled")] - CancelledNotification(::Params), - #[serde(rename = "notifications/progress")] - ProgressNotification(::Params), - #[serde(rename = "notifications/resources/list_changed")] - ResourceListChangedNotification( - ::Params, - ), - #[serde(rename = "notifications/resources/updated")] - ResourceUpdatedNotification( - ::Params, - ), - #[serde(rename = "notifications/prompts/list_changed")] - PromptListChangedNotification( - ::Params, - ), - #[serde(rename = "notifications/tools/list_changed")] - ToolListChangedNotification( - ::Params, - ), - #[serde(rename = "notifications/message")] - LoggingMessageNotification( - ::Params, - ), -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -pub enum ServerRequest { - PingRequest(PingRequest), - CreateMessageRequest(CreateMessageRequest), - ListRootsRequest(ListRootsRequest), - ElicitRequest(ElicitRequest), -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -#[serde(untagged)] -#[allow(clippy::large_enum_variant)] -pub enum ServerResult { - Result(Result), - InitializeResult(InitializeResult), - ListResourcesResult(ListResourcesResult), - ListResourceTemplatesResult(ListResourceTemplatesResult), - ReadResourceResult(ReadResourceResult), - ListPromptsResult(ListPromptsResult), - GetPromptResult(GetPromptResult), - ListToolsResult(ListToolsResult), - CallToolResult(CallToolResult), - CompleteResult(CompleteResult), -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum SetLevelRequest {} - -impl ModelContextProtocolRequest for SetLevelRequest { - const METHOD: &'static str = "logging/setLevel"; - type Params = SetLevelRequestParams; - type Result = Result; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct SetLevelRequestParams { - pub level: LoggingLevel, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct StringSchema { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub format: Option, - #[serde(rename = "maxLength", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub max_length: Option, - #[serde(rename = "minLength", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub min_length: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, - pub r#type: String, // &'static str = "string" -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum SubscribeRequest {} - -impl ModelContextProtocolRequest for SubscribeRequest { - const METHOD: &'static str = "resources/subscribe"; - type Params = SubscribeRequestParams; - type Result = Result; -} - -#[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, JsonSchema, TS)] -pub struct TextContent { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub annotations: Option, - pub text: String, - pub r#type: String, // &'static str = "text" -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct TextResourceContents { - #[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub mime_type: Option, - pub text: String, - pub uri: String, -} - -/// Definition for a tool the client can call. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct Tool { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub annotations: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub description: Option, - #[serde(rename = "inputSchema")] - pub input_schema: ToolInputSchema, - pub name: String, - #[serde( - rename = "outputSchema", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub output_schema: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, -} - -fn tool_output_schema_type_default_str() -> String { - "object".to_string() -} - -/// 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, JsonSchema, TS)] -pub struct ToolOutputSchema { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub properties: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub required: Option>, - #[serde(default = "tool_output_schema_type_default_str")] - pub r#type: String, // &'static str = "object" -} - -fn tool_input_schema_type_default_str() -> String { - "object".to_string() -} - -/// A JSON Schema object defining the expected parameters for the tool. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ToolInputSchema { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub properties: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub required: Option>, - #[serde(default = "tool_input_schema_type_default_str")] - pub r#type: String, // &'static str = "object" -} - -/// Additional properties describing a Tool to clients. -/// -/// NOTE: all properties in ToolAnnotations are **hints**. -/// They are not guaranteed to provide a faithful description of -/// tool behavior (including descriptive properties like `title`). -/// -/// Clients should never make tool use decisions based on ToolAnnotations -/// received from untrusted servers. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct ToolAnnotations { - #[serde( - rename = "destructiveHint", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub destructive_hint: Option, - #[serde( - rename = "idempotentHint", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub idempotent_hint: Option, - #[serde( - rename = "openWorldHint", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub open_world_hint: Option, - #[serde( - rename = "readOnlyHint", - default, - skip_serializing_if = "Option::is_none" - )] - #[ts(optional)] - pub read_only_hint: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub title: Option, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum ToolListChangedNotification {} - -impl ModelContextProtocolNotification for ToolListChangedNotification { - const METHOD: &'static str = "notifications/tools/list_changed"; - type Params = Option; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub enum UnsubscribeRequest {} - -impl ModelContextProtocolRequest for UnsubscribeRequest { - const METHOD: &'static str = "resources/unsubscribe"; - type Params = UnsubscribeRequestParams; - type Result = Result; -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct UnsubscribeRequestParams { - pub uri: String, -} - -impl TryFrom for ClientRequest { - type Error = serde_json::Error; - fn try_from(req: JSONRPCRequest) -> std::result::Result { - match req.method.as_str() { - "initialize" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::InitializeRequest(params)) - } - "ping" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::PingRequest(params)) - } - "resources/list" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::ListResourcesRequest(params)) - } - "resources/templates/list" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::ListResourceTemplatesRequest(params)) - } - "resources/read" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::ReadResourceRequest(params)) - } - "resources/subscribe" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::SubscribeRequest(params)) - } - "resources/unsubscribe" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::UnsubscribeRequest(params)) - } - "prompts/list" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::ListPromptsRequest(params)) - } - "prompts/get" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::GetPromptRequest(params)) - } - "tools/list" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::ListToolsRequest(params)) - } - "tools/call" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::CallToolRequest(params)) - } - "logging/setLevel" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::SetLevelRequest(params)) - } - "completion/complete" => { - let params_json = req.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ClientRequest::CompleteRequest(params)) - } - _ => Err(serde_json::Error::io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Unknown method: {}", req.method), - ))), - } - } -} - -impl TryFrom for ServerNotification { - type Error = serde_json::Error; - fn try_from(n: JSONRPCNotification) -> std::result::Result { - match n.method.as_str() { - "notifications/cancelled" => { - let params_json = n.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ServerNotification::CancelledNotification(params)) - } - "notifications/progress" => { - let params_json = n.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = - serde_json::from_value(params_json)?; - Ok(ServerNotification::ProgressNotification(params)) - } - "notifications/resources/list_changed" => { - let params_json = n.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = serde_json::from_value(params_json)?; - Ok(ServerNotification::ResourceListChangedNotification(params)) - } - "notifications/resources/updated" => { - let params_json = n.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = serde_json::from_value(params_json)?; - Ok(ServerNotification::ResourceUpdatedNotification(params)) - } - "notifications/prompts/list_changed" => { - let params_json = n.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = serde_json::from_value(params_json)?; - Ok(ServerNotification::PromptListChangedNotification(params)) - } - "notifications/tools/list_changed" => { - let params_json = n.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = serde_json::from_value(params_json)?; - Ok(ServerNotification::ToolListChangedNotification(params)) - } - "notifications/message" => { - let params_json = n.params.unwrap_or(serde_json::Value::Null); - let params: ::Params = serde_json::from_value(params_json)?; - Ok(ServerNotification::LoggingMessageNotification(params)) - } - _ => Err(serde_json::Error::io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Unknown method: {}", n.method), - ))), - } - } -} diff --git a/codex-rs/mcp-types/tests/all.rs b/codex-rs/mcp-types/tests/all.rs deleted file mode 100644 index 7e136e4cce..0000000000 --- a/codex-rs/mcp-types/tests/all.rs +++ /dev/null @@ -1,3 +0,0 @@ -// Single integration test binary that aggregates all test modules. -// The submodules live in `tests/suite/`. -mod suite; diff --git a/codex-rs/mcp-types/tests/suite/initialize.rs b/codex-rs/mcp-types/tests/suite/initialize.rs deleted file mode 100644 index 73ff120274..0000000000 --- a/codex-rs/mcp-types/tests/suite/initialize.rs +++ /dev/null @@ -1,70 +0,0 @@ -use mcp_types::ClientCapabilities; -use mcp_types::ClientRequest; -use mcp_types::Implementation; -use mcp_types::InitializeRequestParams; -use mcp_types::JSONRPC_VERSION; -use mcp_types::JSONRPCMessage; -use mcp_types::JSONRPCRequest; -use mcp_types::RequestId; -use serde_json::json; - -#[test] -fn deserialize_initialize_request() { - let raw = r#"{ - "jsonrpc": "2.0", - "id": 1, - "method": "initialize", - "params": { - "capabilities": {}, - "clientInfo": { "name": "acme-client", "title": "Acme", "version": "1.2.3" }, - "protocolVersion": "2025-06-18" - } - }"#; - - // Deserialize full JSONRPCMessage first. - let msg: JSONRPCMessage = - serde_json::from_str(raw).expect("failed to deserialize JSONRPCMessage"); - - // Extract the request variant. - let JSONRPCMessage::Request(json_req) = msg else { - unreachable!() - }; - - let expected_req = JSONRPCRequest { - jsonrpc: JSONRPC_VERSION.into(), - id: RequestId::Integer(1), - method: "initialize".into(), - params: Some(json!({ - "capabilities": {}, - "clientInfo": { "name": "acme-client", "title": "Acme", "version": "1.2.3" }, - "protocolVersion": "2025-06-18" - })), - }; - - assert_eq!(json_req, expected_req); - - let client_req: ClientRequest = - ClientRequest::try_from(json_req).expect("conversion must succeed"); - let ClientRequest::InitializeRequest(init_params) = client_req else { - unreachable!() - }; - - assert_eq!( - init_params, - InitializeRequestParams { - capabilities: ClientCapabilities { - experimental: None, - roots: None, - sampling: None, - elicitation: None, - }, - client_info: Implementation { - name: "acme-client".into(), - title: Some("Acme".to_string()), - version: "1.2.3".into(), - user_agent: None, - }, - protocol_version: "2025-06-18".into(), - } - ); -} diff --git a/codex-rs/mcp-types/tests/suite/mod.rs b/codex-rs/mcp-types/tests/suite/mod.rs deleted file mode 100644 index 94f4709c90..0000000000 --- a/codex-rs/mcp-types/tests/suite/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// Aggregates all former standalone integration tests as modules. -mod initialize; -mod progress_notification; diff --git a/codex-rs/mcp-types/tests/suite/progress_notification.rs b/codex-rs/mcp-types/tests/suite/progress_notification.rs deleted file mode 100644 index 396efca2bd..0000000000 --- a/codex-rs/mcp-types/tests/suite/progress_notification.rs +++ /dev/null @@ -1,43 +0,0 @@ -use mcp_types::JSONRPCMessage; -use mcp_types::ProgressNotificationParams; -use mcp_types::ProgressToken; -use mcp_types::ServerNotification; - -#[test] -fn deserialize_progress_notification() { - let raw = r#"{ - "jsonrpc": "2.0", - "method": "notifications/progress", - "params": { - "message": "Half way there", - "progress": 0.5, - "progressToken": 99, - "total": 1.0 - } - }"#; - - // Deserialize full JSONRPCMessage first. - let msg: JSONRPCMessage = serde_json::from_str(raw).expect("invalid JSONRPCMessage"); - - // Extract the notification variant. - let JSONRPCMessage::Notification(notif) = msg else { - unreachable!() - }; - - // Convert via generated TryFrom. - let server_notif: ServerNotification = - ServerNotification::try_from(notif).expect("conversion must succeed"); - - let ServerNotification::ProgressNotification(params) = server_notif else { - unreachable!() - }; - - let expected_params = ProgressNotificationParams { - message: Some("Half way there".into()), - progress: 0.5, - progress_token: ProgressToken::Integer(99), - total: Some(1.0), - }; - - assert_eq!(params, expected_params); -} From 0999fd82b9653f0280e4ffc9d29e910ad499c1fc Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 2 Feb 2026 18:37:01 -0800 Subject: [PATCH 4/9] app tool tip (#10454) # External (non-OpenAI) Pull Request Requirements Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed: https://github.com/openai/codex/blob/main/docs/contributing.md If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes. Include a link to a bug report or enhancement request. --- codex-rs/tui/src/tooltips.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codex-rs/tui/src/tooltips.rs b/codex-rs/tui/src/tooltips.rs index b59a478a4d..b62831e7e6 100644 --- a/codex-rs/tui/src/tooltips.rs +++ b/codex-rs/tui/src/tooltips.rs @@ -6,10 +6,9 @@ use rand::Rng; const ANNOUNCEMENT_TIP_URL: &str = "https://raw.githubusercontent.com/openai/codex/main/announcement_tip.toml"; -const PAID_TOOLTIP: &str = - "*New* Try the **Codex App** with 2x rate limits until *April 2nd*. https://chatgpt.com/codex"; +const PAID_TOOLTIP: &str = "*New* Try the **Codex App** with 2x rate limits until *April 2nd*. Run 'codex app' or visit https://chatgpt.com/codex"; const OTHER_TOOLTIP: &str = - "*New* Build faster with the **Codex App**. Try it now. https://chatgpt.com/codex"; + "*New* Build faster with the **Codex App**. Run 'codex app' or visit https://chatgpt.com/codex"; const FREE_GO_TOOLTIP: &str = "*New* Codex is included in your plan for free through *March 2nd* – let’s build together."; From fc05374344dd50bdd58888e111fa8372376f45e3 Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Mon, 2 Feb 2026 18:52:26 -0800 Subject: [PATCH 5/9] chore: add phase to message responseitem (#10455) ### What add wiring for `phase` field on `ResponseItem::Message` to lay groundwork for differentiating model preambles and final messages. currently optional. follows pattern in #9698. updated schemas with `just write-app-server-schema` so we can see type changes. ### Tests Updated existing tests for SSE parsing and hydrating from history --- .../schema/json/ClientRequest.json | 17 ++++++++++ .../schema/json/EventMsg.json | 17 ++++++++++ .../schema/json/ServerNotification.json | 17 ++++++++++ .../codex_app_server_protocol.schemas.json | 34 +++++++++++++++++++ .../json/v1/ForkConversationResponse.json | 17 ++++++++++ .../json/v1/ResumeConversationParams.json | 17 ++++++++++ .../json/v1/ResumeConversationResponse.json | 17 ++++++++++ .../v1/SessionConfiguredNotification.json | 17 ++++++++++ .../RawResponseItemCompletedNotification.json | 17 ++++++++++ .../schema/json/v2/ThreadResumeParams.json | 17 ++++++++++ .../schema/typescript/MessagePhase.ts | 5 +++ .../schema/typescript/ResponseItem.ts | 3 +- .../schema/typescript/index.ts | 1 + .../app-server/tests/suite/list_resume.rs | 1 + .../app-server/tests/suite/v2/compaction.rs | 1 + .../tests/suite/v2/thread_resume.rs | 1 + codex-rs/codex-api/src/endpoint/chat.rs | 1 + codex-rs/codex-api/src/requests/chat.rs | 2 ++ codex-rs/codex-api/src/requests/responses.rs | 2 ++ codex-rs/codex-api/src/sse/chat.rs | 1 + codex-rs/codex-api/src/sse/responses.rs | 11 ++++-- codex-rs/codex-api/tests/clients.rs | 1 + codex-rs/core/src/codex.rs | 13 +++++++ codex-rs/core/src/compact.rs | 9 +++++ .../core/src/context_manager/history_tests.rs | 7 ++++ codex-rs/core/src/environment_context.rs | 1 + codex-rs/core/src/event_mapping.rs | 9 +++++ .../src/instructions/user_instructions.rs | 2 ++ codex-rs/core/src/rollout/tests.rs | 1 + codex-rs/core/src/rollout/truncation.rs | 2 ++ codex-rs/core/src/tasks/mod.rs | 1 + codex-rs/core/src/tasks/review.rs | 2 ++ codex-rs/core/src/thread_manager.rs | 2 ++ codex-rs/core/src/user_shell_command.rs | 1 + .../core/tests/chat_completions_payload.rs | 2 ++ codex-rs/core/tests/chat_completions_sse.rs | 1 + codex-rs/core/tests/responses_headers.rs | 4 +++ codex-rs/core/tests/suite/client.rs | 24 +++++++++++++ .../core/tests/suite/client_websockets.rs | 1 + codex-rs/core/tests/suite/compact.rs | 3 ++ codex-rs/core/tests/suite/compact_remote.rs | 5 +++ codex-rs/core/tests/suite/image_rollout.rs | 2 ++ codex-rs/core/tests/suite/review.rs | 2 ++ codex-rs/protocol/src/models.rs | 14 ++++++++ codex-rs/protocol/src/protocol.rs | 1 + codex-rs/state/src/extract.rs | 1 + 46 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/MessagePhase.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 85e5940bc3..6f5ebcdf0f 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1038,6 +1038,13 @@ ], "type": "string" }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ModeKind": { "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ @@ -1317,6 +1324,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json index 9e3cbb4156..3e73bdc8b6 100644 --- a/codex-rs/app-server-protocol/schema/json/EventMsg.json +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -3111,6 +3111,13 @@ } ] }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ModeKind": { "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ @@ -3596,6 +3603,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 6df9a010b1..5d9e719d8f 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3890,6 +3890,13 @@ ], "type": "string" }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ModeKind": { "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ @@ -4636,6 +4643,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index e92d46e7b6..571079c7c0 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -5839,6 +5839,13 @@ } ] }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ModeKind": { "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ @@ -6536,6 +6543,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, @@ -11731,6 +11748,13 @@ ], "type": "string" }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ModeKind": { "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ @@ -12449,6 +12473,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/v2/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json index ae6e705221..ad251a461f 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json @@ -3111,6 +3111,13 @@ } ] }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ModeKind": { "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ @@ -3596,6 +3603,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json index e1cc626cc8..41284b009b 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json @@ -266,6 +266,13 @@ ], "type": "string" }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "NewConversationParams": { "properties": { "approvalPolicy": { @@ -437,6 +444,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json index cdad2018c6..abd5beb211 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json @@ -3111,6 +3111,13 @@ } ] }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ModeKind": { "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ @@ -3596,6 +3603,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json index e6ed4d0427..69c2c4b8b0 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json @@ -3111,6 +3111,13 @@ } ] }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ModeKind": { "description": "Initial collaboration mode to use when the TUI starts.", "enum": [ @@ -3596,6 +3603,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json index 47039df05b..aecb8c2b58 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json @@ -233,6 +233,13 @@ ], "type": "string" }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "ReasoningItemContent": { "oneOf": [ { @@ -324,6 +331,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json index 3da1e3c749..52567cec82 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json @@ -242,6 +242,13 @@ ], "type": "string" }, + "MessagePhase": { + "enum": [ + "commentary", + "final_answer" + ], + "type": "string" + }, "Personality": { "enum": [ "friendly", @@ -340,6 +347,16 @@ ], "writeOnly": true }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, "role": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/MessagePhase.ts b/codex-rs/app-server-protocol/schema/typescript/MessagePhase.ts new file mode 100644 index 0000000000..d339c0fa83 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/MessagePhase.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type MessagePhase = "commentary" | "final_answer"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts b/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts index d720021612..611c7fb22d 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ResponseItem.ts @@ -6,11 +6,12 @@ import type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload"; import type { GhostCommit } from "./GhostCommit"; import type { LocalShellAction } from "./LocalShellAction"; import type { LocalShellStatus } from "./LocalShellStatus"; +import type { MessagePhase } from "./MessagePhase"; import type { ReasoningItemContent } from "./ReasoningItemContent"; import type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSummary"; import type { WebSearchAction } from "./WebSearchAction"; -export type ResponseItem = { "type": "message", role: string, content: Array, end_turn?: boolean, } | { "type": "reasoning", summary: Array, content?: Array, encrypted_content: string | null, } | { "type": "local_shell_call", +export type ResponseItem = { "type": "message", role: string, content: Array, end_turn?: boolean, phase?: MessagePhase, } | { "type": "reasoning", summary: Array, content?: Array, encrypted_content: string | null, } | { "type": "local_shell_call", /** * Set when using the Responses API. */ diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index 191f993864..b24e42a834 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -115,6 +115,7 @@ export type { McpStartupStatus } from "./McpStartupStatus"; export type { McpStartupUpdateEvent } from "./McpStartupUpdateEvent"; export type { McpToolCallBeginEvent } from "./McpToolCallBeginEvent"; export type { McpToolCallEndEvent } from "./McpToolCallEndEvent"; +export type { MessagePhase } from "./MessagePhase"; export type { ModeKind } from "./ModeKind"; export type { NetworkAccess } from "./NetworkAccess"; export type { NewConversationParams } from "./NewConversationParams"; diff --git a/codex-rs/app-server/tests/suite/list_resume.rs b/codex-rs/app-server/tests/suite/list_resume.rs index f2b82c6111..efa90ea351 100644 --- a/codex-rs/app-server/tests/suite/list_resume.rs +++ b/codex-rs/app-server/tests/suite/list_resume.rs @@ -308,6 +308,7 @@ async fn test_list_and_resume_conversations() -> Result<()> { text: fork_history_text.to_string(), }], end_turn: None, + phase: None, }]; let resume_with_history_req_id = mcp .send_resume_conversation_request(ResumeConversationParams { diff --git a/codex-rs/app-server/tests/suite/v2/compaction.rs b/codex-rs/app-server/tests/suite/v2/compaction.rs index 66cf43bc0a..eeedcb286b 100644 --- a/codex-rs/app-server/tests/suite/v2/compaction.rs +++ b/codex-rs/app-server/tests/suite/v2/compaction.rs @@ -126,6 +126,7 @@ async fn auto_compaction_remote_emits_started_and_completed_items() -> Result<() text: "REMOTE_COMPACT_SUMMARY".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Compaction { encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index efa51573cb..358fec351f 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -338,6 +338,7 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> { text: history_text.to_string(), }], end_turn: None, + phase: None, }]; // Resume with explicit history and override the model. diff --git a/codex-rs/codex-api/src/endpoint/chat.rs b/codex-rs/codex-api/src/endpoint/chat.rs index 2148a5ad93..b183597d65 100644 --- a/codex-rs/codex-api/src/endpoint/chat.rs +++ b/codex-rs/codex-api/src/endpoint/chat.rs @@ -194,6 +194,7 @@ impl Stream for AggregatedStream { text: std::mem::take(&mut this.cumulative), }], end_turn: None, + phase: None, }; this.pending .push_back(ResponseEvent::OutputItemDone(aggregated_message)); diff --git a/codex-rs/codex-api/src/requests/chat.rs b/codex-rs/codex-api/src/requests/chat.rs index 5c16a5fb58..60a591e5c2 100644 --- a/codex-rs/codex-api/src/requests/chat.rs +++ b/codex-rs/codex-api/src/requests/chat.rs @@ -387,6 +387,7 @@ mod tests { text: "hi".to_string(), }], end_turn: None, + phase: None, }]; let req = ChatRequestBuilder::new("gpt-test", "inst", &prompt_input, &[]) .conversation_id(Some("conv-1".into())) @@ -414,6 +415,7 @@ mod tests { text: "read these".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::FunctionCall { id: None, diff --git a/codex-rs/codex-api/src/requests/responses.rs b/codex-rs/codex-api/src/requests/responses.rs index 73a413dd93..a0e90db066 100644 --- a/codex-rs/codex-api/src/requests/responses.rs +++ b/codex-rs/codex-api/src/requests/responses.rs @@ -224,12 +224,14 @@ mod tests { role: "assistant".into(), content: Vec::new(), end_turn: None, + phase: None, }, ResponseItem::Message { id: None, role: "assistant".into(), content: Vec::new(), end_turn: None, + phase: None, }, ]; diff --git a/codex-rs/codex-api/src/sse/chat.rs b/codex-rs/codex-api/src/sse/chat.rs index a3effce2e7..4b78845285 100644 --- a/codex-rs/codex-api/src/sse/chat.rs +++ b/codex-rs/codex-api/src/sse/chat.rs @@ -331,6 +331,7 @@ async fn append_assistant_text( role: "assistant".to_string(), content: vec![], end_turn: None, + phase: None, }; *assistant_item = Some(item.clone()); let _ = tx_event diff --git a/codex-rs/codex-api/src/sse/responses.rs b/codex-rs/codex-api/src/sse/responses.rs index b363671a11..2c91104727 100644 --- a/codex-rs/codex-api/src/sse/responses.rs +++ b/codex-rs/codex-api/src/sse/responses.rs @@ -429,6 +429,7 @@ mod tests { use super::*; use assert_matches::assert_matches; use bytes::Bytes; + use codex_protocol::models::MessagePhase; use codex_protocol::models::ResponseItem; use futures::stream; use pretty_assertions::assert_eq; @@ -492,7 +493,8 @@ mod tests { "item": { "type": "message", "role": "assistant", - "content": [{"type": "output_text", "text": "Hello"}] + "content": [{"type": "output_text", "text": "Hello"}], + "phase": "commentary" } }) .to_string(); @@ -523,8 +525,11 @@ mod tests { assert_matches!( &events[0], - Ok(ResponseEvent::OutputItemDone(ResponseItem::Message { role, .. })) - if role == "assistant" + Ok(ResponseEvent::OutputItemDone(ResponseItem::Message { + role, + phase: Some(MessagePhase::Commentary), + .. + })) if role == "assistant" ); assert_matches!( diff --git a/codex-rs/codex-api/tests/clients.rs b/codex-rs/codex-api/tests/clients.rs index b71edf3244..3040fb8724 100644 --- a/codex-rs/codex-api/tests/clients.rs +++ b/codex-rs/codex-api/tests/clients.rs @@ -309,6 +309,7 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> { text: "hi".to_string(), }], end_turn: None, + phase: None, }], tools: Vec::::new(), parallel_tool_calls: false, diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 6cc389b531..7c99093558 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1834,6 +1834,7 @@ impl Session { text: format!("Warning: {}", message.into()), }], end_turn: None, + phase: None, }; self.record_conversation_items(ctx, &[item]).await; @@ -4542,6 +4543,7 @@ mod tests { text: text.to_string(), }], end_turn: None, + phase: None, } } @@ -4823,6 +4825,7 @@ mod tests { text: "turn 1 user".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -4831,6 +4834,7 @@ mod tests { text: "turn 1 assistant".to_string(), }], end_turn: None, + phase: None, }, ]; sess.record_into_history(&turn_1, tc.as_ref()).await; @@ -4843,6 +4847,7 @@ mod tests { text: "turn 2 user".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -4851,6 +4856,7 @@ mod tests { text: "turn 2 assistant".to_string(), }], end_turn: None, + phase: None, }, ]; sess.record_into_history(&turn_2, tc.as_ref()).await; @@ -4883,6 +4889,7 @@ mod tests { text: "turn 1 user".to_string(), }], end_turn: None, + phase: None, }]; sess.record_into_history(&turn_1, tc.as_ref()).await; @@ -5832,6 +5839,7 @@ mod tests { text: "first user".to_string(), }], end_turn: None, + phase: None, }; live_history.record_items(std::iter::once(&user1), turn_context.truncation_policy); rollout_items.push(RolloutItem::ResponseItem(user1.clone())); @@ -5843,6 +5851,7 @@ mod tests { text: "assistant reply one".to_string(), }], end_turn: None, + phase: None, }; live_history.record_items(std::iter::once(&assistant1), turn_context.truncation_policy); rollout_items.push(RolloutItem::ResponseItem(assistant1.clone())); @@ -5868,6 +5877,7 @@ mod tests { text: "second user".to_string(), }], end_turn: None, + phase: None, }; live_history.record_items(std::iter::once(&user2), turn_context.truncation_policy); rollout_items.push(RolloutItem::ResponseItem(user2.clone())); @@ -5879,6 +5889,7 @@ mod tests { text: "assistant reply two".to_string(), }], end_turn: None, + phase: None, }; live_history.record_items(std::iter::once(&assistant2), turn_context.truncation_policy); rollout_items.push(RolloutItem::ResponseItem(assistant2.clone())); @@ -5904,6 +5915,7 @@ mod tests { text: "third user".to_string(), }], end_turn: None, + phase: None, }; live_history.record_items(std::iter::once(&user3), turn_context.truncation_policy); rollout_items.push(RolloutItem::ResponseItem(user3)); @@ -5915,6 +5927,7 @@ mod tests { text: "assistant reply three".to_string(), }], end_turn: None, + phase: None, }; live_history.record_items(std::iter::once(&assistant3), turn_context.truncation_policy); rollout_items.push(RolloutItem::ResponseItem(assistant3)); diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index b44d43f42e..ee94e4994b 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -311,6 +311,7 @@ fn build_compacted_history_with_limit( text: message.clone(), }], end_turn: None, + phase: None, }); } @@ -325,6 +326,7 @@ fn build_compacted_history_with_limit( role: "user".to_string(), content: vec![ContentItem::InputText { text: summary_text }], end_turn: None, + phase: None, }); history @@ -416,6 +418,7 @@ mod tests { text: "ignored".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: Some("user".to_string()), @@ -424,6 +427,7 @@ mod tests { text: "first".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Other, ]; @@ -444,6 +448,7 @@ mod tests { .to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -452,6 +457,7 @@ mod tests { text: "cwd=/tmp".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -460,6 +466,7 @@ mod tests { text: "real user message".to_string(), }], end_turn: None, + phase: None, }, ]; @@ -545,6 +552,7 @@ mod tests { text: marker.clone(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -553,6 +561,7 @@ mod tests { text: "real user message".to_string(), }], end_turn: None, + phase: None, }, ]; diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index 50ee98e5b6..d31f731a98 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -24,6 +24,7 @@ fn assistant_msg(text: &str) -> ResponseItem { text: text.to_string(), }], end_turn: None, + phase: None, } } @@ -43,6 +44,7 @@ fn user_msg(text: &str) -> ResponseItem { text: text.to_string(), }], end_turn: None, + phase: None, } } @@ -54,6 +56,7 @@ fn user_input_text_msg(text: &str) -> ResponseItem { text: text.to_string(), }], end_turn: None, + phase: None, } } @@ -97,6 +100,7 @@ fn filters_non_api_messages() { text: "ignored".to_string(), }], end_turn: None, + phase: None, }; let reasoning = reasoning_msg("thinking..."); h.record_items([&system, &reasoning, &ResponseItem::Other], policy); @@ -127,6 +131,7 @@ fn filters_non_api_messages() { text: "hi".to_string() }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -135,6 +140,7 @@ fn filters_non_api_messages() { text: "hello".to_string() }], end_turn: None, + phase: None, } ] ); @@ -262,6 +268,7 @@ fn replace_last_turn_images_does_not_touch_user_images() { image_url: "data:image/png;base64,AAA".to_string(), }], end_turn: None, + phase: None, }]; let mut history = create_history_with_items(items.clone()); diff --git a/codex-rs/core/src/environment_context.rs b/codex-rs/core/src/environment_context.rs index 87692a2f05..9f5455a69f 100644 --- a/codex-rs/core/src/environment_context.rs +++ b/codex-rs/core/src/environment_context.rs @@ -81,6 +81,7 @@ impl From for ResponseItem { text: ec.serialize_to_xml(), }], end_turn: None, + phase: None, } } } diff --git a/codex-rs/core/src/event_mapping.rs b/codex-rs/core/src/event_mapping.rs index 35804fd47c..2ad19d3df5 100644 --- a/codex-rs/core/src/event_mapping.rs +++ b/codex-rs/core/src/event_mapping.rs @@ -177,6 +177,7 @@ mod tests { }, ], end_turn: None, + phase: None, }; let turn_item = parse_turn_item(&item).expect("expected user message turn item"); @@ -219,6 +220,7 @@ mod tests { }, ], end_turn: None, + phase: None, }; let turn_item = parse_turn_item(&item).expect("expected user message turn item"); @@ -260,6 +262,7 @@ mod tests { }, ], end_turn: None, + phase: None, }; let turn_item = parse_turn_item(&item).expect("expected user message turn item"); @@ -289,6 +292,7 @@ mod tests { text: "test_text".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -297,6 +301,7 @@ mod tests { text: "test_text".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -305,6 +310,7 @@ mod tests { text: "# AGENTS.md instructions for test_directory\n\n\ntest_text\n".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -314,6 +320,7 @@ mod tests { .to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Message { id: None, @@ -322,6 +329,7 @@ mod tests { text: "echo 42".to_string(), }], end_turn: None, + phase: None, }, ]; @@ -340,6 +348,7 @@ mod tests { text: "Hello from Codex".to_string(), }], end_turn: None, + phase: None, }; let turn_item = parse_turn_item(&item).expect("expected agent message turn item"); diff --git a/codex-rs/core/src/instructions/user_instructions.rs b/codex-rs/core/src/instructions/user_instructions.rs index 611bf4cbf8..525834847e 100644 --- a/codex-rs/core/src/instructions/user_instructions.rs +++ b/codex-rs/core/src/instructions/user_instructions.rs @@ -39,6 +39,7 @@ impl From for ResponseItem { ), }], end_turn: None, + phase: None, } } } @@ -73,6 +74,7 @@ impl From for ResponseItem { ), }], end_turn: None, + phase: None, } } } diff --git a/codex-rs/core/src/rollout/tests.rs b/codex-rs/core/src/rollout/tests.rs index cdf16291d3..ee750b126d 100644 --- a/codex-rs/core/src/rollout/tests.rs +++ b/codex-rs/core/src/rollout/tests.rs @@ -902,6 +902,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> { text: format!("reply-{idx}"), }], end_turn: None, + phase: None, }), }; writeln!(file, "{}", serde_json::to_string(&response_line)?)?; diff --git a/codex-rs/core/src/rollout/truncation.rs b/codex-rs/core/src/rollout/truncation.rs index e6a84628fc..c50eacc48b 100644 --- a/codex-rs/core/src/rollout/truncation.rs +++ b/codex-rs/core/src/rollout/truncation.rs @@ -86,6 +86,7 @@ mod tests { text: text.to_string(), }], end_turn: None, + phase: None, } } @@ -97,6 +98,7 @@ mod tests { text: text.to_string(), }], end_turn: None, + phase: None, } } diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index a487041eba..d5d6f60582 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -265,6 +265,7 @@ impl Session { ), }], end_turn: None, + phase: None, }; self.record_into_history(std::slice::from_ref(&marker), task.turn_context.as_ref()) .await; diff --git a/codex-rs/core/src/tasks/review.rs b/codex-rs/core/src/tasks/review.rs index d156d3e0d5..cfc554f856 100644 --- a/codex-rs/core/src/tasks/review.rs +++ b/codex-rs/core/src/tasks/review.rs @@ -222,6 +222,7 @@ pub(crate) async fn exit_review_mode( role: "user".to_string(), content: vec![ContentItem::InputText { text: user_message }], end_turn: None, + phase: None, }], ) .await; @@ -241,6 +242,7 @@ pub(crate) async fn exit_review_mode( text: assistant_message, }], end_turn: None, + phase: None, }, ) .await; diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index 9b633ded37..37bd0efabc 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -462,6 +462,7 @@ mod tests { text: text.to_string(), }], end_turn: None, + phase: None, } } fn assistant_msg(text: &str) -> ResponseItem { @@ -472,6 +473,7 @@ mod tests { text: text.to_string(), }], end_turn: None, + phase: None, } } diff --git a/codex-rs/core/src/user_shell_command.rs b/codex-rs/core/src/user_shell_command.rs index 566f39958e..80128df006 100644 --- a/codex-rs/core/src/user_shell_command.rs +++ b/codex-rs/core/src/user_shell_command.rs @@ -63,6 +63,7 @@ pub fn user_shell_command_record_item( text: format_user_shell_command_record(command, exec_output, turn_context), }], end_turn: None, + phase: None, } } diff --git a/codex-rs/core/tests/chat_completions_payload.rs b/codex-rs/core/tests/chat_completions_payload.rs index 5406b44ad6..2772091441 100644 --- a/codex-rs/core/tests/chat_completions_payload.rs +++ b/codex-rs/core/tests/chat_completions_payload.rs @@ -139,6 +139,7 @@ fn user_message(text: &str) -> ResponseItem { text: text.to_string(), }], end_turn: None, + phase: None, } } @@ -150,6 +151,7 @@ fn assistant_message(text: &str) -> ResponseItem { text: text.to_string(), }], end_turn: None, + phase: None, } } diff --git a/codex-rs/core/tests/chat_completions_sse.rs b/codex-rs/core/tests/chat_completions_sse.rs index 8360237752..98e2fd9f0f 100644 --- a/codex-rs/core/tests/chat_completions_sse.rs +++ b/codex-rs/core/tests/chat_completions_sse.rs @@ -113,6 +113,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec { text: "hello".to_string(), }], end_turn: None, + phase: None, }]; let mut stream = match client.stream(&prompt).await { diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index a40c92d02d..86e511c472 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -110,6 +110,7 @@ async fn responses_stream_includes_subagent_header_on_review() { text: "hello".into(), }], end_turn: None, + phase: None, }]; let mut stream = client_session.stream(&prompt).await.expect("stream failed"); @@ -209,6 +210,7 @@ async fn responses_stream_includes_subagent_header_on_other() { text: "hello".into(), }], end_turn: None, + phase: None, }]; let mut stream = client_session.stream(&prompt).await.expect("stream failed"); @@ -366,6 +368,7 @@ async fn responses_respects_model_info_overrides_from_config() { text: "hello".into(), }], end_turn: None, + phase: None, }]; let mut stream = client.stream(&prompt).await.expect("stream failed"); @@ -473,6 +476,7 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() text: "hello".into(), }], end_turn: None, + phase: None, }]; let first_request = responses::mount_sse_once(&server, response_body.clone()).await; diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index d79ef22b51..d0f2ba1ae7 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -29,6 +29,7 @@ use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::Settings; use codex_protocol::config_types::Verbosity; use codex_protocol::models::FunctionCallOutputPayload; +use codex_protocol::models::MessagePhase; use codex_protocol::models::ReasoningItemContent; use codex_protocol::models::ReasoningItemReasoningSummary; use codex_protocol::models::WebSearchAction; @@ -197,6 +198,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { text: "resumed user message".to_string(), }], end_turn: None, + phase: None, }; let prior_user_json = serde_json::to_value(&prior_user).unwrap(); writeln!( @@ -218,6 +220,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { text: "resumed system instruction".to_string(), }], end_turn: None, + phase: None, }; let prior_system_json = serde_json::to_value(&prior_system).unwrap(); writeln!( @@ -239,6 +242,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { text: "resumed assistant message".to_string(), }], end_turn: None, + phase: Some(MessagePhase::Commentary), }; let prior_item_json = serde_json::to_value(&prior_item).unwrap(); writeln!( @@ -318,6 +322,25 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { .iter() .position(|(role, text)| role == "assistant" && text == "resumed assistant message") .expect("prior assistant message"); + let prior_assistant = input + .iter() + .find(|item| { + item.get("role").and_then(|role| role.as_str()) == Some("assistant") + && item + .get("content") + .and_then(|content| content.as_array()) + .and_then(|content| content.first()) + .and_then(|entry| entry.get("text")) + .and_then(|text| text.as_str()) + == Some("resumed assistant message") + }) + .expect("resumed assistant message request item"); + assert_eq!( + prior_assistant + .get("phase") + .and_then(|phase| phase.as_str()), + Some("commentary") + ); let pos_permissions = messages .iter() .position(|(role, text)| role == "developer" && text.contains("")) @@ -1210,6 +1233,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { text: "message".into(), }], end_turn: None, + phase: None, }); prompt.input.push(ResponseItem::WebSearchCall { id: Some("web-search-id".into()), diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 1acc3756d0..51c94edd7b 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -211,6 +211,7 @@ fn message_item(text: &str) -> ResponseItem { role: "user".into(), content: vec![ContentItem::InputText { text: text.into() }], end_turn: None, + phase: None, } } diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index fa07bf7f9b..ea9d2f92fa 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -1452,6 +1452,7 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() { text: remote_summary.to_string(), }], end_turn: None, + phase: None, }, codex_protocol::models::ResponseItem::Compaction { encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), @@ -2275,6 +2276,7 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() { text: "REMOTE_COMPACT_SUMMARY".to_string(), }], end_turn: None, + phase: None, }, codex_protocol::models::ResponseItem::Compaction { encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), @@ -2395,6 +2397,7 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() { text: "REMOTE_COMPACT_SUMMARY".to_string(), }], end_turn: None, + phase: None, }, codex_protocol::models::ResponseItem::Compaction { encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index 563aff7826..e5446f4a39 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -62,6 +62,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> { text: "REMOTE_COMPACTED_SUMMARY".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Compaction { encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), @@ -184,6 +185,7 @@ async fn remote_compact_runs_automatically() -> Result<()> { text: "REMOTE_COMPACTED_SUMMARY".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Compaction { encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), @@ -251,6 +253,7 @@ async fn remote_manual_compact_emits_context_compaction_items() -> Result<()> { text: "REMOTE_COMPACTED_SUMMARY".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Compaction { encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), @@ -352,6 +355,7 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()> text: "COMPACTED_USER_SUMMARY".to_string(), }], end_turn: None, + phase: None, }, ResponseItem::Compaction { encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(), @@ -363,6 +367,7 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()> text: "COMPACTED_ASSISTANT_NOTE".to_string(), }], end_turn: None, + phase: None, }, ]; let compact_mock = responses::mount_compact_json_once( diff --git a/codex-rs/core/tests/suite/image_rollout.rs b/codex-rs/core/tests/suite/image_rollout.rs index 6aebe6630d..691531e049 100644 --- a/codex-rs/core/tests/suite/image_rollout.rs +++ b/codex-rs/core/tests/suite/image_rollout.rs @@ -158,6 +158,7 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu }, ], end_turn: None, + phase: None, }; assert_eq!(actual, expected); @@ -239,6 +240,7 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()> }, ], end_turn: None, + phase: None, }; assert_eq!(actual, expected); diff --git a/codex-rs/core/tests/suite/review.rs b/codex-rs/core/tests/suite/review.rs index cfc4a80488..1c9c3adf7b 100644 --- a/codex-rs/core/tests/suite/review.rs +++ b/codex-rs/core/tests/suite/review.rs @@ -530,6 +530,7 @@ async fn review_input_isolated_from_parent_history() { text: "parent: earlier user message".to_string(), }], end_turn: None, + phase: None, }; let user_json = serde_json::to_value(&user).unwrap(); let user_line = serde_json::json!({ @@ -549,6 +550,7 @@ async fn review_input_isolated_from_parent_history() { text: "parent: assistant reply".to_string(), }], end_turn: None, + phase: None, }; let assistant_json = serde_json::to_value(&assistant).unwrap(); let assistant_line = serde_json::json!({ diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 07c8856fe6..74cc9a53f1 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -72,6 +72,13 @@ pub enum ContentItem { OutputText { text: String }, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +pub enum MessagePhase { + Commentary, + FinalAnswer, +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ResponseItem { @@ -85,6 +92,11 @@ pub enum ResponseItem { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] end_turn: Option, + // Optional output-message phase (for example: "commentary", "final_answer"). + // Do not use directly; availability can vary by provider and model. + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + phase: Option, }, Reasoning { #[serde(default, skip_serializing)] @@ -439,6 +451,7 @@ impl From for ResponseItem { text: di.into_text(), }], end_turn: None, + phase: None, } } } @@ -596,6 +609,7 @@ impl From for ResponseItem { content, id: None, end_turn: None, + phase: None, }, ResponseInputItem::FunctionCallOutput { call_id, output } => { Self::FunctionCallOutput { call_id, output } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 71a7973c6d..bf193cca9c 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -1691,6 +1691,7 @@ impl From for ResponseItem { text: value.message, }], end_turn: None, + phase: None, } } } diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index 8d327a4d1a..ad40118086 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -151,6 +151,7 @@ mod tests { }, ], end_turn: None, + phase: None, }; let actual = extract_user_message_text(&item); assert_eq!(actual.as_deref(), Some("actual question")); From b8addcddb9f60f81da60648639055f15cf5b17c6 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 2 Feb 2026 18:55:25 -0800 Subject: [PATCH 6/9] Require models refresh on cli version mismatch (#10414) --- .../app-server/tests/common/models_cache.rs | 2 + codex-rs/core/src/models_manager/cache.rs | 29 ++- codex-rs/core/src/models_manager/manager.rs | 88 +++++++-- codex-rs/core/src/models_manager/mod.rs | 10 ++ codex-rs/core/tests/suite/models_cache_ttl.rs | 167 +++++++++++++++++- 5 files changed, 279 insertions(+), 17 deletions(-) diff --git a/codex-rs/app-server/tests/common/models_cache.rs b/codex-rs/app-server/tests/common/models_cache.rs index 59fddc2427..c3f00cf094 100644 --- a/codex-rs/app-server/tests/common/models_cache.rs +++ b/codex-rs/app-server/tests/common/models_cache.rs @@ -75,9 +75,11 @@ pub fn write_models_cache_with_models( let cache_path = codex_home.join("models_cache.json"); // DateTime serializes to RFC3339 format by default with serde let fetched_at: DateTime = Utc::now(); + let client_version = codex_core::models_manager::client_version_to_whole(); let cache = json!({ "fetched_at": fetched_at, "etag": null, + "client_version": client_version, "models": models }); std::fs::write(cache_path, serde_json::to_string_pretty(&cache)?) diff --git a/codex-rs/core/src/models_manager/cache.rs b/codex-rs/core/src/models_manager/cache.rs index 07c5784f6c..e95b634b51 100644 --- a/codex-rs/core/src/models_manager/cache.rs +++ b/codex-rs/core/src/models_manager/cache.rs @@ -27,7 +27,7 @@ impl ModelsCacheManager { } /// Attempt to load a fresh cache entry. Returns `None` if the cache doesn't exist or is stale. - pub(crate) async fn load_fresh(&self) -> Option { + pub(crate) async fn load_fresh(&self, expected_version: &str) -> Option { let cache = match self.load().await { Ok(cache) => cache?, Err(err) => { @@ -35,6 +35,9 @@ impl ModelsCacheManager { return None; } }; + if cache.client_version.as_deref() != Some(expected_version) { + return None; + } if !cache.is_fresh(self.cache_ttl) { return None; } @@ -42,10 +45,16 @@ impl ModelsCacheManager { } /// Persist the cache to disk, creating parent directories as needed. - pub(crate) async fn persist_cache(&self, models: &[ModelInfo], etag: Option) { + pub(crate) async fn persist_cache( + &self, + models: &[ModelInfo], + etag: Option, + client_version: String, + ) { let cache = ModelsCache { fetched_at: Utc::now(), etag, + client_version: Some(client_version), models: models.to_vec(), }; if let Err(err) = self.save_internal(&cache).await { @@ -103,6 +112,20 @@ impl ModelsCacheManager { f(&mut cache.fetched_at); self.save_internal(&cache).await } + + #[cfg(test)] + /// Mutate the full cache contents for testing. + pub(crate) async fn mutate_cache_for_test(&self, f: F) -> io::Result<()> + where + F: FnOnce(&mut ModelsCache), + { + let mut cache = match self.load().await? { + Some(cache) => cache, + None => return Err(io::Error::new(ErrorKind::NotFound, "cache not found")), + }; + f(&mut cache); + self.save_internal(&cache).await + } } /// Serialized snapshot of models and metadata cached on disk. @@ -111,6 +134,8 @@ pub(crate) struct ModelsCache { pub(crate) fetched_at: DateTime, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) etag: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) client_version: Option, pub(crate) models: Vec, } diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index 936e40d1bc..fffc280736 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -210,7 +210,7 @@ impl ModelsManager { let transport = ReqwestTransport::new(build_reqwest_client()); let client = ModelsClient::new(transport, api_provider, api_auth); - let client_version = format_client_version_to_whole(); + let client_version = crate::models_manager::client_version_to_whole(); let (models, etag) = timeout( MODELS_REFRESH_TIMEOUT, client.list_models(&client_version, HeaderMap::new()), @@ -221,7 +221,9 @@ impl ModelsManager { self.apply_remote_models(models.clone()).await; *self.etag.write().await = etag.clone(); - self.cache_manager.persist_cache(&models, etag).await; + self.cache_manager + .persist_cache(&models, etag, client_version) + .await; Ok(()) } @@ -255,7 +257,8 @@ impl ModelsManager { async fn try_load_cache(&self) -> bool { let _timer = codex_otel::start_global_timer("codex.remote_models.load_cache.duration_ms", &[]); - let cache = match self.cache_manager.load_fresh().await { + let client_version = crate::models_manager::client_version_to_whole(); + let cache = match self.cache_manager.load_fresh(&client_version).await { Some(cache) => cache, None => return false, }; @@ -350,16 +353,6 @@ impl ModelsManager { } } -/// Convert a client version string to a whole version string (e.g. "1.2.3-alpha.4" -> "1.2.3") -fn format_client_version_to_whole() -> String { - format!( - "{}.{}.{}", - env!("CARGO_PKG_VERSION_MAJOR"), - env!("CARGO_PKG_VERSION_MINOR"), - env!("CARGO_PKG_VERSION_PATCH") - ) -} - #[cfg(test)] mod tests { use super::*; @@ -613,6 +606,75 @@ mod tests { ); } + #[tokio::test] + async fn refresh_available_models_refetches_when_version_mismatch() { + let server = MockServer::start().await; + let initial_models = vec![remote_model("old", "Old", 1)]; + let initial_mock = mount_models_once( + &server, + ModelsResponse { + models: initial_models.clone(), + }, + ) + .await; + + let codex_home = tempdir().expect("temp dir"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("load default test config"); + config.features.enable(Feature::RemoteModels); + let auth_manager = Arc::new(AuthManager::new( + codex_home.path().to_path_buf(), + false, + AuthCredentialsStoreMode::File, + )); + let provider = provider_for(server.uri()); + let manager = + ModelsManager::with_provider(codex_home.path().to_path_buf(), auth_manager, provider); + + manager + .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .await + .expect("initial refresh succeeds"); + + manager + .cache_manager + .mutate_cache_for_test(|cache| { + let client_version = crate::models_manager::client_version_to_whole(); + cache.client_version = Some(format!("{client_version}-mismatch")); + }) + .await + .expect("cache mutation succeeds"); + + let updated_models = vec![remote_model("new", "New", 2)]; + server.reset().await; + let refreshed_mock = mount_models_once( + &server, + ModelsResponse { + models: updated_models.clone(), + }, + ) + .await; + + manager + .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .await + .expect("second refresh succeeds"); + assert_models_contain(&manager.get_remote_models(&config).await, &updated_models); + assert_eq!( + initial_mock.requests().len(), + 1, + "initial refresh should only hit /models once" + ); + assert_eq!( + refreshed_mock.requests().len(), + 1, + "version mismatch should fetch /models once" + ); + } + #[tokio::test] async fn refresh_available_models_drops_removed_remote_models() { let server = MockServer::start().await; diff --git a/codex-rs/core/src/models_manager/mod.rs b/codex-rs/core/src/models_manager/mod.rs index 3a649d5f14..9527466252 100644 --- a/codex-rs/core/src/models_manager/mod.rs +++ b/codex-rs/core/src/models_manager/mod.rs @@ -6,3 +6,13 @@ pub mod model_presets; #[cfg(any(test, feature = "test-support"))] pub use collaboration_mode_presets::test_builtin_collaboration_mode_presets; + +/// Convert the client version string to a whole version string (e.g. "1.2.3-alpha.4" -> "1.2.3"). +pub fn client_version_to_whole() -> String { + format!( + "{}.{}.{}", + env!("CARGO_PKG_VERSION_MAJOR"), + env!("CARGO_PKG_VERSION_MINOR"), + env!("CARGO_PKG_VERSION_PATCH") + ) +} diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index 5756f46a69..a6f3c1bdba 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -36,6 +36,9 @@ use wiremock::MockServer; const ETAG: &str = "\"models-etag-ttl\""; const CACHE_FILE: &str = "models_cache.json"; const REMOTE_MODEL: &str = "codex-test-ttl"; +const VERSIONED_MODEL: &str = "codex-test-versioned"; +const MISSING_VERSION_MODEL: &str = "codex-test-missing-version"; +const DIFFERENT_VERSION_MODEL: &str = "codex-test-different-version"; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { @@ -131,11 +134,157 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn uses_cache_when_version_matches() -> Result<()> { + let server = MockServer::start().await; + let cached_model = test_remote_model(VERSIONED_MODEL, 1); + let models_mock = responses::mount_models_once( + &server, + ModelsResponse { + models: vec![test_remote_model("remote", 2)], + }, + ) + .await; + + let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()); + builder = builder + .with_pre_build_hook(move |home| { + let cache = ModelsCache { + fetched_at: Utc::now(), + etag: None, + client_version: Some(codex_core::models_manager::client_version_to_whole()), + models: vec![cached_model], + }; + let cache_path = home.join(CACHE_FILE); + write_cache_sync(&cache_path, &cache).expect("write cache"); + }) + .with_config(|config| { + config.features.enable(Feature::RemoteModels); + config.model_provider.request_max_retries = Some(0); + }); + + let test = builder.build(&server).await?; + let models_manager = test.thread_manager.get_models_manager(); + let models = models_manager + .list_models(&test.config, RefreshStrategy::OnlineIfUncached) + .await; + + assert!( + models.iter().any(|preset| preset.model == VERSIONED_MODEL), + "expected cached model" + ); + assert_eq!( + models_mock.requests().len(), + 0, + "/models should not be called when cache version matches" + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn refreshes_when_cache_version_missing() -> Result<()> { + let server = MockServer::start().await; + let cached_model = test_remote_model(MISSING_VERSION_MODEL, 1); + let models_mock = responses::mount_models_once( + &server, + ModelsResponse { + models: vec![test_remote_model("remote-missing", 2)], + }, + ) + .await; + + let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()); + builder = builder + .with_pre_build_hook(move |home| { + let cache = ModelsCache { + fetched_at: Utc::now(), + etag: None, + client_version: None, + models: vec![cached_model], + }; + let cache_path = home.join(CACHE_FILE); + write_cache_sync(&cache_path, &cache).expect("write cache"); + }) + .with_config(|config| { + config.features.enable(Feature::RemoteModels); + config.model_provider.request_max_retries = Some(0); + }); + + let test = builder.build(&server).await?; + let models_manager = test.thread_manager.get_models_manager(); + let models = models_manager + .list_models(&test.config, RefreshStrategy::OnlineIfUncached) + .await; + + assert!( + models.iter().any(|preset| preset.model == "remote-missing"), + "expected refreshed models" + ); + assert_eq!( + models_mock.requests().len(), + 1, + "/models should be called when cache version is missing" + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn refreshes_when_cache_version_differs() -> Result<()> { + let server = MockServer::start().await; + let cached_model = test_remote_model(DIFFERENT_VERSION_MODEL, 1); + let models_mock = responses::mount_models_once( + &server, + ModelsResponse { + models: vec![test_remote_model("remote-different", 2)], + }, + ) + .await; + + let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()); + builder = builder + .with_pre_build_hook(move |home| { + let client_version = codex_core::models_manager::client_version_to_whole(); + let cache = ModelsCache { + fetched_at: Utc::now(), + etag: None, + client_version: Some(format!("{client_version}-diff")), + models: vec![cached_model], + }; + let cache_path = home.join(CACHE_FILE); + write_cache_sync(&cache_path, &cache).expect("write cache"); + }) + .with_config(|config| { + config.features.enable(Feature::RemoteModels); + config.model_provider.request_max_retries = Some(0); + }); + + let test = builder.build(&server).await?; + let models_manager = test.thread_manager.get_models_manager(); + let models = models_manager + .list_models(&test.config, RefreshStrategy::OnlineIfUncached) + .await; + + assert!( + models + .iter() + .any(|preset| preset.model == "remote-different"), + "expected refreshed models" + ); + assert_eq!( + models_mock.requests().len(), + 1, + "/models should be called when cache version differs" + ); + + Ok(()) +} + async fn rewrite_cache_timestamp(path: &Path, fetched_at: DateTime) -> Result<()> { let mut cache = read_cache(path).await?; cache.fetched_at = fetched_at; - let contents = serde_json::to_vec_pretty(&cache)?; - tokio::fs::write(path, contents).await?; + write_cache(path, &cache).await?; Ok(()) } @@ -145,11 +294,25 @@ async fn read_cache(path: &Path) -> Result { Ok(cache) } +async fn write_cache(path: &Path, cache: &ModelsCache) -> Result<()> { + let contents = serde_json::to_vec_pretty(cache)?; + tokio::fs::write(path, contents).await?; + Ok(()) +} + +fn write_cache_sync(path: &Path, cache: &ModelsCache) -> Result<()> { + let contents = serde_json::to_vec_pretty(cache)?; + std::fs::write(path, contents)?; + Ok(()) +} + #[derive(Debug, Clone, Serialize, Deserialize)] struct ModelsCache { fetched_at: DateTime, #[serde(default)] etag: Option, + #[serde(default)] + client_version: Option, models: Vec, } From 7e07ec8f732375255eeaad25ecc6b0621f7b9c51 Mon Sep 17 00:00:00 2001 From: Colin Young Date: Mon, 2 Feb 2026 18:56:39 -0800 Subject: [PATCH 7/9] [Codex][CLI] Gate image inputs by model modalities (#10271) ###### Summary - Add input_modalities to model metadata so clients can determine supported input types. - Gate image paste/attach in TUI when the selected model does not support images. - Block submits that include images for unsupported models and show a clear warning. - Propagate modality metadata through app-server protocol/model-list responses. - Update related tests/fixtures. ###### Rationale - Models support different input modalities. - Clients need an explicit capability signal to prevent unsupported requests. - Backward-compatible defaults preserve existing behavior when modality metadata is absent. ###### Scope - codex-rs/protocol, codex-rs/core, codex-rs/tui - codex-rs/app-server-protocol, codex-rs/app-server - Generated app-server types / schema fixtures ###### Trade-offs - Default behavior assumes text + image when field is absent for compatibility. - Server-side validation remains the source of truth. ###### Follow-up - Non-TUI clients should consume input_modalities to disable unsupported attachments. - Model catalogs should explicitly set input_modalities for text-only models. ###### Testing - cargo fmt --all - cargo test -p codex-tui - env -u GITHUB_APP_KEY cargo test -p codex-core --lib - just write-app-server-schema - cargo run -p codex-cli --bin codex -- app-server generate-ts --out app-server-types - test against local backend image --------- Co-authored-by: Josh McKinney --- .../codex_app_server_protocol.schemas.json | 29 +++++++ .../schema/json/v2/ModelListResponse.json | 29 +++++++ .../schema/typescript/InputModality.ts | 8 ++ .../schema/typescript/index.ts | 1 + .../schema/typescript/v2/Model.ts | 3 +- .../app-server-protocol/src/protocol/v2.rs | 4 + codex-rs/app-server/src/models.rs | 1 + .../app-server/tests/common/models_cache.rs | 2 + .../app-server/tests/suite/v2/model_list.rs | 5 ++ .../codex-api/tests/models_integration.rs | 2 + .../core/src/models_manager/model_info.rs | 2 + .../core/src/models_manager/model_presets.rs | 12 +++ codex-rs/core/tests/suite/list_models.rs | 12 +++ codex-rs/core/tests/suite/models_cache_ttl.rs | 2 + codex-rs/core/tests/suite/personality.rs | 4 + codex-rs/core/tests/suite/remote_models.rs | 4 + codex-rs/protocol/src/openai_models.rs | 45 +++++++++++ codex-rs/tui/src/bottom_pane/chat_composer.rs | 39 +++++++++- codex-rs/tui/src/bottom_pane/mod.rs | 33 ++++++++ codex-rs/tui/src/chatwidget.rs | 76 +++++++++++++++++++ codex-rs/tui/src/chatwidget/tests.rs | 46 +++++++++++ docs/contributing.md | 9 +++ docs/tui-chat-composer.md | 8 ++ 23 files changed, 373 insertions(+), 3 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/InputModality.ts diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 571079c7c0..5a5ddd1011 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -11280,6 +11280,25 @@ }, "type": "object" }, + "InputModality": { + "description": "Canonical user-input modality tags advertised by a model.", + "oneOf": [ + { + "description": "Plain text turns and tool payloads.", + "enum": [ + "text" + ], + "type": "string" + }, + { + "description": "Image attachments included in user turns.", + "enum": [ + "image" + ], + "type": "string" + } + ] + }, "ItemCompletedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -11780,6 +11799,16 @@ "id": { "type": "string" }, + "inputModalities": { + "default": [ + "text", + "image" + ], + "items": { + "$ref": "#/definitions/v2/InputModality" + }, + "type": "array" + }, "isDefault": { "type": "boolean" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json index d41f13a87d..8c8126bb6b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json @@ -1,6 +1,25 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { + "InputModality": { + "description": "Canonical user-input modality tags advertised by a model.", + "oneOf": [ + { + "description": "Plain text turns and tool payloads.", + "enum": [ + "text" + ], + "type": "string" + }, + { + "description": "Image attachments included in user turns.", + "enum": [ + "image" + ], + "type": "string" + } + ] + }, "Model": { "properties": { "defaultReasoningEffort": { @@ -15,6 +34,16 @@ "id": { "type": "string" }, + "inputModalities": { + "default": [ + "text", + "image" + ], + "items": { + "$ref": "#/definitions/InputModality" + }, + "type": "array" + }, "isDefault": { "type": "boolean" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/InputModality.ts b/codex-rs/app-server-protocol/schema/typescript/InputModality.ts new file mode 100644 index 0000000000..73661938b3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/InputModality.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Canonical user-input modality tags advertised by a model. + */ +export type InputModality = "text" | "image"; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index b24e42a834..91da0708e1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -90,6 +90,7 @@ export type { InitializeCapabilities } from "./InitializeCapabilities"; export type { InitializeParams } from "./InitializeParams"; export type { InitializeResponse } from "./InitializeResponse"; export type { InputItem } from "./InputItem"; +export type { InputModality } from "./InputModality"; export type { InterruptConversationParams } from "./InterruptConversationParams"; export type { InterruptConversationResponse } from "./InterruptConversationResponse"; export type { ItemCompletedEvent } from "./ItemCompletedEvent"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts index b664024d0a..72daa93edd 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts @@ -1,7 +1,8 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { InputModality } from "../InputModality"; import type { ReasoningEffort } from "../ReasoningEffort"; import type { ReasoningEffortOption } from "./ReasoningEffortOption"; -export type Model = { id: string, model: string, displayName: string, description: string, supportedReasoningEfforts: Array, defaultReasoningEffort: ReasoningEffort, supportsPersonality: boolean, isDefault: boolean, }; +export type Model = { id: string, model: string, displayName: string, description: string, supportedReasoningEfforts: Array, defaultReasoningEffort: ReasoningEffort, inputModalities: Array, supportsPersonality: boolean, isDefault: boolean, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 4f37e2889d..19db13723a 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -19,7 +19,9 @@ use codex_protocol::mcp::Resource as McpResource; use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate; use codex_protocol::mcp::Tool as McpTool; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::InputModality; use codex_protocol::openai_models::ReasoningEffort; +use codex_protocol::openai_models::default_input_modalities; use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand; use codex_protocol::plan_tool::PlanItemArg as CorePlanItemArg; use codex_protocol::plan_tool::StepStatus as CorePlanStepStatus; @@ -992,6 +994,8 @@ pub struct Model { pub description: String, pub supported_reasoning_efforts: Vec, pub default_reasoning_effort: ReasoningEffort, + #[serde(default = "default_input_modalities")] + pub input_modalities: Vec, #[serde(default)] pub supports_personality: bool, // Only one model should be marked as default. diff --git a/codex-rs/app-server/src/models.rs b/codex-rs/app-server/src/models.rs index 4189435c05..133dc73cc0 100644 --- a/codex-rs/app-server/src/models.rs +++ b/codex-rs/app-server/src/models.rs @@ -28,6 +28,7 @@ fn model_from_preset(preset: ModelPreset) -> Model { preset.supported_reasoning_efforts, ), default_reasoning_effort: preset.default_reasoning_effort, + input_modalities: preset.input_modalities, supports_personality: preset.supports_personality, is_default: preset.is_default, } diff --git a/codex-rs/app-server/tests/common/models_cache.rs b/codex-rs/app-server/tests/common/models_cache.rs index c3f00cf094..14b4e8d458 100644 --- a/codex-rs/app-server/tests/common/models_cache.rs +++ b/codex-rs/app-server/tests/common/models_cache.rs @@ -6,6 +6,7 @@ use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ModelVisibility; use codex_protocol::openai_models::TruncationPolicyConfig; +use codex_protocol::openai_models::default_input_modalities; use serde_json::json; use std::path::Path; @@ -38,6 +39,7 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo { auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), + input_modalities: default_input_modalities(), } } diff --git a/codex-rs/app-server/tests/suite/v2/model_list.rs b/codex-rs/app-server/tests/suite/v2/model_list.rs index a496e4a590..c3b4ec7088 100644 --- a/codex-rs/app-server/tests/suite/v2/model_list.rs +++ b/codex-rs/app-server/tests/suite/v2/model_list.rs @@ -12,6 +12,7 @@ use codex_app_server_protocol::ModelListParams; use codex_app_server_protocol::ModelListResponse; use codex_app_server_protocol::ReasoningEffortOption; use codex_app_server_protocol::RequestId; +use codex_protocol::openai_models::InputModality; use codex_protocol::openai_models::ReasoningEffort; use pretty_assertions::assert_eq; use tempfile::TempDir; @@ -72,6 +73,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { }, ], default_reasoning_effort: ReasoningEffort::Medium, + input_modalities: vec![InputModality::Text, InputModality::Image], supports_personality: false, is_default: true, }, @@ -100,6 +102,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { }, ], default_reasoning_effort: ReasoningEffort::Medium, + input_modalities: vec![InputModality::Text, InputModality::Image], supports_personality: false, is_default: false, }, @@ -120,6 +123,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { }, ], default_reasoning_effort: ReasoningEffort::Medium, + input_modalities: vec![InputModality::Text, InputModality::Image], supports_personality: false, is_default: false, }, @@ -154,6 +158,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { }, ], default_reasoning_effort: ReasoningEffort::Medium, + input_modalities: vec![InputModality::Text, InputModality::Image], supports_personality: false, is_default: false, }, diff --git a/codex-rs/codex-api/tests/models_integration.rs b/codex-rs/codex-api/tests/models_integration.rs index c75c46a28a..2145be6542 100644 --- a/codex-rs/codex-api/tests/models_integration.rs +++ b/codex-rs/codex-api/tests/models_integration.rs @@ -11,6 +11,7 @@ use codex_protocol::openai_models::ModelsResponse; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffortPreset; use codex_protocol::openai_models::TruncationPolicyConfig; +use codex_protocol::openai_models::default_input_modalities; use http::HeaderMap; use http::Method; use wiremock::Mock; @@ -88,6 +89,7 @@ async fn models_client_hits_models_endpoint() { auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), + input_modalities: default_input_modalities(), }], }; diff --git a/codex-rs/core/src/models_manager/model_info.rs b/codex-rs/core/src/models_manager/model_info.rs index eaab160001..5cccefdd21 100644 --- a/codex-rs/core/src/models_manager/model_info.rs +++ b/codex-rs/core/src/models_manager/model_info.rs @@ -9,6 +9,7 @@ use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffortPreset; use codex_protocol::openai_models::TruncationMode; use codex_protocol::openai_models::TruncationPolicyConfig; +use codex_protocol::openai_models::default_input_modalities; use crate::config::Config; use crate::features::Feature; @@ -66,6 +67,7 @@ macro_rules! model_info { auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), + input_modalities: default_input_modalities(), }; $( diff --git a/codex-rs/core/src/models_manager/model_presets.rs b/codex-rs/core/src/models_manager/model_presets.rs index f9105c6448..a597f7f922 100644 --- a/codex-rs/core/src/models_manager/model_presets.rs +++ b/codex-rs/core/src/models_manager/model_presets.rs @@ -3,6 +3,7 @@ use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ModelUpgrade; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffortPreset; +use codex_protocol::openai_models::default_input_modalities; use indoc::indoc; use once_cell::sync::Lazy; @@ -41,6 +42,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: None, show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "gpt-5.1-codex-max".to_string(), @@ -71,6 +73,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: Some(gpt_52_codex_upgrade()), show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "gpt-5.1-codex-mini".to_string(), @@ -94,6 +97,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: Some(gpt_52_codex_upgrade()), show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "gpt-5.2".to_string(), @@ -124,6 +128,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: Some(gpt_52_codex_upgrade()), show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "bengalfox".to_string(), @@ -154,6 +159,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: None, show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "boomslang".to_string(), @@ -184,6 +190,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: None, show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), }, // Deprecated models. ModelPreset { @@ -211,6 +218,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: Some(gpt_52_codex_upgrade()), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "gpt-5-codex-mini".to_string(), @@ -233,6 +241,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: Some(gpt_52_codex_upgrade()), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "gpt-5.1-codex".to_string(), @@ -260,6 +269,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: Some(gpt_52_codex_upgrade()), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "gpt-5".to_string(), @@ -290,6 +300,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: Some(gpt_52_codex_upgrade()), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), }, ModelPreset { id: "gpt-5.1".to_string(), @@ -316,6 +327,7 @@ static PRESETS: Lazy> = Lazy::new(|| { upgrade: Some(gpt_52_codex_upgrade()), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), }, ] }); diff --git a/codex-rs/core/tests/suite/list_models.rs b/codex-rs/core/tests/suite/list_models.rs index f6db54af7b..aee3a60e0f 100644 --- a/codex-rs/core/tests/suite/list_models.rs +++ b/codex-rs/core/tests/suite/list_models.rs @@ -7,6 +7,7 @@ use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ModelUpgrade; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffortPreset; +use codex_protocol::openai_models::default_input_modalities; use core_test_support::load_default_config_for_test; use indoc::indoc; use pretty_assertions::assert_eq; @@ -99,6 +100,7 @@ fn gpt_52_codex() -> ModelPreset { upgrade: None, show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -142,6 +144,7 @@ fn gpt_5_1_codex_max() -> ModelPreset { )), show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -177,6 +180,7 @@ fn gpt_5_1_codex_mini() -> ModelPreset { )), show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -222,6 +226,7 @@ fn gpt_5_2() -> ModelPreset { )), show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -255,6 +260,7 @@ fn bengalfox() -> ModelPreset { upgrade: None, show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -288,6 +294,7 @@ fn boomslang() -> ModelPreset { upgrade: None, show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -327,6 +334,7 @@ fn gpt_5_codex() -> ModelPreset { )), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -362,6 +370,7 @@ fn gpt_5_codex_mini() -> ModelPreset { )), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -401,6 +410,7 @@ fn gpt_5_1_codex() -> ModelPreset { )), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -444,6 +454,7 @@ fn gpt_5() -> ModelPreset { )), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), } } @@ -483,6 +494,7 @@ fn gpt_5_1() -> ModelPreset { )), show_in_picker: false, supported_in_api: true, + input_modalities: default_input_modalities(), } } diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index a6f3c1bdba..49d05b83e2 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -19,6 +19,7 @@ use codex_protocol::openai_models::ModelsResponse; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffortPreset; use codex_protocol::openai_models::TruncationPolicyConfig; +use codex_protocol::openai_models::default_input_modalities; use codex_protocol::user_input::UserInput; use core_test_support::responses; use core_test_support::responses::ev_assistant_message; @@ -349,5 +350,6 @@ fn test_remote_model(slug: &str, priority: i32) -> ModelInfo { auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), + input_modalities: default_input_modalities(), } } diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 85cc25f4cd..87978ceb5a 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -16,6 +16,7 @@ use codex_protocol::openai_models::ModelsResponse; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffortPreset; use codex_protocol::openai_models::TruncationPolicyConfig; +use codex_protocol::openai_models::default_input_modalities; use codex_protocol::user_input::UserInput; use core_test_support::load_default_config_for_test; use core_test_support::responses::ev_completed; @@ -512,6 +513,7 @@ async fn ignores_remote_personality_if_remote_models_disabled() -> anyhow::Resul auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), + input_modalities: default_input_modalities(), }; let _models_mock = mount_models_once( @@ -627,6 +629,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), + input_modalities: default_input_modalities(), }; let _models_mock = mount_models_once( @@ -737,6 +740,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), + input_modalities: default_input_modalities(), }; let _models_mock = mount_models_once( diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index f59f73fd14..ed46855f9c 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -25,6 +25,7 @@ use codex_protocol::openai_models::ModelsResponse; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffortPreset; use codex_protocol::openai_models::TruncationPolicyConfig; +use codex_protocol::openai_models::default_input_modalities; use codex_protocol::user_input::UserInput; use core_test_support::load_default_config_for_test; use core_test_support::responses::ev_assistant_message; @@ -76,6 +77,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { shell_type: ConfigShellToolType::UnifiedExec, visibility: ModelVisibility::List, supported_in_api: true, + input_modalities: default_input_modalities(), priority: 1, upgrade: None, base_instructions: "base instructions".to_string(), @@ -313,6 +315,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { shell_type: ConfigShellToolType::ShellCommand, visibility: ModelVisibility::List, supported_in_api: true, + input_modalities: default_input_modalities(), priority: 1, upgrade: None, base_instructions: remote_base.to_string(), @@ -787,6 +790,7 @@ fn test_remote_model_with_policy( shell_type: ConfigShellToolType::ShellCommand, visibility, supported_in_api: true, + input_modalities: default_input_modalities(), priority, upgrade: None, base_instructions: "base instructions".to_string(), diff --git a/codex-rs/protocol/src/openai_models.rs b/codex-rs/protocol/src/openai_models.rs index 9a940539d5..90cf34f393 100644 --- a/codex-rs/protocol/src/openai_models.rs +++ b/codex-rs/protocol/src/openai_models.rs @@ -1,3 +1,8 @@ +//! Shared model metadata types exchanged between Codex services and clients. +//! +//! These types are serialized across core, TUI, app-server, and SDK boundaries, so field defaults +//! are used to preserve compatibility when older payloads omit newly introduced attributes. + use std::collections::HashMap; use std::collections::HashSet; @@ -43,6 +48,38 @@ pub enum ReasoningEffort { XHigh, } +/// Canonical user-input modality tags advertised by a model. +#[derive( + Debug, + Serialize, + Deserialize, + Clone, + Copy, + PartialEq, + Eq, + Display, + JsonSchema, + TS, + EnumIter, + Hash, +)] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum InputModality { + /// Plain text turns and tool payloads. + Text, + /// Image attachments included in user turns. + Image, +} + +/// Backward-compatible default when `input_modalities` is omitted on the wire. +/// +/// Legacy payloads predate modality metadata, so we conservatively assume both text and images are +/// accepted unless a preset explicitly narrows support. +pub fn default_input_modalities() -> Vec { + vec![InputModality::Text, InputModality::Image] +} + /// A reasoning effort option that can be surfaced for a model. #[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)] pub struct ReasoningEffortPreset { @@ -88,6 +125,9 @@ pub struct ModelPreset { pub show_in_picker: bool, /// whether this model is supported in the api pub supported_in_api: bool, + /// Input modalities accepted when composing user turns for this preset. + #[serde(default = "default_input_modalities")] + pub input_modalities: Vec, } /// Visibility of a model in the picker or APIs. @@ -206,6 +246,9 @@ pub struct ModelInfo { #[serde(default = "default_effective_context_window_percent")] pub effective_context_window_percent: i64, pub experimental_supported_tools: Vec, + /// Input modalities accepted by the backend for this model. + #[serde(default = "default_input_modalities")] + pub input_modalities: Vec, } impl ModelInfo { @@ -350,6 +393,7 @@ impl From for ModelPreset { }), show_in_picker: info.visibility == ModelVisibility::List, supported_in_api: info.supported_in_api, + input_modalities: info.input_modalities, } } } @@ -460,6 +504,7 @@ mod tests { auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: vec![], + input_modalities: default_input_modalities(), } } diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 068674b418..116e9c3abe 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -394,6 +394,14 @@ impl ChatComposer { self.skills = skills; } + /// Toggle composer-side image paste handling. + /// + /// This only affects whether image-like paste content is converted into attachments; the + /// `ChatWidget` layer still performs capability checks before images are submitted. + pub fn set_image_paste_enabled(&mut self, enabled: bool) { + self.config.image_paste_enabled = enabled; + } + pub fn set_connector_mentions(&mut self, connectors_snapshot: Option) { self.connectors_snapshot = connectors_snapshot; } @@ -712,18 +720,45 @@ impl ChatComposer { } /// Replace the entire composer content with `text` and reset cursor. - /// This clears any pending paste payloads. + /// + /// This is the "fresh draft" path: it clears pending paste payloads and + /// mention link targets. Callers restoring a previously submitted draft + /// that must keep `$name -> path` resolution should use + /// [`Self::set_text_content_with_mention_paths`] instead. pub(crate) fn set_text_content( &mut self, text: String, text_elements: Vec, local_image_paths: Vec, + ) { + self.set_text_content_with_mention_paths( + text, + text_elements, + local_image_paths, + HashMap::new(), + ); + } + + /// Replace the entire composer content while restoring mention link targets. + /// + /// Mention popup insertion stores both visible text (for example `$file`) + /// and hidden `mention_paths` used to resolve the canonical target during + /// submission. Use this method when restoring an interrupted or blocked + /// draft; if callers restore only text and images, mentions can appear + /// intact to users while resolving to the wrong target or dropping on + /// retry. + pub(crate) fn set_text_content_with_mention_paths( + &mut self, + text: String, + text_elements: Vec, + local_image_paths: Vec, + mention_paths: HashMap, ) { // Clear any existing content, placeholders, and attachments first. self.textarea.set_text_clearing_elements(""); self.pending_pastes.clear(); self.attached_images.clear(); - self.mention_paths.clear(); + self.mention_paths = mention_paths; self.textarea.set_text_with_elements(&text, &text_elements); diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index 6d3a6755a4..cd5e2adb80 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -209,6 +209,14 @@ impl BottomPane { self.request_redraw(); } + /// Update image-paste behavior for the active composer and repaint immediately. + /// + /// Callers use this to keep composer affordances aligned with model capabilities. + pub fn set_image_paste_enabled(&mut self, enabled: bool) { + self.composer.set_image_paste_enabled(enabled); + self.request_redraw(); + } + pub fn set_connectors_snapshot(&mut self, snapshot: Option) { self.composer.set_connector_mentions(snapshot); self.request_redraw(); @@ -402,6 +410,10 @@ impl BottomPane { } /// Replace the composer text with `text`. + /// + /// This is intended for fresh input where mention linkage does not need to + /// survive; it routes to `ChatComposer::set_text_content`, which resets + /// `mention_paths`. pub(crate) fn set_composer_text( &mut self, text: String, @@ -414,6 +426,27 @@ impl BottomPane { self.request_redraw(); } + /// Replace the composer text while preserving mention link targets. + /// + /// Use this when rehydrating a draft after a local validation/gating + /// failure (for example unsupported image submit) so previously selected + /// mention targets remain stable across retry. + pub(crate) fn set_composer_text_with_mention_paths( + &mut self, + text: String, + text_elements: Vec, + local_image_paths: Vec, + mention_paths: HashMap, + ) { + self.composer.set_text_content_with_mention_paths( + text, + text_elements, + local_image_paths, + mention_paths, + ); + self.request_redraw(); + } + #[allow(dead_code)] pub(crate) fn set_composer_input_enabled( &mut self, diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index cc99aef7cf..ce4af0d2ff 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -208,6 +208,7 @@ use codex_core::ThreadManager; use codex_core::protocol::AskForApproval; use codex_core::protocol::SandboxPolicy; use codex_file_search::FileMatch; +use codex_protocol::openai_models::InputModality; use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::plan_tool::UpdatePlanArgs; @@ -2748,7 +2749,18 @@ impl ChatWidget { } } + /// Attach a local image to the composer when the active model supports image inputs. + /// + /// When the model does not advertise image support, we keep the draft unchanged and surface a + /// warning event so users can switch models or remove attachments. pub(crate) fn attach_image(&mut self, path: PathBuf) { + if !self.current_model_supports_images() { + self.add_to_history(history_cell::new_warning_event( + self.image_inputs_not_supported_message(), + )); + self.request_redraw(); + return; + } tracing::info!("attach_image path={path:?}"); self.bottom_pane.attach_image(path); self.request_redraw(); @@ -3225,6 +3237,10 @@ impl ChatWidget { if text.is_empty() && local_images.is_empty() { return; } + if !local_images.is_empty() && !self.current_model_supports_images() { + self.restore_blocked_image_submission(text, text_elements, local_images, mention_paths); + return; + } let mut items: Vec = Vec::new(); @@ -3339,6 +3355,34 @@ impl ChatWidget { self.needs_final_message_separator = false; } + /// Restore the blocked submission draft without losing mention resolution state. + /// + /// The blocked-image path intentionally keeps the draft in the composer so + /// users can remove attachments and retry. We must restore + /// `mention_paths` alongside visible text; restoring only `$name` tokens + /// makes the draft look correct while degrading mention resolution to + /// name-only heuristics on retry. + fn restore_blocked_image_submission( + &mut self, + text: String, + text_elements: Vec, + local_images: Vec, + mention_paths: HashMap, + ) { + // Preserve the user's composed payload so they can retry after changing models. + let local_image_paths = local_images.iter().map(|img| img.path.clone()).collect(); + self.bottom_pane.set_composer_text_with_mention_paths( + text, + text_elements, + local_image_paths, + mention_paths, + ); + self.add_to_history(history_cell::new_warning_event( + self.image_inputs_not_supported_message(), + )); + self.request_redraw(); + } + /// Replay a subset of initial events into the UI to seed the transcript when /// resuming an existing session. This approximates the live event flow and /// is intentionally conservative: only safe-to-replay items are rendered to @@ -5279,6 +5323,36 @@ impl ChatWidget { .unwrap_or(false) } + /// Return whether the effective model currently advertises image-input support. + /// + /// We intentionally default to `true` when model metadata cannot be read so transient catalog + /// failures do not hard-block user input in the UI. + fn current_model_supports_images(&self) -> bool { + let model = self.current_model(); + self.models_manager + .try_list_models(&self.config) + .ok() + .and_then(|models| { + models + .into_iter() + .find(|preset| preset.model == model) + .map(|preset| preset.input_modalities.contains(&InputModality::Image)) + }) + .unwrap_or(true) + } + + fn sync_image_paste_enabled(&mut self) { + let enabled = self.current_model_supports_images(); + self.bottom_pane.set_image_paste_enabled(enabled); + } + + fn image_inputs_not_supported_message(&self) -> String { + format!( + "Model {} does not support image inputs. Remove images or switch models.", + self.current_model() + ) + } + #[allow(dead_code)] // Used in tests pub(crate) fn current_collaboration_mode(&self) -> &CollaborationMode { &self.current_collaboration_mode @@ -5351,6 +5425,8 @@ impl ChatWidget { fn refresh_model_display(&mut self) { let effective = self.effective_collaboration_mode(); self.session_header.set_model(effective.model()); + // Keep composer paste affordances aligned with the currently effective model. + self.sync_image_paste_enabled(); } fn model_display_name(&self) -> &str { diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index ea0cd43636..00141a0963 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -70,6 +70,7 @@ use codex_protocol::config_types::Personality; use codex_protocol::config_types::Settings; use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ReasoningEffortPreset; +use codex_protocol::openai_models::default_input_modalities; use codex_protocol::parse_command::ParsedCommand; use codex_protocol::plan_tool::PlanItemArg; use codex_protocol::plan_tool::StepStatus; @@ -324,6 +325,49 @@ async fn submission_preserves_text_elements_and_local_images() { assert_eq!(stored_images, local_images); } +#[tokio::test] +async fn blocked_image_restore_preserves_mention_paths() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + + let placeholder = "[Image #1]"; + let text = format!("{placeholder} check $file"); + let text_elements = vec![TextElement::new( + (0..placeholder.len()).into(), + Some(placeholder.to_string()), + )]; + let local_images = vec![LocalImageAttachment { + placeholder: placeholder.to_string(), + path: PathBuf::from("/tmp/blocked.png"), + }]; + let mention_paths = + HashMap::from([("file".to_string(), "/tmp/skills/file/SKILL.md".to_string())]); + + chat.restore_blocked_image_submission( + text.clone(), + text_elements.clone(), + local_images.clone(), + mention_paths.clone(), + ); + + assert_eq!(chat.bottom_pane.composer_text(), text); + assert_eq!(chat.bottom_pane.composer_text_elements(), text_elements); + assert_eq!( + chat.bottom_pane.composer_local_image_paths(), + vec![local_images[0].path.clone()], + ); + assert_eq!(chat.bottom_pane.take_mention_paths(), mention_paths); + + let cells = drain_insert_history(&mut rx); + let warning = cells + .last() + .map(|lines| lines_to_single_string(lines)) + .expect("expected warning cell"); + assert!( + warning.contains("does not support image inputs"), + "expected image warning, got: {warning:?}" + ); +} + #[tokio::test] async fn interrupted_turn_restores_queued_messages_with_images_and_elements() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; @@ -3154,6 +3198,7 @@ async fn model_picker_hides_show_in_picker_false_models_from_cache() { upgrade: None, show_in_picker, supported_in_api: true, + input_modalities: default_input_modalities(), }; chat.open_model_popup_with_presets(vec![ @@ -3392,6 +3437,7 @@ async fn single_reasoning_option_skips_selection() { upgrade: None, show_in_picker: true, supported_in_api: true, + input_modalities: default_input_modalities(), }; chat.open_reasoning_popup(preset); diff --git a/docs/contributing.md b/docs/contributing.md index d410123d2d..19b31073e9 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -41,6 +41,15 @@ If you are invited by a Codex team member to contribute a PR, here is the recomm 3. **Document behavior.** If your change affects user-facing behavior, update the README, inline help (`codex --help`), or relevant example projects. 4. **Keep commits atomic.** Each commit should compile and the tests should pass. This makes reviews and potential rollbacks easier. +### Model metadata updates + +When a change updates model catalogs or model metadata (`/models` payloads, presets, or fixtures): + +- Set `input_modalities` explicitly for any model that does not support images. +- Keep compatibility defaults in mind: omitted `input_modalities` currently implies text + image support. +- Ensure client surfaces that accept images (for example, TUI paste/attach) consume the same capability signal. +- Add/update tests that cover unsupported-image behavior and warning paths. + ### Opening a pull request (by invitation only) - Fill in the PR template (or include similar information) - **What? Why? How?** diff --git a/docs/tui-chat-composer.md b/docs/tui-chat-composer.md index 6211cbcfce..b927e2db44 100644 --- a/docs/tui-chat-composer.md +++ b/docs/tui-chat-composer.md @@ -84,6 +84,9 @@ Key effects when disabled: `prepare_submission_text`. - When `slash_commands_enabled` is `false`, slash-context paste-burst exceptions are disabled. - When `image_paste_enabled` is `false`, file-path paste image attachment is skipped. +- `ChatWidget` may toggle `image_paste_enabled` at runtime based on the selected model's + `input_modalities`; attach and submit paths also re-check support and emit a warning instead of + dropping the draft. Built-in slash command availability is centralized in `codex-rs/tui/src/bottom_pane/slash_commands.rs` and reused by both the composer and the command @@ -252,6 +255,11 @@ Non-char input must not leak burst state across unrelated actions: inserting, deleting, flushing a burst, applying a paste placeholder, etc. - Shortcut overlay toggling via `?` is gated on `!is_in_paste_burst()` so pastes cannot flip UI modes while streaming. +- Mention popup selection has two payloads: visible `$name` text and hidden + `mention_paths[name] -> canonical target` linkage. The generic + `set_text_content` path intentionally clears linkage for fresh drafts; restore + paths that rehydrate blocked/interrupted submissions must use the + mention-preserving setter so retry keeps the originally selected target. ## Tests that pin behavior From cbfd2a37cce2df901dcbc886c72179210cf97b96 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 2 Feb 2026 19:03:11 -0800 Subject: [PATCH 8/9] Trim compaction input (#10374) Two fixes: 1. Include trailing tool output in the total context size calculation. Otherwise when checking whether compaction should run we ignore newly added outputs. 2. Trim trailing tool output/tool calls until we can fit the request into the model context size. Otherwise the compaction endpoint will fail to compact. We only trim items that can be reproduced again by the model (tool calls, tool call outputs). --- codex-rs/core/src/codex.rs | 19 +++ codex-rs/core/src/compact_remote.rs | 42 +++++- codex-rs/core/src/context_manager/history.rs | 106 +++++++++------ .../core/src/context_manager/history_tests.rs | 98 ++++++++++++++ codex-rs/core/src/context_manager/mod.rs | 1 + codex-rs/core/tests/suite/compact_remote.rs | 128 ++++++++++++++++++ 6 files changed, 354 insertions(+), 40 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 7c99093558..ba84b6b432 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1051,6 +1051,11 @@ impl Session { state.get_total_token_usage(state.server_reasoning_included()) } + async fn get_estimated_token_count(&self, turn_context: &TurnContext) -> Option { + let state = self.state.lock().await; + state.history.estimate_token_count(turn_context) + } + pub(crate) async fn get_base_instructions(&self) -> BaseInstructions { let state = self.state.lock().await; BaseInstructions { @@ -3310,6 +3315,7 @@ pub(crate) async fn run_turn( let model_info = turn_context.client.get_model_info(); let auto_compact_limit = model_info.auto_compact_token_limit().unwrap_or(i64::MAX); let total_usage_tokens = sess.get_total_token_usage().await; + let event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.client.get_model_context_window(), collaboration_mode_kind: turn_context.collaboration_mode.mode, @@ -3465,6 +3471,19 @@ pub(crate) async fn run_turn( let total_usage_tokens = sess.get_total_token_usage().await; let token_limit_reached = total_usage_tokens >= auto_compact_limit; + let estimated_token_count = + sess.get_estimated_token_count(turn_context.as_ref()).await; + + info!( + turn_id = %turn_context.sub_id, + total_usage_tokens, + estimated_token_count = ?estimated_token_count, + auto_compact_limit, + token_limit_reached, + needs_follow_up, + "post sampling token usage" + ); + // as long as compaction works well in getting us way below the token limit, we shouldn't worry about being in an infinite loop. if token_limit_reached && needs_follow_up { run_auto_compact(&sess, &turn_context).await; diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index 9f7a8dfea3..12bc769ce2 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -3,6 +3,8 @@ use std::sync::Arc; use crate::Prompt; use crate::codex::Session; use crate::codex::TurnContext; +use crate::context_manager::ContextManager; +use crate::context_manager::is_codex_generated_item; use crate::error::Result as CodexResult; use crate::protocol::CompactedItem; use crate::protocol::EventMsg; @@ -11,6 +13,7 @@ use crate::protocol::TurnStartedEvent; use codex_protocol::items::ContextCompactionItem; use codex_protocol::items::TurnItem; use codex_protocol::models::ResponseItem; +use tracing::info; pub(crate) async fn run_inline_remote_auto_compact_task( sess: Arc, @@ -45,7 +48,16 @@ async fn run_remote_compact_task_inner_impl( let compaction_item = TurnItem::ContextCompaction(ContextCompactionItem::new()); sess.emit_turn_item_started(turn_context, &compaction_item) .await; - let history = sess.clone_history().await; + let mut history = sess.clone_history().await; + let deleted_items = + trim_function_call_history_to_fit_context_window(&mut history, turn_context.as_ref()); + if deleted_items > 0 { + info!( + turn_id = %turn_context.sub_id, + deleted_items, + "trimmed history items before remote compaction" + ); + } // Required to keep `/undo` available after compaction let ghost_snapshots: Vec = history @@ -86,3 +98,31 @@ async fn run_remote_compact_task_inner_impl( .await; Ok(()) } + +fn trim_function_call_history_to_fit_context_window( + history: &mut ContextManager, + turn_context: &TurnContext, +) -> usize { + let mut deleted_items = 0usize; + let Some(context_window) = turn_context.client.get_model_context_window() else { + return deleted_items; + }; + + while history + .estimate_token_count(turn_context) + .is_some_and(|estimated_tokens| estimated_tokens > context_window) + { + let Some(last_item) = history.raw_items().last() else { + break; + }; + if !is_codex_generated_item(last_item) { + break; + } + if !history.remove_last_item() { + break; + } + deleted_items += 1; + } + + deleted_items +} diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index 92dcef5ba4..a29f7df7e0 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -93,24 +93,7 @@ impl ContextManager { let base_tokens = i64::try_from(approx_token_count(&base_instructions)).unwrap_or(i64::MAX); let items_tokens = self.items.iter().fold(0i64, |acc, item| { - acc + match item { - ResponseItem::GhostSnapshot { .. } => 0, - ResponseItem::Reasoning { - encrypted_content: Some(content), - .. - } - | ResponseItem::Compaction { - encrypted_content: content, - } => { - let reasoning_bytes = estimate_reasoning_length(content.len()); - i64::try_from(approx_tokens_from_byte_count(reasoning_bytes)) - .unwrap_or(i64::MAX) - } - item => { - let serialized = serde_json::to_string(item).unwrap_or_default(); - i64::try_from(approx_token_count(&serialized)).unwrap_or(i64::MAX) - } - } + acc.saturating_add(estimate_item_token_count(item)) }); Some(base_tokens.saturating_add(items_tokens)) @@ -128,6 +111,15 @@ impl ContextManager { } } + pub(crate) fn remove_last_item(&mut self) -> bool { + if let Some(removed) = self.items.pop() { + normalize::remove_corresponding_for(&mut self.items, &removed); + true + } else { + false + } + } + pub(crate) fn replace(&mut self, items: Vec) { self.items = items; } @@ -207,36 +199,42 @@ impl ContextManager { ); } - fn get_non_last_reasoning_items_tokens(&self) -> usize { - // get reasoning items excluding all the ones after the last user message + fn get_non_last_reasoning_items_tokens(&self) -> i64 { + // Get reasoning items excluding all the ones after the last user message. let Some(last_user_index) = self .items .iter() .rposition(|item| matches!(item, ResponseItem::Message { role, .. } if role == "user")) else { - return 0usize; + return 0; }; - let total_reasoning_bytes = self - .items + self.items .iter() .take(last_user_index) - .filter_map(|item| { - if let ResponseItem::Reasoning { - encrypted_content: Some(content), - .. - } = item - { - Some(content.len()) - } else { - None - } + .filter(|item| { + matches!( + item, + ResponseItem::Reasoning { + encrypted_content: Some(_), + .. + } + ) }) - .map(estimate_reasoning_length) - .fold(0usize, usize::saturating_add); + .fold(0i64, |acc, item| { + acc.saturating_add(estimate_item_token_count(item)) + }) + } - let token_estimate = approx_tokens_from_byte_count(total_reasoning_bytes); - token_estimate as usize + fn get_trailing_codex_generated_items_tokens(&self) -> i64 { + let mut total = 0i64; + for item in self.items.iter().rev() { + if !is_codex_generated_item(item) { + break; + } + total = total.saturating_add(estimate_item_token_count(item)); + } + total } /// When true, the server already accounted for past reasoning tokens and @@ -247,10 +245,13 @@ impl ContextManager { .as_ref() .map(|info| info.last_token_usage.total_tokens) .unwrap_or(0); + let trailing_codex_generated_tokens = self.get_trailing_codex_generated_items_tokens(); if server_reasoning_included { - last_tokens + last_tokens.saturating_add(trailing_codex_generated_tokens) } else { - last_tokens.saturating_add(self.get_non_last_reasoning_items_tokens() as i64) + last_tokens + .saturating_add(self.get_non_last_reasoning_items_tokens()) + .saturating_add(trailing_codex_generated_tokens) } } @@ -332,6 +333,33 @@ fn estimate_reasoning_length(encoded_len: usize) -> usize { .saturating_sub(650) } +fn estimate_item_token_count(item: &ResponseItem) -> i64 { + match item { + ResponseItem::GhostSnapshot { .. } => 0, + ResponseItem::Reasoning { + encrypted_content: Some(content), + .. + } + | ResponseItem::Compaction { + encrypted_content: content, + } => { + let reasoning_bytes = estimate_reasoning_length(content.len()); + i64::try_from(approx_tokens_from_byte_count(reasoning_bytes)).unwrap_or(i64::MAX) + } + item => { + let serialized = serde_json::to_string(item).unwrap_or_default(); + i64::try_from(approx_token_count(&serialized)).unwrap_or(i64::MAX) + } + } +} + +pub(crate) fn is_codex_generated_item(item: &ResponseItem) -> bool { + matches!( + item, + ResponseItem::FunctionCallOutput { .. } | ResponseItem::CustomToolCallOutput { .. } + ) || matches!(item, ResponseItem::Message { role, .. } if role == "developer") +} + pub(crate) fn is_user_turn_boundary(item: &ResponseItem) -> bool { let ResponseItem::Message { role, content, .. } = item else { return false; diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index d31f731a98..a6eba62f1a 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -60,6 +60,23 @@ fn user_input_text_msg(text: &str) -> ResponseItem { } } +fn function_call_output(call_id: &str, content: &str) -> ResponseItem { + ResponseItem::FunctionCallOutput { + call_id: call_id.to_string(), + output: FunctionCallOutputPayload { + content: content.to_string(), + ..Default::default() + }, + } +} + +fn custom_tool_call_output(call_id: &str, output: &str) -> ResponseItem { + ResponseItem::CustomToolCallOutput { + call_id: call_id.to_string(), + output: output.to_string(), + } +} + fn reasoning_msg(text: &str) -> ResponseItem { ResponseItem::Reasoning { id: String::new(), @@ -168,6 +185,63 @@ fn non_last_reasoning_tokens_ignore_entries_after_last_user() { assert_eq!(history.get_non_last_reasoning_items_tokens(), 32); } +#[test] +fn trailing_codex_generated_tokens_stop_at_first_non_generated_item() { + let earlier_output = function_call_output("call-earlier", "earlier output"); + let trailing_function_output = function_call_output("call-tail-1", "tail function output"); + let trailing_custom_output = custom_tool_call_output("call-tail-2", "tail custom output"); + let history = create_history_with_items(vec![ + earlier_output, + user_msg("boundary item"), + trailing_function_output.clone(), + trailing_custom_output.clone(), + ]); + let expected_tokens = estimate_item_token_count(&trailing_function_output) + .saturating_add(estimate_item_token_count(&trailing_custom_output)); + + assert_eq!( + history.get_trailing_codex_generated_items_tokens(), + expected_tokens + ); +} + +#[test] +fn trailing_codex_generated_tokens_exclude_function_call_tail() { + let history = create_history_with_items(vec![ResponseItem::FunctionCall { + id: None, + name: "not-generated".to_string(), + arguments: "{}".to_string(), + call_id: "call-tail".to_string(), + }]); + + assert_eq!(history.get_trailing_codex_generated_items_tokens(), 0); +} + +#[test] +fn total_token_usage_includes_only_trailing_codex_generated_items() { + let non_trailing_output = function_call_output("call-before-message", "not trailing"); + let trailing_assistant = assistant_msg("assistant boundary"); + let trailing_output = custom_tool_call_output("tool-tail", "trailing output"); + let mut history = create_history_with_items(vec![ + non_trailing_output, + user_msg("boundary"), + trailing_assistant, + trailing_output.clone(), + ]); + history.update_token_info( + &TokenUsage { + total_tokens: 100, + ..Default::default() + }, + None, + ); + + assert_eq!( + history.get_total_token_usage(true), + 100 + estimate_item_token_count(&trailing_output) + ); +} + #[test] fn get_history_for_prompt_drops_ghost_commits() { let items = vec![ResponseItem::GhostSnapshot { @@ -222,6 +296,30 @@ fn remove_first_item_removes_matching_call_for_output() { assert_eq!(h.raw_items(), vec![]); } +#[test] +fn remove_last_item_removes_matching_call_for_output() { + let items = vec![ + user_msg("before tool call"), + ResponseItem::FunctionCall { + id: None, + name: "do_it".to_string(), + arguments: "{}".to_string(), + call_id: "call-delete-last".to_string(), + }, + ResponseItem::FunctionCallOutput { + call_id: "call-delete-last".to_string(), + output: FunctionCallOutputPayload { + content: "ok".to_string(), + ..Default::default() + }, + }, + ]; + let mut h = create_history_with_items(items); + + assert!(h.remove_last_item()); + assert_eq!(h.raw_items(), vec![user_msg("before tool call")]); +} + #[test] fn replace_last_turn_images_replaces_tool_output_images() { let items = vec![ diff --git a/codex-rs/core/src/context_manager/mod.rs b/codex-rs/core/src/context_manager/mod.rs index baae93c775..22e9682fe3 100644 --- a/codex-rs/core/src/context_manager/mod.rs +++ b/codex-rs/core/src/context_manager/mod.rs @@ -2,4 +2,5 @@ mod history; mod normalize; pub(crate) use history::ContextManager; +pub(crate) use history::is_codex_generated_item; pub(crate) use history::is_user_turn_boundary; diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index e5446f4a39..55985c9b7a 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -222,6 +222,134 @@ async fn remote_compact_runs_automatically() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn remote_compact_trims_function_call_history_to_fit_context_window() -> Result<()> { + skip_if_no_network!(Ok(())); + + let first_user_message = "turn with retained shell call"; + let second_user_message = "turn with trimmed shell call"; + let retained_call_id = "retained-call"; + let trimmed_call_id = "trimmed-call"; + let retained_command = "echo retained-shell-output"; + let trimmed_command = "yes x | head -n 3000"; + + let harness = TestCodexHarness::with_builder( + test_codex() + .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) + .with_config(|config| { + config.features.enable(Feature::RemoteCompaction); + config.model_context_window = Some(2_000); + }), + ) + .await?; + let codex = harness.test().codex.clone(); + + let response_log = responses::mount_sse_sequence( + harness.server(), + vec![ + sse(vec![ + responses::ev_shell_command_call(retained_call_id, retained_command), + responses::ev_completed("retained-call-response"), + ]), + sse(vec![ + responses::ev_assistant_message("retained-assistant", "retained complete"), + responses::ev_completed("retained-final-response"), + ]), + sse(vec![ + responses::ev_shell_command_call(trimmed_call_id, trimmed_command), + responses::ev_completed("trimmed-call-response"), + ]), + sse(vec![responses::ev_completed("trimmed-final-response")]), + ], + ) + .await; + + codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: first_user_message.into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; + + codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: second_user_message.into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; + + let compact_mock = + responses::mount_compact_json_once(harness.server(), serde_json::json!({ "output": [] })) + .await; + + codex.submit(Op::Compact).await?; + wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; + + assert!( + response_log + .function_call_output_text(retained_call_id) + .is_some(), + "expected retained shell call to produce function_call_output before compaction" + ); + assert!( + response_log + .function_call_output_text(trimmed_call_id) + .is_some(), + "expected trimmed shell call to produce function_call_output before compaction" + ); + + let compact_request = compact_mock.single_request(); + let user_messages = compact_request.message_input_texts("user"); + assert!( + user_messages + .iter() + .any(|message| message == first_user_message), + "expected compact request to retain earlier user history" + ); + assert!( + user_messages + .iter() + .any(|message| message == second_user_message), + "expected compact request to retain the user boundary message" + ); + + assert!( + compact_request.has_function_call(retained_call_id) + && compact_request + .function_call_output_text(retained_call_id) + .is_some(), + "expected compact request to keep the older function call/result pair" + ); + assert!( + !compact_request.has_function_call(trimmed_call_id) + && compact_request + .function_call_output_text(trimmed_call_id) + .is_none(), + "expected compact request to drop the trailing function call/result pair past the boundary" + ); + + assert_eq!( + compact_request.inputs_of_type("function_call").len(), + 1, + "expected exactly one function call after trimming" + ); + assert_eq!( + compact_request.inputs_of_type("function_call_output").len(), + 1, + "expected exactly one function call output after trimming" + ); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn remote_manual_compact_emits_context_compaction_items() -> Result<()> { skip_if_no_network!(Ok(())); From d3afd4e31ad117c81fb6e72f1c95d35155bb14f2 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 3 Feb 2026 08:05:55 -0800 Subject: [PATCH 9/9] fix: make $PWD/.agent read-only like $PWD/.codex --- codex-rs/protocol/src/protocol.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index bf193cca9c..640d3c381e 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -589,13 +589,18 @@ impl SandboxPolicy { } subpaths.push(top_level_git); } - #[allow(clippy::expect_used)] - let top_level_codex = writable_root - .join(".codex") - .expect(".codex is a valid relative path"); - if top_level_codex.as_path().is_dir() { - subpaths.push(top_level_codex); + + // Make .agent/skills and .codex/config.toml and related + // files read-only to the agent, by default. + for subdir in &[".agent", ".codex"] { + #[allow(clippy::expect_used)] + let top_level_codex = + writable_root.join(subdir).expect("valid relative path"); + if top_level_codex.as_path().is_dir() { + subpaths.push(top_level_codex); + } } + WritableRoot { root: writable_root, read_only_subpaths: subpaths,